From 897ba9310e0eb2a4d446b46c560be5230a51fa61 Mon Sep 17 00:00:00 2001 From: Mark van Renswoude Date: Sun, 4 Jun 2017 11:18:48 +0200 Subject: [PATCH] Added first two Lua script conversions; autopilot airspeed and ATC visibility Updated DelphiLua Added scripting documentation --- .gitignore | 1 + Docs/autoPilotAirspeedStates.png | Bin 0 -> 5501 bytes Docs/index.rst | 3 +- Docs/introduction.rst | 9 +- Docs/scripting.rst | 112 +++++- Docs/scriptingreference.rst | 323 ++++++++++++++++++ G940LEDControl/DelphiLua/Lua.API.pas | 2 +- G940LEDControl/DelphiLua/Lua.pas | 52 +++ G940LEDControl/Forms/MainFrm.dfm | 8 +- G940LEDControl/Forms/MainFrm.pas | 7 + G940LEDControl/G940LEDControl.dproj | 4 +- G940LEDControl/Scripts/FSX/autopilot.lua | 35 ++ G940LEDControl/Scripts/FSX/lib/strings.lua | 16 + G940LEDControl/Scripts/FSX/panels.lua | 26 ++ .../Units/FSXLEDFunctionProvider.pas | 237 +++++++------ .../Units/LuaLEDFunctionProvider.pas | 225 ++++++++++-- 16 files changed, 905 insertions(+), 155 deletions(-) create mode 100644 Docs/autoPilotAirspeedStates.png create mode 100644 Docs/scriptingreference.rst create mode 100644 G940LEDControl/Scripts/FSX/autopilot.lua create mode 100644 G940LEDControl/Scripts/FSX/lib/strings.lua create mode 100644 G940LEDControl/Scripts/FSX/panels.lua diff --git a/.gitignore b/.gitignore index 9fd89ac..218fa61 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ __history *.exe *.identcache Docs/_build/ +*.sublime-workspace diff --git a/Docs/autoPilotAirspeedStates.png b/Docs/autoPilotAirspeedStates.png new file mode 100644 index 0000000000000000000000000000000000000000..3cbedfba0ae5da57a3763bb6d05abe23698307a5 GIT binary patch literal 5501 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 (1.0.4) +* `VirtualTreeView `_ (5.3.0) * `X2Log `_ * `X2Utils `_ -* ToDo: any more? -A copy of `DelphiLua `_ is already included in the G940LEDControl repository. \ No newline at end of file +Newer versions Delphi and/or of the libraries might work as well, though have not been tested yet. + +A copy of `DelphiLua `_ is included in the G940LEDControl repository. \ No newline at end of file diff --git a/Docs/scripting.rst b/Docs/scripting.rst index aff5bb5..c6041e6 100644 --- a/Docs/scripting.rst +++ b/Docs/scripting.rst @@ -1,7 +1,117 @@ Scripting ========= +All functionality introduced by G940LEDControl is described in the :doc:`scriptingreference`. For more information about Lua in general, please refer to the `Lua 5.2 Reference Manual `_. + +This guide will walk through how Lua scripting works in G940LEDControl. To avoid confusion when talking about functions in this context they will be referred to as 'button functions' and 'Lua functions'. + + +Script locations +---------------- +The default scripts included with G940LEDControl can be found in the folder selected during installation, for example "C:\\Program Files (x86)\\G940 LED Control". In there you will find a Scripts\\FSX folder containing the Lua files. + +In addition scripts are loaded from your user data path. This folder is automatically created by G940LEDControl after you have changed any button configuration or settings. To open the folder, type or paste "%APPDATA%\\G940LEDControl" into a Windows Explorer address bar and press Enter. Inside you can create the same Scripts\\FSX folder structure. Scripts in this folder will not be overwritten when installing a new version. + + +Anatomy of a button function +---------------------------- +Let's take the Autopilot airspeed button function as an example, which at the time of writing looks like this: :: - -- this could be a great Lua example! + local strings = require './lib/strings' + + RegisterFunction( + { + uid = 'autoPilotAirspeed', + category = strings.Category.FSX.AutoPilot, + displayName = 'Autopilot airspeed', + states = { + on = { displayName = 'On', default = LEDColor.Green }, + off = { displayName = 'Off', default = LEDColor.Red }, + notAvailable = { displayName = 'Not available', default = LEDColor.Off } + } + }, + function(context) + SetState(context, 'notAvailable') + + OnSimConnect(context, + { + autoPilotAvailable = { variable = 'AUTOPILOT AVAILABLE', type = SimConnectDataType.Bool }, + autoPilotAirspeed = { variable = 'AUTOPILOT AIRSPEED HOLD', type = SimConnectDataType.Bool } + }, + function(context, data) + if data.autoPilotAvailable then + if data.autoPilotAirspeed then + SetState(context, 'on') + else + SetState(context, 'off') + end + else + SetState(context, 'notAvailable') + end + end) + end + ) + +Using anonymous functions like this to implement the various callbacks results in compact code, which can arguably be more difficult to read. For clarity let's expand it first. The following example works exactly the same: + +:: + + local strings = require './lib/strings' + + local function variablesChanged(context, data) + if data.autoPilotAvailable then + if data.autoPilotAirspeed then + SetState(context, 'on') + else + SetState(context, 'off') + end + else + SetState(context, 'notAvailable') + end + end + + local function setup(context) + SetState(context, 'notAvailable') + + OnSimConnect(context, + { + autoPilotAvailable = { variable = 'AUTOPILOT AVAILABLE', type = SimConnectDataType.Bool }, + autoPilotAirspeed = { variable = 'AUTOPILOT AIRSPEED HOLD', type = SimConnectDataType.Bool } + }, + variablesChanged) + end + + + RegisterFunction( + { + uid = 'autoPilotAirspeed', + category = strings.Category.FSX.AutoPilot, + displayName = 'Autopilot airspeed', + states = { + on = { displayName = 'On', default = LEDColor.Green }, + off = { displayName = 'Off', default = LEDColor.Red }, + notAvailable = { displayName = 'Not available', default = LEDColor.Off } + } + }, + setup) + + +So what's happening? When the script is loaded it is automatically run. At this time you should call :ref:`ref-registerfunction` for each button function you want to be visible in G940LEDControl. :ref:`ref-registerfunction` accepts two parameters: a table which describes the button function you want to add, and a Lua function to be called when the button function is assigned to a button. + +Every button function must have a unique 'uid'. It is used to save and load profiles and should therefore not be changed once in use. The category and displayName are only used in the main and button function selection screens and can be freely changed. + +A button function must also have one or more states. Each state has a key which, like the 'uid', is used to save and load profiles and should also not be changed once in use. A default :ref:`LED color ` can also be set, which sets the initial value in the selection screen when assigning it to a button: + +.. image:: autoPilotAirspeedStates.png + + +Setup function +~~~~~~~~~~~~~~ + +As soon as the button function is attached to one of the buttons the setup function passed to :ref:`ref-registerfunction` is called. It receives a 'context' parameter, the contents of which are not useable by the script directly, but which you need to pass along to for example :ref:`ref-setstate` later on so that it knows which button function's state needs to be changed. + +In the above example the first thing we do is to set the default state using :ref:`ref-setstate`. The second parameter is a string containing the key of one of the states as defined in the :ref:`ref-registerfunction` call. + +After that you will normally call one of the built-in Lua functions to be notified of certain events. At the time of writing you can either call :ref:`ref-ontimer` to perform checks on a regular interval or, more common in the case of FSX, :ref:`ref-onsimconnect` to be notified when one or more of the `simulation variables `_ change. \ No newline at end of file diff --git a/Docs/scriptingreference.rst b/Docs/scriptingreference.rst new file mode 100644 index 0000000..00ff89f --- /dev/null +++ b/Docs/scriptingreference.rst @@ -0,0 +1,323 @@ +Scripting reference +=================== + +G940LEDControl uses Lua 5.2. Please refer to the `Lua 5.2 Reference Manual `_ for more information. + +.. contents:: + :local: + +Global functions +---------------- + +.. _ref-log: + +Log functions +~~~~~~~~~~~~~ + +:: + + Log.Verbose(msg, ...) + Log.Info(msg, ...) + Log.Warning(msg, ...) + Log.Error(msg, ...) + +Writes a message to the application log. If you pass more than one parameter, they will be concatenated in the log message separated by spaces. The parameters do not need to be strings, other simple types will be converted and tables will be output as '{ key = value, ... }'. + +The application log can be opened on the Configuration tab of the main screen. + +.. _ref-registerfunction: + +RegisterFunction +~~~~~~~~~~~~~~~~ +:: + + RegisterFunction(info, setupCallback) + +Registers a button function. + +**Parameters** + +| **info**: table +| A Lua table describing the function. The following keys are recognized: +| +| **uid**: string +| *Required.* A unique ID for this function. Used to save and load profiles. +| +| **category**: string +| The category under which this function is grouped in the button function selection screen. +| +| **displayName**: string +| The name of the function which is shown in the main screen and button function selection screen. +| +| **states**: table +| A table of states supported by this function. +| Each state has it's own unique key and a table describing the state. The following keys are recognized for the table: +| +| **displayName**: string +| The name of the state which is shown in the button function selection screen. +| +| **default**: string +| The default color and/or animation assigned to the state when it is first selected. See :ref:`ref-ledcolor` for a list of valid values. +| +| **setupCallback**: function +| A Lua function which is called when the button function is configured. Please note that if a button function is linked to multiple G940 throttle buttons, setupCallback is called multiple times, so be careful with variables which are outside of the setupCallback's scope (global or script-local)! +| +| setupCallback is passed a single parameter 'context'. +| + +**Example** +:: + + RegisterFunction( + { + uid = 'autoPilotAirspeed', + category = strings.Category.FSX.AutoPilot, + displayName = 'Autopilot airspeed', + states = { + on = { displayName = 'On', default = LEDColor.Green }, + off = { displayName = 'Off', default = LEDColor.Red }, + notAvailable = { displayName = 'Not available', default = LEDColor.Off } + } + }, + function(context) + -- implementation of setupCallback + end) + +.. _ref-setstate: + +SetState +~~~~~~~~ +:: + + SetState(context, newState) + +Sets the current state of a button function. + +**Parameters** + +| **context** +| The context parameter as passed to setupCallback which determines the button function to be updated. +| +| **newState**: string +| The new state. Must be the name of a state key as passed to :ref:`ref-registerfunction`. +| + +**Example** +:: + + SetState(context, 'on') + + +.. _ref-onsimconnect: + +OnSimConnect +~~~~~~~~~~~~ +:: + + OnSimConnect(context, variables, variablesChangedCallback) + +Registers a Lua function to be called when the specified SimConnect variable(s) change. For a list of variables please refer to `Simulation variables `_. + +**Parameters** + +| **context** +| The context parameter as passed to setupCallback. +| +| **variables**: table +| A table containing information about the simulation variables you want to monitor. Each key will be reflected in the 'data' table passed to the variablesChangedCallback. Each value is a Lua table describing the variable. +| +| **variable**: string +| The name of the variable as described in `Simulation variables `_. +| +| **data**: string +| One of the :ref:`ref-simconnectdatatype` values. +| +| **variablesChangedCallback**: function +| A Lua function which is called when the variable's value changes. It receives 2 parameters: 'context' and 'data'. The data parameter is a Lua table where each key corresponds to a variable defined in the 'variables' parameter and it's value is the current value of the simulation variable. +| + +**Example** + +:: + + OnSimConnect(context, + { + autoPilotAvailable = { variable = 'AUTOPILOT AVAILABLE', type = SimConnectDataType.Bool }, + autoPilotAirspeed = { variable = 'AUTOPILOT AIRSPEED HOLD', type = SimConnectDataType.Bool } + }, + function(context, data) + if data.autoPilotAvailable then + if data.autoPilotAirspeed then + SetState(context, 'on') + else + SetState(context, 'off') + end + else + SetState(context, 'notAvailable') + end + end) + + +.. _ref-ontimer: + +OnTimer +~~~~~~~ +:: + + OnTimer(context, interval, timerCallback) + +Registers a Lua function to be called when the specified interval elapses. + +**Parameters** + +| **context** +| The context parameter as passed to setupCallback. +| +| **interval** +| The interval between calls to timerCallback in milliseconds. At the time of writing the minimum value is 100 milliseconds. +| +| **timerCallback** +| A Lua function which is called when the interval elapses. It is passed a single parameter 'context'. +| + +**Example** + +:: + + OnTimer(context, 1000, + function(context) + if FSXWindowVisible('ATC Menu') then + SetState(context, 'visible') + else + SetState(context, 'hidden') + end + end) + + + +.. _ref-windowvisible: + +WindowVisible +~~~~~~~~~~~~~ + +Checks if a window is currently visible. This is a thin wrapper around the FindWindow/FindWindowEx/IsWindowVisible Windows API. In the context of FSX panels you are probably looking for :ref:`ref-fsxwindowvisible`. + +All parameters are optional, but at least one parameter is required. To skip a parameter simply pass nil instead. + +To get a window's class name, use a tool like `Greatis Windowse `_. + +**Parameters** + +| **className** +| The window class name of the window +| +| **title** +| The title / caption / text of the window +| +| **parentClassName** +| The parent window's class name. If specified, the first two parameters are considered to be a child window of this parent. +| +| **parentTitle** +| The parent window's title / caption / text. If specified, the first two parameters are considered to be a child window of this parent. +| + +.. _ref-fsxwindowvisible: + +FSXWindowVisible +~~~~~~~~~~~~~~~~ + +Checks if an FSX window is currently visible. Uses WindowVisible as a workaround because SimConnect does not expose this information directly. + +**Parameters** + +| **title** +| The title of the panel. +| + +Checks for both docked and undocked windows. Equal to: + +:: + + WindowVisible('FS98CHILD', title, 'FS98MAIN') or WindowVisible('FS98FLOAT', title) + + + +Global variables +---------------- + +.. _ref-ledcolor: + +LEDColor +~~~~~~~~ + +**Keys** + +- Off +- Green +- Amber +- Red +- FlashingGreenFast +- FlashingGreenNormal +- FlashingAmberFast +- FlashingAmberNormal +- FlashingRedFast +- FlashingRedNormal + +The 'Fast' flashing versions stay on and off for half a second, the 'Normal' version for 1 second. + +**Example** + +:: + + { default = LEDColor.Green } + + +.. _ref-simconnectdatatype: + +SimConnectDataType +~~~~~~~~~~~~~~~~~~ + +**Keys** + +- Float64 +- Float32 +- Int64 +- Int32 +- String +- Bool +- XYZ +- LatLonAlt +- Waypoint + +The XYZ, LatLonAlt and Waypoint data types will return a table in the 'data' parameter for the OnSimConnect callback with the following keys: + +**XYZ** + +- X +- Y +- Z + +**LatLonAlt** + +- Latitude +- Longitude +- Altitude + +**Waypoint** + +- Latitude +- Longitude +- Altitude +- KtsSpeed +- PercentThrottle +- Flags + +The Flags value is a table containing the following keys, where each is a boolean: + +- SpeedRequested +- ThrottleRequested +- ComputeVerticalSpeed +- IsAGL +- OnGround +- Reverse +- WrapToFirst \ No newline at end of file diff --git a/G940LEDControl/DelphiLua/Lua.API.pas b/G940LEDControl/DelphiLua/Lua.API.pas index 6ef1730..d10642a 100644 --- a/G940LEDControl/DelphiLua/Lua.API.pas +++ b/G940LEDControl/DelphiLua/Lua.API.pas @@ -340,7 +340,7 @@ type isvararg: Byte; istailcall: Byte; short_src: array[0..LUA_IDSIZE - 1] of AnsiChar; - //struct CallInfo *i_ci; /* active function */ + i_ci: Integer; end; { Functions to be called by the debugger in specific events } diff --git a/G940LEDControl/DelphiLua/Lua.pas b/G940LEDControl/DelphiLua/Lua.pas index 781a75b..920260b 100644 --- a/G940LEDControl/DelphiLua/Lua.pas +++ b/G940LEDControl/DelphiLua/Lua.pas @@ -398,6 +398,13 @@ type procedure OpenLibraries(ALibraries: TLuaLibraries); virtual; + { Get or set the current path(s) used for require calls. Paths must be separated + by semicolons and questions marks will be replaced with the requested file name, + as per the Lua documentation at: https://www.lua.org/pil/8.1.html } + function GetRequirePath: string; + procedure SetRequirePath(const APath: string); + procedure AddRequirePath(const APath: string); + { These methods should only be called right after one of the LoadFrom methods, which must have AutoRun set to False. } procedure Run; virtual; @@ -1400,6 +1407,51 @@ begin end; +function TLua.GetRequirePath: string; +begin + CheckState; + + lua_getglobal(State, 'package'); + try + lua_getfield(State, -1, 'path'); + try + Result := TLuaHelpers.LuaToString(State, -1); + finally + lua_pop(State, 1); + end; + finally + lua_pop(State, 1); + end; +end; + + +procedure TLua.SetRequirePath(const APath: string); +begin + CheckState; + + lua_getglobal(State, 'package'); + try + TLuaHelpers.PushString(State, APath); + lua_setfield(State, -2, 'path'); + finally + lua_pop(State, 1); + end; +end; + + +procedure TLua.AddRequirePath(const APath: string); +var + path: string; + +begin + path := GetRequirePath; + if (Length(path) > 0) and (path[Length(path)] <> ';') then + path := path + ';'; + + SetRequirePath(path + APath); +end; + + procedure TLua.Run; begin CheckIsFunction; diff --git a/G940LEDControl/Forms/MainFrm.dfm b/G940LEDControl/Forms/MainFrm.dfm index c99ab00..cf3a781 100644 --- a/G940LEDControl/Forms/MainFrm.dfm +++ b/G940LEDControl/Forms/MainFrm.dfm @@ -1,7 +1,7 @@ object MainForm: TMainForm Left = 0 Top = 0 - ActiveControl = cmbProfiles + ActiveControl = cbTrayIcon BorderIcons = [biSystemMenu, biMinimize] BorderStyle = bsSingle Caption = 'G940 LED Control' @@ -30,7 +30,7 @@ object MainForm: TMainForm Margins.Top = 8 Margins.Right = 8 Margins.Bottom = 8 - ActivePage = tsButtons + ActivePage = tsConfiguration Align = alClient TabOrder = 0 object tsButtons: TTabSheet @@ -375,10 +375,6 @@ object MainForm: TMainForm object tsConfiguration: TTabSheet Caption = ' Configuration ' ImageIndex = 2 - ExplicitLeft = 0 - ExplicitTop = 0 - ExplicitWidth = 0 - ExplicitHeight = 0 DesignSize = ( 442 452) diff --git a/G940LEDControl/Forms/MainFrm.pas b/G940LEDControl/Forms/MainFrm.pas index 18e5eff..ec7ae0c 100644 --- a/G940LEDControl/Forms/MainFrm.pas +++ b/G940LEDControl/Forms/MainFrm.pas @@ -228,6 +228,7 @@ type implementation uses + System.IOUtils, System.SysUtils, System.Types, System.Win.ComObj, @@ -334,6 +335,12 @@ begin scriptPaths[0] := App.Path + FSXScriptsPath; scriptPaths[1] := App.UserPath + UserDataPath + FSXScriptsPath; + if DebugHook <> 0 then + begin + SetLength(scriptPaths, 3); + scriptPaths[2] := TPath.GetFullPath(App.Path + '..\' + FSXScriptsPath); + end; + FunctionRegistry.Register(TFSXLEDFunctionProvider.Create(scriptPaths)); diff --git a/G940LEDControl/G940LEDControl.dproj b/G940LEDControl/G940LEDControl.dproj index a9bb03c..c4f07dd 100644 --- a/G940LEDControl/G940LEDControl.dproj +++ b/G940LEDControl/G940LEDControl.dproj @@ -51,7 +51,7 @@ 6 1 - rtl;dbrtl;$(DCC_UsePackage) + rtl;dbrtl;xmlrtl;$(DCC_UsePackage) Lib Bin ..\Shared;$(DCC_UnitSearchPath) @@ -63,10 +63,12 @@ 1033 + vclx;vcl;vclactnband;$(DCC_UsePackage) G940LEDControl_Icon.ico $(BDS)\bin\default_app.manifest + vclx;vcl;vclactnband;$(DCC_UsePackage) false false false diff --git a/G940LEDControl/Scripts/FSX/autopilot.lua b/G940LEDControl/Scripts/FSX/autopilot.lua new file mode 100644 index 0000000..a79556a --- /dev/null +++ b/G940LEDControl/Scripts/FSX/autopilot.lua @@ -0,0 +1,35 @@ +local strings = require './lib/strings' + + +RegisterFunction( + { + uid = 'autoPilotAirspeed', + category = strings.Category.FSX.AutoPilot, + displayName = 'Autopilot airspeed', + states = { + on = { displayName = 'On', default = LEDColor.Green }, + off = { displayName = 'Off', default = LEDColor.Red }, + notAvailable = { displayName = 'Not available', default = LEDColor.Off } + } + }, + function(context) + SetState(context, 'notAvailable') + + OnSimConnect(context, + { + autoPilotAvailable = { variable = 'AUTOPILOT AVAILABLE', type = SimConnectDataType.Bool }, + autoPilotAirspeed = { variable = 'AUTOPILOT AIRSPEED HOLD', type = SimConnectDataType.Bool } + }, + function(context, data) + if data.autoPilotAvailable then + if data.autoPilotAirspeed then + SetState(context, 'on') + else + SetState(context, 'off') + end + else + SetState(context, 'notAvailable') + end + end) + end +) diff --git a/G940LEDControl/Scripts/FSX/lib/strings.lua b/G940LEDControl/Scripts/FSX/lib/strings.lua new file mode 100644 index 0000000..e0e61ad --- /dev/null +++ b/G940LEDControl/Scripts/FSX/lib/strings.lua @@ -0,0 +1,16 @@ +local strings = {} + +strings.Category = {} + +strings.Category.FSX = {} +strings.Category.FSX.Default = 'Flight Simulator X' +strings.Category.FSX.AutoPilot = strings.Category.FSX.Default..' - Autopilot' +strings.Category.FSX.ControlSurfaces = strings.Category.FSX.Default..' - Control surfaces' +strings.Category.FSX.Engines = strings.Category.FSX.Default..' - Engines' +strings.Category.FSX.Instruments = strings.Category.FSX.Default..' - Instruments' +strings.Category.FSX.Lights = strings.Category.FSX.Default..' - Lights' +strings.Category.FSX.Panels = strings.Category.FSX.Default..' - Panels' +strings.Category.FSX.Radios = strings.Category.FSX.Default..' - Radios' +strings.Category.FSX.Systems = strings.Category.FSX.Default..' - Systems' + +return strings \ No newline at end of file diff --git a/G940LEDControl/Scripts/FSX/panels.lua b/G940LEDControl/Scripts/FSX/panels.lua new file mode 100644 index 0000000..e9445e8 --- /dev/null +++ b/G940LEDControl/Scripts/FSX/panels.lua @@ -0,0 +1,26 @@ +local strings = require './lib/strings' + + +RegisterFunction( + { + uid = 'atcVisiblity', + category = strings.Category.FSX.Panels, + displayName = 'ATC Visibility', + states = { + hidden = { displayName = 'Hidden', default = LEDColor.Green }, + visible = { displayName = 'Visible', default = LEDColor.FlashingAmberNormal }, + } + }, + function(context) + SetState(context, 'hidden') + + OnTimer(context, 1000, + function(context) + if FSXWindowVisible('ATC Menu') then + SetState(context, 'visible') + else + SetState(context, 'hidden') + end + end) + end +) diff --git a/G940LEDControl/Units/FSXLEDFunctionProvider.pas b/G940LEDControl/Units/FSXLEDFunctionProvider.pas index 4f0275e..c4fa943 100644 --- a/G940LEDControl/Units/FSXLEDFunctionProvider.pas +++ b/G940LEDControl/Units/FSXLEDFunctionProvider.pas @@ -26,12 +26,14 @@ type FSimConnect: TInterfacedObject; FSimConnectLock: TCriticalSection; FProfileMenuSimConnect: IFSXSimConnectProfileMenu; - FScriptSimConnect: TObject; protected function GetUID: string; override; function CreateLuaLEDFunction(AInfo: ILuaTable; AOnSetup: ILuaFunction): TCustomLuaLEDFunction; override; procedure InitInterpreter; override; + procedure ScriptOnSimConnect(Context: ILuaContext); + procedure ScriptFSXWindowVisible(Context: ILuaContext); + procedure SetupWorker(AWorker: TFSXLEDFunctionWorker; AOnSetup: ILuaFunction); { IFSXSimConnectObserver } @@ -132,18 +134,6 @@ type end; - TLuaSimConnect = class(TPersistent) - private - FProvider: TFSXLEDFunctionProvider; - protected - property Provider: TFSXLEDFunctionProvider read FProvider; - public - constructor Create(AProvider: TFSXLEDFunctionProvider); - published - procedure Monitor(Context: ILuaContext); - end; - - const LuaSimConnectDataTypes: array[TLuaSimConnectDataType] of string = ( @@ -173,7 +163,6 @@ end; constructor TFSXLEDFunctionProvider.Create(const AScriptFolders: TStringDynArray); begin FSimConnectLock := TCriticalSection.Create; - FScriptSimConnect := TLuaSimConnect.Create(Self); inherited Create(AScriptFolders); end; @@ -183,7 +172,6 @@ destructor TFSXLEDFunctionProvider.Destroy; begin inherited Destroy; - FreeAndNil(FScriptSimConnect); FreeAndNil(FSimConnectLock); end; @@ -271,7 +259,8 @@ var begin inherited InitInterpreter; - Interpreter.RegisterFunctions(FScriptSimConnect, 'SimConnect'); + Interpreter.RegisterFunction('OnSimConnect', ScriptOnSimConnect); + Interpreter.RegisterFunction('FSXWindowVisible', ScriptFSXWindowVisible); simConnectDataType := TLuaTable.Create; for dataType := Low(TLuaSimConnectDataType) to High(TLuaSimConnectDataType) do @@ -281,6 +270,116 @@ begin end; +procedure TFSXLEDFunctionProvider.ScriptOnSimConnect(Context: ILuaContext); +var + workerID: string; + variables: ILuaTable; + onData: ILuaFunction; + worker: TCustomLuaLEDFunctionWorker; + definition: IFSXSimConnectDefinition; + variable: TLuaKeyValuePair; + info: ILuaTable; + dataType: TLuaSimConnectDataType; + simConnectDataType: SIMCONNECT_DATAType; + units: string; + luaVariables: TList; + luaVariable: TLuaSimConnectVariable; + +begin + CheckParameters('OnSimConnect', Context.Parameters, [VariableString, VariableTable, VariableFunction]); + + workerID := Context.Parameters[0].AsString; + variables := Context.Parameters[1].AsTable; + onData := Context.Parameters[2].AsFunction; + + worker := FindWorker(workerID); + if not Assigned(worker) then + raise ELuaScriptError.Create('OnSimConnect: invalid context'); + + definition := GetSimConnect.CreateDefinition; + + luaVariables := TList.Create; + try + for variable in variables do + begin + if variable.Value.VariableType = VariableTable then + begin + info := variable.Value.AsTable; + if info.HasValue('variable') then + begin + luaVariable.Name := variable.Key.AsString; + units := ''; + simConnectDataType := SIMCONNECT_DATAType_FLOAT64; + + if info.HasValue('type') and GetDataType(info.GetValue('type').AsString, dataType) then + begin + luaVariable.DataType := dataType; + + case dataType of + Float32: simConnectDataType := SIMCONNECT_DATAType_FLOAT32; + Int64: simConnectDataType := SIMCONNECT_DATAType_INT64; + Int32, + Bool: + begin + simConnectDataType := SIMCONNECT_DATAType_INT32; + units := 'bool'; + end; + + // TODO change to STRINGV + StringValue: simConnectDataType := SIMCONNECT_DATAType_STRING256; + XYZ: simConnectDataType := SIMCONNECT_DATAType_XYZ; + LatLonAlt: simConnectDataType := SIMCONNECT_DATAType_LATLONALT; + Waypoint: simConnectDataType := SIMCONNECT_DATAType_WAYPOINT; + end; + + if info.HasValue('units') then + units := info.GetValue('units').AsString + else if not (dataType in [Bool, StringValue, XYZ, LatLonAlt, Waypoint]) then + raise ELuaScriptError.CreateFmt('OnSimConnect: missing units for variable %s', [variable.Key.AsString]); + end else + begin + if not info.HasValue('units') then + raise ELuaScriptError.CreateFmt('OnSimConnect: missing units or type for variable %s', [variable.Key.AsString]); + + units := info.GetValue('units').AsString; + end; + + luaVariables.Add(luaVariable); + definition.AddVariable(info.GetValue('variable').AsString, units, simConnectDataType); + end; + end; + end; + + (worker as TFSXLEDFunctionWorker).AddDefinition(definition, TFSXFunctionWorkerDataHandler.Create(luaVariables, worker.UID, onData)); + finally + FreeAndNil(luaVariables); + end; +end; + + +procedure TFSXLEDFunctionProvider.ScriptFSXWindowVisible(Context: ILuaContext); +const + ClassNameMainWindow = 'FS98MAIN'; + ClassNameChildWindow = 'FS98CHILD'; + ClassNameFloatWindow = 'FS98FLOAT'; + +var + windowTitle: string; + +begin + CheckParameters('FSXWindowVisible', Context.Parameters, [VariableString]); + windowTitle := Context.Parameters[0].AsString; + + Context.Result.Push( + { Docked } + WindowVisible(ClassNameChildWindow, windowTitle, ClassNameMainWindow, '') or + + { Undocked } + WindowVisible(ClassNameFloatWindow, windowTitle, '', '')); +end; + + +// #ToDo1 -oMvR: 4-6-2017: move up to LuaLEDFunctionProvider procedure TFSXLEDFunctionProvider.SetupWorker(AWorker: TFSXLEDFunctionWorker; AOnSetup: ILuaFunction); begin try @@ -562,110 +661,4 @@ begin end; end; - -{ TLuaSimConnect } -constructor TLuaSimConnect.Create(AProvider: TFSXLEDFunctionProvider); -begin - inherited Create; - - FProvider := AProvider; -end; - - -procedure TLuaSimConnect.Monitor(Context: ILuaContext); -var - workerID: string; - variables: ILuaTable; - onData: ILuaFunction; - worker: TCustomLuaLEDFunctionWorker; - definition: IFSXSimConnectDefinition; - variable: TLuaKeyValuePair; - info: ILuaTable; - dataType: TLuaSimConnectDataType; - simConnectDataType: SIMCONNECT_DATAType; - units: string; - luaVariables: TList; - luaVariable: TLuaSimConnectVariable; - -begin - if Context.Parameters.Count < 3 then - raise ELuaScriptError.Create('Not enough parameters for SimConnect.Monitor'); - - if Context.Parameters[0].VariableType <> VariableString then - raise ELuaScriptError.Create('Context expected for SimConnect.Monitor parameter 1'); - - if Context.Parameters[1].VariableType <> VariableTable then - raise ELuaScriptError.Create('Table expected for SimConnect.Monitor parameter 2'); - - if Context.Parameters[2].VariableType <> VariableFunction then - raise ELuaScriptError.Create('Function expected for SimConnect.Monitor parameter 3'); - - workerID := Context.Parameters[0].AsString; - variables := Context.Parameters[1].AsTable; - onData := Context.Parameters[2].AsFunction; - - worker := Provider.FindWorker(workerID); - if not Assigned(worker) then - raise ELuaScriptError.Create('Context expected for SimConnect.Monitor parameter 1'); - - definition := Provider.GetSimConnect.CreateDefinition; - - luaVariables := TList.Create; - try - for variable in variables do - begin - if variable.Value.VariableType = VariableTable then - begin - info := variable.Value.AsTable; - if info.HasValue('variable') then - begin - luaVariable.Name := variable.Key.AsString; - units := ''; - simConnectDataType := SIMCONNECT_DATAType_FLOAT64; - - if info.HasValue('type') and GetDataType(info.GetValue('type').AsString, dataType) then - begin - luaVariable.DataType := dataType; - - case dataType of - Float32: simConnectDataType := SIMCONNECT_DATAType_FLOAT32; - Int64: simConnectDataType := SIMCONNECT_DATAType_INT64; - Int32, - Bool: - begin - simConnectDataType := SIMCONNECT_DATAType_INT32; - units := 'bool'; - end; - - // TODO change to STRINGV - StringValue: simConnectDataType := SIMCONNECT_DATAType_STRING256; - XYZ: simConnectDataType := SIMCONNECT_DATAType_XYZ; - LatLonAlt: simConnectDataType := SIMCONNECT_DATAType_LATLONALT; - Waypoint: simConnectDataType := SIMCONNECT_DATAType_WAYPOINT; - end; - - if info.HasValue('units') then - units := info.GetValue('units').AsString - else if not (dataType in [Bool, StringValue, XYZ, LatLonAlt, Waypoint]) then - raise ELuaScriptError.CreateFmt('Missing units for variable %s', [variable.Key.AsString]); - end else - begin - if not info.HasValue('units') then - raise ELuaScriptError.CreateFmt('Missing units or type for variable %s', [variable.Key.AsString]); - - units := info.GetValue('units').AsString; - end; - - luaVariables.Add(luaVariable); - definition.AddVariable(info.GetValue('variable').AsString, units, simConnectDataType); - end; - end; - end; - - (worker as TFSXLEDFunctionWorker).AddDefinition(definition, TFSXFunctionWorkerDataHandler.Create(luaVariables, worker.UID, onData)); - finally - FreeAndNil(luaVariables); - end; -end; - end. diff --git a/G940LEDControl/Units/LuaLEDFunctionProvider.pas b/G940LEDControl/Units/LuaLEDFunctionProvider.pas index 9803884..c2dd37c 100644 --- a/G940LEDControl/Units/LuaLEDFunctionProvider.pas +++ b/G940LEDControl/Units/LuaLEDFunctionProvider.pas @@ -6,6 +6,8 @@ uses System.SysUtils, System.Types, + OtlTask, + OtlTaskControl, X2Log.Intf, LEDFunction, @@ -52,6 +54,7 @@ type protected function CreateLuaLEDFunction(AInfo: ILuaTable; ASetup: ILuaFunction): TCustomLuaLEDFunction; virtual; abstract; + procedure CheckParameters(const AFunctionName: string; AParameters: ILuaReadParameters; AExpectedTypes: array of TLuaVariableType); procedure AppendVariable(ABuilder: TStringBuilder; AVariable: ILuaVariable); procedure AppendTable(ABuilder: TStringBuilder; ATable: ILuaTable); @@ -60,6 +63,10 @@ type procedure ScriptRegisterFunction(Context: ILuaContext); procedure ScriptSetState(Context: ILuaContext); + procedure ScriptOnTimer(Context: ILuaContext); + procedure ScriptWindowVisible(Context: ILuaContext); + + function WindowVisible(const AClassName, AWindowTitle, AParentClassName, AParentWindowTitle: string): Boolean; procedure InitInterpreter; virtual; procedure RegisterFunctions; override; @@ -81,10 +88,14 @@ type private FProvider: TCustomLuaLEDFunctionProvider; FUID: string; + FTasks: TList; procedure SetProvider(const Value: TCustomLuaLEDFunctionProvider); protected + procedure AddTask(ATask: IOmniTaskControl); + property Provider: TCustomLuaLEDFunctionProvider read FProvider write SetProvider; + property Tasks: TList read FTasks; public constructor Create(const AProviderUID, AFunctionUID: string; AStates: ILEDMultiStateFunction; ASettings: ILEDFunctionWorkerSettings; const APreviousState: string = ''); override; destructor Destroy; override; @@ -97,6 +108,8 @@ implementation uses System.Classes, System.IOUtils, + System.StrUtils, + Winapi.Windows, Lua.API, X2Log.Global, @@ -123,6 +136,18 @@ type end; + TLuaTimerTask = class(TOmniWorker) + private + FOnTimer: TProc; + protected + property OnTimer: TProc read FOnTimer; + public + constructor Create(AOnTimer: TProc); + + procedure Run; + end; + + const LuaLEDColors: array[TLEDColor] of string = ( @@ -173,12 +198,33 @@ end; procedure TCustomLuaLEDFunctionProvider.InitInterpreter; var + requirePath: TStringBuilder; + scriptFolder: string; table: ILuaTable; color: TLEDColor; begin + requirePath := TStringBuilder.Create; + try + for scriptFolder in ScriptFolders do + begin + if requirePath.Length > 0 then + requirePath.Append(';'); + + requirePath.Append(IncludeTrailingPathDelimiter(scriptFolder)).Append('?;') + .Append(IncludeTrailingPathDelimiter(scriptFolder)).Append('?.lua'); + end; + + Interpreter.SetRequirePath(requirePath.ToString); + Interpreter.GetRequirePath; + finally + FreeAndNil(requirePath); + end; + Interpreter.RegisterFunction('RegisterFunction', ScriptRegisterFunction); Interpreter.RegisterFunction('SetState', ScriptSetState); + Interpreter.RegisterFunction('OnTimer', ScriptOnTimer); + Interpreter.RegisterFunction('WindowVisible', ScriptWindowVisible); Interpreter.RegisterFunctions(FScriptLog, 'Log'); @@ -187,9 +233,30 @@ begin table.SetValue(LuaLEDColors[color], LuaLEDColors[color]); Interpreter.SetGlobalVariable('LEDColor', table); +end; - // #ToDo1 -oMvR: 29-5-2017: Timer - // #ToDo1 -oMvR: 29-5-2017: FindWindow / FindFSXWindow + +procedure TCustomLuaLEDFunctionProvider.CheckParameters(const AFunctionName: string; AParameters: ILuaReadParameters; AExpectedTypes: array of TLuaVariableType); +const + VariableTypeName: array[TLuaVariableType] of string = + ( + 'None', 'Boolean', 'Integer', + 'Number', 'UserData', 'String', + 'Table', 'Function' + ); + +var + parameterIndex: Integer; + +begin + if AParameters.Count < Length(AExpectedTypes) then + raise ELuaScriptError.CreateFmt('%s: expected at least %d parameter%s', [AFunctionName, Length(AExpectedTypes), IfThen(Length(AExpectedTypes) <> 1, 's', '')]); + + for parameterIndex := 0 to High(AExpectedTypes) do + if AParameters[parameterIndex].VariableType <> AExpectedTypes[parameterIndex] then + raise ELuaScriptError.CreateFmt('%s: expected %s for parameter %d, got %s', + [AFunctionName, VariableTypeName[AExpectedTypes[parameterIndex]], + Succ(parameterIndex), VariableTypeName[AParameters[parameterIndex].VariableType]]); end; @@ -280,20 +347,13 @@ var setup: ILuaFunction; begin - if Context.Parameters.Count < 2 then - raise ELuaScriptError.Create('Not enough parameters for RegisterFunction'); - - if Context.Parameters[0].VariableType <> VariableTable then - raise ELuaScriptError.Create('Table expected for RegisterFunction parameter 1'); - - if Context.Parameters[1].VariableType <> VariableFunction then - raise ELuaScriptError.Create('Function expected for RegisterFunction parameter 2'); + CheckParameters('RegisterFunction', Context.Parameters, [VariableTable, VariableFunction]); info := Context.Parameters[0].AsTable; setup := Context.Parameters[1].AsFunction; if not info.HasValue('uid') then - raise ELuaScriptError.Create('"uid" value is required for RegisterFunction parameter 1'); + raise ELuaScriptError.Create('RegisterFunction: "uid" value is required'); DoLogMessage(Context, TX2LogLevel.Info, Format('Registering function: %s', [info.GetValue('uid').AsString])); RegisterFunction(CreateLuaLEDFunction(info, setup)); @@ -307,27 +367,114 @@ var stateUID: string; begin - if Context.Parameters.Count < 2 then - raise ELuaScriptError.Create('Not enough parameters for SetState'); - - if Context.Parameters[0].VariableType <> VariableString then - raise ELuaScriptError.Create('Context expected for SetState parameter 1'); - - if Context.Parameters[1].VariableType <> VariableString then - raise ELuaScriptError.Create('State expected for SetState parameter 2'); + CheckParameters('SetState', Context.Parameters, [VariableString, VariableString]); workerID := Context.Parameters[0].AsString; stateUID := Context.Parameters[1].AsString; worker := FindWorker(workerID); if not Assigned(worker) then - raise ELuaScriptError.Create('Context expected for SetState parameter 1'); + raise ELuaScriptError.Create('SetState: invalid context'); DoLogMessage(Context, TX2LogLevel.Info, Format('Setting state for %s to: %s', [worker.GetFunctionUID, stateUID])); worker.SetCurrentState(stateUID); end; +procedure TCustomLuaLEDFunctionProvider.ScriptOnTimer(Context: ILuaContext); +var + workerID: string; + interval: Integer; + timerCallback: ILuaFunction; + worker: TCustomLuaLEDFunctionWorker; + +begin + CheckParameters('OnTimer', Context.Parameters, [VariableString, VariableNumber, VariableFunction]); + + workerID := Context.Parameters[0].AsString; + interval := Context.Parameters[1].AsInteger; + timerCallback := Context.Parameters[2].AsFunction; + + worker := FindWorker(workerID); + if not Assigned(worker) then + raise ELuaScriptError.Create('OnTimer: invalid context'); + + DoLogMessage(Context, TX2LogLevel.Info, Format('Adding timer for %s, interval: %d', [worker.GetFunctionUID, interval])); + worker.AddTask(CreateTask(TLuaTimerTask.Create( + procedure + begin + try + timerCallback.Call([workerID]); + except + on E:Exception do + TX2GlobalLog.Category('Lua').Exception(E); + end; + end)) + .SetTimer(1, MSecsPerSec, @TLuaTimerTask.Run) + .Run); +end; + + +procedure TCustomLuaLEDFunctionProvider.ScriptWindowVisible(Context: ILuaContext); +var + className: string; + windowTitle: string; + parentClassName: string; + parentWindowTitle: string; + +begin + if Context.Parameters.Count = 0 then + raise ELuaScriptError.Create('WindowVisible: expected at least 1 parameter'); + + className := ''; + windowTitle := ''; + parentClassName := ''; + parentWindowTitle := ''; + + if Context.Parameters.Count >= 1 then className := Context.Parameters[0].AsString; + if Context.Parameters.Count >= 2 then windowTitle := Context.Parameters[1].AsString; + if Context.Parameters.Count >= 3 then parentClassName := Context.Parameters[2].AsString; + if Context.Parameters.Count >= 4 then parentWindowTitle := Context.Parameters[3].AsString; + + Context.Result.Push(WindowVisible(className, windowTitle, parentClassName, parentWindowTitle)); +end; + + +function TCustomLuaLEDFunctionProvider.WindowVisible(const AClassName, AWindowTitle, AParentClassName, AParentWindowTitle: string): Boolean; + + function GetNilPChar(const AValue: string): PChar; + begin + if Length(AValue) > 0 then + Result := PChar(AValue) + else + Result := nil; + end; + +var + parentWindow: THandle; + childWindow: THandle; + window: THandle; + +begin + Result := False; + + if (Length(AParentClassName) > 0) or (Length(AParentWindowTitle) > 0) then + begin + parentWindow := FindWindow(GetNilPChar(AParentClassName), GetNilPChar(AParentWindowTitle)); + if parentWindow <> 0 then + begin + childWindow := FindWindowEx(parentWindow, 0, GetNilPChar(AClassName), GetNilPChar(AWindowTitle)); + Result := (childWindow <> 0) and IsWindowVisible(childWindow); + end; + end else + begin + window := FindWindow(GetNilPChar(AClassName), GetNilPChar(AWindowTitle)); + Result := (window <> 0) and IsWindowVisible(window); + end; +end; + + + procedure TCustomLuaLEDFunctionProvider.RegisterFunctions; var scriptFolder: string; @@ -461,13 +608,36 @@ begin end; destructor TCustomLuaLEDFunctionWorker.Destroy; +var + task: IOmniTaskControl; + begin + if Assigned(Tasks) then + begin + for task in Tasks do + begin + task.Stop; + task.WaitFor(INFINITE); + end; + + FreeAndNil(FTasks); + end; + SetProvider(nil); inherited Destroy; end; +procedure TCustomLuaLEDFunctionWorker.AddTask(ATask: IOmniTaskControl); +begin + if not Assigned(Tasks) then + FTasks := TList.Create; + + Tasks.Add(ATask); +end; + + procedure TCustomLuaLEDFunctionWorker.SetProvider(const Value: TCustomLuaLEDFunctionProvider); begin if Value <> FProvider then @@ -515,4 +685,19 @@ begin OnLog(Context, TX2LogLevel.Error); end; + +{ TLuaTimerTask } +constructor TLuaTimerTask.Create(AOnTimer: TProc); +begin + inherited Create; + + FOnTimer := AOnTimer; +end; + + +procedure TLuaTimerTask.Run; +begin + FOnTimer(); +end; + end.