SimulatorFans/Client/source/MainFrm.pas

516 lines
10 KiB
ObjectPascal

unit MainFrm;
// #ToDo1 -oMvR: 2-9-2017: device change notification -> refresh COM port list
// #ToDo1 -oMvR: 2-9-2017: visualize fan power
// #ToDo1 -oMvR: 10-9-2017: save settings
interface
uses
System.Classes,
System.SysUtils,
Vcl.Controls,
Vcl.ExtCtrls,
Vcl.Forms,
Vcl.StdCtrls,
CPort,
Simulator.Registry, Vcl.Buttons;
type
TMainForm = class(TForm, ISimulatorFans)
PortComboBox: TComboBox;
HardwareGroupBox: TGroupBox;
PortLabel: TLabel;
PortStatusLabel: TLabel;
ConnectTimer: TTimer;
FansLabel: TLabel;
SimulatorGroupBox: TGroupBox;
ResponseTimer: TTimer;
FanCountLabel: TLabel;
CalibrationButton: TButton;
SimulatorComboBox: TComboBox;
SimulatorPanel: TPanel;
SimulatorSelectionPanel: TPanel;
SimulatorLockButton: TSpeedButton;
ConnectWaitTimer: TTimer;
procedure FormCreate(Sender: TObject);
procedure PortComboBoxClick(Sender: TObject);
procedure ConnectTimerTimer(Sender: TObject);
procedure ResponseTimerTimer(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure CalibrationButtonClick(Sender: TObject);
procedure SimulatorComboBoxClick(Sender: TObject);
procedure SimulatorLockButtonClick(Sender: TObject);
procedure ConnectWaitTimerTimer(Sender: TObject);
private
FComPort: TComPort;
FReceived: string;
FOnResponse: TProc<string>;
FOnTimeout: TProc;
FReady: Boolean;
FFanValues: array of Byte;
FSimulator: ISimulator;
procedure LoadSimulators;
procedure RefreshPorts;
procedure InitDevice(const AInfo: string);
procedure TryConnect;
procedure SendCommand(const ACommand: string; AOnResponse: TProc<string>; AOnTimeout: TProc = nil);
procedure StartCommand;
procedure EndCommand;
procedure OnReceiveChar(Sender: TObject; Count: Integer);
procedure OnAfterClose(Sender: TObject);
function ValidFanIndex(AFan: Byte): Boolean; inline;
procedure SelectSimulator;
property ComPort: TComPort read FComPort;
property Ready: Boolean read FReady;
property Simulator: ISimulator read FSimulator;
public
{ ISimulatorFans }
function GetFanCount: Integer;
function GetReady: Boolean;
function GetIsRunning: Boolean;
function GetValue(const AFan: Byte): Byte;
function GetMaxValue: Byte;
procedure SetValue(const AFan, AValue: Byte);
procedure SetAll(const AValue: Byte);
procedure SetFull;
end;
implementation
uses
System.Math,
System.StrUtils,
Vcl.Dialogs,
ESCCalibrationFrm;
{$R *.dfm}
{ TMainForm }
procedure TMainForm.FormCreate(Sender: TObject);
begin
LoadSimulators;
RefreshPorts;
end;
procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if Ready then
begin
FReady := False;
SendCommand('>SetFans:0',
procedure(Response: string)
begin
Close;
end);
Action := caNone;
end;
end;
procedure TMainForm.PortComboBoxClick(Sender: TObject);
begin
TryConnect;
end;
procedure TMainForm.SimulatorComboBoxClick(Sender: TObject);
begin
SelectSimulator;
end;
procedure TMainForm.SimulatorLockButtonClick(Sender: TObject);
begin
SimulatorComboBox.Enabled := not SimulatorLockButton.Down;
end;
procedure TMainForm.CalibrationButtonClick(Sender: TObject);
begin
if Assigned(Simulator) then
Simulator.Stop;
TESCCalibrationForm.Execute(Self);
if Assigned(Simulator) then
Simulator.Start;
end;
procedure TMainForm.LoadSimulators;
var
simulator: TRegisteredSimulator;
begin
SimulatorComboBox.Items.BeginUpdate;
try
SimulatorComboBox.Items.Clear;
for simulator in GetRegisteredSimulators do
SimulatorComboBox.Items.AddObject(simulator.Name, simulator);
SimulatorComboBox.ItemIndex := 0;
finally
SimulatorComboBox.Items.EndUpdate;
end;
SelectSimulator;
end;
procedure TMainForm.RefreshPorts;
var
currentPort: string;
begin
currentPort := '';
if PortComboBox.ItemIndex > -1 then
currentPort := PortComboBox.Items[PortComboBox.ItemIndex];
EnumComPorts(PortComboBox.Items);
if Length(currentPort) > 0 then
PortComboBox.ItemIndex := PortComboBox.Items.IndexOf(currentPort);
TryConnect;
end;
procedure TMainForm.InitDevice(const AInfo: string);
var
info: TStringList;
value: string;
fanCount: Integer;
begin
info := TStringList.Create;
try
for value in SplitString(AInfo, ',') do
info.Add(value);
if TryStrToInt(info.Values['Fans'], fanCount) then
begin
SetLength(FFanValues, fanCount);
FanCountLabel.Caption := IntToStr(fanCount);
end else
SetLength(FFanValues, 0);
CalibrationButton.Enabled := info.Values['Mode'] = 'Servo';
finally
FreeAndNil(info)
end;
end;
procedure TMainForm.SendCommand(const ACommand: string; AOnResponse: TProc<string>; AOnTimeout: TProc);
begin
if (not Assigned(ComPort)) or (not ComPort.Connected) then
exit;
StartCommand;
FOnResponse := AOnResponse;
FOnTimeout := AOnTimeout;
try
ComPort.WriteStr(ACommand + #10);
except
on E:Exception do
PortStatusLabel.Caption := 'Failed to send command';
end;
end;
function TMainForm.GetFanCount: Integer;
begin
Result := Length(FFanValues);
end;
function TMainForm.GetReady: Boolean;
begin
Result := FReady;
end;
function TMainForm.GetIsRunning: Boolean;
begin
Result := Ready and (GetMaxValue > 0);
end;
function TMainForm.GetValue(const AFan: Byte): Byte;
begin
if ValidFanIndex(AFan) then
Result := FFanValues[AFan]
else
Result := 0;
end;
function TMainForm.GetMaxValue: Byte;
var
value: Byte;
begin
Result := 0;
for value in FFanValues do
if value > Result then
Result := value;
end;
procedure TMainForm.SetValue(const AFan, AValue: Byte);
var
values: TStringBuilder;
value: Byte;
begin
if not Ready then
exit;
if ValidFanIndex(AFan) and (FFanValues[AFan] <> AValue) then
begin
FFanValues[AFan] := AValue;
values := TStringBuilder.Create;
try
for value in FFanValues do
begin
if values.Length > 0 then
values.Append(',');
values.Append(value);
end;
SendCommand('>SetFans:' + values.ToString, nil);
finally
FreeAndNil(values);
end;
end;
end;
procedure TMainForm.SetAll(const AValue: Byte);
var
fanIndex: Integer;
changed: Boolean;
begin
if not Ready then
exit;
changed := False;
for fanIndex := 0 to High(FFanValues) do
begin
if FFanValues[fanIndex] <> AValue then
begin
FFanValues[fanIndex] := AValue;
changed := True;
end;
end;
if changed then
SendCommand('>SetFans:A,' + IntToStr(AValue), nil);
end;
procedure TMainForm.SetFull;
var
fanIndex: Integer;
begin
if not Ready then
exit;
for fanIndex := 0 to High(FFanValues) do
FFanValues[fanIndex] := 255;
SendCommand('>SetFans:M', nil);
end;
procedure TMainForm.TryConnect;
var
newPort: string;
begin
if PortComboBox.ItemIndex = -1 then
exit;
ConnectTimer.Enabled := False;
FReady := False;
CalibrationButton.Enabled := False;
PortStatusLabel.Caption := 'Attempting to connect';
PortStatusLabel.Update;
if not Assigned(FComPort) then
begin
FComPort := TComPort.Create(Self);
FComPort.BaudRate := br19200;
FComPort.OnRxChar := OnReceiveChar;
FComPort.OnAfterClose := OnAfterClose;
end;
newPort := PortComboBox.Items[PortComboBox.ItemIndex];
if ComPort.Connected and (newPort <> ComPort.Port) then
ComPort.Close;
ComPort.Port := newPort;
try
ComPort.Open;
{ The Arduino resets when a serial connection is made, wait for a bit
before sending the first Info command }
ConnectWaitTimer.Enabled := True;
except
on E:Exception do
begin
PortStatusLabel.Caption := 'Failed to connect';
ConnectTimer.Enabled := True;
end;
end;
end;
procedure TMainForm.ConnectWaitTimerTimer(Sender: TObject);
begin
ConnectWaitTimer.Enabled := False;
SendCommand(#10'>Info',
procedure(Response: string)
begin
if AnsiStartsText('<Info:', Response) then
begin
PortStatusLabel.Caption := 'Connected';
InitDevice(Copy(Response, 7, MaxInt));
FReady := True;
end else
begin
PortStatusLabel.Caption := 'Invalid response';
ConnectTimer.Enabled := True;
end;
end,
procedure
begin
PortStatusLabel.Caption := 'No response';
ConnectTimer.Enabled := True;
end);
end;
procedure TMainForm.OnReceiveChar(Sender: TObject; Count: Integer);
var
data: string;
terminatorPos: Integer;
begin
(Sender as TComPort).ReadStr(data, Count);
FReceived := FReceived + data;
terminatorPos := AnsiPos(#10, FReceived);
if terminatorPos > 0 then
begin
// Since the protocol is quite synchronous, this is good enough for now
SetLength(FReceived, terminatorPos - 1);
if Assigned(FOnResponse) then
FOnResponse(FReceived);
EndCommand;
end;
end;
procedure TMainForm.OnAfterClose(Sender: TObject);
begin
PortStatusLabel.Caption := 'Not connected';
ConnectTimer.Enabled := True;
end;
function TMainForm.ValidFanIndex(AFan: Byte): Boolean;
begin
Result := AFan < Length(FFanValues);
end;
procedure TMainForm.SelectSimulator;
var
registeredSimulator: TRegisteredSimulator;
begin
if Assigned(FSimulator) then
begin
FSimulator.Stop;
FSimulator := nil;
end;
if SimulatorComboBox.ItemIndex = -1 then
exit;
registeredSimulator := (SimulatorComboBox.Items.Objects[SimulatorComboBox.ItemIndex] as TRegisteredSimulator);
FSimulator := registeredSimulator.ConstructorFunc(Self);
Simulator.SetUIParent(SimulatorPanel);
Simulator.Start;
end;
procedure TMainForm.ConnectTimerTimer(Sender: TObject);
begin
TryConnect;
end;
procedure TMainForm.ResponseTimerTimer(Sender: TObject);
begin
if Assigned(FOnTimeout) then
FOnTimeout;
EndCommand;
end;
procedure TMainForm.StartCommand;
begin
FReceived := '';
ResponseTimer.Enabled := True;
end;
procedure TMainForm.EndCommand;
begin
FReceived := '';
FOnResponse := nil;
FOnTimeout := nil;
ResponseTimer.Enabled := False;
end;
end.