Added Servo mode
Refactored client to allow more simulators
11
.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
AssettoCorsa/source/__history
|
||||
AssettoCorsa/*.dproj.local
|
||||
AssettoCorsa/*.identcache
|
||||
AssettoCorsa/bin
|
||||
AssettoCorsa/lib
|
||||
Client/source/__history
|
||||
Client/*.dproj.local
|
||||
Client/*.identcache
|
||||
Client/bin
|
||||
Client/lib
|
||||
Client/__history
|
@ -1,19 +0,0 @@
|
||||
program AssettoCorsaSF;
|
||||
|
||||
uses
|
||||
Vcl.Forms,
|
||||
MainFrm in 'source\MainFrm.pas' {MainForm},
|
||||
AssettoCorsa.SharedMemory in 'source\AssettoCorsa.SharedMemory.pas',
|
||||
CPort in 'source\CPort.pas';
|
||||
|
||||
{$R *.res}
|
||||
|
||||
var
|
||||
MainForm: TMainForm;
|
||||
|
||||
begin
|
||||
Application.Initialize;
|
||||
Application.MainFormOnTaskbar := True;
|
||||
Application.CreateForm(TMainForm, MainForm);
|
||||
Application.Run;
|
||||
end.
|
@ -1,115 +0,0 @@
|
||||
object MainForm: TMainForm
|
||||
Left = 0
|
||||
Top = 0
|
||||
BorderIcons = [biSystemMenu, biMinimize]
|
||||
BorderStyle = bsSingle
|
||||
Caption = 'SimulatorFans - Assetto Corsa'
|
||||
ClientHeight = 390
|
||||
ClientWidth = 645
|
||||
Color = clBtnFace
|
||||
Font.Charset = DEFAULT_CHARSET
|
||||
Font.Color = clWindowText
|
||||
Font.Height = -11
|
||||
Font.Name = 'Tahoma'
|
||||
Font.Style = []
|
||||
OldCreateOrder = False
|
||||
OnCreate = FormCreate
|
||||
PixelsPerInch = 96
|
||||
TextHeight = 13
|
||||
object PortComboBox: TComboBox
|
||||
Left = 24
|
||||
Top = 24
|
||||
Width = 201
|
||||
Height = 21
|
||||
Style = csDropDownList
|
||||
TabOrder = 0
|
||||
end
|
||||
object RefreshPortsButton: TButton
|
||||
Left = 231
|
||||
Top = 23
|
||||
Width = 75
|
||||
Height = 23
|
||||
Caption = 'Refresh'
|
||||
TabOrder = 1
|
||||
OnClick = RefreshPortsButtonClick
|
||||
end
|
||||
object Button1: TButton
|
||||
Left = 192
|
||||
Top = 176
|
||||
Width = 75
|
||||
Height = 25
|
||||
Caption = 'Connect'
|
||||
TabOrder = 2
|
||||
OnClick = Button1Click
|
||||
end
|
||||
object Button2: TButton
|
||||
Left = 288
|
||||
Top = 176
|
||||
Width = 75
|
||||
Height = 25
|
||||
Caption = 'Fan 1 full'
|
||||
TabOrder = 3
|
||||
OnClick = Button2Click
|
||||
end
|
||||
object Button3: TButton
|
||||
Left = 384
|
||||
Top = 176
|
||||
Width = 75
|
||||
Height = 25
|
||||
Caption = 'Fan 2 full'
|
||||
TabOrder = 4
|
||||
OnClick = Button3Click
|
||||
end
|
||||
object Button4: TButton
|
||||
Left = 480
|
||||
Top = 176
|
||||
Width = 75
|
||||
Height = 25
|
||||
Caption = 'Off'
|
||||
TabOrder = 5
|
||||
OnClick = Button4Click
|
||||
end
|
||||
object Button5: TButton
|
||||
Left = 288
|
||||
Top = 207
|
||||
Width = 75
|
||||
Height = 25
|
||||
Caption = 'Fan 1 half'
|
||||
TabOrder = 6
|
||||
OnClick = Button5Click
|
||||
end
|
||||
object Button6: TButton
|
||||
Left = 384
|
||||
Top = 207
|
||||
Width = 75
|
||||
Height = 25
|
||||
Caption = 'Fan 2 half'
|
||||
TabOrder = 7
|
||||
OnClick = Button6Click
|
||||
end
|
||||
object Edit1: TEdit
|
||||
Left = 288
|
||||
Top = 253
|
||||
Width = 75
|
||||
Height = 21
|
||||
TabOrder = 8
|
||||
Text = '0'
|
||||
end
|
||||
object Button7: TButton
|
||||
Left = 288
|
||||
Top = 280
|
||||
Width = 171
|
||||
Height = 25
|
||||
Caption = 'Set'
|
||||
TabOrder = 9
|
||||
OnClick = Button7Click
|
||||
end
|
||||
object Edit2: TEdit
|
||||
Left = 384
|
||||
Top = 253
|
||||
Width = 75
|
||||
Height = 21
|
||||
TabOrder = 10
|
||||
Text = '0'
|
||||
end
|
||||
end
|
@ -1,208 +0,0 @@
|
||||
unit MainFrm;
|
||||
|
||||
interface
|
||||
uses
|
||||
System.Classes,
|
||||
System.SysUtils,
|
||||
Vcl.Controls,
|
||||
Vcl.ExtCtrls,
|
||||
Vcl.Forms,
|
||||
Vcl.StdCtrls,
|
||||
|
||||
CPort;
|
||||
|
||||
|
||||
type
|
||||
TMainForm = class(TForm)
|
||||
PortComboBox: TComboBox;
|
||||
RefreshPortsButton: TButton;
|
||||
Button1: TButton;
|
||||
Button2: TButton;
|
||||
Button3: TButton;
|
||||
Button4: TButton;
|
||||
Button5: TButton;
|
||||
Button6: TButton;
|
||||
Edit1: TEdit;
|
||||
Button7: TButton;
|
||||
Edit2: TEdit;
|
||||
|
||||
procedure FormCreate(Sender: TObject);
|
||||
procedure RefreshPortsButtonClick(Sender: TObject);
|
||||
procedure Button1Click(Sender: TObject);
|
||||
procedure Button2Click(Sender: TObject);
|
||||
procedure Button3Click(Sender: TObject);
|
||||
procedure Button4Click(Sender: TObject);
|
||||
procedure Button5Click(Sender: TObject);
|
||||
procedure Button6Click(Sender: TObject);
|
||||
procedure Button7Click(Sender: TObject);
|
||||
private
|
||||
FComPort: TComPort;
|
||||
FReceived: string;
|
||||
FOnResponse: TProc<string>;
|
||||
|
||||
procedure OnReceiveChar(Sender: TObject; Count: Integer);
|
||||
|
||||
procedure RefreshPorts;
|
||||
procedure SendCommand(const ACommand: string; AOnResponse: TProc<string>);
|
||||
|
||||
property ComPort: TComPort read FComPort;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.RegularExpressions, Vcl.Dialogs;
|
||||
|
||||
{$R *.dfm}
|
||||
|
||||
procedure TMainForm.Button1Click(Sender: TObject);
|
||||
begin
|
||||
SendCommand('>Info',
|
||||
procedure(Response: string)
|
||||
var
|
||||
match: TMatch;
|
||||
|
||||
begin
|
||||
match := TRegEx.Match(Response, '<Info:Fans=(\d+)', [roSingleLine]);
|
||||
if match.Success then
|
||||
ShowMessage('Connected, got ' + match.Groups[1].Value + ' fan(s)')
|
||||
else
|
||||
ShowMessage('Invalid response: ' + Response);
|
||||
end);
|
||||
end;
|
||||
|
||||
procedure TMainForm.Button2Click(Sender: TObject);
|
||||
begin
|
||||
Edit1.Text := '255';
|
||||
SendCommand('>SetFans:255,0',
|
||||
procedure(Response: string)
|
||||
begin
|
||||
if Response <> '<SetFans' then
|
||||
ShowMessage('Invalid response: ' + Response);
|
||||
end);
|
||||
end;
|
||||
|
||||
procedure TMainForm.Button3Click(Sender: TObject);
|
||||
begin
|
||||
Edit2.Text := '255';
|
||||
SendCommand('>SetFans:0,255',
|
||||
procedure(Response: string)
|
||||
begin
|
||||
if Response <> '<SetFans' then
|
||||
ShowMessage('Invalid response: ' + Response);
|
||||
end);
|
||||
end;
|
||||
|
||||
procedure TMainForm.Button4Click(Sender: TObject);
|
||||
begin
|
||||
Edit1.Text := '0';
|
||||
Edit2.Text := '0';
|
||||
SendCommand('>SetFans:0,0',
|
||||
procedure(Response: string)
|
||||
begin
|
||||
if Response <> '<SetFans' then
|
||||
ShowMessage('Invalid response: ' + Response);
|
||||
end);
|
||||
end;
|
||||
|
||||
procedure TMainForm.Button5Click(Sender: TObject);
|
||||
begin
|
||||
Edit1.Text := '128';
|
||||
SendCommand('>SetFans:128,0',
|
||||
procedure(Response: string)
|
||||
begin
|
||||
if Response <> '<SetFans' then
|
||||
ShowMessage('Invalid response: ' + Response);
|
||||
end);
|
||||
end;
|
||||
|
||||
procedure TMainForm.Button6Click(Sender: TObject);
|
||||
begin
|
||||
Edit2.Text := '128';
|
||||
SendCommand('>SetFans:0,128',
|
||||
procedure(Response: string)
|
||||
begin
|
||||
if Response <> '<SetFans' then
|
||||
ShowMessage('Invalid response: ' + Response);
|
||||
end);
|
||||
end;
|
||||
|
||||
procedure TMainForm.Button7Click(Sender: TObject);
|
||||
begin
|
||||
SendCommand('>SetFans:' + Edit1.Text + ',' + Edit2.Text,
|
||||
procedure(Response: string)
|
||||
begin
|
||||
if Response <> '<SetFans' then
|
||||
ShowMessage('Invalid response: ' + Response);
|
||||
end);
|
||||
end;
|
||||
|
||||
procedure TMainForm.FormCreate(Sender: TObject);
|
||||
begin
|
||||
RefreshPorts;
|
||||
PortComboBox.ItemIndex := Pred(PortComboBox.Items.Count);
|
||||
end;
|
||||
|
||||
|
||||
procedure TMainForm.RefreshPortsButtonClick(Sender: TObject);
|
||||
begin
|
||||
RefreshPorts;
|
||||
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);
|
||||
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);
|
||||
|
||||
FReceived := '';
|
||||
FOnResponse := nil;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TMainForm.SendCommand(const ACommand: string; AOnResponse: TProc<string>);
|
||||
begin
|
||||
if not Assigned(FComPort) then
|
||||
begin
|
||||
FComPort := TComPort.Create(Self);
|
||||
FComPort.Port := PortComboBox.Items[PortComboBox.ItemIndex];
|
||||
FComPort.BaudRate := br19200;
|
||||
FComPort.OnRxChar := OnReceiveChar;
|
||||
FComPort.Open;
|
||||
end;
|
||||
|
||||
FOnResponse := AOnResponse;
|
||||
ComPort.WriteStr(ACommand + #10);
|
||||
end;
|
||||
|
||||
end.
|
24
Client/SimulatorFans.dpr
Normal file
@ -0,0 +1,24 @@
|
||||
program SimulatorFans;
|
||||
|
||||
uses
|
||||
Vcl.Forms,
|
||||
MainFrm in 'source\MainFrm.pas' {MainForm},
|
||||
AssettoCorsa.SharedMemory in 'source\AssettoCorsa.SharedMemory.pas',
|
||||
CPort in 'source\CPort.pas',
|
||||
Simulator.Registry in 'source\Simulator.Registry.pas',
|
||||
Simulator.Base in 'source\Simulator.Base.pas' {BaseSimulatorFrame: TFrame},
|
||||
Simulator.AssettoCorsa in 'source\Simulator.AssettoCorsa.pas' {AssettoCorsaFrame: TFrame},
|
||||
ESCCalibrationFrm in 'source\ESCCalibrationFrm.pas' {ESCCalibrationForm},
|
||||
Simulator.Manual in 'source\Simulator.Manual.pas' {ManualFrame: TFrame};
|
||||
|
||||
{$R *.res}
|
||||
|
||||
var
|
||||
MainForm: TMainForm;
|
||||
|
||||
begin
|
||||
Application.Initialize;
|
||||
Application.MainFormOnTaskbar := True;
|
||||
Application.CreateForm(TMainForm, MainForm);
|
||||
Application.Run;
|
||||
end.
|
@ -3,7 +3,7 @@
|
||||
<ProjectGuid>{3CBA51E1-33C8-4599-8659-3A1F26AA5E70}</ProjectGuid>
|
||||
<ProjectVersion>13.4</ProjectVersion>
|
||||
<FrameworkType>VCL</FrameworkType>
|
||||
<MainSource>AssettoCorsaSF.dpr</MainSource>
|
||||
<MainSource>SimulatorFans.dpr</MainSource>
|
||||
<Base>True</Base>
|
||||
<Config Condition="'$(Config)'==''">Debug</Config>
|
||||
<Platform Condition="'$(Platform)'==''">Win32</Platform>
|
||||
@ -41,10 +41,10 @@
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Base)'!=''">
|
||||
<VerInfo_Keys>CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
|
||||
<Manifest_File>None</Manifest_File>
|
||||
<Manifest_File>$(BDS)\bin\default_app.manifest</Manifest_File>
|
||||
<VerInfo_Locale>1043</VerInfo_Locale>
|
||||
<DCC_Namespace>System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace)</DCC_Namespace>
|
||||
<Icon_MainIcon>$(BDS)\bin\delphi_PROJECTICON.ico</Icon_MainIcon>
|
||||
<Icon_MainIcon>..\Logo\SimulatorFans.ico</Icon_MainIcon>
|
||||
<DCC_UsePackage>bindcompfmx;fmx;rtl;dbrtl;IndySystem;DbxClientDriver;bindcomp;inetdb;DBXInterBaseDriver;DataSnapCommon;DataSnapClient;DataSnapServer;DataSnapProviderClient;xmlrtl;DbxCommonDriver;IndyProtocols;DBXMySQLDriver;dbxcds;soaprtl;FMXTee;bindengine;DBXOracleDriver;CustomIPTransport;dsnap;DBXInformixDriver;IndyCore;FmxTeeUI;DBXFirebirdDriver;inet;inetdbxpress;DBXSybaseASADriver;IPIndyImpl;dbexpress;DataSnapIndy10ServerTransport;$(DCC_UsePackage)</DCC_UsePackage>
|
||||
<DCC_DcuOutput>.\$(Platform)\$(Config)</DCC_DcuOutput>
|
||||
<DCC_ExeOutput>.\$(Platform)\$(Config)</DCC_ExeOutput>
|
||||
@ -74,6 +74,7 @@
|
||||
<DCC_RemoteDebug>true</DCC_RemoteDebug>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Cfg_1_Win32)'!=''">
|
||||
<Manifest_File>$(BDS)\bin\default_app.manifest</Manifest_File>
|
||||
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
|
||||
<VerInfo_Locale>1033</VerInfo_Locale>
|
||||
<DCC_RemoteDebug>false</DCC_RemoteDebug>
|
||||
@ -94,6 +95,23 @@
|
||||
</DCCReference>
|
||||
<DCCReference Include="source\AssettoCorsa.SharedMemory.pas"/>
|
||||
<DCCReference Include="source\CPort.pas"/>
|
||||
<DCCReference Include="source\Simulator.Registry.pas"/>
|
||||
<DCCReference Include="source\Simulator.Base.pas">
|
||||
<Form>BaseSimulatorFrame</Form>
|
||||
<DesignClass>TFrame</DesignClass>
|
||||
</DCCReference>
|
||||
<DCCReference Include="source\Simulator.AssettoCorsa.pas">
|
||||
<Form>AssettoCorsaFrame</Form>
|
||||
<DesignClass>TFrame</DesignClass>
|
||||
</DCCReference>
|
||||
<DCCReference Include="source\ESCCalibrationFrm.pas">
|
||||
<Form>ESCCalibrationForm</Form>
|
||||
<FormType>dfm</FormType>
|
||||
</DCCReference>
|
||||
<DCCReference Include="source\Simulator.Manual.pas">
|
||||
<Form>ManualFrame</Form>
|
||||
<DesignClass>TFrame</DesignClass>
|
||||
</DCCReference>
|
||||
<BuildConfiguration Include="Release">
|
||||
<Key>Cfg_2</Key>
|
||||
<CfgParent>Base</CfgParent>
|
||||
@ -112,7 +130,7 @@
|
||||
<BorlandProject>
|
||||
<Delphi.Personality>
|
||||
<Source>
|
||||
<Source Name="MainSource">AssettoCorsaSF.dpr</Source>
|
||||
<Source Name="MainSource">SimulatorFans.dpr</Source>
|
||||
</Source>
|
||||
<VersionInfo>
|
||||
<VersionInfo Name="IncludeVerInfo">False</VersionInfo>
|
BIN
Client/SimulatorFans.res
Normal file
BIN
Client/resources/lock.bmp
Normal file
After Width: | Height: | Size: 488 B |
92
Client/source/ESCCalibrationFrm.dfm
Normal file
@ -0,0 +1,92 @@
|
||||
object ESCCalibrationForm: TESCCalibrationForm
|
||||
Left = 0
|
||||
Top = 0
|
||||
ActiveControl = CloseButton
|
||||
BorderStyle = bsDialog
|
||||
Caption = 'ESC calibration'
|
||||
ClientHeight = 213
|
||||
ClientWidth = 565
|
||||
Color = clBtnFace
|
||||
Font.Charset = DEFAULT_CHARSET
|
||||
Font.Color = clWindowText
|
||||
Font.Height = -11
|
||||
Font.Name = 'Tahoma'
|
||||
Font.Style = []
|
||||
OldCreateOrder = False
|
||||
Position = poMainFormCenter
|
||||
OnClose = FormClose
|
||||
DesignSize = (
|
||||
565
|
||||
213)
|
||||
PixelsPerInch = 96
|
||||
TextHeight = 13
|
||||
object CloseButton: TButton
|
||||
Left = 245
|
||||
Top = 179
|
||||
Width = 75
|
||||
Height = 25
|
||||
Anchors = [akLeft, akBottom]
|
||||
Cancel = True
|
||||
Caption = 'Close'
|
||||
ModalResult = 8
|
||||
TabOrder = 0
|
||||
end
|
||||
object CalibrationGroupBox: TGroupBox
|
||||
Left = 8
|
||||
Top = 8
|
||||
Width = 549
|
||||
Height = 161
|
||||
Anchors = [akLeft, akTop, akRight, akBottom]
|
||||
TabOrder = 1
|
||||
ExplicitWidth = 525
|
||||
DesignSize = (
|
||||
549
|
||||
161)
|
||||
object HelpLabel1: TLabel
|
||||
Left = 16
|
||||
Top = 16
|
||||
Width = 521
|
||||
Height = 33
|
||||
Anchors = [akLeft, akTop, akRight]
|
||||
AutoSize = False
|
||||
Caption =
|
||||
'You can use the Full button below to set the PWM output to it'#39's ' +
|
||||
'maximum value, disregarding the regular maximum value as program' +
|
||||
'med into the SimulatorFans device.'
|
||||
WordWrap = True
|
||||
ExplicitWidth = 497
|
||||
end
|
||||
object HelpLabel2: TLabel
|
||||
Left = 16
|
||||
Top = 55
|
||||
Width = 521
|
||||
Height = 34
|
||||
Anchors = [akLeft, akTop, akRight]
|
||||
AutoSize = False
|
||||
Caption =
|
||||
'Disconnect your Electronic Speed Controller before continuing, o' +
|
||||
'r it can spin up the motor to it'#39's full power. USE WITH CARE!'
|
||||
WordWrap = True
|
||||
ExplicitWidth = 497
|
||||
end
|
||||
object FullButton: TButton
|
||||
Left = 200
|
||||
Top = 104
|
||||
Width = 75
|
||||
Height = 25
|
||||
Caption = 'Full'
|
||||
TabOrder = 0
|
||||
OnClick = FullButtonClick
|
||||
end
|
||||
object OffButton: TButton
|
||||
Left = 281
|
||||
Top = 104
|
||||
Width = 75
|
||||
Height = 25
|
||||
Caption = 'Off'
|
||||
Enabled = False
|
||||
TabOrder = 1
|
||||
OnClick = OffButtonClick
|
||||
end
|
||||
end
|
||||
end
|
78
Client/source/ESCCalibrationFrm.pas
Normal file
@ -0,0 +1,78 @@
|
||||
unit ESCCalibrationFrm;
|
||||
|
||||
interface
|
||||
uses
|
||||
System.Classes,
|
||||
Vcl.Controls,
|
||||
Vcl.Forms,
|
||||
Vcl.StdCtrls,
|
||||
|
||||
Simulator.Registry;
|
||||
|
||||
|
||||
type
|
||||
TESCCalibrationForm = class(TForm)
|
||||
CloseButton: TButton;
|
||||
CalibrationGroupBox: TGroupBox;
|
||||
HelpLabel1: TLabel;
|
||||
HelpLabel2: TLabel;
|
||||
FullButton: TButton;
|
||||
OffButton: TButton;
|
||||
procedure FormClose(Sender: TObject; var Action: TCloseAction);
|
||||
procedure FullButtonClick(Sender: TObject);
|
||||
procedure OffButtonClick(Sender: TObject);
|
||||
private
|
||||
FFans: ISimulatorFans;
|
||||
FFull: Boolean;
|
||||
protected
|
||||
property Fans: ISimulatorFans read FFans;
|
||||
public
|
||||
class procedure Execute(AFans: ISimulatorFans);
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
|
||||
|
||||
{$R *.dfm}
|
||||
|
||||
{ TESCCalibrationForm }
|
||||
class procedure TESCCalibrationForm.Execute(AFans: ISimulatorFans);
|
||||
begin
|
||||
with Self.Create(nil) do
|
||||
try
|
||||
FFans := AFans;
|
||||
ShowModal;
|
||||
finally
|
||||
Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TESCCalibrationForm.FormClose(Sender: TObject; var Action: TCloseAction);
|
||||
begin
|
||||
if FFull then
|
||||
Fans.SetAll(0);
|
||||
end;
|
||||
|
||||
|
||||
procedure TESCCalibrationForm.FullButtonClick(Sender: TObject);
|
||||
begin
|
||||
FullButton.Enabled := False;
|
||||
OffButton.Enabled := True;
|
||||
|
||||
Fans.SetFull;
|
||||
FFull := True;
|
||||
end;
|
||||
|
||||
|
||||
procedure TESCCalibrationForm.OffButtonClick(Sender: TObject);
|
||||
begin
|
||||
FullButton.Enabled := True;
|
||||
OffButton.Enabled := False;
|
||||
|
||||
Fans.SetAll(0);
|
||||
FFull := False;
|
||||
end;
|
||||
|
||||
end.
|
184
Client/source/MainFrm.dfm
Normal file
@ -0,0 +1,184 @@
|
||||
object MainForm: TMainForm
|
||||
Left = 0
|
||||
Top = 0
|
||||
BorderIcons = [biSystemMenu, biMinimize]
|
||||
BorderStyle = bsSingle
|
||||
Caption = 'SimulatorFans'
|
||||
ClientHeight = 352
|
||||
ClientWidth = 580
|
||||
Color = clBtnFace
|
||||
Font.Charset = DEFAULT_CHARSET
|
||||
Font.Color = clWindowText
|
||||
Font.Height = -11
|
||||
Font.Name = 'Tahoma'
|
||||
Font.Style = []
|
||||
OldCreateOrder = False
|
||||
Position = poScreenCenter
|
||||
ShowHint = True
|
||||
OnClose = FormClose
|
||||
OnCreate = FormCreate
|
||||
PixelsPerInch = 96
|
||||
TextHeight = 13
|
||||
object HardwareGroupBox: TGroupBox
|
||||
AlignWithMargins = True
|
||||
Left = 8
|
||||
Top = 8
|
||||
Width = 564
|
||||
Height = 137
|
||||
Margins.Left = 8
|
||||
Margins.Top = 8
|
||||
Margins.Right = 8
|
||||
Margins.Bottom = 8
|
||||
Align = alTop
|
||||
Caption = ' Hardware configuration '
|
||||
TabOrder = 0
|
||||
object PortLabel: TLabel
|
||||
Left = 12
|
||||
Top = 27
|
||||
Width = 50
|
||||
Height = 13
|
||||
Caption = 'COM port:'
|
||||
end
|
||||
object PortStatusLabel: TLabel
|
||||
Left = 288
|
||||
Top = 27
|
||||
Width = 121
|
||||
Height = 13
|
||||
AutoSize = False
|
||||
Caption = 'Not connected'
|
||||
end
|
||||
object FansLabel: TLabel
|
||||
Left = 12
|
||||
Top = 54
|
||||
Width = 27
|
||||
Height = 13
|
||||
Caption = 'Fans:'
|
||||
end
|
||||
object FanCountLabel: TLabel
|
||||
Left = 88
|
||||
Top = 54
|
||||
Width = 185
|
||||
Height = 13
|
||||
AutoSize = False
|
||||
Caption = '?'
|
||||
end
|
||||
object PortComboBox: TComboBox
|
||||
Left = 88
|
||||
Top = 24
|
||||
Width = 185
|
||||
Height = 21
|
||||
Style = csDropDownList
|
||||
TabOrder = 0
|
||||
OnClick = PortComboBoxClick
|
||||
end
|
||||
object CalibrationButton: TButton
|
||||
Left = 88
|
||||
Top = 96
|
||||
Width = 113
|
||||
Height = 25
|
||||
Caption = 'ESC calibration...'
|
||||
Enabled = False
|
||||
TabOrder = 1
|
||||
OnClick = CalibrationButtonClick
|
||||
end
|
||||
end
|
||||
object SimulatorGroupBox: TGroupBox
|
||||
AlignWithMargins = True
|
||||
Left = 8
|
||||
Top = 153
|
||||
Width = 564
|
||||
Height = 191
|
||||
Margins.Left = 8
|
||||
Margins.Top = 0
|
||||
Margins.Right = 8
|
||||
Margins.Bottom = 8
|
||||
Align = alClient
|
||||
Caption = ' Simulator '
|
||||
TabOrder = 1
|
||||
object SimulatorPanel: TPanel
|
||||
AlignWithMargins = True
|
||||
Left = 14
|
||||
Top = 68
|
||||
Width = 536
|
||||
Height = 109
|
||||
Margins.Left = 12
|
||||
Margins.Top = 12
|
||||
Margins.Right = 12
|
||||
Margins.Bottom = 12
|
||||
Align = alClient
|
||||
BevelOuter = bvNone
|
||||
TabOrder = 0
|
||||
ExplicitTop = 56
|
||||
ExplicitHeight = 121
|
||||
end
|
||||
object SimulatorSelectionPanel: TPanel
|
||||
Left = 2
|
||||
Top = 15
|
||||
Width = 560
|
||||
Height = 41
|
||||
Align = alTop
|
||||
BevelOuter = bvNone
|
||||
TabOrder = 1
|
||||
ExplicitLeft = 192
|
||||
ExplicitTop = 72
|
||||
ExplicitWidth = 185
|
||||
DesignSize = (
|
||||
560
|
||||
41)
|
||||
object SimulatorLockButton: TSpeedButton
|
||||
Left = 527
|
||||
Top = 7
|
||||
Width = 23
|
||||
Height = 23
|
||||
Hint =
|
||||
'Lock the simulator selection dropdown to prevent accidental chan' +
|
||||
'ges'
|
||||
AllowAllUp = True
|
||||
GroupIndex = 1
|
||||
Glyph.Data = {
|
||||
E6010000424DE60100000000000036000000280000000C0000000C0000000100
|
||||
180000000000B0010000120B0000120B00000000000000000000FF00FF000000
|
||||
000000000000000000000000000000000000000000000000000000FF00FFFF00
|
||||
FF000000000000000000000000000000000000000000000000000000000000FF
|
||||
00FFFF00FF000000000000000000000000000000000000000000000000000000
|
||||
000000FF00FFFF00FF0000000000000000000000000000000000000000000000
|
||||
00000000000000FF00FFFF00FF00000000000000000000000000000000000000
|
||||
0000000000000000000000FF00FFFF00FF000000000000000000000000000000
|
||||
000000000000000000000000000000FF00FFFF00FF0000000000000000000000
|
||||
00000000000000000000000000000000000000FF00FFFF00FFFF00FF00000000
|
||||
0000FF00FFFF00FFFF00FFFF00FF000000000000FF00FFFF00FFFF00FFFF00FF
|
||||
000000000000FF00FFFF00FFFF00FFFF00FF000000000000FF00FFFF00FFFF00
|
||||
FFFF00FF000000000000FF00FFFF00FFFF00FFFF00FF000000000000FF00FFFF
|
||||
00FFFF00FFFF00FFFF00FF000000000000000000000000000000000000FF00FF
|
||||
FF00FFFF00FFFF00FFFF00FFFF00FFFF00FF000000000000000000000000FF00
|
||||
FFFF00FFFF00FFFF00FF}
|
||||
OnClick = SimulatorLockButtonClick
|
||||
end
|
||||
object SimulatorComboBox: TComboBox
|
||||
Left = 12
|
||||
Top = 8
|
||||
Width = 509
|
||||
Height = 21
|
||||
Style = csDropDownList
|
||||
Anchors = [akLeft, akTop, akRight]
|
||||
Sorted = True
|
||||
TabOrder = 0
|
||||
OnClick = SimulatorComboBoxClick
|
||||
end
|
||||
end
|
||||
end
|
||||
object ConnectTimer: TTimer
|
||||
Enabled = False
|
||||
Interval = 5000
|
||||
OnTimer = ConnectTimerTimer
|
||||
Left = 424
|
||||
Top = 24
|
||||
end
|
||||
object ResponseTimer: TTimer
|
||||
Enabled = False
|
||||
Interval = 2000
|
||||
OnTimer = ResponseTimerTimer
|
||||
Left = 424
|
||||
Top = 80
|
||||
end
|
||||
end
|
466
Client/source/MainFrm.pas
Normal file
@ -0,0 +1,466 @@
|
||||
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;
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
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
|
||||
SendCommand('>SetFans:0',
|
||||
procedure(Response: string)
|
||||
begin
|
||||
FReady := False;
|
||||
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;
|
||||
ComPort.WriteStr(ACommand + #10);
|
||||
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 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;
|
||||
|
||||
begin
|
||||
for fanIndex := 0 to High(FFanValues) do
|
||||
FFanValues[fanIndex] := AValue;
|
||||
|
||||
SendCommand('>SetFans:A,' + IntToStr(AValue), nil);
|
||||
end;
|
||||
|
||||
|
||||
procedure TMainForm.SetFull;
|
||||
var
|
||||
fanIndex: Integer;
|
||||
|
||||
begin
|
||||
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;
|
||||
end;
|
||||
|
||||
newPort := PortComboBox.Items[PortComboBox.ItemIndex];
|
||||
if ComPort.Connected and (newPort <> ComPort.Port) then
|
||||
ComPort.Close;
|
||||
|
||||
ComPort.Port := newPort;
|
||||
try
|
||||
ComPort.Open;
|
||||
|
||||
SendCommand('>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);
|
||||
except
|
||||
on E:Exception do
|
||||
begin
|
||||
PortStatusLabel.Caption := 'Failed to connect';
|
||||
ConnectTimer.Enabled := True;
|
||||
end;
|
||||
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;
|
||||
|
||||
|
||||
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.
|
72
Client/source/Simulator.AssettoCorsa.dfm
Normal file
@ -0,0 +1,72 @@
|
||||
inherited AssettoCorsaFrame: TAssettoCorsaFrame
|
||||
Width = 544
|
||||
Height = 78
|
||||
ParentFont = False
|
||||
ExplicitWidth = 544
|
||||
ExplicitHeight = 78
|
||||
DesignSize = (
|
||||
544
|
||||
78)
|
||||
object StatusCaptionLabel: TLabel
|
||||
Left = 0
|
||||
Top = 0
|
||||
Width = 35
|
||||
Height = 13
|
||||
Caption = 'Status:'
|
||||
end
|
||||
object StatusLabel: TLabel
|
||||
Left = 92
|
||||
Top = 0
|
||||
Width = 452
|
||||
Height = 13
|
||||
Anchors = [akLeft, akTop, akRight]
|
||||
AutoSize = False
|
||||
Caption = 'Checking...'
|
||||
end
|
||||
object CurrentSpeedCaptionLabel: TLabel
|
||||
Left = 0
|
||||
Top = 28
|
||||
Width = 73
|
||||
Height = 13
|
||||
Caption = 'Current speed:'
|
||||
end
|
||||
object FanFullSpeedCaptionLabel: TLabel
|
||||
Left = 0
|
||||
Top = 56
|
||||
Width = 84
|
||||
Height = 13
|
||||
Caption = 'Fan full speed at:'
|
||||
end
|
||||
object FanFullSpeedUnitsLabel: TLabel
|
||||
Left = 163
|
||||
Top = 56
|
||||
Width = 23
|
||||
Height = 13
|
||||
Caption = 'km/h'
|
||||
end
|
||||
object CurrentSpeedLabel: TLabel
|
||||
Left = 92
|
||||
Top = 28
|
||||
Width = 94
|
||||
Height = 13
|
||||
AutoSize = False
|
||||
Caption = '0 km/h'
|
||||
end
|
||||
object FanFullSpeedEdit: TSpinEdit
|
||||
Left = 92
|
||||
Top = 53
|
||||
Width = 65
|
||||
Height = 22
|
||||
MaxValue = 2147483647
|
||||
MinValue = 1
|
||||
TabOrder = 0
|
||||
Value = 300
|
||||
end
|
||||
object SimTimer: TTimer
|
||||
Enabled = False
|
||||
Interval = 250
|
||||
OnTimer = SimTimerTimer
|
||||
Left = 480
|
||||
Top = 24
|
||||
end
|
||||
end
|
114
Client/source/Simulator.AssettoCorsa.pas
Normal file
@ -0,0 +1,114 @@
|
||||
unit Simulator.AssettoCorsa;
|
||||
|
||||
// #ToDo1 -oMvR: 2-9-2017: configurable speed for full fan power
|
||||
|
||||
interface
|
||||
uses
|
||||
System.Classes,
|
||||
Vcl.Controls,
|
||||
Vcl.ExtCtrls,
|
||||
Vcl.Forms,
|
||||
Vcl.StdCtrls,
|
||||
|
||||
Simulator.Base,
|
||||
Simulator.Registry, Vcl.Samples.Spin;
|
||||
|
||||
|
||||
type
|
||||
TAssettoCorsaFrame = class(TSimulatorBaseFrame)
|
||||
SimTimer: TTimer;
|
||||
StatusCaptionLabel: TLabel;
|
||||
StatusLabel: TLabel;
|
||||
CurrentSpeedCaptionLabel: TLabel;
|
||||
FanFullSpeedCaptionLabel: TLabel;
|
||||
FanFullSpeedUnitsLabel: TLabel;
|
||||
FanFullSpeedEdit: TSpinEdit;
|
||||
CurrentSpeedLabel: TLabel;
|
||||
|
||||
procedure SimTimerTimer(Sender: TObject);
|
||||
public
|
||||
procedure Initialize; override;
|
||||
|
||||
procedure Start; override;
|
||||
procedure Stop; override;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
System.SysUtils,
|
||||
|
||||
AssettoCorsa.SharedMemory;
|
||||
|
||||
|
||||
{$R *.dfm}
|
||||
|
||||
|
||||
{ TAssettoCorsaFrame }
|
||||
procedure TAssettoCorsaFrame.Initialize;
|
||||
begin
|
||||
inherited Initialize;
|
||||
|
||||
CurrentSpeedLabel.Caption := '';
|
||||
end;
|
||||
|
||||
|
||||
procedure TAssettoCorsaFrame.SimTimerTimer(Sender: TObject);
|
||||
var
|
||||
gameState: IAssettoCorsaSharedMemory;
|
||||
physics: TACSMPhysics;
|
||||
fanValue: Integer;
|
||||
|
||||
begin
|
||||
gameState := GetAssettoCorsaSharedMemory;
|
||||
if gameState.GetPhysics(physics) then
|
||||
begin
|
||||
StatusLabel.Caption := 'Connected';
|
||||
CurrentSpeedLabel.Caption := Format('%.0f km/h', [physics.speedKmh]);
|
||||
|
||||
if Fans.Ready then
|
||||
begin
|
||||
fanValue := Trunc((physics.speedKmh / FanFullSpeedEdit.Value) * 255);
|
||||
if fanValue > 255 then
|
||||
fanValue := 255;
|
||||
|
||||
Fans.SetAll(fanValue);
|
||||
end;
|
||||
|
||||
SimTimer.Interval := 250;
|
||||
end else
|
||||
begin
|
||||
StatusLabel.Caption := 'Assetto Corsa not running';
|
||||
CurrentSpeedLabel.Caption := '';
|
||||
SimTimer.Interval := 3000;
|
||||
|
||||
if Fans.IsRunning then
|
||||
Fans.SetAll(0);
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TAssettoCorsaFrame.Start;
|
||||
begin
|
||||
inherited Start;
|
||||
|
||||
SimTimer.Enabled := True;
|
||||
end;
|
||||
|
||||
|
||||
procedure TAssettoCorsaFrame.Stop;
|
||||
begin
|
||||
SimTimer.Enabled := False;
|
||||
|
||||
inherited Stop;
|
||||
end;
|
||||
|
||||
|
||||
initialization
|
||||
RegisterSimulator('AssettoCorsa', 'Assetto Corsa',
|
||||
function(AFans: ISimulatorFans): ISimulator
|
||||
begin
|
||||
Result := TSimulatorFrame<TAssettoCorsaFrame>.Create(AFans);
|
||||
end);
|
||||
|
||||
end.
|
7
Client/source/Simulator.Base.dfm
Normal file
@ -0,0 +1,7 @@
|
||||
object SimulatorBaseFrame: TSimulatorBaseFrame
|
||||
Left = 0
|
||||
Top = 0
|
||||
Width = 467
|
||||
Height = 165
|
||||
TabOrder = 0
|
||||
end
|
109
Client/source/Simulator.Base.pas
Normal file
@ -0,0 +1,109 @@
|
||||
unit Simulator.Base;
|
||||
|
||||
interface
|
||||
uses
|
||||
Vcl.Controls,
|
||||
Vcl.Forms,
|
||||
|
||||
Simulator.Registry;
|
||||
|
||||
|
||||
type
|
||||
TSimulatorBaseFrameClass = class of TSimulatorBaseFrame;
|
||||
|
||||
TSimulatorBaseFrame = class(TFrame)
|
||||
private
|
||||
FFans: ISimulatorFans;
|
||||
public
|
||||
constructor Create(AFans: ISimulatorFans); reintroduce; virtual;
|
||||
|
||||
procedure Initialize; virtual;
|
||||
|
||||
procedure Start; virtual;
|
||||
procedure Stop; virtual;
|
||||
|
||||
procedure SetUIParent(AParent: TWinControl); virtual;
|
||||
|
||||
property Fans: ISimulatorFans read FFans;
|
||||
end;
|
||||
|
||||
|
||||
TSimulatorFrame<T: TSimulatorBaseFrame> = class(TInterfacedObject, ISimulator)
|
||||
private
|
||||
FSimulatorFrame: TSimulatorBaseFrame;
|
||||
protected
|
||||
property SimulatorFrame: TSimulatorBaseFrame read FSimulatorFrame;
|
||||
public
|
||||
constructor Create(AFans: ISimulatorFans);
|
||||
|
||||
{ ISimulator }
|
||||
procedure Start;
|
||||
procedure Stop;
|
||||
|
||||
procedure SetUIParent(AParent: TWinControl);
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
|
||||
{$R *.dfm}
|
||||
|
||||
{ TSimulatorBaseFrame }
|
||||
constructor TSimulatorBaseFrame.Create(AFans: ISimulatorFans);
|
||||
begin
|
||||
inherited Create(nil);
|
||||
|
||||
FFans := AFans;
|
||||
Initialize;
|
||||
end;
|
||||
|
||||
|
||||
procedure TSimulatorBaseFrame.Initialize;
|
||||
begin
|
||||
end;
|
||||
|
||||
|
||||
procedure TSimulatorBaseFrame.Start;
|
||||
begin
|
||||
end;
|
||||
|
||||
|
||||
procedure TSimulatorBaseFrame.Stop;
|
||||
begin
|
||||
end;
|
||||
|
||||
|
||||
procedure TSimulatorBaseFrame.SetUIParent(AParent: TWinControl);
|
||||
begin
|
||||
Align := alClient;
|
||||
Parent := AParent;
|
||||
end;
|
||||
|
||||
|
||||
{ TSimulatorFrame<T> }
|
||||
constructor TSimulatorFrame<T>.Create(AFans: ISimulatorFans);
|
||||
begin
|
||||
inherited Create;
|
||||
|
||||
FSimulatorFrame := TSimulatorBaseFrameClass(T).Create(AFans);
|
||||
end;
|
||||
|
||||
|
||||
procedure TSimulatorFrame<T>.Start;
|
||||
begin
|
||||
FSimulatorFrame.Start;
|
||||
end;
|
||||
|
||||
|
||||
procedure TSimulatorFrame<T>.Stop;
|
||||
begin
|
||||
FSimulatorFrame.Stop;
|
||||
end;
|
||||
|
||||
|
||||
procedure TSimulatorFrame<T>.SetUIParent(AParent: TWinControl);
|
||||
begin
|
||||
FSimulatorFrame.SetUIParent(AParent);
|
||||
end;
|
||||
|
||||
end.
|
27
Client/source/Simulator.Manual.dfm
Normal file
@ -0,0 +1,27 @@
|
||||
inherited ManualFrame: TManualFrame
|
||||
Width = 544
|
||||
Height = 49
|
||||
ParentFont = False
|
||||
ExplicitWidth = 544
|
||||
ExplicitHeight = 49
|
||||
object ValueTrackBar: TTrackBar
|
||||
Left = 0
|
||||
Top = 0
|
||||
Width = 544
|
||||
Height = 41
|
||||
Align = alTop
|
||||
Max = 255
|
||||
Frequency = 5
|
||||
PositionToolTip = ptBottom
|
||||
ShowSelRange = False
|
||||
TabOrder = 0
|
||||
TickMarks = tmBoth
|
||||
end
|
||||
object UpdateTimer: TTimer
|
||||
Enabled = False
|
||||
Interval = 100
|
||||
OnTimer = UpdateTimerTimer
|
||||
Left = 488
|
||||
Top = 8
|
||||
end
|
||||
end
|
67
Client/source/Simulator.Manual.pas
Normal file
@ -0,0 +1,67 @@
|
||||
unit Simulator.Manual;
|
||||
|
||||
// #ToDo1 -oMvR: 2-9-2017: configurable speed for full fan power
|
||||
|
||||
interface
|
||||
uses
|
||||
System.Classes,
|
||||
Vcl.Controls,
|
||||
Vcl.ExtCtrls,
|
||||
Vcl.Forms,
|
||||
Vcl.StdCtrls,
|
||||
|
||||
Simulator.Base,
|
||||
Simulator.Registry, Vcl.Samples.Spin, Vcl.ComCtrls;
|
||||
|
||||
|
||||
type
|
||||
TManualFrame = class(TSimulatorBaseFrame)
|
||||
UpdateTimer: TTimer;
|
||||
ValueTrackBar: TTrackBar;
|
||||
|
||||
procedure UpdateTimerTimer(Sender: TObject);
|
||||
public
|
||||
procedure Start; override;
|
||||
procedure Stop; override;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
System.SysUtils;
|
||||
|
||||
|
||||
{$R *.dfm}
|
||||
|
||||
|
||||
{ TManualFrame }
|
||||
procedure TManualFrame.UpdateTimerTimer(Sender: TObject);
|
||||
begin
|
||||
Fans.SetAll(ValueTrackBar.Position);
|
||||
end;
|
||||
|
||||
|
||||
procedure TManualFrame.Start;
|
||||
begin
|
||||
inherited Start;
|
||||
|
||||
UpdateTimer.Enabled := True;
|
||||
end;
|
||||
|
||||
|
||||
procedure TManualFrame.Stop;
|
||||
begin
|
||||
UpdateTimer.Enabled := False;
|
||||
|
||||
inherited Stop;
|
||||
end;
|
||||
|
||||
|
||||
initialization
|
||||
RegisterSimulator('Manual', 'Manual fan control',
|
||||
function(AFans: ISimulatorFans): ISimulator
|
||||
begin
|
||||
Result := TSimulatorFrame<TManualFrame>.Create(AFans);
|
||||
end);
|
||||
|
||||
end.
|
96
Client/source/Simulator.Registry.pas
Normal file
@ -0,0 +1,96 @@
|
||||
unit Simulator.Registry;
|
||||
|
||||
interface
|
||||
uses
|
||||
Vcl.Controls,
|
||||
|
||||
System.Generics.Collections;
|
||||
|
||||
|
||||
type
|
||||
ISimulator = interface
|
||||
['{732D49D6-2E54-4257-887A-B09D8F2771B1}']
|
||||
procedure Start;
|
||||
procedure Stop;
|
||||
|
||||
procedure SetUIParent(AParent: TWinControl);
|
||||
end;
|
||||
|
||||
|
||||
ISimulatorFans = interface
|
||||
['{23A005ED-907A-4776-A9FB-70D213C7381C}']
|
||||
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;
|
||||
|
||||
property Ready: Boolean read GetReady;
|
||||
property IsRunning: Boolean read GetIsRunning;
|
||||
property FanCount: Integer read GetFanCount;
|
||||
end;
|
||||
|
||||
|
||||
TSimulatorConstructor = reference to function(AFans: ISimulatorFans): ISimulator;
|
||||
|
||||
TRegisteredSimulator = class(TObject)
|
||||
private
|
||||
FKey: string;
|
||||
FName: string;
|
||||
FConstructorFunc: TSimulatorConstructor;
|
||||
public
|
||||
constructor Create(const AKey, AName: string; AConstructor: TSimulatorConstructor);
|
||||
|
||||
property Key: string read FKey;
|
||||
property Name: string read FName;
|
||||
property ConstructorFunc: TSimulatorConstructor read FConstructorFunc;
|
||||
end;
|
||||
|
||||
procedure RegisterSimulator(const AKey, AName: string; AConstructor: TSimulatorConstructor);
|
||||
function GetRegisteredSimulators: TEnumerable<TRegisteredSimulator>;
|
||||
|
||||
|
||||
implementation
|
||||
uses
|
||||
System.SysUtils;
|
||||
|
||||
var
|
||||
SimulatorsList: TObjectList<TRegisteredSimulator>;
|
||||
|
||||
|
||||
procedure RegisterSimulator(const AKey, AName: string; AConstructor: TSimulatorConstructor);
|
||||
begin
|
||||
SimulatorsList.Add(TRegisteredSimulator.Create(AKey, AName, AConstructor));
|
||||
end;
|
||||
|
||||
|
||||
function GetRegisteredSimulators: TEnumerable<TRegisteredSimulator>;
|
||||
begin
|
||||
Result := SimulatorsList;
|
||||
end;
|
||||
|
||||
|
||||
{ TRegisteredSimulator }
|
||||
constructor TRegisteredSimulator.Create(const AKey, AName: string; AConstructor: TSimulatorConstructor);
|
||||
begin
|
||||
inherited Create;
|
||||
|
||||
FKey := AKey;
|
||||
FName := AName;
|
||||
FConstructorFunc := AConstructor;
|
||||
end;
|
||||
|
||||
|
||||
initialization
|
||||
SimulatorsList := TObjectList<TRegisteredSimulator>.Create;
|
||||
|
||||
finalization
|
||||
FreeAndNil(SimulatorsList);
|
||||
|
||||
end.
|
BIN
Logo/SimulatorFans-16.png
Normal file
After Width: | Height: | Size: 515 B |
BIN
Logo/SimulatorFans-32.png
Normal file
After Width: | Height: | Size: 1008 B |
BIN
Logo/SimulatorFans-48.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Logo/SimulatorFans-64.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
4950
Logo/SimulatorFans.ai
Normal file
BIN
Logo/SimulatorFans.ico
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
Logo/SimulatorFans.png
Normal file
After Width: | Height: | Size: 15 KiB |
@ -4,7 +4,7 @@
|
||||
* https://git.x2software.net/pub/SimulatorFans
|
||||
*
|
||||
*
|
||||
* Accepts serial commands to control one or more fans using PWM.
|
||||
* Accepts serial commands to control one or more fans.
|
||||
*
|
||||
* All commands are terminated with a #10 character and are case sensitive.
|
||||
* Values are passed as ASCII text. This makes it easy to test it using the
|
||||
@ -29,6 +29,11 @@
|
||||
*
|
||||
* >SetFans:v1,v2,v...
|
||||
* Updates the fan values. Each value ranges from 0 to 255.
|
||||
* If the first value is 'A', the second value is applied to each fan.
|
||||
*
|
||||
* When in Servo mode, if the first value is 'M', the Servo is
|
||||
* set to it's maximum pulse width, disregarding the MaxValue.
|
||||
* Used for ESC calibration. USE WITH CARE!
|
||||
*
|
||||
* Example response:
|
||||
* <SetFans
|
||||
@ -39,45 +44,98 @@
|
||||
* Configuration
|
||||
*/
|
||||
|
||||
// Defines the method used for controlling the fans.
|
||||
#define ModePWM 0
|
||||
#define ModeServo 1
|
||||
|
||||
// ModePWM:
|
||||
// Fans are connected through a transistor (or preferably MOSFET) directly
|
||||
// from the pins. The PWM frequency is set low to reduce noise.
|
||||
//
|
||||
// ModeServo:
|
||||
// Intended for use with an ESC (Electronic Speed Controller). The PWM signal
|
||||
// on each pin will be compatible with standard ESCs or servos.
|
||||
#define FanMode ModeServo
|
||||
|
||||
|
||||
// The number of fans connected. Each fan must connect to it's own
|
||||
// PWM-enabled pin through a transistor (or preferably MOSFET).
|
||||
#define FanCount 2
|
||||
// PWM-enabled or Servo-supported pin.
|
||||
#define FanCount 1
|
||||
|
||||
// The pin on which each of the fans is connected. The number of
|
||||
// values must be equal to FanCount (not sure why the compiler
|
||||
// doesn't enforce this if FanCount is bigger). The order is
|
||||
// assumed to be left to right. In a setup of more than 180 degrees,
|
||||
// start counting behind the point of view and in a clockwise manner.
|
||||
//
|
||||
// Possibly the PC software could support remapping at some point in the
|
||||
// future as that's easier than recompiling for the Arduino, but I
|
||||
// wanted to keep it simple until then.
|
||||
// assumed to be left to right.
|
||||
const byte FanPin[FanCount] =
|
||||
{
|
||||
5,
|
||||
6
|
||||
9
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* PWM mode configuration
|
||||
*/
|
||||
// When a fan goes from completely off to a value below full, this
|
||||
// determines how long the fan will run on full power before changing
|
||||
// to the actual value, to give it a chance to start up.
|
||||
#define StartingFansTime 200
|
||||
#define PWMStartingFansTime 200
|
||||
|
||||
|
||||
// The minimum value at which the fan still spins reliably. Anything below
|
||||
// will be considered as 0.
|
||||
#define MinimumValue 32
|
||||
#define PWMMinimumValue 32
|
||||
|
||||
|
||||
/*
|
||||
* Servo mode configuration
|
||||
*/
|
||||
// The minimum pulse width. Servo library uses a default of 544.
|
||||
#define ServoMinPulseWidth 544
|
||||
|
||||
// The maximum pulse width. Servo library uses a default of 2400.
|
||||
#define ServoMaxPulseWidth 2400
|
||||
|
||||
#define ServoPulseRange (ServoMaxPulseWidth - ServoMinPulseWidth)
|
||||
|
||||
|
||||
// The pulse width when the input value is 0, in microseconds.
|
||||
// Using microseconds instead of the Servo library's simpler 0-180 degree mapping
|
||||
// allows for more finegrained changes when limiting the values.
|
||||
#define ServoMinValue ServoMinPulseWidth
|
||||
|
||||
// The pulse width when the input value is 255, in microseconds.
|
||||
// Use this to limit the maximum "throttle" to the ESC.
|
||||
#define ServoMaxValue (ServoMinPulseWidth + (ServoPulseRange / 4))
|
||||
|
||||
|
||||
/*
|
||||
* Actual code. Lasciate ogne speranza, voi ch'intrate.
|
||||
*/
|
||||
#define IsPWM (FanMode == ModePWM)
|
||||
#define IsServo (FanMode == ModeServo)
|
||||
|
||||
#if IsServo
|
||||
#include <Servo.h>
|
||||
#endif
|
||||
|
||||
typedef struct
|
||||
{
|
||||
byte value;
|
||||
|
||||
#if IsPWM
|
||||
unsigned long startTime;
|
||||
#endif
|
||||
|
||||
#if IsServo
|
||||
Servo servo;
|
||||
#endif
|
||||
} FanStatus;
|
||||
|
||||
|
||||
#if IsPWM
|
||||
unsigned long currentTime = 0;
|
||||
#endif
|
||||
|
||||
FanStatus fanStatus[FanCount];
|
||||
|
||||
|
||||
@ -85,9 +143,11 @@ FanStatus fanStatus[FanCount];
|
||||
void handleInfoCommand();
|
||||
void handleGetFansCommand();
|
||||
void handleSetFansCommand();
|
||||
void handleUnknownCommand();
|
||||
void handleUnknownCommand(char* command);
|
||||
|
||||
#if IsPWM
|
||||
void checkStartingFans();
|
||||
#endif
|
||||
void setFan(byte fan, byte value);
|
||||
|
||||
|
||||
@ -96,8 +156,19 @@ void setup()
|
||||
memset(fanStatus, 0, sizeof(fanStatus));
|
||||
|
||||
for (byte fan = 0; fan < FanCount; fan++)
|
||||
{
|
||||
#if IsPWM
|
||||
pinMode(FanPin[fan], OUTPUT);
|
||||
#endif
|
||||
|
||||
#if IsServo
|
||||
fanStatus[fan].servo.attach(FanPin[fan]);
|
||||
fanStatus[fan].servo.writeMicroseconds(ServoMinValue);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#if IsPWM
|
||||
// Set all clock select bits to clk/1024. This will result in a 30 ~ 60 Hz PWM frequency.
|
||||
// The default frequency causes annoying noises in the fan, the low frequency is still
|
||||
// audible but good enough since the game sounds should easily drown that out.
|
||||
@ -108,6 +179,7 @@ void setup()
|
||||
|
||||
// 64 is the default prescaler
|
||||
#define correctedMillis() (millis() * (1024 / 64))
|
||||
#endif
|
||||
|
||||
// Set up serial communication (through USB or the default pins)
|
||||
// 19.2k is fast enough for our purpose, and according to the ATMega's datasheet
|
||||
@ -118,13 +190,16 @@ void setup()
|
||||
|
||||
char command[50];
|
||||
byte commandLength;
|
||||
byte offset;
|
||||
char* token;
|
||||
|
||||
|
||||
void loop()
|
||||
{
|
||||
#if IsPWM
|
||||
currentTime = correctedMillis();
|
||||
checkStartingFans();
|
||||
#endif
|
||||
|
||||
if (Serial.available() > 0)
|
||||
{
|
||||
@ -133,7 +208,16 @@ void loop()
|
||||
commandLength = Serial.readBytesUntil('\n', command, sizeof(command) - 1);
|
||||
if (commandLength > 0 && commandLength < sizeof(command))
|
||||
{
|
||||
token = strtok(&command[0], ":");
|
||||
// The Windows application always sends two 0xFF characters when it first connects.
|
||||
// I'm sure it's a setting somewhere, but I couldn't figure it out just yet. Instead,
|
||||
// simply look for the start of the command, thus skipping all other bytes. A bit
|
||||
// of a hack, but if you know the proper solution, I'd love to hear it!
|
||||
offset = 0;
|
||||
|
||||
while (offset < commandLength && command[offset] != '>')
|
||||
offset++;
|
||||
|
||||
token = strtok(&command[offset], ":");
|
||||
if (token != NULL)
|
||||
{
|
||||
if (strcmp(token, ">Info") == 0)
|
||||
@ -143,7 +227,7 @@ void loop()
|
||||
else if (strcmp(token, ">SetFans") == 0)
|
||||
handleSetFansCommand();
|
||||
else
|
||||
handleUnknownCommand();
|
||||
handleUnknownCommand(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -154,6 +238,13 @@ void handleInfoCommand()
|
||||
{
|
||||
Serial.write("<Info:Fans=");
|
||||
Serial.print(FanCount);
|
||||
Serial.write(",Mode=");
|
||||
#if IsPWM
|
||||
Serial.write("PWM");
|
||||
#endif
|
||||
#if IsServo
|
||||
Serial.write("Servo");
|
||||
#endif
|
||||
Serial.write("\n");
|
||||
}
|
||||
|
||||
@ -176,38 +267,75 @@ void handleGetFansCommand()
|
||||
|
||||
void handleSetFansCommand()
|
||||
{
|
||||
for (byte fan = 0; fan < FanCount; fan++)
|
||||
token = strtok(NULL, ",");
|
||||
if (token != NULL)
|
||||
{
|
||||
token = strtok(NULL, ",");
|
||||
if (token == NULL)
|
||||
break;
|
||||
if (strcmp(token, "A") == 0)
|
||||
{
|
||||
// Set all fans to the same value
|
||||
token = strtok(NULL, ",");
|
||||
if (token != NULL)
|
||||
{
|
||||
int value = atoi(token);
|
||||
|
||||
if (value < 0) value = 0;
|
||||
if (value > 255) value = 0;
|
||||
|
||||
for (byte fan = 0; fan < FanCount; fan++)
|
||||
setFan(fan, value);
|
||||
}
|
||||
}
|
||||
#if IsServo
|
||||
else if (strcmp(token, "M") == 0)
|
||||
{
|
||||
for (byte fan = 0; fan < FanCount; fan++)
|
||||
{
|
||||
// Join me for a drive, maximum overdrive!
|
||||
fanStatus[fan].servo.writeMicroseconds(ServoMaxPulseWidth);
|
||||
fanStatus[fan].value = 255;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
// Set individual fan values
|
||||
for (byte fan = 0; fan < FanCount; fan++)
|
||||
{
|
||||
int value = atoi(token);
|
||||
|
||||
if (value < 0) value = 0;
|
||||
if (value > 255) value = 0;
|
||||
|
||||
setFan(fan, value);
|
||||
|
||||
int value = atoi(token);
|
||||
|
||||
if (value < 0) value = 0;
|
||||
if (value > 255) value = 0;
|
||||
|
||||
setFan(fan, value);
|
||||
token = strtok(NULL, ",");
|
||||
if (token == NULL)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Serial.write("<SetFans\n");
|
||||
}
|
||||
|
||||
|
||||
void handleUnknownCommand()
|
||||
void handleUnknownCommand(char* command)
|
||||
{
|
||||
Serial.write("<Error:unknown command\n");
|
||||
Serial.write("<Error:unknown command:");
|
||||
Serial.write(command);
|
||||
Serial.write("\n");
|
||||
}
|
||||
|
||||
|
||||
#if IsPWM
|
||||
void checkStartingFans()
|
||||
{
|
||||
// Check if any of the fans are currently starting up and
|
||||
// have been for at least StartingFansTime
|
||||
// have been for at least PWMStartingFansTime
|
||||
for (byte fan = 0; fan < FanCount; fan++)
|
||||
{
|
||||
if ((fanStatus[fan].startTime > 0) &&
|
||||
(currentTime - fanStatus[fan].startTime >= StartingFansTime))
|
||||
(currentTime - fanStatus[fan].startTime >= PWMStartingFansTime))
|
||||
{
|
||||
fanStatus[fan].startTime = 0;
|
||||
setFan(fan, fanStatus[fan].value);
|
||||
@ -218,7 +346,7 @@ void checkStartingFans()
|
||||
void setFan(byte fan, byte value)
|
||||
{
|
||||
byte correctedValue = value;
|
||||
if (correctedValue < MinimumValue)
|
||||
if (correctedValue < PWMMinimumValue)
|
||||
correctedValue = 0;
|
||||
|
||||
if ((fanStatus[fan].value == 0 || fanStatus[fan].startTime > 0) && correctedValue > 0)
|
||||
@ -240,3 +368,12 @@ void setFan(byte fan, byte value)
|
||||
|
||||
fanStatus[fan].value = correctedValue;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if IsServo
|
||||
void setFan(byte fan, byte value)
|
||||
{
|
||||
fanStatus[fan].servo.writeMicroseconds(map(value, 0, 255, ServoMinValue, ServoMaxValue));
|
||||
fanStatus[fan].value = value;
|
||||
}
|
||||
#endif
|
||||
|