├── .husky
├── pre-commit
└── commit-msg
├── .prettierrc.json
├── .prettierignore
├── .gitignore
├── packages
└── simhub-sli-plugin
│ ├── assets
│ ├── SLI-F1.png
│ └── SLI-PRO-RevA.png
│ ├── packages.config
│ ├── src
│ ├── Devices
│ │ ├── IInputReport.cs
│ │ ├── IBrightnessReport.cs
│ │ ├── IConstants.cs
│ │ ├── ILedStateReport.cs
│ │ ├── DeviceInfo.cs
│ │ ├── DevicePoller.cs
│ │ ├── SliPro
│ │ │ ├── SliProConstants.cs
│ │ │ └── SliProReportFormats.cs
│ │ ├── SliF1
│ │ │ ├── SliF1Constants.cs
│ │ │ └── SliF1ReportFormats.cs
│ │ ├── DeviceDescriptor.cs
│ │ └── RotarySwitchDetector.cs
│ ├── SegmentDisplays
│ │ ├── FuelSegmentDisplay.cs
│ │ ├── LapsToGoSegmentDisplay.cs
│ │ ├── PositionSegmentDisplay.cs
│ │ ├── RpmSegmentDisplay.cs
│ │ ├── BrakeBiasSegmentDisplay.cs
│ │ ├── SpeedSegmentDisplay.cs
│ │ ├── LapCounterSegmentDisplay.cs
│ │ ├── LapTimeSegmentDisplay.cs
│ │ ├── DeltaSegmentDisplay.cs
│ │ ├── TempSegmentDisplay.cs
│ │ ├── SegmentDisplay.cs
│ │ ├── Formatters
│ │ │ ├── IOutputFormatters.cs
│ │ │ ├── SliProOutputFormatters.cs
│ │ │ └── SliF1OutputFormatters.cs
│ │ └── SegmentDisplayManager.cs
│ ├── Controls
│ │ ├── RpmLedsEditor.xaml.cs
│ │ ├── RpmLedsEditor.xaml
│ │ ├── SliPluginControl.xaml.cs
│ │ ├── SegmentDisplayControl.xaml
│ │ ├── SliPluginControl.xaml
│ │ ├── RotarySwitchMappingControl.xaml
│ │ ├── RotarySwitchMappingControl.xaml.cs
│ │ ├── StatusLedArray.xaml.cs
│ │ ├── StatusLedArray.xaml
│ │ ├── SegmentDisplayControl.xaml.cs
│ │ ├── DeviceInstanceControl.xaml.cs
│ │ └── DeviceInstanceControl.xaml
│ ├── VJoyWrap.cs
│ ├── NormalizedData.cs
│ ├── Led.cs
│ ├── SliPluginDeviceDescriptor.cs
│ ├── VJoyManager.cs
│ └── SliPlugin.cs
│ ├── SimElation.SimHub.SliPlugin.csproj.user
│ ├── Properties
│ ├── Styles.xaml
│ ├── AssemblyInfo.cs
│ ├── DesignTimeResources.xaml
│ └── ThemeProperties.cs
│ ├── SimElation.SimHub.SliPlugin.sln
│ ├── .vscode
│ └── tasks.json
│ ├── package.json
│ ├── CHANGELOG.md
│ └── SimElation.SimHub.SliPlugin.csproj
├── .lintstagedrc.json
├── .config
└── dotnet-tools.json
├── commitlint.config.js
├── lerna.json
├── README.md
├── LICENSE
├── package.json
└── .editorconfig
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # pre-commit
4 | yarn --silent lint-staged -r
5 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # commit-msg.
4 | yarn --silent commitlint -e $1
5 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 132,
3 | "tabWidth": 4,
4 | "arrowParens": "always"
5 | }
6 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .git
2 | node_modules
3 |
4 | packages/**/bin
5 | packages/**/lib
6 | packages/**/obj
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | *.log
4 | .vs
5 |
6 | packages/**/lib
7 | packages/**/bin
8 | packages/**/obj
9 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/assets/SLI-F1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simelation/simhub-plugins/HEAD/packages/simhub-sli-plugin/assets/SLI-F1.png
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/assets/SLI-PRO-RevA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simelation/simhub-plugins/HEAD/packages/simhub-sli-plugin/assets/SLI-PRO-RevA.png
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.lintstagedrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "*.{js,jsx,ts,tsx,json,css,md,html}": "prettier --write",
3 | "*.cs": "dotnet dotnet-format -f --include",
4 | "*.{c,cpp,h,hpp}": "clang-format --style=file -i"
5 | }
6 |
--------------------------------------------------------------------------------
/.config/dotnet-tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "isRoot": true,
4 | "tools": {
5 | "dotnet-format": {
6 | "version": "5.1.250801",
7 | "commands": ["dotnet-format"]
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ["@commitlint/config-conventional", "@commitlint/config-lerna-scopes"],
3 | rules: {
4 | "subject-full-stop": [2, "always", "."],
5 | "header-max-length": [1, "always", 132],
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": ["packages/*"],
3 | "version": "independent",
4 | "npmClient": "yarn",
5 | "useWorkspaces": true,
6 | "command": {
7 | "bootstrap": {
8 | "ci": false
9 | },
10 | "publish": {
11 | "conventionalCommits": true,
12 | "message": "chore: publish.",
13 | "registry": "https://npm.pkg.github.com"
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Devices/IInputReport.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Input report from device.
3 | */
4 |
5 | namespace SimElation.SliDevices
6 | {
7 | /// Input report format from a device..
8 | public interface IInputReport
9 | {
10 | /// Offset in input report to the rotary switch data.
11 | uint RotarySwitchesOffset { get; }
12 |
13 | /// Number of bytes in the input report containing rotary switch data.
14 | uint RotarySwitchesLength { get; }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/SimElation.SimHub.SliPlugin.csproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Program
5 | $(MSBuildProjectDirectory)\SimHub\SimHubWPF.exe
6 |
7 |
8 | Program
9 | $(MSBuildProjectDirectory)\SimHub\SimHubWPF.exe
10 |
11 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/Properties/Styles.xaml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | [assembly: AssemblyTitle("SimElation.SimHub.SliPlugin")]
5 | [assembly: AssemblyDescription("SLI plugin for SimHub.")]
6 | [assembly: AssemblyCompany("")]
7 | [assembly: AssemblyProduct("SimElation.SimHub.SliPlugin")]
8 | [assembly: AssemblyCopyright("Copyright © 2020")]
9 | [assembly: AssemblyTrademark("")]
10 | [assembly: AssemblyCulture("")]
11 | [assembly: ComVisible(false)]
12 | [assembly: Guid("064096f9-41a4-4304-811d-b2af7725254b")]
13 | [assembly: AssemblyVersion("0.11.2.0")]
14 | [assembly: AssemblyFileVersion("0.11.2.0")]
15 |
16 | #if (DEBUG)
17 | [assembly: AssemblyConfiguration("Debug")]
18 | #else
19 | [assembly: AssemblyConfiguration("Release")]
20 | #endif
21 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Devices/IBrightnessReport.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * LED brightness report format for sending to a device.
3 | */
4 |
5 | // ---------------------------------------------------------------------------------------------------------------------------------
6 |
7 | namespace SimElation.SliDevices
8 | {
9 | /// LED brightness report format for sending to a device.
10 | public interface IBrightnessReport
11 | {
12 | /// Offset into the report for the type field.
13 | uint ReportTypeOffset { get; }
14 |
15 | /// Value for the type field.
16 | byte ReportType { get; }
17 |
18 | /// Offset into the report for the brightness field.
19 | uint BrightnessOffset { get; }
20 |
21 | /// The report length.
22 | uint Length { get; }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SimElation SimHub plugins.
2 |
3 | ## Configuring for development
4 |
5 | [yarn](https://classic.yarnpkg.com/en/docs/install/#windows-stable) is currently used for package management and release generation.
6 |
7 | ### Install dependencies
8 |
9 | `yarn install` in the root of the repository will install some git hooks to manage commit message style (conventional commits for
10 | semnatic versioning) and code style.
11 |
12 | ### Development
13 |
14 | `yarn develop` will build debug versions of all packages. Requires `msbuild.exe` in the path
15 | ([build tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/)) and
16 | [.NET Framework](https://dotnet.microsoft.com/download/visual-studio-sdks).
17 |
18 | ### Production
19 |
20 | `yarn build` will build production versions of all packages.
21 |
22 | ### Publish a release
23 |
24 | `yarn release` will build production versions of all packages and publish to github.
25 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/Properties/DesignTimeResources.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/SegmentDisplays/FuelSegmentDisplay.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Fuel status segment display.
3 | */
4 |
5 | using System;
6 | using SimElation.SliDevices;
7 |
8 | // ---------------------------------------------------------------------------------------------------------------------------------
9 |
10 | namespace SimElation.Simhub.SliPlugin
11 | {
12 | /// Fuel status segment display.
13 | public sealed class FuelSegmentDisplay : SegmentDisplay
14 | {
15 | /// Constructor.
16 | public FuelSegmentDisplay() : base("Fuel", "Fuel remaining")
17 | {
18 | }
19 |
20 | ///
21 | public override void ProcessData(NormalizedData normalizedData, Device.SegmentDisplayPosition position,
22 | IOutputFormatters outputFormatters, ref String str, ref uint[] decimalOrPrimeIndexList)
23 | {
24 | outputFormatters.Fuel(normalizedData.StatusData.Fuel, normalizedData.FuelRemainingLaps, ref str,
25 | ref decimalOrPrimeIndexList);
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/SegmentDisplays/LapsToGoSegmentDisplay.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Laps remaining segment display.
3 | */
4 |
5 | using System;
6 | using SimElation.SliDevices;
7 |
8 | // ---------------------------------------------------------------------------------------------------------------------------------
9 |
10 | namespace SimElation.Simhub.SliPlugin
11 | {
12 | /// Laps remaining segment display.
13 | public sealed class LapsToGoSegmentDisplay : SegmentDisplay
14 | {
15 | /// Constructor.
16 | public LapsToGoSegmentDisplay() : base("togo", "Laps remaining")
17 | {
18 | }
19 |
20 | ///
21 | public override void ProcessData(NormalizedData normalizedData, Device.SegmentDisplayPosition position,
22 | IOutputFormatters outputFormatters, ref String str, ref uint[] decimalOrPrimeIndexList)
23 | {
24 | if (normalizedData.StatusData.RemainingLaps > 0)
25 | outputFormatters.LapsToGo(normalizedData.StatusData.RemainingLaps, ref str, ref decimalOrPrimeIndexList);
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Controls/RpmLedsEditor.xaml.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * RPM LED editing control.
3 | */
4 |
5 | using System.Collections;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 |
9 | // ---------------------------------------------------------------------------------------------------------------------------------
10 |
11 | namespace SimElation.Simhub.SliPlugin
12 | {
13 | /// Interaction logic for RpmLedsEditor.xaml.
14 | public partial class RpmLedsEditor : UserControl
15 | {
16 | private static readonly DependencyProperty ItemsSourceProperty =
17 | DependencyProperty.Register(nameof(ItemsSource), typeof(IEnumerable), typeof(RpmLedsEditor));
18 |
19 | /// Set of s.
20 | public IEnumerable ItemsSource
21 | {
22 | get => GetValue(ItemsSourceProperty) as IEnumerable;
23 | set => SetValue(ItemsSourceProperty, value);
24 | }
25 |
26 | /// Constructor.
27 | public RpmLedsEditor()
28 | {
29 | InitializeComponent();
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 SimElation
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/VJoyWrap.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Wrap over vJoyInterfaceWrap itself to make it an optional dependency.
3 | */
4 |
5 | using vJoyInterfaceWrap;
6 |
7 | // ---------------------------------------------------------------------------------------------------------------------------------
8 |
9 | namespace SimElation
10 | {
11 | /// vJoyInterfaceWrap wrap.
12 | public class VJoyWrap
13 | {
14 | ///
15 | public VJoyWrap()
16 | {
17 | m_vJoy = new vJoy();
18 | }
19 |
20 | ///
21 | public bool DriverMatch(ref uint dllVersion, ref uint driverVersion)
22 | {
23 | return m_vJoy.DriverMatch(ref dllVersion, ref driverVersion);
24 | }
25 |
26 | ///
27 | public VjdStat GetVJDStatus(uint id)
28 | {
29 | return m_vJoy.GetVJDStatus(id);
30 | }
31 |
32 | ///
33 | public bool AcquireVJD(uint id)
34 | {
35 | return m_vJoy.AcquireVJD(id);
36 | }
37 |
38 | ///
39 | public bool SetBtn(bool isSet, uint id, uint buttonId)
40 | {
41 | return m_vJoy.SetBtn(isSet, id, buttonId);
42 | }
43 |
44 | private readonly vJoy m_vJoy;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/SegmentDisplays/PositionSegmentDisplay.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Position segment display.
3 | */
4 |
5 | using System;
6 | using SimElation.SliDevices;
7 |
8 | // ---------------------------------------------------------------------------------------------------------------------------------
9 |
10 | namespace SimElation.Simhub.SliPlugin
11 | {
12 | /// Position segment display.
13 | ///
14 | /// Shows current position as "Pxx-yy" where xx is the current position and yy is the number of runners.
15 | ///
16 | public sealed class PositionSegmentDisplay : SegmentDisplay
17 | {
18 | /// Constructor.
19 | public PositionSegmentDisplay() : base("Posn", "Position")
20 | {
21 | }
22 |
23 | ///
24 | public override void ProcessData(NormalizedData normalizedData, Device.SegmentDisplayPosition position,
25 | IOutputFormatters outputFormatters, ref String str, ref uint[] decimalOrPrimeIndexList)
26 | {
27 | outputFormatters.Position(normalizedData.StatusData.Position, normalizedData.StatusData.OpponentsCount,
28 | ref str, ref decimalOrPrimeIndexList);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/SegmentDisplays/RpmSegmentDisplay.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * RPM segment display.
3 | */
4 |
5 | using System;
6 | using SimElation.SliDevices;
7 |
8 | // ---------------------------------------------------------------------------------------------------------------------------------
9 |
10 | namespace SimElation.Simhub.SliPlugin
11 | {
12 | /// RPM segment display.
13 | public sealed class RpmSegmentDisplay : SegmentDisplay
14 | {
15 | /// Constructor.
16 | /// Description of the information show by the display. This will be displayed when switching
17 | /// to this segment display for a period of time.
18 | public RpmSegmentDisplay(String shortName) : base(shortName, "Current RPM")
19 | {
20 | }
21 |
22 | ///
23 | public override void ProcessData(NormalizedData normalizedData, Device.SegmentDisplayPosition position,
24 | IOutputFormatters outputFormatters, ref String str, ref uint[] decimalOrPrimeIndexList)
25 | {
26 | // TODO what is FilteredRpms?
27 | outputFormatters.Rpm(normalizedData.StatusData.Rpms, ref str, ref decimalOrPrimeIndexList);
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/SegmentDisplays/BrakeBiasSegmentDisplay.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Brake bias segment display.
3 | */
4 |
5 | using System;
6 | using SimElation.SliDevices;
7 |
8 | // ---------------------------------------------------------------------------------------------------------------------------------
9 |
10 | namespace SimElation.Simhub.SliPlugin
11 | {
12 | /// Brake bias segment display.
13 | public sealed class BrakeBiasSegmentDisplay : SegmentDisplay
14 | {
15 | /// Constructor.
16 | /// Description of the information show by the display. This will be displayed when switching
17 | /// to this segment display for a period of time.
18 | public BrakeBiasSegmentDisplay(String shortName) : base(shortName, "Brake bias")
19 | {
20 | }
21 |
22 | ///
23 | public override void ProcessData(NormalizedData normalizedData, Device.SegmentDisplayPosition position,
24 | IOutputFormatters outputFormatters, ref String str, ref uint[] decimalOrPrimeIndexList)
25 | {
26 | outputFormatters.BrakeBias(normalizedData.StatusData.BrakeBias, ref str, ref decimalOrPrimeIndexList);
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/SegmentDisplays/SpeedSegmentDisplay.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Speed segment display.
3 | */
4 |
5 | using System;
6 | using SimElation.SliDevices;
7 |
8 | // ---------------------------------------------------------------------------------------------------------------------------------
9 |
10 | namespace SimElation.Simhub.SliPlugin
11 | {
12 | /// Speed segment display.
13 | public sealed class SpeedSegmentDisplay : SegmentDisplay
14 | {
15 | /// Constructor.
16 | /// Description of the information show by the display. This will be displayed when switching
17 | /// to this segment display for a period of time.
18 | public SpeedSegmentDisplay(String shortName) : base(shortName, "Current speed")
19 | {
20 | }
21 |
22 | ///
23 | public override void ProcessData(NormalizedData normalizedData, Device.SegmentDisplayPosition position,
24 | IOutputFormatters outputFormatters, ref String str, ref uint[] decimalOrPrimeIndexList)
25 | {
26 | // TODO what is FilteredSpeedLocal?
27 | outputFormatters.Speed(normalizedData.StatusData.SpeedLocal, ref str, ref decimalOrPrimeIndexList);
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/SimElation.SimHub.SliPlugin.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31903.59
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimElation.SimHub.SliPlugin", "SimElation.SimHub.SliPlugin.csproj", "{829D79D8-F56E-4F95-82E2-C385F7DFA5E5}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|x86 = Debug|x86
11 | Release|x86 = Release|x86
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {829D79D8-F56E-4F95-82E2-C385F7DFA5E5}.Debug|x86.ActiveCfg = Debug|x86
15 | {829D79D8-F56E-4F95-82E2-C385F7DFA5E5}.Debug|x86.Build.0 = Debug|x86
16 | {829D79D8-F56E-4F95-82E2-C385F7DFA5E5}.Release|x86.ActiveCfg = Release|x86
17 | {829D79D8-F56E-4F95-82E2-C385F7DFA5E5}.Release|x86.Build.0 = Release|x86
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {2C31B571-3D00-456A-B70C-B4FCE840BDB9}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/SegmentDisplays/LapCounterSegmentDisplay.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Lap counter segment display.
3 | */
4 |
5 | using System;
6 | using SimElation.SliDevices;
7 |
8 | // ---------------------------------------------------------------------------------------------------------------------------------
9 |
10 | namespace SimElation.Simhub.SliPlugin
11 | {
12 | /// Lap counter display.
13 | ///
14 | /// Shows "Lxx-yy" where xx is the current lap and yy is the total number of laps, or
15 | /// "Lxxxxx" where xxxxx is the number of completed laps (if current of total is not applicable).
16 | ///
17 | public sealed class LapsCounterSegmentDisplay : SegmentDisplay
18 | {
19 | /// Constructor.
20 | public LapsCounterSegmentDisplay() : base("Lap", "Current lap #")
21 | {
22 | }
23 |
24 | ///
25 | public override void ProcessData(NormalizedData normalizedData, Device.SegmentDisplayPosition position,
26 | IOutputFormatters outputFormatters, ref String str, ref uint[] decimalOrPrimeIndexList)
27 | {
28 | outputFormatters.LapCounter(normalizedData.StatusData.TotalLaps, normalizedData.StatusData.CurrentLap,
29 | normalizedData.StatusData.CompletedLaps, ref str, ref decimalOrPrimeIndexList);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/Properties/ThemeProperties.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Theme properties.
3 | */
4 |
5 | using System.Windows;
6 | using System.Windows.Media;
7 |
8 | // ---------------------------------------------------------------------------------------------------------------------------------
9 |
10 | namespace SimElation.Simhub.SliPlugin
11 | {
12 | /// Common styling stuff.
13 | ///
14 | /// This is based on https://thomaslevesque.com/2011/10/01/wpf-creating-parameterized-styles-with-attached-properties/
15 | /// to allow parameters to style templates.
16 | ///
17 | public static class ThemeProperties
18 | {
19 | ///
20 | public static Brush GetSetLedBrush(DependencyObject dependencyObject)
21 | {
22 | return (Brush)dependencyObject.GetValue(SetLedBrushProperty);
23 | }
24 |
25 | ///
26 | public static void SetSetLedBrush(DependencyObject dependencyObject, Brush value)
27 | {
28 | dependencyObject.SetValue(SetLedBrushProperty, value);
29 | }
30 |
31 | /// Attached property for the color of a lit LED in the UI. Defaults to blue.
32 | public static readonly DependencyProperty SetLedBrushProperty = DependencyProperty.RegisterAttached("SetLedBrush",
33 | typeof(Brush), typeof(ThemeProperties), new FrameworkPropertyMetadata(Brushes.Blue));
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Devices/IConstants.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Interface for various device-specific constant values.
3 | */
4 |
5 | using System.Windows.Media;
6 |
7 | // ---------------------------------------------------------------------------------------------------------------------------------
8 |
9 | namespace SimElation.SliDevices
10 | {
11 | /// Interface for various device-specific constant values.
12 | public interface IConstants
13 | {
14 | /// The number of characters in each segment display.
15 | uint SegmentDisplayWidth { get; }
16 |
17 | /// Colors of the rev LED array. Length of this is therefore the number of rev LEDs.
18 | Color[] RevLedColors { get; }
19 |
20 | /// Colors of the left status LED array.
21 | Color[] LeftStatusLedColors { get; }
22 |
23 | /// Colors of the right status LED array.
24 | Color[] RightStatusLedColors { get; }
25 |
26 | /// The number of status LEDs (total of left and right).
27 | uint NumberOfStatusLeds { get; }
28 |
29 | /// The number of external LEDs.
30 | uint NumberOfExternalLeds { get; }
31 |
32 | /// The number of supported rotary switches.
33 | uint MaxNumberOfRotarySwitches { get; }
34 |
35 | /// The number of supported potentiometers.
36 | uint MaxNumberOfPots { get; }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@simelation/simhub-plugins",
3 | "version": "0.0.0",
4 | "description": "SimElation SimHub plugins monorepo",
5 | "repository": "https://github.com/simelation/simhub-plugins.git",
6 | "author": "Luke Elliott ",
7 | "license": "MIT",
8 | "private": true,
9 | "workspaces": [
10 | "packages/**"
11 | ],
12 | "scripts": {
13 | "lerna": "lerna",
14 | "preinstall": "dotnet tool restore",
15 | "postinstall": "husky install",
16 | "prepare_comment": "echo Workaround for https://github.com/yarnpkg/yarn/issues/3911",
17 | "prepare": "lerna run prepare --",
18 | "build": "lerna --concurrency 4 run build --",
19 | "develop": "lerna run develop --",
20 | "clean": "lerna run clean && lerna clean",
21 | "release": "lerna version && yarn build && lerna exec yarn pack",
22 | "test": "lerna run test --",
23 | "upgrade-all": "yarn upgrade --latest -W && lerna --concurrency 1 exec yarn-upgrade-all",
24 | "yarn:publish-links": "lerna exec yarn link --",
25 | "yarn:unpublish-links": "lerna exec yarn unlink --"
26 | },
27 | "devDependencies": {
28 | "@commitlint/cli": "^17.3.0",
29 | "@commitlint/config-conventional": "^17.3.0",
30 | "@commitlint/config-lerna-scopes": "^17.2.1",
31 | "husky": "^8.0.2",
32 | "lerna": "^6.1.0",
33 | "lint-staged": "^13.1.0",
34 | "prettier": "^2.8.1",
35 | "yarn-upgrade-all": "^0.7.1"
36 | },
37 | "lint-staged": {
38 | "*.{js,jsx,ts,tsx,json,css,md,html}": "prettier --write",
39 | "*.cs": "dotnet dotnet-format -f --include"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Devices/ILedStateReport.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * LED state report format for sending to a device.
3 | */
4 |
5 | // ---------------------------------------------------------------------------------------------------------------------------------
6 |
7 | namespace SimElation.SliDevices
8 | {
9 | /// LED state report format for sending to a device.
10 | public interface ILedStateReport
11 | {
12 | /// Offset into the report for the type field.
13 | uint ReportTypeOffset { get; }
14 |
15 | /// Value for the type field.
16 | byte ReportType { get; }
17 |
18 | /// Offset into the report for the gear number field.
19 | uint GearOffset { get; }
20 |
21 | /// Offset into the report for the first rev LED.
22 | uint RevLed1Offset { get; }
23 |
24 | /// Offset into the report for the first status LED.
25 | uint StatusLed1Offset { get; }
26 |
27 | /// Offset into the report for the first external LED.
28 | uint ExternalLed1Offset { get; }
29 |
30 | /// Offset into the report for the first character of the left segment display.
31 | uint LeftSegmentDisplayOffset { get; }
32 |
33 | /// Offset into the report for the first character of the right segment display.
34 | uint RightSegmentDisplayOffset { get; }
35 |
36 | /// The report length.
37 | uint Length { get; }
38 |
39 | /// Value to OR with segment display character to set . or '
40 | byte SegmentDisplayDecimalOrPrimeBit { get; }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "label": "Build Debug",
8 | "type": "shell",
9 | "command": "msbuild",
10 | "args": [
11 | // Ask msbuild to generate full paths for file names.
12 | "/property:GenerateFullPaths=true",
13 | "/t:build",
14 | "/p:Configuration=Debug",
15 | "/p:Platform='Any CPU'",
16 | // Do not generate summary otherwise it leads to duplicate errors in Problems panel
17 | "/consoleloggerparameters:NoSummary"
18 | ],
19 | "group": "build",
20 | "presentation": {
21 | // Reveal the output only if unrecognized errors occur.
22 | "reveal": "silent"
23 | },
24 | // Use the standard MS compiler pattern to detect errors, warnings and infos
25 | "problemMatcher": "$msCompile"
26 | },
27 | {
28 | "label": "Build Release",
29 | "type": "shell",
30 | "command": "msbuild",
31 | "args": [
32 | // Ask msbuild to generate full paths for file names.
33 | "/property:GenerateFullPaths=true",
34 | "/t:build",
35 | "/p:Configuration=Release",
36 | "/p:Platform='Any CPU'",
37 | // Do not generate summary otherwise it leads to duplicate errors in Problems panel
38 | "/consoleloggerparameters:NoSummary"
39 | ],
40 | "group": "build",
41 | "presentation": {
42 | // Reveal the output only if unrecognized errors occur.
43 | "reveal": "silent"
44 | },
45 | // Use the standard MS compiler pattern to detect errors, warnings and infos
46 | "problemMatcher": "$msCompile"
47 | }
48 | ]
49 | }
50 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/SegmentDisplays/LapTimeSegmentDisplay.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Lap time segment display.
3 | */
4 |
5 | using System;
6 | using SimElation.SliDevices;
7 |
8 | // ---------------------------------------------------------------------------------------------------------------------------------
9 |
10 | namespace SimElation.Simhub.SliPlugin
11 | {
12 | /// Lap time segment display.
13 | public sealed class LapTimeSegmentDisplay : SegmentDisplay
14 | {
15 | /// Function type to get a lap time value from .
16 | ///
17 | /// Lap time or null if not available.
18 | public delegate TimeSpan GetTimeSpan(NormalizedData normalizedData);
19 |
20 | /// Constructor.
21 | /// Description of the lap time. This will be displayed when switching
22 | /// to this segment display for a period of time.
23 | /// Description of the lap time for the UI.
24 | /// Function to get a delta value from .
25 | public LapTimeSegmentDisplay(String shortName, String longName,
26 | GetTimeSpan getTimeSpan) : base(shortName, longName)
27 | {
28 | m_getTimeSpan = getTimeSpan;
29 | }
30 |
31 | ///
32 | public override void ProcessData(NormalizedData normalizedData, Device.SegmentDisplayPosition position,
33 | IOutputFormatters outputFormatters, ref String str, ref uint[] decimalOrPrimeIndexList)
34 | {
35 | outputFormatters.LapTime(m_getTimeSpan(normalizedData), ref str, ref decimalOrPrimeIndexList);
36 | }
37 |
38 | private readonly GetTimeSpan m_getTimeSpan;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/SegmentDisplays/DeltaSegmentDisplay.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Delta segment display.
3 | */
4 |
5 | using System;
6 | using SimElation.SliDevices;
7 |
8 | // ---------------------------------------------------------------------------------------------------------------------------------
9 |
10 | namespace SimElation.Simhub.SliPlugin
11 | {
12 | /// Delta time segment display.
13 | public sealed class DeltaSegmentDisplay : SegmentDisplay
14 | {
15 | /// Function type to get a delta value from .
16 | ///
17 | /// Delta time or null if not available.
18 | public delegate double? GetDelta(NormalizedData normalizedData);
19 |
20 | /// Constructor.
21 | /// Description of the delta time. This will be displayed when switching
22 | /// to this segment display for a period of time.
23 | /// Description of the delta time for the UI.
24 | /// Function to get a delta value from .
25 | public DeltaSegmentDisplay(String shortName, String longName, GetDelta getDelta) :
26 | base(shortName, longName)
27 | {
28 | m_getDelta = getDelta;
29 | }
30 |
31 | ///
32 | public override void ProcessData(NormalizedData normalizedData, Device.SegmentDisplayPosition position,
33 | IOutputFormatters outputFormatters, ref String str, ref uint[] decimalOrPrimeIndexList)
34 | {
35 | double? value = m_getDelta(normalizedData);
36 |
37 | if (value != null)
38 | outputFormatters.DeltaTime((double)value, ref str, ref decimalOrPrimeIndexList);
39 | }
40 |
41 | private readonly GetDelta m_getDelta;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@simelation/simhub-sli-plugin",
3 | "version": "0.11.2",
4 | "description": "SLI plugin for SimHub.",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/simelation/simhub-plugins.git"
8 | },
9 | "author": {
10 | "name": "Luke Elliott",
11 | "email": "luke.b.elliott@gmail.com"
12 | },
13 | "license": "MIT",
14 | "private": false,
15 | "publishConfig": {
16 | "registry": "https://npm.pkg.github.com/",
17 | "access": "public"
18 | },
19 | "files": [
20 | "/bin/x86/Release/*.dll",
21 | "CHANGELOG.md"
22 | ],
23 | "scripts": {
24 | "prepare": "cmd /c if not exist SimHub mklink /D SimHub \"C:\\Program Files (x86)\\SimHub\" && nuget.exe restore",
25 | "build": "msbuild.exe SimElation.SimHub.SliPlugin.csproj /property:GenerateFullPaths=true /t:build /p:Configuration=Release /p:Platform=x86 /consoleloggerparameters:NoSummary",
26 | "develop": "msbuild.exe SimElation.SimHub.SliPlugin.csproj /property:GenerateFullPaths=true /t:build /p:Configuration=Debug /p:Platform=x86 /consoleloggerparameters:NoSummary",
27 | "clean-build": "msbuild.exe SimElation.SimHub.SliPlugin.csproj /property:GenerateFullPaths=true /t:clean /p:Configuration=Release /p:Platform=x86 /consoleloggerparameters:NoSummary",
28 | "clean-develop": "msbuild.exe SimElation.SimHub.SliPlugin.csproj /property:GenerateFullPaths=true /t:clean /p:Configuration=Debug /p:Platform=x86 /consoleloggerparameters:NoSummary",
29 | "clean": "yarn clean-build && yarn clean-develop",
30 | "preversion": "rpl \"%npm_package_version%.0\" \"A.B.C.D\" Properties/AssemblyInfo.cs",
31 | "version": "rpl \"A.B.C.D\" \"%npm_package_version%.0\" Properties/AssemblyInfo.cs && git add Properties/AssemblyInfo.cs",
32 | "prepublishOnly": "yarn build",
33 | "test": "echo TODO"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Devices/DeviceInfo.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Info about a device.
3 | */
4 |
5 | using System;
6 | using HidLibrary;
7 | using Newtonsoft.Json;
8 |
9 | // ---------------------------------------------------------------------------------------------------------------------------------
10 |
11 | namespace SimElation.SliDevices
12 | {
13 | /// Info about a device.
14 | public sealed class DeviceInfo
15 | {
16 | /// Constructor.
17 | /// Device handle from HidLibrary.
18 | /// Device's product id.
19 | /// Device's serial number. Used as unique key.
20 | /// Prettied up device info (e.g. for displaying in UI).
21 | public DeviceInfo(HidDevice hidDevice, int productId, String serialNumber, String prettyInfo)
22 | {
23 | HidDevice = hidDevice;
24 | // NB caching ProductId separately from HidDevice for serializing to config; upon restart the device might not be
25 | // plugged in so would be no HidDevice.
26 | // Also Newtonsoft.Json requires the parameter names to match the property names for this to "just work" (ignoring
27 | // case). Otherwise we could add a default constructor but the properties would need to be read/write.
28 |
29 | ProductId = productId;
30 | SerialNumber = serialNumber;
31 | PrettyInfo = prettyInfo;
32 | }
33 |
34 | /// Device handle from HidLibrary.
35 | [JsonIgnoreAttribute]
36 | public HidDevice HidDevice { get; set; }
37 |
38 | /// Device's product id.
39 | public int ProductId { get; }
40 |
41 | /// Device's serial number. Used as unique key.
42 | public String SerialNumber { get; }
43 |
44 | /// Prettied up device info (e.g. for displaying in UI).
45 | public String PrettyInfo { get; }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/SegmentDisplays/TempSegmentDisplay.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Temperature segment display.
3 | */
4 |
5 | using System;
6 | using SimElation.SliDevices;
7 |
8 | // ---------------------------------------------------------------------------------------------------------------------------------
9 |
10 | namespace SimElation.Simhub.SliPlugin
11 | {
12 | /// Temperature segment display.
13 | public sealed class TempSegmentDisplay : SegmentDisplay
14 | {
15 | /// Function type to get a temperature value from .
16 | ///
17 | /// Temperature.
18 | public delegate double GetTemperature(NormalizedData normalizedData);
19 |
20 | /// Constructor.
21 | /// Description of the temperature. This will be displayed when switching
22 | /// to this segment display for a period of time.
23 | /// Description of the temperature for the UI.
24 | /// String to prefix the temperature with, e.g. "Oil", "H20".
25 | /// Function to get a temperature from .
26 | public TempSegmentDisplay(String shortName, String longName, String prefix,
27 | GetTemperature getTemperature) : base(shortName, longName)
28 | {
29 | m_prefix = prefix;
30 | m_getTemperature = getTemperature;
31 | }
32 |
33 | ///
34 | public override void ProcessData(NormalizedData normalizedData, Device.SegmentDisplayPosition position,
35 | IOutputFormatters outputFormatters, ref String str, ref uint[] decimalOrPrimeIndexList)
36 | {
37 | outputFormatters.Temperature(m_prefix, m_getTemperature(normalizedData), ref str, ref decimalOrPrimeIndexList);
38 | }
39 |
40 | private readonly String m_prefix;
41 | private readonly GetTemperature m_getTemperature;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Controls/RpmLedsEditor.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Devices/DevicePoller.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Poller for SLI devices.
3 | */
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Text;
8 | using HidLibrary;
9 |
10 | // ---------------------------------------------------------------------------------------------------------------------------------
11 |
12 | namespace SimElation.SliDevices
13 | {
14 | /// Poller for SLI devices.
15 | public static class DevicePoller
16 | {
17 | /// Poll for devices. Multiple products of a single vendor are supported in one call.
18 | ///
19 | /// List of product ids.
20 | /// A dictionary keyed by a device's serial number. The value is .
21 | public static Dictionary Poll(int vendorId, params int[] productIds)
22 | {
23 | var hidDevices = HidDevices.Enumerate(vendorId, productIds);
24 | var deviceSet = new Dictionary();
25 |
26 | foreach (var device in hidDevices)
27 | {
28 | var deviceInfo = ProcessDevice(device);
29 |
30 | if (deviceInfo != null)
31 | deviceSet.Add(deviceInfo.SerialNumber, deviceInfo);
32 | }
33 |
34 | return deviceSet;
35 | }
36 |
37 | private static DeviceInfo ProcessDevice(HidDevice hidDevice)
38 | {
39 | // Format up a nice device info string.
40 | byte[] data;
41 | String serialNumber = hidDevice.ReadSerialNumber(out data) ? Encoding.Unicode.GetString(data).TrimEnd('\0') : "";
42 |
43 | // For some users, reading the serial number from a device that's already open is failing. I can't repro, so
44 | // just ignore those devices here rather than in SliPlugin.PollForDevicesOnce().
45 | if (serialNumber.Length == 0)
46 | return null;
47 |
48 | String manufacturer = hidDevice.ReadManufacturer(out data) ? Encoding.Unicode.GetString(data).TrimEnd('\0') : "";
49 | String product = hidDevice.ReadProduct(out data) ? Encoding.Unicode.GetString(data).TrimEnd('\0') : "";
50 |
51 | String prettyInfo = String.Format("{0}{1}{2}{3}{4}{5}{6}",
52 | manufacturer, (manufacturer.Length > 0) ? " " : "",
53 | product, (product.Length > 0) ? " " : "",
54 | (serialNumber.Length > 0) ? "(" : "",
55 | serialNumber,
56 | (serialNumber.Length > 0) ? ")" : "").Trim();
57 | if (prettyInfo.Length == 0)
58 | prettyInfo = hidDevice.Description;
59 |
60 | return new DeviceInfo(hidDevice, hidDevice.Attributes.ProductId, serialNumber, prettyInfo);
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/SegmentDisplays/SegmentDisplay.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Segment display base functionality.
3 | */
4 |
5 | using System;
6 | using SimElation.SliDevices;
7 |
8 | // ---------------------------------------------------------------------------------------------------------------------------------
9 |
10 | // Ambiguous reference in cref attribute.
11 | #pragma warning disable CS0419
12 |
13 | namespace SimElation.Simhub.SliPlugin
14 | {
15 | /// Base class for a segment display of some data.
16 | ///
17 | /// Left segment display has decimals at indices 0, 1, 2, 4 and primes at 3, 5.
18 | /// Right segment display has decimals at indices 0, 2, 3, 4 and primes at 1, 5.
19 | ///
20 | public abstract class SegmentDisplay
21 | {
22 | /// Name for the segment display, to show in UI.
23 | public String FriendlyName { get; }
24 |
25 | /// Constructor.
26 | /// Description of the information show by the display. This will be displayed when switching
27 | /// to this segment display for a period of time.
28 | /// Longer form name for the segment display, to show in UI.
29 | public SegmentDisplay(String shortName, String friendlyName)
30 | {
31 | m_shortName = shortName;
32 | FriendlyName = friendlyName;
33 | }
34 |
35 | /// Show the name provided to the constructor.
36 | ///
37 | /// To pass to .
38 | public void ShowName(Device sliDevice, Device.SegmentDisplayPosition position)
39 | {
40 | sliDevice.SetSegment(position, m_shortName);
41 | }
42 |
43 | /// Process game data from SimHub.
44 | ///
45 | /// The implementation of this function should probably call .
46 | ///
47 | ///
48 | /// Whether the segment is display on the left or right. Can affect decimal/primes on the SLI-Pro.
49 | ///
50 | ///
51 | /// Reference to a string to assign to.
52 | /// To assign to if decimal (or primes with SLI-Pro) are to be set. Should be a
53 | /// list of indexes.
54 | public abstract void ProcessData(NormalizedData normalizedData, Device.SegmentDisplayPosition position,
55 | IOutputFormatters outputFormatters, ref String str, ref uint[] decimalOrPrimeIndexList);
56 |
57 | private readonly String m_shortName;
58 | }
59 | }
60 |
61 | // Ambiguous reference in cref attribute.
62 | #pragma warning restore CS0419
63 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/NormalizedData.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Game data with extras from the PersistantTrackerPlugin.
3 | */
4 |
5 | using GameReaderCommon;
6 | using SimHub.Plugins;
7 |
8 | // ---------------------------------------------------------------------------------------------------------------------------------
9 |
10 | namespace SimElation.Simhub.SliPlugin
11 | {
12 | /// Game data with extras from the PersistantTrackerPlugin.
13 | public sealed class NormalizedData
14 | {
15 | /// Game data as passed to the plugin's method.
16 | public GameReaderCommon.StatusDataBase StatusData { get => m_statusData; }
17 |
18 | /// In pit garage or pit lane?
19 | ///
20 | /// AMS2 note: both these are false when leaving garage!
21 | ///
22 | public bool IsInPit { get => m_isInPit; }
23 |
24 | ///
25 | /// if non-null,
26 | /// otherwise PersistantTrackerPlugin.SessionBestLiveDeltaSeconds.
27 | ///
28 | public double? DeltaToSessionBest { get => m_deltaToSessionBest; }
29 |
30 | ///
31 | /// if non-null,
32 | /// otherwise PersistantTrackerPlugin.AllTimeBestLiveDeltaSeconds.
33 | ///
34 | public double? DeltaToAllTimeBest { get => m_deltaToAllTimeBest; }
35 |
36 | ///
37 | /// if non-null,
38 | /// otherwise PersistantTrackerPlugin.EstimatedFuelRemaingLaps.
39 | ///
40 | public double? FuelRemainingLaps { get => m_fuelRemainingLaps; }
41 |
42 | /// Populate the fields from a game data update.
43 | ///
44 | ///
45 | public void Populate(PluginManager pluginManager, StatusDataBase statusData)
46 | {
47 | m_statusData = statusData;
48 | m_isInPit = (statusData.IsInPitLane != 0) || (statusData.IsInPit != 0);
49 |
50 | m_deltaToSessionBest = statusData.DeltaToSessionBest ??
51 | (double?)pluginManager.GetPropertyValue("PersistantTrackerPlugin.SessionBestLiveDeltaSeconds");
52 |
53 | m_deltaToAllTimeBest = statusData.DeltaToAllTimeBest ??
54 | (double?)pluginManager.GetPropertyValue("PersistantTrackerPlugin.AllTimeBestLiveDeltaSeconds");
55 |
56 | m_fuelRemainingLaps = statusData.EstimatedFuelRemaingLaps ??
57 | (double?)pluginManager.GetPropertyValue("DataCorePlugin.Computed.Fuel_RemainingLaps");
58 | }
59 |
60 | private GameReaderCommon.StatusDataBase m_statusData;
61 | private bool m_isInPit;
62 | private double? m_deltaToSessionBest;
63 | private double? m_deltaToAllTimeBest;
64 | private double? m_fuelRemainingLaps;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Devices/SliPro/SliProConstants.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Various constant values for the SLI-Pro.
3 | */
4 |
5 | using System.Windows.Media;
6 |
7 | // ---------------------------------------------------------------------------------------------------------------------------------
8 |
9 | namespace SimElation.SliDevices.Pro
10 | {
11 | /// Various constant values for the SLI-Pro.
12 | public sealed class Constants : IConstants
13 | {
14 | /// Various compile-time constant values for the SLI-Pro.
15 | public static class CompileTime
16 | {
17 | /// Vendor id for an SLI-Pro.
18 | public const int vendorId = 0x1dd2;
19 |
20 | /// Product id for an SLI-Pro.
21 | public const int productId = 0x0103;
22 |
23 | /// The number of characters in each segment display.
24 | public const uint segmentDisplayWidth = 6;
25 |
26 | /// The number of LEDs in the rev display.
27 | public const uint numberOfRevLeds = 13;
28 |
29 | /// The number of status LEDs (3 left, 3 right).
30 | public const uint numberOfStatusLeds = 6;
31 |
32 | /// The number of external LEDs.
33 | public const uint numberOfExternalLeds = 5;
34 |
35 | /// The number of supported rotary switches.
36 | public const uint maxNumberOfRotarySwitches = 6;
37 |
38 | /// The number of supported potentiometers.
39 | public const uint maxNumberOfPots = 2;
40 | }
41 |
42 | ///
43 | public uint SegmentDisplayWidth { get => CompileTime.segmentDisplayWidth; }
44 |
45 | ///
46 | public Color[] RevLedColors { get; } =
47 | new Color[]
48 | {
49 | // 13 for the SLI-Pro.
50 | Colors.LimeGreen,
51 | Colors.LimeGreen,
52 | Colors.LimeGreen,
53 | Colors.LimeGreen,
54 |
55 | Colors.Red,
56 | Colors.Red,
57 | Colors.Red,
58 | Colors.Red,
59 | Colors.Red,
60 |
61 | Colors.Blue,
62 | Colors.Blue,
63 | Colors.Blue,
64 | Colors.Blue
65 | };
66 |
67 | ///
68 | public Color[] LeftStatusLedColors { get; } =
69 | new Color[]
70 | {
71 | Colors.Blue,
72 | Colors.Yellow,
73 | Colors.Red
74 | };
75 |
76 | ///
77 | public Color[] RightStatusLedColors { get; } =
78 | new Color[]
79 | {
80 | Colors.Red,
81 | Colors.LimeGreen,
82 | Colors.Blue
83 | };
84 |
85 | ///
86 | public uint NumberOfStatusLeds { get => CompileTime.numberOfStatusLeds; }
87 |
88 | ///
89 | public uint NumberOfExternalLeds { get => CompileTime.numberOfExternalLeds; }
90 |
91 | ///
92 | public uint MaxNumberOfRotarySwitches { get => CompileTime.maxNumberOfRotarySwitches; }
93 |
94 | ///
95 | public uint MaxNumberOfPots { get => CompileTime.maxNumberOfPots; }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Controls/SliPluginControl.xaml.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Code-behind for plugin UI.
3 | */
4 |
5 | using System;
6 | using System.Globalization;
7 | using System.Linq;
8 | using System.Reflection;
9 | using System.Windows;
10 | using System.Windows.Controls;
11 | using System.Windows.Data;
12 | using SimHub.Plugins.Styles;
13 |
14 | // ---------------------------------------------------------------------------------------------------------------------------------
15 |
16 | namespace SimElation.Simhub.SliPlugin
17 | {
18 | /// Interaction logic for SliPluginControl.xaml.
19 | public partial class SliPluginControl : UserControl
20 | {
21 | /// accessor.
22 | public SliPlugin Plugin { get; }
23 |
24 | /// Title for UI.
25 | public static String Title
26 | {
27 | get => String.Format("SLI PLUGIN {0} ({1} build)", Assembly.GetExecutingAssembly().GetName().Version.ToString(),
28 | Assembly.GetExecutingAssembly().GetCustomAttributes().
29 | FirstOrDefault().Configuration);
30 | }
31 |
32 | /// Constructor.
33 | ///
34 | public SliPluginControl(SliPlugin sliPlugin)
35 | {
36 | DataContext = this;
37 | Plugin = sliPlugin;
38 | InitializeComponent();
39 | }
40 |
41 | private void OnHelpClick(object sender, System.Windows.RoutedEventArgs e)
42 | {
43 | const String rootUrl = "https://github.com/simelation/simhub-plugins/blob";
44 | String branch = "master", version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
45 | int index = version.LastIndexOf(".");
46 |
47 | if (index != -1)
48 | {
49 | String tagVersion = version.Substring(0, index);
50 | branch = String.Format("%40simelation/simhub-sli-plugin%40{0}", tagVersion);
51 | }
52 |
53 | String url = String.Format("{0}/{1}/packages/simhub-sli-plugin/README.md", rootUrl, branch);
54 | System.Diagnostics.Process.Start(url);
55 | }
56 |
57 | private void OnAddManagedDevice(object sender, System.Windows.RoutedEventArgs e)
58 | {
59 | var deviceInstance = (DeviceInstance)((SHToggleButton)e.Source).DataContext;
60 |
61 | Plugin.AddManagedDevice(deviceInstance);
62 | }
63 |
64 | private void OnRemoveManagedDevice(object sender, System.Windows.RoutedEventArgs e)
65 | {
66 | var deviceInstance = (DeviceInstance)((SHToggleButton)e.Source).DataContext;
67 |
68 | Plugin.RemoveManagedDevice(deviceInstance);
69 | }
70 | }
71 |
72 | /// Convert to a string for the UI.
73 | public sealed class IsManagedToStatusConverter : IValueConverter
74 | {
75 | ///
76 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
77 | {
78 | var isManaged = (bool)value;
79 |
80 | return isManaged ? "Unplugged" : "Unmanaged";
81 | }
82 |
83 | ///
84 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
85 | {
86 | return DependencyProperty.UnsetValue;
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Devices/SliF1/SliF1Constants.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Various constant values for the SLI-F1.
3 | */
4 |
5 | using System.Windows.Media;
6 |
7 | // ---------------------------------------------------------------------------------------------------------------------------------
8 |
9 | namespace SimElation.SliDevices.F1
10 | {
11 | /// Various constant values for the SLI-F1.
12 | public sealed class Constants : IConstants
13 | {
14 | /// Various compile-time constant values for the SLI-F1.
15 | public static class CompileTime
16 | {
17 | /// Product id for an SLI-F1.
18 | public const int productId = 0x1110;
19 |
20 | /// The number of characters in each segment display.
21 | public const uint segmentDisplayWidth = 4;
22 |
23 | /// The number of LEDs in the rev display.
24 | public const uint numberOfRevLeds = 15;
25 |
26 | /// The number of status LEDs (3 left, 3 right).
27 | public const uint numberOfStatusLeds = 6;
28 |
29 | /// The number of external LEDs.
30 | public const uint numberOfExternalLeds = 5;
31 |
32 | /// The number of supported rotary switches.
33 | ///
34 | /// The 8th rotary is mapped to controller buttons. Looks like it's at offset 17 in the report but we don't need it.
35 | ///
36 | public const uint maxNumberOfRotarySwitches = 7;
37 |
38 | /// The number of supported potentiometers.
39 | public const uint maxNumberOfPots = 1;
40 | }
41 |
42 | ///
43 | public uint SegmentDisplayWidth { get => CompileTime.segmentDisplayWidth; }
44 |
45 | ///
46 | public Color[] RevLedColors { get; } =
47 | new Color[]
48 | {
49 | // 15 for the SLI-F1.
50 | Colors.LimeGreen,
51 | Colors.LimeGreen,
52 | Colors.LimeGreen,
53 | Colors.LimeGreen,
54 | Colors.LimeGreen,
55 |
56 | Colors.Red,
57 | Colors.Red,
58 | Colors.Red,
59 | Colors.Red,
60 | Colors.Red,
61 |
62 | Colors.Blue,
63 | Colors.Blue,
64 | Colors.Blue,
65 | Colors.Blue,
66 | Colors.Blue
67 | };
68 |
69 | ///
70 | public Color[] LeftStatusLedColors { get; } =
71 | new Color[]
72 | {
73 | Colors.Yellow,
74 | Colors.Blue,
75 | Colors.Red
76 | };
77 |
78 | ///
79 | public Color[] RightStatusLedColors { get; } =
80 | new Color[]
81 | {
82 | Colors.Yellow,
83 | Colors.Blue,
84 | Colors.Red
85 | };
86 |
87 | ///
88 | public uint NumberOfStatusLeds { get => CompileTime.numberOfStatusLeds; }
89 |
90 | ///
91 | public uint NumberOfExternalLeds { get => CompileTime.numberOfExternalLeds; }
92 |
93 | /// The number of supported rotary switches.
94 | ///
95 | /// The 8th rotary is mapped to controller buttons. Looks like it's at offset 17 in the report but we don't need it.
96 | ///
97 | public uint MaxNumberOfRotarySwitches { get => CompileTime.maxNumberOfRotarySwitches; }
98 |
99 | ///
100 | public uint MaxNumberOfPots { get => CompileTime.maxNumberOfPots; }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Devices/DeviceDescriptor.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Device description.
3 | */
4 |
5 | using System;
6 | using System.Collections.Generic;
7 |
8 | // ---------------------------------------------------------------------------------------------------------------------------------
9 |
10 | namespace SimElation.SliDevices
11 | {
12 | /// Interface describing a device.
13 | public interface IDeviceDescriptor
14 | {
15 | /// Various device-specific constant values (number of LEDs, etc.).
16 | IConstants Constants { get; }
17 |
18 | /// LED state report format for sending to a device.
19 | ILedStateReport LedStateReport { get; }
20 |
21 | /// LED brightness report format for sending to a device.
22 | IBrightnessReport BrightnessReport { get; }
23 |
24 | /// Input report format from a device.
25 | IInputReport InputReport { get; }
26 | }
27 |
28 | /// Concrete class describing a device.
29 | /// Constants describing the device.
30 | /// LED state report format.
31 | /// LED brightness report format.
32 | /// Input report format.
33 | public sealed class DeviceDescriptor : IDeviceDescriptor
34 | where TConstants : IConstants, new()
35 | where TLedStateReport : ILedStateReport, new()
36 | where TBrightnessReport : IBrightnessReport, new()
37 | where TInputReport : IInputReport, new()
38 | {
39 | ///
40 | public IConstants Constants { get; } = new TConstants();
41 |
42 | ///
43 | public ILedStateReport LedStateReport { get; } = new TLedStateReport();
44 |
45 | ///
46 | public IBrightnessReport BrightnessReport { get; } = new TBrightnessReport();
47 |
48 | ///
49 | public IInputReport InputReport { get; } = new TInputReport();
50 | }
51 | }
52 |
53 | // ---------------------------------------------------------------------------------------------------------------------------------
54 |
55 | namespace SimElation.SliDevices
56 | {
57 | using F1Descriptor = DeviceDescriptor;
58 | using ProDescriptor = DeviceDescriptor;
59 |
60 | /// Singleton of device descriptors.
61 | public sealed class DeviceDescriptors
62 | {
63 | /// Singleton instance.
64 | public static DeviceDescriptors Instance { get => m_instance.Value; }
65 |
66 | /// Dictionary of descriptors, indexed by USB product id.
67 | public Dictionary Dictionary { get => m_dictionary; }
68 |
69 | /// Array of supported product ids.
70 | public int[] ProductIds { get => m_productIds; }
71 |
72 | private static readonly Lazy m_instance = new Lazy(() => new DeviceDescriptors());
73 | private readonly Dictionary m_dictionary;
74 | private readonly int[] m_productIds;
75 |
76 | private DeviceDescriptors()
77 | {
78 | m_dictionary =
79 | new Dictionary()
80 | {
81 | { Pro.Constants.CompileTime.productId, new ProDescriptor() },
82 | { F1.Constants.CompileTime.productId, new F1Descriptor() }
83 | };
84 |
85 | m_productIds = new int[m_dictionary.Keys.Count];
86 | m_dictionary.Keys.CopyTo(m_productIds, 0);
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Devices/RotarySwitchDetector.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Rotary switch detection.
3 | */
4 |
5 | using System;
6 | using System.Threading;
7 |
8 | // ---------------------------------------------------------------------------------------------------------------------------------
9 |
10 | namespace SimElation.SliDevices
11 | {
12 | /// Class to handle detecting rotary switches changing position.
13 | public sealed class RotarySwitchDetector : IDisposable
14 | {
15 | /// Offset value for undetected rotary switch.
16 | public const int unknownIndex = -1;
17 |
18 | /// Callback type for when a rotary switch is detected, or times out.
19 | ///
20 | /// The index of the detected rotary switch.
21 | /// If no rotary is found when the detection times out, the callback is invoked with .
22 | ///
23 | public delegate void Callback(int rotarySwitchIndex);
24 |
25 | /// Constructor.
26 | /// How long to detect for, in milliseconds.
27 | /// Callback for when a rotary switch is detected.
28 | /// The device descriptor, for input report format.
29 | public RotarySwitchDetector(int timeoutMs, Callback callback, IDeviceDescriptor deviceDescriptor)
30 | {
31 | m_callback = callback;
32 | m_deviceDescriptor = deviceDescriptor;
33 |
34 | // Cancelleation timer for nothing found.
35 | m_timer = new Timer((object state) =>
36 | {
37 | if (!m_isDisposed && !m_isFound)
38 | m_callback(unknownIndex);
39 | }, null, timeoutMs, Timeout.Infinite);
40 |
41 | m_previousPositions = new int[(int)m_deviceDescriptor.Constants.MaxNumberOfRotarySwitches];
42 | for (int i = 0; i < m_previousPositions.Length; ++i)
43 | {
44 | m_previousPositions[i] = -1;
45 | }
46 | }
47 |
48 | /// Dispose.
49 | public void Dispose()
50 | {
51 | if (m_isDisposed)
52 | return;
53 |
54 | // TODO does disposing timer invoke its callback?
55 | m_timer.Dispose();
56 |
57 | m_isDisposed = true;
58 | }
59 |
60 | /// Process a HidReport data buffer and look for a rotary switch change.
61 | /// Received from the SLI.
62 | public void ProcessHidReport(byte[] rxBuffer)
63 | {
64 | if (m_isFound)
65 | return;
66 |
67 | // Note rotary switch position is a uint16 in the InputReport but we are only reading the low byte (0-11).
68 | for (int i = 0; i < m_deviceDescriptor.Constants.MaxNumberOfRotarySwitches; ++i)
69 | {
70 | var offset = m_deviceDescriptor.InputReport.RotarySwitchesOffset + (i * sizeof(ushort));
71 |
72 | if (offset < rxBuffer.Length)
73 | {
74 | int newPosition = rxBuffer[offset];
75 |
76 | if (-1 != m_previousPositions[i])
77 | {
78 | if (newPosition != m_previousPositions[i])
79 | {
80 | m_isFound = true;
81 | m_callback(i);
82 | return;
83 | }
84 | }
85 |
86 | m_previousPositions[i] = newPosition;
87 | }
88 | }
89 | }
90 |
91 | /// Display indexed from 1 for UI purposes.
92 | ///
93 | public static int RotarySwitchIndexToUiValue(int rotarySwitchIndex)
94 | {
95 | return rotarySwitchIndex + 1;
96 | }
97 |
98 | private bool m_isDisposed = false;
99 |
100 | private readonly Callback m_callback;
101 | private readonly Timer m_timer;
102 |
103 | private readonly int[] m_previousPositions;
104 | private bool m_isFound = false;
105 |
106 | private readonly IDeviceDescriptor m_deviceDescriptor;
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Led.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * LED handling.
3 | */
4 |
5 | using System;
6 | using System.Diagnostics;
7 | using System.Windows.Media;
8 | using SimHub.Plugins.OutputPlugins.Dash.GLCDTemplating;
9 | using SimHub.Plugins.OutputPlugins.Dash.TemplatingCommon;
10 | using SimHub.Plugins.OutputPlugins.GraphicalDash.Models;
11 |
12 | // ---------------------------------------------------------------------------------------------------------------------------------
13 |
14 | namespace SimElation.Simhub.SliPlugin
15 | {
16 | /// Class handling LED binding to game properties.
17 | public sealed class Led
18 | {
19 | /// SimHub data for binding to game properties / ncalc.
20 | public ExpressionValue ExpressionValue { get; set; } = new ExpressionValue();
21 |
22 | /// For FormulaPickerButton.EditPropertyName.
23 | public String EditPropertyName { get; set; } = "";
24 |
25 | /// Brush to paint the LED when on.
26 | /// No JsonIgnoreAttribute as saving the color out to config file simplifies reload.
27 | public Brush SetBrush { get; set; } = Brushes.Blue;
28 |
29 | /// LED state.
30 | public enum State
31 | {
32 | /// LED off.
33 | off,
34 |
35 | /// LED on (solid).
36 | on,
37 |
38 | /// LED blinking.
39 | blink
40 | }
41 |
42 | /// Blinking state.
43 | public enum BlinkState
44 | {
45 | /// In blinking on state.
46 | on,
47 |
48 | /// In blinking off state.
49 | off
50 | }
51 |
52 | /// Default constructor.
53 | /// For deserializing config.
54 | public Led()
55 | {
56 | }
57 |
58 | /// Constructor.
59 | /// Color of the LED when set in the UI.
60 | public Led(Color setColor)
61 | {
62 | SetBrush = new SolidColorBrush(setColor);
63 | }
64 |
65 | /// Called on a game data update.
66 | ///
67 | ///
68 | /// true if this LED should be on.
69 | public bool ProcessGameData(NCalcEngineBase ncalcEngine, long blinkIntervalMs)
70 | {
71 | bool isSet = false;
72 |
73 | if (!ExpressionValue.IsNone)
74 | {
75 | var res = ncalcEngine.ParseValueOrDefault(ExpressionValue, 0);
76 |
77 | State ledState = (Led.State)res;
78 | isSet = ProcessLedState(ledState, blinkIntervalMs);
79 | }
80 |
81 | return isSet;
82 | }
83 |
84 | // Figure out if an LED should be on from a state. Handles blinking.
85 | private bool ProcessLedState(State ledState, long blinkIntervalMs)
86 | {
87 | if (State.blink == ledState)
88 | {
89 | return HandleBlink(blinkIntervalMs);
90 | }
91 | else
92 | {
93 | m_stopwatch.Stop();
94 | return (State.on == ledState);
95 | }
96 | }
97 |
98 | private bool HandleBlink(long blinkIntervalMs)
99 | {
100 | if (!m_stopwatch.IsRunning)
101 | {
102 | m_stopwatch.Restart();
103 | m_blinkState = BlinkState.on;
104 | }
105 |
106 | switch (m_blinkState)
107 | {
108 | case BlinkState.on:
109 | if (m_stopwatch.ElapsedMilliseconds >= blinkIntervalMs)
110 | {
111 | m_stopwatch.Restart();
112 | m_blinkState = BlinkState.off;
113 | }
114 | break;
115 |
116 | case BlinkState.off:
117 | if (m_stopwatch.ElapsedMilliseconds >= blinkIntervalMs)
118 | {
119 | m_stopwatch.Restart();
120 | m_blinkState = BlinkState.on;
121 | }
122 | break;
123 | }
124 |
125 | return (BlinkState.on == m_blinkState);
126 | }
127 |
128 | private BlinkState m_blinkState;
129 | private readonly Stopwatch m_stopwatch = new Stopwatch();
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Controls/SegmentDisplayControl.xaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
26 |
27 |
28 |
29 |
32 |
33 |
34 |
35 |
36 |
40 |
41 |
42 |
43 |
45 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
56 |
57 |
59 |
60 |
61 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Controls/SliPluginControl.xaml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Help
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
46 |
47 |
48 |
49 |
50 |
52 |
53 |
54 |
55 |
56 |
57 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Devices/SliF1/SliF1ReportFormats.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * SLI-F1 report formats.
3 | */
4 |
5 | // ---------------------------------------------------------------------------------------------------------------------------------
6 |
7 | namespace SimElation.SliDevices.F1
8 | {
9 | /// Input report format from SLI-F1.
10 | public sealed class InputReport : IInputReport
11 | {
12 | private static class CompileTime
13 | {
14 | public const uint rotarySwitchesOffset = 0;
15 | public const uint rotarySwitchesLength = Constants.CompileTime.maxNumberOfRotarySwitches * sizeof(ushort); // uint16 for each rotary.
16 |
17 | public const uint potsOffset = rotarySwitchesOffset + rotarySwitchesLength;
18 | public const uint potsLength = Constants.CompileTime.maxNumberOfPots * sizeof(ushort); // uint16 for each pot.
19 | }
20 |
21 | ///
22 | public uint RotarySwitchesOffset { get => CompileTime.rotarySwitchesOffset; }
23 |
24 | ///
25 | public uint RotarySwitchesLength { get => CompileTime.rotarySwitchesLength; }
26 | }
27 |
28 | /// Common header for message format to send to SLI-F1.
29 | static class Header
30 | {
31 | /// Offset into report format for the type field.
32 | public const uint reportTypeIndex = 0;
33 | }
34 |
35 | /// LED state report format for sending to SLI-F1.
36 | public sealed class LedStateReport : ILedStateReport
37 | {
38 | private static class CompileTime
39 | {
40 | public const uint reportTypeIndex = Header.reportTypeIndex;
41 | public const byte reportType = 0x01;
42 | public const uint gearIndex = Header.reportTypeIndex + 1;
43 | public const uint revLed1Index = gearIndex + 1;
44 | public const uint statusLed1Index = revLed1Index + Constants.CompileTime.numberOfRevLeds;
45 | public const uint externalLed1Index = statusLed1Index + Constants.CompileTime.numberOfStatusLeds;
46 | public const uint leftDisplaySegmentIndex = externalLed1Index + Constants.CompileTime.numberOfExternalLeds;
47 | public const uint rightDisplaySegmentIndex = leftDisplaySegmentIndex + Constants.CompileTime.segmentDisplayWidth;
48 | public const uint length = rightDisplaySegmentIndex + Constants.CompileTime.segmentDisplayWidth;
49 | public const byte segmentDisplayDecimalOrPrimeBit = 0x80;
50 | }
51 |
52 | ///
53 | public uint ReportTypeOffset { get => CompileTime.reportTypeIndex; }
54 |
55 | ///
56 | public byte ReportType { get => CompileTime.reportType; }
57 |
58 | ///
59 | public uint GearOffset { get => CompileTime.gearIndex; }
60 |
61 | ///
62 | public uint RevLed1Offset { get => CompileTime.revLed1Index; }
63 |
64 | ///
65 | public uint StatusLed1Offset { get => CompileTime.statusLed1Index; }
66 |
67 | ///
68 | public uint ExternalLed1Offset { get => CompileTime.externalLed1Index; }
69 |
70 | ///
71 | public uint LeftSegmentDisplayOffset { get => CompileTime.leftDisplaySegmentIndex; }
72 |
73 | ///
74 | public uint RightSegmentDisplayOffset { get => CompileTime.rightDisplaySegmentIndex; }
75 |
76 | ///
77 | public uint Length { get => CompileTime.length; }
78 |
79 | ///
80 | public byte SegmentDisplayDecimalOrPrimeBit { get => CompileTime.segmentDisplayDecimalOrPrimeBit; }
81 | }
82 |
83 | /// LED brightness report format for sending to SLI-F1.
84 | public sealed class BrightnessReport : IBrightnessReport
85 | {
86 | private static class CompileTime
87 | {
88 | public const uint reportTypeIndex = Header.reportTypeIndex;
89 | public const byte reportType = 0x02;
90 | public const uint brightnessIndex = reportTypeIndex + 1;
91 | public const uint length = brightnessIndex + 1;
92 | }
93 |
94 | ///
95 | public uint ReportTypeOffset { get => CompileTime.reportTypeIndex; }
96 |
97 | ///
98 | public byte ReportType { get => CompileTime.reportType; }
99 |
100 | ///
101 | public uint BrightnessOffset { get => CompileTime.brightnessIndex; }
102 |
103 | ///
104 | public uint Length { get => CompileTime.length; }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Controls/RotarySwitchMappingControl.xaml:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
24 |
25 |
26 |
29 |
35 |
36 |
37 |
41 |
45 |
46 |
47 |
48 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
64 |
65 |
66 |
67 |
68 |
74 |
75 |
76 |
77 |
78 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Devices/SliPro/SliProReportFormats.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * SLI-Pro report formats.
3 | */
4 |
5 | // ---------------------------------------------------------------------------------------------------------------------------------
6 |
7 | namespace SimElation.SliDevices.Pro
8 | {
9 | /// Input report format from SLI-Pro.
10 | public sealed class InputReport : IInputReport
11 | {
12 | private static class CompileTime
13 | {
14 | public const uint button0to7Offset = 0;
15 | public const uint button0to7Length = 1;
16 | public const uint button8to15Offset = button0to7Offset + button0to7Length;
17 | public const uint button8to15Length = 1;
18 | public const uint button16to23Offset = button8to15Offset + button8to15Length;
19 | public const uint button16to23Length = 1;
20 | public const uint button24to31Offset = button16to23Offset + button16to23Length;
21 | public const uint button24to31Length = 1;
22 | public const uint rotarySwitchesOffset = button24to31Offset + button24to31Length;
23 | public const uint rotarySwitchesLength = Constants.CompileTime.maxNumberOfRotarySwitches * sizeof(ushort); // uint16 for each rotary.
24 | public const uint potsOffset = rotarySwitchesOffset + rotarySwitchesLength;
25 | public const uint potsLength = Constants.CompileTime.maxNumberOfPots * sizeof(ushort); // uint16 for each pot.
26 | }
27 |
28 | ///
29 | public uint RotarySwitchesOffset { get => CompileTime.rotarySwitchesOffset; }
30 |
31 | ///
32 | public uint RotarySwitchesLength { get => CompileTime.rotarySwitchesLength; }
33 | }
34 |
35 | /// Common header for message format to send to SLI-Pro.
36 | static class Header
37 | {
38 | /// Offset into report format for the type field.
39 | public const uint reportTypeOffset = 0;
40 | }
41 |
42 | /// LED state report format for sending to SLI-Pro.
43 | public sealed class LedStateReport : ILedStateReport
44 | {
45 | private static class CompileTime
46 | {
47 | public const uint reportTypeOffset = Header.reportTypeOffset;
48 | public const byte reportType = 0x01;
49 | public const uint gearOffset = Header.reportTypeOffset + 1;
50 | public const uint revLed1Offset = gearOffset + 1;
51 | public const uint statusLed1Offset = revLed1Offset + Constants.CompileTime.numberOfRevLeds;
52 | public const uint externalLed1Offset = statusLed1Offset + Constants.CompileTime.numberOfStatusLeds;
53 | public const uint leftDisplaySegmentOffset = externalLed1Offset + Constants.CompileTime.numberOfExternalLeds;
54 | public const uint rightDisplaySegmentOffset = leftDisplaySegmentOffset + Constants.CompileTime.segmentDisplayWidth;
55 | public const uint length = rightDisplaySegmentOffset + Constants.CompileTime.segmentDisplayWidth;
56 | public const byte segmentDisplayDecimalOrPrimeBit = 0x80;
57 | }
58 |
59 | ///
60 | public uint ReportTypeOffset { get => CompileTime.reportTypeOffset; }
61 |
62 | ///
63 | public byte ReportType { get => CompileTime.reportType; }
64 |
65 | ///
66 | public uint GearOffset { get => CompileTime.gearOffset; }
67 |
68 | ///
69 | public uint RevLed1Offset { get => CompileTime.revLed1Offset; }
70 |
71 | ///
72 | public uint StatusLed1Offset { get => CompileTime.statusLed1Offset; }
73 |
74 | ///
75 | public uint ExternalLed1Offset { get => CompileTime.externalLed1Offset; }
76 |
77 | ///
78 | public uint LeftSegmentDisplayOffset { get => CompileTime.leftDisplaySegmentOffset; }
79 |
80 | ///
81 | public uint RightSegmentDisplayOffset { get => CompileTime.rightDisplaySegmentOffset; }
82 |
83 | ///
84 | public uint Length { get => CompileTime.length; }
85 |
86 | ///
87 | public byte SegmentDisplayDecimalOrPrimeBit { get => CompileTime.segmentDisplayDecimalOrPrimeBit; }
88 | }
89 |
90 | /// LED brightness report format for sending to SLI-Pro.
91 | public sealed class BrightnessReport : IBrightnessReport
92 | {
93 | private static class CompileTime
94 | {
95 | public const uint reportTypeOffset = Header.reportTypeOffset;
96 | public const byte reportType = 0x02;
97 | public const uint brightnessOffset = reportTypeOffset + 1;
98 | public const uint length = brightnessOffset + 1;
99 | }
100 |
101 | ///
102 | public uint ReportTypeOffset { get => CompileTime.reportTypeOffset; }
103 |
104 | ///
105 | public byte ReportType { get => CompileTime.reportType; }
106 |
107 | ///
108 | public uint BrightnessOffset { get => CompileTime.brightnessOffset; }
109 |
110 | ///
111 | public uint Length { get => CompileTime.length; }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/SegmentDisplays/Formatters/IOutputFormatters.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Output formatters interface.
3 | */
4 |
5 | using System;
6 |
7 | // ---------------------------------------------------------------------------------------------------------------------------------
8 |
9 | namespace SimElation.Simhub.SliPlugin
10 | {
11 | /// Output formatters interface.
12 | public interface IOutputFormatters
13 | {
14 | /// Format a lap time.
15 | ///
16 | /// As "m:ss.ttt" (t=thousandths) when minutes is less than 10, or "mmm.ss.t" (t=tenths) otherwise.
17 | ///
18 | /// The lap time.
19 | /// To receive the formatted string.
20 | /// To receive the indexes of decimal points/primes.
21 | void LapTime(TimeSpan timeSpan, ref String str, ref uint[] decimalOrPrimeIndexList);
22 |
23 | /// Format a delta time.
24 | /// The delta time (in seconds).
25 | /// To receive the formatted string.
26 | /// To receive the index of decimal point.
27 | void DeltaTime(double deltaTime, ref String str, ref uint[] decimalOrPrimeIndexList);
28 |
29 | /// Short name for brake bias display.
30 | String BrakeBiasShortName();
31 |
32 | /// Format a brake bias display.
33 | /// The brake bias as front percentage.
34 | /// To receive the formatted string.
35 | /// To receive the index of decimal point.
36 | void BrakeBias(double frontBias, ref String str, ref uint[] decimalOrPrimeIndexList);
37 |
38 | /// Format a fuel display.
39 | /// Current fuel level.
40 | /// Remaining laps (optional; may not be known).
41 | /// To receive the formatted string.
42 | /// To receive the index of decimal point.
43 | void Fuel(double fuelLevel, double? remainingLaps, ref String str, ref uint[] decimalOrPrimeIndexList);
44 |
45 | /// Format a lap counter display.
46 | ///
47 | ///
48 | ///
49 | /// To receive the formatted string.
50 | /// To receive the index of decimal point.
51 | void LapCounter(int totalLaps, int currentLap, int completedLaps, ref String str, ref uint[] decimalOrPrimeIndexList);
52 |
53 | /// Format a laps remaining display.
54 | ///
55 | /// To receive the formatted string.
56 | /// To receive the index of decimal point.
57 | void LapsToGo(int remainingLaps, ref String str, ref uint[] decimalOrPrimeIndexList);
58 |
59 | /// Format a current position display.
60 | ///
61 | ///
62 | /// To receive the formatted string.
63 | /// To receive the index of decimal point.
64 | void Position(int currentPosition, int numberOfOpponents, ref String str, ref uint[] decimalOrPrimeIndexList);
65 |
66 | /// Format a temperature display.
67 | /// String to prefix the display with.
68 | ///
69 | /// To receive the formatted string.
70 | /// To receive the index of decimal point.
71 | void Temperature(String prefix, double temperature, ref String str, ref uint[] decimalOrPrimeIndexList);
72 |
73 | /// Format a speed.
74 | ///
75 | /// To receive the formatted string.
76 | /// To receive the index of decimal point.
77 | void Speed(double speed, ref String str, ref uint[] decimalOrPrimeIndexList);
78 |
79 | /// Format an RPM.
80 | ///
81 | /// To receive the formatted string.
82 | /// To receive the index of decimal point.
83 | void Rpm(double rpm, ref String str, ref uint[] decimalOrPrimeIndexList);
84 |
85 | /// Prefix for oil temperature (e.g. "o" or "Oil").
86 | String OilTemperaturePrefix();
87 |
88 | /// Prefix for oil temperature (e.g. "H2O" or "w").
89 | String WaterTemperaturePrefix();
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Controls/RotarySwitchMappingControl.xaml.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Code-behind for RotarySwitchMappingControl.
3 | */
4 |
5 | using System;
6 | using System.Collections.Specialized;
7 | using System.Globalization;
8 | using System.Threading.Tasks;
9 | using System.Windows;
10 | using System.Windows.Controls;
11 | using System.Windows.Data;
12 | using System.Windows.Media;
13 | using MahApps.Metro.Controls.Dialogs;
14 |
15 | // ---------------------------------------------------------------------------------------------------------------------------------
16 |
17 | namespace SimElation.Simhub.SliPlugin
18 | {
19 | /// Workaround for ComboBox wiping out Text when Items changes, even if IsEditable!
20 | ///
21 | /// See: https://stackoverflow.com/questions/22221199/how-to-disable-itemssource-synchronization-with-text-property-of-combobox
22 | ///
23 | sealed class RotarySwitchMappingControlComboBox : ComboBox
24 | {
25 | protected override void OnSelectionChanged(SelectionChangedEventArgs e)
26 | {
27 | if (!m_isUpdatingItems)
28 | base.OnSelectionChanged(e);
29 | }
30 |
31 | protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
32 | {
33 | try
34 | {
35 | m_isUpdatingItems = true;
36 | base.OnItemsChanged(e);
37 | }
38 | finally
39 | {
40 | m_isUpdatingItems = false;
41 | }
42 | }
43 |
44 | private bool m_isUpdatingItems = false;
45 | }
46 |
47 | /// Interaction logic for RotarySwitchMappingControl.xaml.
48 | public partial class RotarySwitchMappingControl : UserControl
49 | {
50 | /// Simulate rotary switch position.
51 | public uint SimulateRotarySwitchPosition { get; set; } = 1;
52 |
53 | /// How long to pause after pressing simulate button press before doing so.
54 | public int SimulatePressPauseTimeMs { get; set; } = 5000;
55 |
56 | /// Constructor.
57 | public RotarySwitchMappingControl()
58 | {
59 | InitializeComponent();
60 |
61 | // TODO I'm sure this isn't the best way. DataContext is null after InitializeComponent() so handle its changed event.
62 | DataContextChanged +=
63 | (object sender, DependencyPropertyChangedEventArgs e) =>
64 | {
65 | m_rotarySwitchMapping = (DeviceInstance.Settings.RotarySwitchMapping)DataContext;
66 |
67 | if (m_rotarySwitchMapping != null)
68 | {
69 | // TODO ditto. Using dialog:DialogParticipation.Register="{Binding}" in the xaml doesn't work, presumably
70 | // because {Binding} resolves to null at that point.
71 | DialogParticipation.SetRegister(this, this);
72 | }
73 | };
74 | }
75 |
76 | private async void OnRemoveClick(object sender, System.Windows.RoutedEventArgs e)
77 | {
78 | // TODO ugh. Should learn about commands I suppose.
79 | var deviceInstanceControl = FindParent(this);
80 | if ((deviceInstanceControl != null) && (m_rotarySwitchMapping != null))
81 | {
82 | var res = await DialogCoordinator.Instance.ShowMessageAsync(this, "Confirm delete",
83 | "Are you sure you want to delete this mapping?",
84 | MessageDialogStyle.AffirmativeAndNegative,
85 | new MetroDialogSettings()
86 | {
87 | AnimateShow = false,
88 | AnimateHide = false
89 | });
90 |
91 | if (res != MessageDialogResult.Affirmative)
92 | return;
93 |
94 | deviceInstanceControl.RemoveRotarySwitchMapping(m_rotarySwitchMapping);
95 | }
96 | }
97 |
98 | private async void OnTestButtonClick(object sender, System.Windows.RoutedEventArgs e)
99 | {
100 | if (m_rotarySwitchMapping == null)
101 | return;
102 |
103 | var deviceInstanceControl = FindParent(this);
104 | int pulseMs = deviceInstanceControl?.VJoyPulseButtonMs ?? 50;
105 |
106 | var buttonId = SimulateRotarySwitchPosition + m_rotarySwitchMapping.FirstVJoyButtonId - 1;
107 | await Task.Delay(SimulatePressPauseTimeMs);
108 | _ = VJoyManager.Instance.PulseButton(m_rotarySwitchMapping.VJoyDeviceId, buttonId, pulseMs);
109 | }
110 |
111 | private static T FindParent(DependencyObject dependencyObject)
112 | where T : DependencyObject
113 | {
114 | DependencyObject parentObject = VisualTreeHelper.GetParent(dependencyObject);
115 |
116 | if (parentObject == null)
117 | return null;
118 |
119 | T parent = parentObject as T;
120 | if (parent != null)
121 | return parent;
122 | else
123 | return FindParent(parentObject);
124 | }
125 |
126 | private DeviceInstance.Settings.RotarySwitchMapping m_rotarySwitchMapping;
127 | }
128 |
129 | /// Convert a rotary switch index to a number for the UI.
130 | public sealed class RotarySwitchIndexToTitleConverter : IValueConverter
131 | {
132 | ///
133 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
134 | {
135 | var rotarySwitchIndex = (int)value;
136 | return String.Format("Configuration for rotary switch {0}",
137 | SliDevices.RotarySwitchDetector.RotarySwitchIndexToUiValue(rotarySwitchIndex));
138 | }
139 |
140 | ///
141 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
142 | {
143 | return DependencyProperty.UnsetValue;
144 | }
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Controls/StatusLedArray.xaml.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Code-behind for status LED array.
3 | */
4 |
5 | using System;
6 | using System.Collections;
7 | using System.Globalization;
8 | using System.Windows;
9 | using System.Windows.Controls;
10 | using System.Windows.Data;
11 |
12 | // ---------------------------------------------------------------------------------------------------------------------------------
13 |
14 | namespace SimElation.Simhub.SliPlugin
15 | {
16 | /// Interaction logic for StatusLedArray.xaml.
17 | public partial class StatusLedArray : UserControl
18 | {
19 | private static readonly DependencyProperty TitleProperty =
20 | DependencyProperty.Register(nameof(Title), typeof(string), typeof(StatusLedArray));
21 |
22 | private static readonly DependencyProperty OrientationProperty =
23 | DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(StatusLedArray),
24 | new PropertyMetadata(Orientation.Horizontal));
25 |
26 | private static readonly DependencyProperty StatusLedsProperty =
27 | DependencyProperty.Register(nameof(StatusLeds), typeof(IEnumerable), typeof(StatusLedArray));
28 |
29 | private static readonly DependencyProperty NumberEnabledProperty =
30 | DependencyProperty.Register(nameof(NumberEnabled), typeof(int), typeof(StatusLedArray), new PropertyMetadata(-1));
31 |
32 | /// Title property for display (e.g. "Left status LEDs").
33 | public string Title
34 | {
35 | get => GetValue(TitleProperty) as string;
36 | set => SetValue(TitleProperty, value);
37 | }
38 |
39 | /// Orientation of the array.
40 | public Orientation Orientation
41 | {
42 | get => (Orientation)GetValue(OrientationProperty);
43 | set => SetValue(OrientationProperty, value);
44 | }
45 |
46 | /// The collection of LEDs.
47 | public IEnumerable StatusLeds
48 | {
49 | get => GetValue(StatusLedsProperty) as IEnumerable;
50 | set => SetValue(StatusLedsProperty, value);
51 | }
52 |
53 | /// The number of LEDs at the start of the array than can be set.
54 | public int NumberEnabled
55 | {
56 | get => (int)GetValue(NumberEnabledProperty);
57 | set => SetValue(NumberEnabledProperty, value);
58 | }
59 |
60 | /// Default constructor.
61 | public StatusLedArray()
62 | {
63 | InitializeComponent();
64 | }
65 | }
66 |
67 | /// Convert a SimHib to a bool.
68 | public sealed class IsDashBindingDataModeNotNone : IValueConverter
69 | {
70 | ///
71 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
72 | {
73 | var bindingData = (SimHub.Plugins.OutputPlugins.GraphicalDash.Models.DashboardBindingData)value;
74 |
75 | // NB Only Mode gets serialized to config, not IsNone etc.
76 | return bindingData.Mode != SimHub.Plugins.OutputPlugins.GraphicalDash.Models.BindingMode.None;
77 | }
78 |
79 | ///
80 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
81 | {
82 | return DependencyProperty.UnsetValue;
83 | }
84 | }
85 |
86 | /// Should the control for number of enabled LEDs be visible?
87 | public class NumberEnabledVisibilityConverter : IValueConverter
88 | {
89 | ///
90 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
91 | {
92 | return ((int)value == -1) ? Visibility.Collapsed : Visibility.Visible;
93 | }
94 |
95 | ///
96 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
97 | {
98 | return DependencyProperty.UnsetValue;
99 | }
100 | }
101 |
102 | /// Common functionality for status LED converters.
103 | public class StatusLedConverterBase
104 | {
105 | ///
106 | /// Is an LED enabled? Its index must be less than the number of enabled LEDs in the array (or number enabled is -1).
107 | ///
108 | protected bool IsEnabled(object[] values)
109 | {
110 | var led = (Led)values[0];
111 | var statusLeds = (Led[])(values[1]);
112 | var numberEnabled = (int)values[2];
113 |
114 | return (-1 == numberEnabled) ? true : (Array.IndexOf(statusLeds, led) < numberEnabled);
115 | }
116 | }
117 |
118 | /// Is an LED clickable?
119 | public sealed class IsStatusLedEnabled : StatusLedConverterBase, IMultiValueConverter
120 | {
121 | ///
122 | public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
123 | {
124 | return IsEnabled(values);
125 | }
126 |
127 | ///
128 | public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
129 | {
130 | throw new NotSupportedException();
131 | }
132 | }
133 |
134 | /// Is a formula assigned?
135 | public sealed class IsStatusLedAssigned : StatusLedConverterBase, IMultiValueConverter
136 | {
137 | ///
138 | public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
139 | {
140 | var isNone = (bool)values[3];
141 | return IsEnabled(values) && !isNone;
142 | }
143 |
144 | ///
145 | public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
146 | {
147 | throw new NotSupportedException();
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Controls/StatusLedArray.xaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
35 |
36 |
37 |
41 |
42 |
43 |
44 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/SegmentDisplays/Formatters/SliProOutputFormatters.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Output formatters for SLI-Pro.
3 | */
4 |
5 | using System;
6 |
7 | // ---------------------------------------------------------------------------------------------------------------------------------
8 |
9 | namespace SimElation.Simhub.SliPlugin
10 | {
11 | /// Output formatters for SLI-Pro.
12 | public sealed class SliProFormatters : IOutputFormatters
13 | {
14 | ///
15 | public void LapTime(TimeSpan timeSpan, ref String str, ref uint[] decimalOrPrimeIndexList)
16 | {
17 | if (timeSpan.TotalMinutes < 10.0)
18 | {
19 | decimalOrPrimeIndexList = s_decimalOrPrimeIndexList0123;
20 | str = String.Format("{0:0}{1:00}{2:000}", (int)timeSpan.TotalMinutes, timeSpan.Seconds, timeSpan.Milliseconds);
21 | }
22 | else if (timeSpan.TotalMinutes < 1000.0)
23 | {
24 | decimalOrPrimeIndexList = s_decimalOrPrimeIndexList24;
25 | str = String.Format("{0,3}{1:00}{2:0}", (int)timeSpan.TotalMinutes, timeSpan.Seconds, timeSpan.Milliseconds / 100);
26 | }
27 | else
28 | {
29 | str = "slo";
30 | }
31 | }
32 |
33 | ///
34 | public void DeltaTime(double deltaTime, ref String str, ref uint[] decimalOrPrimeIndexList)
35 | {
36 | const String overflowStr = "-";
37 |
38 | if (deltaTime >= 0.0)
39 | {
40 | if (deltaTime < 1.0)
41 | {
42 | str = String.Format(" {0:000}", deltaTime * 100.0);
43 | decimalOrPrimeIndexList = s_decimalOrPrimeIndexList3;
44 | }
45 | else if (deltaTime < 10000.0)
46 | {
47 | str = String.Format("{0,6:F0}", deltaTime * 100.0);
48 | decimalOrPrimeIndexList = s_decimalOrPrimeIndexList3;
49 | }
50 | else if (deltaTime < 100000.0)
51 | {
52 | str = String.Format("{0,6:F0}", deltaTime * 10.0);
53 | decimalOrPrimeIndexList = s_decimalOrPrimeIndexList4;
54 | }
55 | else if (deltaTime <= 1000000.0)
56 | {
57 | str = String.Format("{0,6:F0}", deltaTime);
58 | }
59 | else
60 | {
61 | str = overflowStr;
62 | }
63 | }
64 | else
65 | {
66 | if (deltaTime > -1.0)
67 | {
68 | str = String.Format(" {0:000}", deltaTime * 100.0);
69 | decimalOrPrimeIndexList = s_decimalOrPrimeIndexList3;
70 | }
71 | else if (deltaTime > -1000.0)
72 | {
73 | str = String.Format("{0,6:F0}", deltaTime * 100.0);
74 | decimalOrPrimeIndexList = s_decimalOrPrimeIndexList3;
75 | }
76 | else if (deltaTime > -10000.0)
77 | {
78 | str = String.Format("{0,6:F0}", deltaTime * 10.0);
79 | decimalOrPrimeIndexList = s_decimalOrPrimeIndexList4;
80 | }
81 | else if (deltaTime > -100000.0)
82 | {
83 | str = String.Format("{0,6:F0}", deltaTime);
84 | }
85 | else
86 | {
87 | str = overflowStr;
88 | }
89 | }
90 | }
91 |
92 | ///
93 | public String BrakeBiasShortName()
94 | {
95 | return "bbias";
96 | }
97 |
98 | ///
99 | public void BrakeBias(double frontBias, ref String str, ref uint[] decimalOrPrimeIndexList)
100 | {
101 | str = String.Format("f{0:00}r{1:00}", frontBias, 100 - frontBias);
102 | }
103 |
104 | ///
105 | public void Fuel(double fuelLevel, double? remainingLaps, ref String str, ref uint[] decimalOrPrimeIndexList)
106 | {
107 | // NB round down both the fuel level and estimated laps.
108 | str = String.Format("F{0:00}", Math.Min((int)fuelLevel, 99));
109 | double value = remainingLaps ?? 0.0;
110 |
111 | if (value != 0.0)
112 | str += String.Format("L{0:00}", Math.Min((int)value, 99));
113 | else
114 | str += " ";
115 | }
116 |
117 | ///
118 | public void LapCounter(int totalLaps, int currentLap, int completedLaps, ref String str, ref uint[] decimalOrPrimeIndexList)
119 | {
120 | if ((0 != totalLaps) && (currentLap <= 99) && (totalLaps <= 99))
121 | str = String.Format("L{0,2}-{1,2}", Math.Min(99, currentLap), Math.Min(99, totalLaps));
122 | else
123 | str = String.Format("L{0,5}", completedLaps);
124 | }
125 |
126 | ///
127 | public void LapsToGo(int remainingLaps, ref String str, ref uint[] decimalOrPrimeIndexList)
128 | {
129 | str = String.Format("Lr{0,4}", remainingLaps);
130 | }
131 |
132 | ///
133 | public void Position(int currentPosition, int numberOfOpponents, ref String str, ref uint[] decimalOrPrimeIndexList)
134 | {
135 | str = String.Format("P{0,2}-{1,2}", Math.Min(99, currentPosition), Math.Min(99, numberOfOpponents));
136 | }
137 |
138 | ///
139 | public void Temperature(String prefix, double temperature, ref String str, ref uint[] decimalOrPrimeIndexList)
140 | {
141 | str = String.Format("{0}{1,3}", prefix, Math.Round(temperature));
142 | decimalOrPrimeIndexList = s_decimalOrPrimeIndexList23;
143 | }
144 |
145 | ///
146 | public void Speed(double speed, ref String str, ref uint[] decimalOrPrimeIndexList)
147 | {
148 | str = String.Format("{0,6:F0}", speed);
149 | }
150 |
151 | ///
152 | public void Rpm(double rpm, ref String str, ref uint[] decimalOrPrimeIndexList)
153 | {
154 | str = String.Format("{0,6:F0}", rpm);
155 | }
156 |
157 | ///
158 | public String OilTemperaturePrefix()
159 | {
160 | return "Oil";
161 | }
162 |
163 | ///
164 | public String WaterTemperaturePrefix()
165 | {
166 | return "H20";
167 | }
168 |
169 | private readonly static uint[] s_decimalOrPrimeIndexList0123 = { 0, 1, 2 };
170 | private readonly static uint[] s_decimalOrPrimeIndexList23 = { 2, 3 };
171 | private readonly static uint[] s_decimalOrPrimeIndexList24 = { 2, 4 };
172 | private readonly static uint[] s_decimalOrPrimeIndexList3 = { 3 };
173 | private readonly static uint[] s_decimalOrPrimeIndexList4 = { 4 };
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/SegmentDisplays/Formatters/SliF1OutputFormatters.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Output formatters for SLI-F1.
3 | */
4 |
5 | using System;
6 |
7 | // ---------------------------------------------------------------------------------------------------------------------------------
8 |
9 | namespace SimElation.Simhub.SliPlugin
10 | {
11 | /// Output formatters for SLI-F1.
12 | public sealed class SliF1Formatters : IOutputFormatters
13 | {
14 | ///
15 | public void LapTime(TimeSpan timeSpan, ref String str, ref uint[] decimalOrPrimeIndexList)
16 | {
17 | if (timeSpan.TotalMinutes < 10.0)
18 | {
19 | decimalOrPrimeIndexList = s_decimalOrPrimeIndexList02;
20 | str = String.Format("{0:0}{1:00}{2:0}", (int)timeSpan.TotalMinutes, timeSpan.Seconds, timeSpan.Milliseconds / 100);
21 | }
22 | else if (timeSpan.TotalMinutes < 100.0)
23 | {
24 | decimalOrPrimeIndexList = s_decimalOrPrimeIndexList1;
25 | str = String.Format("{0:00}{1:00}", (int)timeSpan.TotalMinutes, timeSpan.Seconds);
26 | }
27 | else
28 | {
29 | str = "slow";
30 | }
31 | }
32 |
33 | ///
34 | public void DeltaTime(double deltaTime, ref String str, ref uint[] decimalOrPrimeIndexList)
35 | {
36 | const String overflowStr = "-";
37 |
38 | if (deltaTime >= 0.0)
39 | {
40 | if (deltaTime < 1.0)
41 | {
42 | str = String.Format(" {0:000}", deltaTime * 100.0);
43 | decimalOrPrimeIndexList = s_decimalOrPrimeIndexList1;
44 | }
45 | else if (deltaTime < 100.0)
46 | {
47 | str = String.Format("{0,4:F0}", deltaTime * 100.0);
48 | decimalOrPrimeIndexList = s_decimalOrPrimeIndexList1;
49 | }
50 | else if (deltaTime < 1000.0)
51 | {
52 | str = String.Format("{0,4:F0}", deltaTime * 10.0);
53 | decimalOrPrimeIndexList = s_decimalOrPrimeIndexList2;
54 | }
55 | else if (deltaTime <= 10000.0)
56 | {
57 | str = String.Format("{0,4:F0}", deltaTime);
58 | }
59 | else
60 | {
61 | str = overflowStr;
62 | }
63 | }
64 | else
65 | {
66 | if (deltaTime > -1.0)
67 | {
68 | str = String.Format("{0:000}", deltaTime * 100.0);
69 | decimalOrPrimeIndexList = s_decimalOrPrimeIndexList1;
70 | }
71 | else if (deltaTime > -10.0)
72 | {
73 | str = String.Format("{0,4:F0}", deltaTime * 100.0);
74 | decimalOrPrimeIndexList = s_decimalOrPrimeIndexList1;
75 | }
76 | else if (deltaTime > -100.0)
77 | {
78 | str = String.Format("{0,4:F0}", deltaTime * 10.0);
79 | decimalOrPrimeIndexList = s_decimalOrPrimeIndexList2;
80 | }
81 | else if (deltaTime > -1000.0)
82 | {
83 | str = String.Format("{0,4:F0}", deltaTime);
84 | }
85 | else
86 | {
87 | str = overflowStr;
88 | }
89 | }
90 | }
91 |
92 | ///
93 | public String BrakeBiasShortName()
94 | {
95 | return "bias";
96 | }
97 |
98 | ///
99 | public void BrakeBias(double frontBias, ref String str, ref uint[] decimalOrPrimeIndexList)
100 | {
101 | str = String.Format("{0:00}{1:00}", frontBias, 100 - frontBias);
102 | decimalOrPrimeIndexList = s_decimalOrPrimeIndexList1;
103 | }
104 |
105 | ///
106 | public void Fuel(double fuelLevel, double? remainingLaps, ref String str, ref uint[] decimalOrPrimeIndexList)
107 | {
108 | // NB round down both the fuel level and estimated laps.
109 |
110 | str = String.Format("{0:00}", Math.Min((int)fuelLevel, 99));
111 | double value = remainingLaps ?? 0.0;
112 |
113 | if (value != 0.0)
114 | {
115 | str += String.Format("{0:00}", Math.Min((int)value, 99));
116 | decimalOrPrimeIndexList = s_decimalOrPrimeIndexList1;
117 | }
118 | else
119 | {
120 | str += " ";
121 | }
122 | }
123 |
124 | ///
125 | public void LapCounter(int totalLaps, int currentLap, int completedLaps, ref String str, ref uint[] decimalOrPrimeIndexList)
126 | {
127 | if ((0 != totalLaps) && (currentLap <= 99) && (totalLaps <= 99))
128 | {
129 | str = String.Format("{0,2}{1,2}", Math.Min(99, currentLap), Math.Min(99, totalLaps));
130 | decimalOrPrimeIndexList = s_decimalOrPrimeIndexList1;
131 | }
132 | else
133 | {
134 | str = String.Format("{0,4}", completedLaps);
135 | }
136 | }
137 |
138 | ///
139 | public void LapsToGo(int remainingLaps, ref String str, ref uint[] decimalOrPrimeIndexList)
140 | {
141 | str = String.Format("{0,4}", remainingLaps);
142 | }
143 |
144 | ///
145 | public void Position(int currentPosition, int numberOfOpponents, ref String str, ref uint[] decimalOrPrimeIndexList)
146 | {
147 | str = String.Format("{0,2}{1,2}", Math.Min(99, currentPosition), Math.Min(99, numberOfOpponents));
148 | decimalOrPrimeIndexList = s_decimalOrPrimeIndexList1;
149 | }
150 |
151 | ///
152 | public void Temperature(String prefix, double temperature, ref String str, ref uint[] decimalOrPrimeIndexList)
153 | {
154 | str = String.Format("{0}{1,3}", prefix, Math.Round(temperature));
155 | decimalOrPrimeIndexList = s_decimalOrPrimeIndexList0;
156 | }
157 |
158 | ///
159 | public void Speed(double speed, ref String str, ref uint[] decimalOrPrimeIndexList)
160 | {
161 | str = String.Format("{0,4:F0}", speed);
162 | }
163 |
164 | ///
165 | public void Rpm(double rpm, ref String str, ref uint[] decimalOrPrimeIndexList)
166 | {
167 | str = String.Format("{0:F0}", rpm);
168 |
169 | if (str.Length > 4)
170 | str = "-";
171 | }
172 |
173 | ///
174 | public String OilTemperaturePrefix()
175 | {
176 | return "o";
177 | }
178 |
179 | ///
180 | public String WaterTemperaturePrefix()
181 | {
182 | return "w";
183 | }
184 |
185 | private readonly static uint[] s_decimalOrPrimeIndexList02 = { 0, 2 };
186 | private readonly static uint[] s_decimalOrPrimeIndexList0 = { 0 };
187 | private readonly static uint[] s_decimalOrPrimeIndexList1 = { 1 };
188 | private readonly static uint[] s_decimalOrPrimeIndexList2 = { 2 };
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/SliPluginDeviceDescriptor.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Extended device description with SliPlugin-specific details.
3 | */
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Windows.Media;
8 |
9 | // ---------------------------------------------------------------------------------------------------------------------------------
10 |
11 | namespace SimElation.Simhub.SliPlugin
12 | {
13 | /// Extended device description with SliPlugin-specific details.
14 | ///
15 | /// For factoring out segment display formatting (6 vs 4 chars difference) and status LED color differences.
16 | ///
17 | public interface ISliPluginDeviceDescriptor
18 | {
19 | /// The plugin-agnostic device descriptor.
20 | SliDevices.IDeviceDescriptor DeviceDescriptor { get; }
21 |
22 | /// Output formatters interface.
23 | IOutputFormatters OutputFormatters { get; }
24 |
25 | /// Left status LEDs.
26 | Led[] LeftStatusLeds { get; }
27 |
28 | /// Right status LEDs.
29 | Led[] RightStatusLeds { get; }
30 | }
31 |
32 | /// Singleton of supported device descriptors.
33 | public sealed class SliPluginDeviceDescriptors
34 | {
35 | /// Singleton instance.
36 | public static SliPluginDeviceDescriptors Instance { get => m_instance.Value; }
37 |
38 | /// Dictionary of descriptors, indexed by USB product id.
39 | public Dictionary Dictionary { get => m_dictionary; }
40 |
41 | private static readonly Lazy m_instance =
42 | new Lazy(() => new SliPluginDeviceDescriptors());
43 | private readonly Dictionary m_dictionary;
44 |
45 | private SliPluginDeviceDescriptors()
46 | {
47 | m_dictionary =
48 | new Dictionary()
49 | {
50 | { SliDevices.Pro.Constants.CompileTime.productId, new SliProDeviceDescriptor() },
51 | { SliDevices.F1.Constants.CompileTime.productId, new SliF1DeviceDescriptor() }
52 | };
53 | }
54 | }
55 |
56 | // SLI-Pro descriptor.
57 | sealed class SliProDeviceDescriptor : ISliPluginDeviceDescriptor
58 | {
59 | public SliDevices.IDeviceDescriptor DeviceDescriptor { get; } = s_descriptor;
60 | public IOutputFormatters OutputFormatters { get; } = new SliProFormatters();
61 | public Led[] LeftStatusLeds { get; } =
62 | new Led[]
63 | {
64 | MakeStatusLed.Run(s_descriptor.Constants.LeftStatusLedColors[0], "Left status LED 1",
65 | String.Format("if ([Flag_Blue], {0}, {1})", (int) Led.State.on, (int) Led.State.off)),
66 |
67 | MakeStatusLed.Run(s_descriptor.Constants.LeftStatusLedColors[1], "Left status LED 2",
68 | String.Format("if ([Flag_Yellow], {0}, {1})", (int) Led.State.on, (int) Led.State.off)),
69 |
70 | MakeStatusLed.Run(s_descriptor.Constants.LeftStatusLedColors[2], "Left status LED 3",
71 | String.Format("if ([CarSettings_FuelAlertActive], {0}, {1})", (int) Led.State.blink, (int) Led.State.off))
72 | };
73 | public Led[] RightStatusLeds { get; } =
74 | new Led[]
75 | {
76 | MakeStatusLed.Run(s_descriptor.Constants.RightStatusLedColors[0], "Right status LED 1",
77 | String.Format("if ([ABSActive], {0}, {1})", (int) Led.State.on, (int) Led.State.off)),
78 |
79 | MakeStatusLed.Run(s_descriptor.Constants.RightStatusLedColors[1], "Right status LED 2",
80 | String.Format("if ([TCActive], {0}, {1})", (int) Led.State.on, (int) Led.State.off)),
81 |
82 | MakeStatusLed.Run(s_descriptor.Constants.RightStatusLedColors[2], "Right status LED 3",
83 | // Flash for DRS available (to get attention!), solid for DRS on.
84 | // NB rf2 (at least?) reports DRS available in the pits (in a practise session), so ignore DRS state if in pit.
85 | String.Format("if (([IsInPitLane] || [IsInPit]), {2}, if ([DRSEnabled], {0}, if ([DRSAvailable], {1}, {2})))",
86 | (int) Led.State.on, (int) Led.State.blink, (int) Led.State.off))
87 | };
88 |
89 | private static SliDevices.IDeviceDescriptor s_descriptor =
90 | SliDevices.DeviceDescriptors.Instance.Dictionary[SliDevices.Pro.Constants.CompileTime.productId];
91 | }
92 |
93 | // SLI-F1 descriptor.
94 | sealed class SliF1DeviceDescriptor : ISliPluginDeviceDescriptor
95 | {
96 | public SliDevices.IDeviceDescriptor DeviceDescriptor { get; } = s_descriptor;
97 | public IOutputFormatters OutputFormatters { get; } = new SliF1Formatters();
98 | public Led[] LeftStatusLeds { get; } =
99 | new Led[]
100 | {
101 | MakeStatusLed.Run(s_descriptor.Constants.LeftStatusLedColors[0], "Left status LED 1",
102 | String.Format("if ([Flag_Blue], {0}, {1})", (int) Led.State.on, (int) Led.State.off)),
103 |
104 | MakeStatusLed.Run(s_descriptor.Constants.LeftStatusLedColors[1], "Left status LED 2",
105 | String.Format("if ([Flag_Yellow], {0}, {1})", (int) Led.State.on, (int) Led.State.off)),
106 |
107 | MakeStatusLed.Run(s_descriptor.Constants.LeftStatusLedColors[2], "Left status LED 3",
108 | String.Format("if ([CarSettings_FuelAlertActive], {0}, {1})", (int) Led.State.blink, (int) Led.State.off))
109 | };
110 | public Led[] RightStatusLeds { get; } =
111 | new Led[]
112 | {
113 | MakeStatusLed.Run(s_descriptor.Constants.RightStatusLedColors[0], "Right status LED 1",
114 | String.Format("if ([ABSActive], {0}, {1})", (int) Led.State.on, (int) Led.State.off)),
115 |
116 | MakeStatusLed.Run(s_descriptor.Constants.RightStatusLedColors[1], "Right status LED 2",
117 | String.Format("if ([TCActive], {0}, {1})", (int) Led.State.on, (int) Led.State.off)),
118 |
119 | MakeStatusLed.Run(s_descriptor.Constants.RightStatusLedColors[2], "Right status LED 3",
120 | // Flash for DRS available (to get attention!), solid for DRS on.
121 | // NB rf2 (at least?) reports DRS available in the pits (in a practise session), so ignore DRS state if in pit.
122 | String.Format("if (([IsInPitLane] || [IsInPit]), {2}, if ([DRSEnabled], {0}, if ([DRSAvailable], {1}, {2})))",
123 | (int) Led.State.on, (int) Led.State.blink, (int) Led.State.off))
124 | };
125 |
126 | private static SliDevices.IDeviceDescriptor s_descriptor =
127 | SliDevices.DeviceDescriptors.Instance.Dictionary[SliDevices.F1.Constants.CompileTime.productId];
128 | }
129 |
130 | static class MakeStatusLed
131 | {
132 | public static Led Run(Color color, String title, String formula)
133 | {
134 | var statusLed =
135 | new Led(color)
136 | {
137 | ExpressionValue = formula,
138 | EditPropertyName = title
139 | };
140 |
141 | return statusLed;
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/VJoyManager.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * vJoy device handling.
3 | */
4 |
5 | using System;
6 | using System.Collections.ObjectModel;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 | using System.Reflection;
10 | using Logging = SimHub.Logging;
11 |
12 | // ---------------------------------------------------------------------------------------------------------------------------------
13 |
14 | namespace SimElation
15 | {
16 | /// vJoy device manager.
17 | public class VJoyManager
18 | {
19 | /// Singleton instance.
20 | public static VJoyManager Instance { get => m_instance.Value; }
21 |
22 | /// Do the vJoy driver and dll versions match sufficiently?
23 | public bool IsValidVersion { get => m_isValidVersion; }
24 |
25 | /// Is the vJoy dll version the known bad one that crashes?
26 | ///
27 | /// See http://vjoystick.sourceforge.net/site/index.php/forum/4-Help/1171-vjoy-crashes-development
28 | ///
29 | public bool IsBadVersion { get => m_isBadVersion; }
30 |
31 | /// The vJoy dll version.
32 | public uint DllVersion { get => m_dllVersion; }
33 |
34 | /// The vJoy driver version.
35 | public uint DriverVersion { get => m_driverVersion; }
36 |
37 | /// The current list of available vJoy device ids.
38 | public ObservableCollection DeviceIds { get => GetDeviceIds(); }
39 |
40 | /// Pulse a vJoy device's button.
41 | ///
42 | ///
43 | ///
44 | /// A Task that resolves to a boolean as to whether the press, pause, release was successful.
45 | public Task PulseButton(uint vJoyDeviceId, uint buttonId, int pulseMs)
46 | {
47 | return GetDevice(vJoyDeviceId)?.PulseButton(buttonId, pulseMs);
48 | }
49 |
50 | /// Get a default device id (when user adds a new mapping from the UI).
51 | /// First vjoy device id that's available, or 1.
52 | public uint GetDefaultDeviceId()
53 | {
54 | var deviceIds = DeviceIds;
55 |
56 | return (deviceIds.Count == 0) ? firstDeviceId : deviceIds[0];
57 | }
58 |
59 | /// Instance of a vJoy device.
60 | private class Device
61 | {
62 | /// Constructor.
63 | ///
64 | /// This device's id, indexed from 1.
65 | public Device(VJoyWrap vJoy, uint id)
66 | {
67 | m_vJoy = vJoy;
68 | m_id = id;
69 | }
70 |
71 | /// Pulse a button.
72 | ///
73 | ///
74 | /// A Task that resolves to a boolean as to whether the press, pause, release was successful.
75 | public async Task PulseButton(uint buttonId, int pulseMs)
76 | {
77 | bool res = false;
78 |
79 | try
80 | {
81 | if (!m_hasAcquired)
82 | {
83 | if (m_vJoy.AcquireVJD(m_id))
84 | m_hasAcquired = true;
85 | else
86 | return false;
87 | }
88 |
89 | // Press button.
90 | if (m_vJoy.SetBtn(true, m_id, buttonId))
91 | {
92 | // Wait then release.
93 | await Task.Delay(pulseMs);
94 | m_vJoy.SetBtn(false, m_id, buttonId);
95 |
96 | res = true;
97 | }
98 | }
99 | catch (Exception)
100 | {
101 | }
102 |
103 | return res;
104 | }
105 |
106 | /// Get the VjdStat state.
107 | ///
108 | public VjdStat GetState()
109 | {
110 | return m_vJoy.GetVJDStatus(m_id);
111 | }
112 |
113 | private delegate void SetButtonFn(bool isSet);
114 |
115 | private readonly uint m_id;
116 | private readonly VJoyWrap m_vJoy;
117 | private bool m_hasAcquired = false;
118 | }
119 |
120 | private VJoyManager()
121 | {
122 | try
123 | {
124 | // Note extra level of indirection here. VJoyManager needs to work if vJoyInterfaceWrap isn't available, so wrap
125 | // the calls we need to make to it in VJoyWrap.cs.
126 | m_vJoy = new VJoyWrap();
127 | }
128 | catch (Exception e)
129 | {
130 | m_vJoy = null;
131 | Logging.Current.InfoFormat("{0}: no vjoy found by thread {1}, {2}", Assembly.GetExecutingAssembly().GetName().Name,
132 | Thread.CurrentThread.ManagedThreadId, e.Message);
133 | }
134 |
135 | m_isValidVersion = (m_vJoy != null) && m_vJoy.DriverMatch(ref m_dllVersion, ref m_driverVersion);
136 |
137 | // 0x216 is definitely bad. 0x218 or later seem OK. Can't find 0x217 to test so assume bad.
138 | m_isBadVersion = m_dllVersion < 0x218;
139 | m_isAvailable = m_isValidVersion && !m_isBadVersion;
140 |
141 | if (m_isAvailable)
142 | {
143 | for (uint i = firstDeviceId; i < m_instances.Length; ++i)
144 | {
145 | // NB doesn't appear to be a way to enumerate using HidLibrary and get back to the vJoy device id.
146 | // Since there's a max of 16, just try them all via the vJoy interface.
147 | m_instances[i] = new Device(m_vJoy, i);
148 | }
149 | }
150 | }
151 |
152 | private ObservableCollection GetDeviceIds()
153 | {
154 | m_deviceIds.Clear();
155 |
156 | if (m_isAvailable)
157 | {
158 | for (uint i = firstDeviceId; i < m_instances.Length; ++i)
159 | {
160 | try
161 | {
162 | var state = m_instances[i].GetState();
163 |
164 | switch (state)
165 | {
166 | case VjdStat.VJD_STAT_OWN:
167 | case VjdStat.VJD_STAT_FREE:
168 | m_deviceIds.Add(i);
169 | break;
170 |
171 | default:
172 | break;
173 | }
174 | }
175 | catch (Exception)
176 | {
177 | }
178 | }
179 | }
180 |
181 | return m_deviceIds;
182 | }
183 |
184 | private Device GetDevice(uint id)
185 | {
186 | return (m_isAvailable && (id >= 1) && (id < m_instances.Length)) ? m_instances[id] : null;
187 | }
188 |
189 | private const uint firstDeviceId = 1; // NB vJoy API starts device ids at 1.
190 | private const uint maxDevices = 16;
191 |
192 | private readonly VJoyWrap m_vJoy;
193 | private uint m_dllVersion = 0;
194 | private uint m_driverVersion = 0;
195 | private readonly bool m_isValidVersion;
196 | private readonly bool m_isBadVersion;
197 | private readonly bool m_isAvailable;
198 | private readonly Device[] m_instances = new Device[maxDevices + firstDeviceId];
199 | private readonly ObservableCollection m_deviceIds = new ObservableCollection();
200 |
201 | private static readonly Lazy m_instance = new Lazy(() => new VJoyManager());
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Controls/SegmentDisplayControl.xaml.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Code-behind for segment display control UI.
3 | */
4 |
5 | using System;
6 | using System.Collections;
7 | using System.Globalization;
8 | using System.Windows;
9 | using System.Windows.Controls;
10 | using System.Windows.Controls.Primitives;
11 | using System.Windows.Data;
12 |
13 | // ---------------------------------------------------------------------------------------------------------------------------------
14 |
15 | namespace SimElation.Simhub.SliPlugin
16 | {
17 | /// Interaction logic for SegmentDisplayControl.xaml.
18 | public partial class SegmentDisplayControl : UserControl
19 | {
20 | private static readonly DependencyProperty LabelProperty =
21 | DependencyProperty.Register(nameof(Label), typeof(string), typeof(SegmentDisplayControl));
22 |
23 | private static readonly DependencyProperty RotarySwitchIndexProperty =
24 | DependencyProperty.Register(nameof(RotarySwitchIndex), typeof(int), typeof(SegmentDisplayControl));
25 |
26 | private static readonly DependencyProperty LearnRotaryButtonContentProperty =
27 | DependencyProperty.Register(nameof(LearnRotaryButtonContent), typeof(string), typeof(SegmentDisplayControl));
28 |
29 | private static readonly DependencyProperty SegmentDisplayFriendlyNamesProperty =
30 | DependencyProperty.Register(nameof(SegmentDisplayFriendlyNames), typeof(IEnumerable), typeof(SegmentDisplayControl));
31 |
32 | private static readonly DependencyProperty SelectedIndexProperty =
33 | DependencyProperty.Register(nameof(SelectedIndex), typeof(int), typeof(SegmentDisplayControl));
34 |
35 | private static readonly DependencyProperty NextDisplayProperty =
36 | DependencyProperty.Register(nameof(NextDisplay), typeof(string), typeof(SegmentDisplayControl));
37 |
38 | private static readonly DependencyProperty PreviousDisplayProperty =
39 | DependencyProperty.Register(nameof(PreviousDisplay), typeof(string), typeof(SegmentDisplayControl));
40 |
41 | private static readonly DependencyProperty PeekCurrentDisplayProperty =
42 | DependencyProperty.Register(nameof(PeekCurrentDisplay), typeof(string), typeof(SegmentDisplayControl));
43 |
44 | private static readonly RoutedEvent LearnRotaryClickEvent = ButtonBase.ClickEvent.AddOwner(typeof(SegmentDisplayControl));
45 |
46 | /// Title property for segment display (e.g. "Left segment", "Right segment").
47 | public string Label
48 | {
49 | get => GetValue(LabelProperty) as string;
50 | set => SetValue(LabelProperty, value);
51 | }
52 |
53 | /// Assigned rotary switch index.
54 | public int RotarySwitchIndex
55 | {
56 | get => (GetValue(RotarySwitchIndexProperty) as int?) ?? SliDevices.RotarySwitchDetector.unknownIndex;
57 | set => SetValue(RotarySwitchIndexProperty, value);
58 | }
59 |
60 | /// Content for the learn rotary button.
61 | public string LearnRotaryButtonContent
62 | {
63 | get => GetValue(LearnRotaryButtonContentProperty) as string;
64 | set => SetValue(LearnRotaryButtonContentProperty, value);
65 | }
66 |
67 | /// Property for an enumerable of items for the drop down (the various displays available).
68 | public IEnumerable SegmentDisplayFriendlyNames
69 | {
70 | get => GetValue(SegmentDisplayFriendlyNamesProperty) as IEnumerable;
71 | set => SetValue(SegmentDisplayFriendlyNamesProperty, value);
72 | }
73 |
74 | /// Currently selected display property.
75 | public int SelectedIndex
76 | {
77 | get => (GetValue(SelectedIndexProperty) as int?) ?? -1;
78 | set => SetValue(SelectedIndexProperty, value);
79 | }
80 |
81 | /// Name for next display action.
82 | public string NextDisplay
83 | {
84 | get => GetValue(NextDisplayProperty) as string;
85 | set => SetValue(NextDisplayProperty, value);
86 | }
87 |
88 | /// Name for previous display action.
89 | public string PreviousDisplay
90 | {
91 | get => GetValue(PreviousDisplayProperty) as string;
92 | set => SetValue(PreviousDisplayProperty, value);
93 | }
94 |
95 | /// Name for peek current display action.
96 | public string PeekCurrentDisplay
97 | {
98 | get => GetValue(PeekCurrentDisplayProperty) as string;
99 | set => SetValue(PeekCurrentDisplayProperty, value);
100 | }
101 |
102 | /// Click handler for learn/forget rotary.
103 | public event RoutedEventHandler LearnRotaryClick
104 | {
105 | // Button has x:Name property ("learnRotaryButton") in the xaml so we can access it here.
106 | add => learnRotaryButton.AddHandler(LearnRotaryClickEvent, value);
107 | remove => learnRotaryButton.RemoveHandler(LearnRotaryClickEvent, value);
108 | }
109 |
110 | /// Constructor.
111 | public SegmentDisplayControl()
112 | {
113 | InitializeComponent();
114 | }
115 |
116 | private void OnLoaded(object sender, RoutedEventArgs e)
117 | {
118 | // TODO figure out how to do this in xaml. Problem is NextDisplay property is null before InitializeComponent()
119 | // and then it blows up making the ui:ControlEditor trying to assign null for ActionName.
120 | nextButtonEditor.ActionName = NextDisplay;
121 | previousButtonEditor.ActionName = PreviousDisplay;
122 | peekCurrentButtonEditor.ActionName = PeekCurrentDisplay;
123 |
124 | // TODO and here. "peekModeContainer" is an x:Name give to the ItemsControl for the list of segment display modes.
125 | // So then search for all ui:ControlEditors in that ItemsControl to set their ActionName.
126 | var deviceInstance = (DeviceInstance)DataContext;
127 | foreach (var item in peekModeContainer.Items)
128 | {
129 | var contentPresenter = (ContentPresenter)peekModeContainer.ItemContainerGenerator.ContainerFromItem(item);
130 |
131 | // contentPresenter can be null but OnLoaded will be invoked again later when it's not null.
132 | if (contentPresenter != null)
133 | {
134 | var controlsEditor =
135 | (SimHub.Plugins.UI.ControlsEditor)contentPresenter.ContentTemplate.FindName("peekMode", contentPresenter);
136 | var modeName = ((SegmentDisplayManager.SegmentDisplayMode)controlsEditor.DataContext).FriendlyName;
137 | controlsEditor.ActionName = deviceInstance.MakeActionNameFQ(deviceInstance.MakeActionName(modeName));
138 | }
139 | }
140 | }
141 | }
142 |
143 | /// Convert a segment display name to a tool tip for the peek control editor.
144 | public sealed class SegmentDisplayFriendlyNameToPeekControlEditorToolTip : IValueConverter
145 | {
146 | ///
147 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
148 | {
149 | return String.Format("Assign a control to peek the current value of '{0}'.", value);
150 | }
151 |
152 | ///
153 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
154 | {
155 | return DependencyProperty.UnsetValue;
156 | }
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/SegmentDisplays/SegmentDisplayManager.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Segment display manager.
3 | */
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Threading;
8 | using SimElation.SliDevices;
9 | using SimHub.Plugins;
10 |
11 | // ---------------------------------------------------------------------------------------------------------------------------------
12 |
13 | namespace SimElation.Simhub.SliPlugin
14 | {
15 | /// Class to manage left/right segment displays.
16 | public class SegmentDisplayManager
17 | {
18 | /// Class wrapping the property for name of a display mode for the UI.
19 | ///
20 | /// Needed to be able to use the name in xaml for the peek configuration buttons.
21 | /// Surely there's some util classes that are just property wrappers over a String...
22 | ///
23 | public sealed class SegmentDisplayMode
24 | {
25 | /// Property for name of a display mode for the UI.
26 | public String FriendlyName { get; set; }
27 | };
28 |
29 | /// A list of the segment display modes for presenting in the UI.
30 | public SegmentDisplayMode[] FriendlyNameList
31 | {
32 | get => Array.ConvertAll(m_segmentDisplayList,
33 | (SegmentDisplay segmentDisplay) => new SegmentDisplayMode() { FriendlyName = segmentDisplay.FriendlyName });
34 | }
35 |
36 | /// Constrictor.
37 | ///
38 | ///
39 | ///
40 | ///
41 | ///
42 | public SegmentDisplayManager(PluginManager pluginManager, DeviceInstance deviceInstance,
43 | Device.SegmentDisplayPosition position, IOutputFormatters outputFormatters, SegmentDisplay[] segmentDisplayList)
44 | {
45 | m_position = position;
46 | m_segmentDisplayList = segmentDisplayList;
47 | m_outputFormatters = outputFormatters;
48 | m_timer = new Timer((object state) => m_showNameTimer = false);
49 |
50 | // Actions for peek.
51 | foreach (var item in segmentDisplayList)
52 | {
53 | pluginManager.AddAction(deviceInstance.MakeActionName(item.FriendlyName),
54 | (_, __) =>
55 | {
56 | // Start peeking at a different mode than current on future game updates.
57 | // Note we don't show the peeked mode's name for a time like we do when the current mode is changed -
58 | // user probably wants to see the data RIGHT NOW.
59 | int index = Array.FindIndex(m_segmentDisplayList,
60 | (segmentDisplay) => segmentDisplay.FriendlyName == item.FriendlyName);
61 | if (index != -1)
62 | m_peekList.Add(index);
63 | },
64 | (_, __) =>
65 | {
66 | // Stop peeking.
67 | int index = Array.FindIndex(m_segmentDisplayList,
68 | (segmentDisplay) => segmentDisplay.FriendlyName == item.FriendlyName);
69 | if (index != -1)
70 | m_peekList.Remove(index);
71 | });
72 | }
73 | }
74 |
75 | /// Called from when a game is running and not paused.
76 | ///
77 | ///
78 | /// The device to set the display on.
79 | public void ProcessData(PluginManager pluginManager, NormalizedData normalizedData, Device device)
80 | {
81 | int index = -1;
82 | var showNameTimer = m_showNameTimer;
83 |
84 | // Peek active?
85 | if (m_peekList.Count > 0)
86 | {
87 | index = m_peekList[m_peekList.Count - 1];
88 |
89 | // Don't show name after a mode change if peek is active.
90 | // NB "peek current mode name" still works, so you can peek a particular mode and hold the show name button...
91 | showNameTimer = false;
92 | }
93 | else
94 | {
95 | index = m_currentIndex;
96 | }
97 |
98 | if (ValidateIndex(index) != -1)
99 | {
100 | var segmentDisplay = m_segmentDisplayList[index];
101 |
102 | if ((m_showNameButtonCount > 0) || showNameTimer)
103 | {
104 | segmentDisplay.ShowName(device, m_position);
105 | }
106 | else
107 | {
108 | String str = "";
109 | uint[] decimalOrPrimeIndexList = s_decimalOrPrimeIndexListEmpty;
110 |
111 | segmentDisplay.ProcessData(normalizedData, m_position, m_outputFormatters, ref str,
112 | ref decimalOrPrimeIndexList);
113 |
114 | device.SetSegment(m_position, str, decimalOrPrimeIndexList);
115 | }
116 | }
117 | else
118 | {
119 | device.SetSegment(m_position, "n-a");
120 | }
121 | }
122 |
123 | /// Get the index of the display mode that is after the current one in the list. Handles cycling.
124 | public int GetNextIndex()
125 | {
126 | int newIndex = ValidateIndex(m_currentIndex + 1);
127 | return (newIndex == -1) ? 0 : newIndex;
128 | }
129 |
130 | /// Get the index of the display mode that is before the current one in the list. Handles cycling.
131 | public int GetPreviousIndex()
132 | {
133 | int newIndex = ValidateIndex(m_currentIndex - 1);
134 | return (newIndex == -1) ? (m_segmentDisplayList.Length - 1) : newIndex;
135 | }
136 |
137 | /// Set the current mode by index.
138 | ///
139 | /// How long to display the name of the new mode, rather than its data.
140 | public void SetByIndex(int index, long segmentNameTimeoutMs)
141 | {
142 | m_currentIndex = index;
143 |
144 | if (segmentNameTimeoutMs > 0)
145 | {
146 | // For future data updates, show the name of the current segment until the timer fires.
147 | m_timer.Change(segmentNameTimeoutMs, Timeout.Infinite);
148 | m_showNameTimer = true;
149 | }
150 | }
151 |
152 | /// Display the current mode's name on future game updates.
153 | /// If true, start peeking at the current mode's name.
154 | public void PeekName(bool isPress)
155 | {
156 | if (isPress)
157 | ++m_showNameButtonCount;
158 | else
159 | m_showNameButtonCount = Math.Max(0, m_showNameButtonCount - 1);
160 | }
161 |
162 | /// Validate a display mode index.
163 | ///
164 | /// The if it is valid, otherwise -1.
165 | private int ValidateIndex(int index)
166 | {
167 | return ((index >= 0) && (index < m_segmentDisplayList.Length)) ? index : -1;
168 | }
169 |
170 | private readonly Device.SegmentDisplayPosition m_position;
171 | private readonly SegmentDisplay[] m_segmentDisplayList;
172 | private readonly IOutputFormatters m_outputFormatters;
173 |
174 | private readonly Timer m_timer;
175 | private bool m_showNameTimer = false;
176 | private int m_showNameButtonCount = 0;
177 | private readonly List m_peekList = new List();
178 | private int m_currentIndex = -1;
179 |
180 | private readonly static uint[] s_decimalOrPrimeIndexListEmpty = { };
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Default settings:
7 | # A newline ending every file
8 | # Use 4 spaces as indentation
9 | [*]
10 | insert_final_newline = true
11 | indent_style = tab
12 | indent_size = 4
13 | trim_trailing_whitespace = true
14 |
15 | [project.json]
16 | indent_size = 2
17 |
18 | # C# files
19 | [*.cs]
20 | # New line preferences
21 | csharp_new_line_before_open_brace = all
22 | csharp_new_line_before_else = true
23 | csharp_new_line_before_catch = true
24 | csharp_new_line_before_finally = true
25 | csharp_new_line_before_members_in_object_initializers = true
26 | csharp_new_line_before_members_in_anonymous_types = true
27 | csharp_new_line_between_query_expression_clauses = true
28 |
29 | # Indentation preferences
30 | csharp_indent_block_contents = true
31 | csharp_indent_braces = false
32 | csharp_indent_case_contents = true
33 | csharp_indent_case_contents_when_block = true
34 | csharp_indent_switch_labels = true
35 | csharp_indent_labels = one_less_than_current
36 |
37 | # Modifier preferences
38 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
39 |
40 | # avoid this. unless absolutely necessary
41 | dotnet_style_qualification_for_field = false:suggestion
42 | dotnet_style_qualification_for_property = false:suggestion
43 | dotnet_style_qualification_for_method = false:suggestion
44 | dotnet_style_qualification_for_event = false:suggestion
45 |
46 | # Types: use keywords instead of BCL types, and permit var only when the type is clear
47 | csharp_style_var_for_built_in_types = false:suggestion
48 | csharp_style_var_when_type_is_apparent = false:none
49 | csharp_style_var_elsewhere = false:suggestion
50 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
51 | dotnet_style_predefined_type_for_member_access = true:suggestion
52 |
53 | # name all constant fields using PascalCase
54 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
55 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
56 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
57 | dotnet_naming_symbols.constant_fields.applicable_kinds = field
58 | dotnet_naming_symbols.constant_fields.required_modifiers = const
59 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case
60 |
61 | # static fields should have s_ prefix
62 | dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
63 | dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields
64 | dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style
65 | dotnet_naming_symbols.static_fields.applicable_kinds = field
66 | dotnet_naming_symbols.static_fields.required_modifiers = static
67 | dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected
68 | dotnet_naming_style.static_prefix_style.required_prefix = s_
69 | dotnet_naming_style.static_prefix_style.capitalization = camel_case
70 |
71 | # internal and private fields should be _camelCase
72 | dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
73 | dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
74 | dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
75 | dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
76 | dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
77 | dotnet_naming_style.camel_case_underscore_style.required_prefix = _
78 | dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
79 |
80 | # Code style defaults
81 | csharp_using_directive_placement = outside_namespace:suggestion
82 | dotnet_sort_system_directives_first = true
83 | csharp_prefer_braces = true:refactoring
84 | csharp_preserve_single_line_blocks = true:none
85 | csharp_preserve_single_line_statements = false:none
86 | csharp_prefer_static_local_function = true:suggestion
87 | csharp_prefer_simple_using_statement = false:none
88 | csharp_style_prefer_switch_expression = true:suggestion
89 |
90 | # Code quality
91 | dotnet_style_readonly_field = true:suggestion
92 | dotnet_code_quality_unused_parameters = non_public:suggestion
93 |
94 | # Expression-level preferences
95 | dotnet_style_object_initializer = true:suggestion
96 | dotnet_style_collection_initializer = true:suggestion
97 | dotnet_style_explicit_tuple_names = true:suggestion
98 | dotnet_style_coalesce_expression = true:suggestion
99 | dotnet_style_null_propagation = true:suggestion
100 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
101 | dotnet_style_prefer_inferred_tuple_names = true:suggestion
102 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
103 | dotnet_style_prefer_auto_properties = true:suggestion
104 | dotnet_style_prefer_conditional_expression_over_assignment = true:refactoring
105 | dotnet_style_prefer_conditional_expression_over_return = true:refactoring
106 | csharp_prefer_simple_default_expression = true:suggestion
107 |
108 | # Expression-bodied members
109 | csharp_style_expression_bodied_methods = true:refactoring
110 | csharp_style_expression_bodied_constructors = true:refactoring
111 | csharp_style_expression_bodied_operators = true:refactoring
112 | csharp_style_expression_bodied_properties = true:refactoring
113 | csharp_style_expression_bodied_indexers = true:refactoring
114 | csharp_style_expression_bodied_accessors = true:refactoring
115 | csharp_style_expression_bodied_lambdas = true:refactoring
116 | csharp_style_expression_bodied_local_functions = true:refactoring
117 |
118 | # Pattern matching
119 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
120 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
121 | csharp_style_inlined_variable_declaration = true:suggestion
122 |
123 | # Null checking preferences
124 | csharp_style_throw_expression = true:suggestion
125 | csharp_style_conditional_delegate_call = true:suggestion
126 |
127 | # Other features
128 | csharp_style_prefer_index_operator = false:none
129 | csharp_style_prefer_range_operator = false:none
130 | csharp_style_pattern_local_over_anonymous_function = false:none
131 |
132 | # Space preferences
133 | csharp_space_after_cast = false
134 | csharp_space_after_colon_in_inheritance_clause = true
135 | csharp_space_after_comma = true
136 | csharp_space_after_dot = false
137 | csharp_space_after_keywords_in_control_flow_statements = true
138 | csharp_space_after_semicolon_in_for_statement = true
139 | csharp_space_around_binary_operators = before_and_after
140 | csharp_space_around_declaration_statements = do_not_ignore
141 | csharp_space_before_colon_in_inheritance_clause = true
142 | csharp_space_before_comma = false
143 | csharp_space_before_dot = false
144 | csharp_space_before_open_square_brackets = false
145 | csharp_space_before_semicolon_in_for_statement = false
146 | csharp_space_between_empty_square_brackets = false
147 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
148 | csharp_space_between_method_call_name_and_opening_parenthesis = false
149 | csharp_space_between_method_call_parameter_list_parentheses = false
150 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
151 | csharp_space_between_method_declaration_name_and_open_parenthesis = false
152 | csharp_space_between_method_declaration_parameter_list_parentheses = false
153 | csharp_space_between_parentheses = false
154 | csharp_space_between_square_brackets = false
155 |
156 | # Analyzers
157 | dotnet_code_quality.ca1802.api_surface = private, internal
158 |
159 | # C++ Files
160 | [*.{cpp,h,in}]
161 | curly_bracket_next_line = true
162 | indent_brace_style = Allman
163 |
164 | # Xml project files
165 | [*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
166 | indent_size = 2
167 |
168 | # Xml build files
169 | [*.builds]
170 | indent_size = 2
171 |
172 | # Xml files
173 | [*.{xml,stylecop,resx,ruleset}]
174 | indent_size = 2
175 |
176 | # Xml config files
177 | [*.{props,targets,config,nuspec}]
178 | indent_size = 2
179 |
180 | # Shell scripts
181 | [*.sh]
182 | end_of_line = lf
183 | [*.{cmd, bat}]
184 | end_of_line = crlf
185 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5 |
6 | ## [0.11.2](https://github.com/simelation/simhub-plugins/compare/@simelation/simhub-sli-plugin@0.11.1...@simelation/simhub-sli-plugin@0.11.2) (2024-05-07)
7 |
8 | ### Bug Fixes
9 |
10 | - build fix for SimHub >= 9.2.9 (FormulaPicker -> FormulaPickerButton). ([ed09d46](https://github.com/simelation/simhub-plugins/commit/ed09d46aec7a945b92fb92e9dbb495df09836154))
11 |
12 | ## [0.11.1](https://github.com/simelation/simhub-plugins/compare/@simelation/simhub-sli-plugin@0.11.0...@simelation/simhub-sli-plugin@0.11.1) (2022-12-23)
13 |
14 | ### Bug Fixes
15 |
16 | - **simhub-sli-plugin:** properly save status led expressions. ([dd9872f](https://github.com/simelation/simhub-plugins/commit/dd9872f50e7a4a51564394e3e7db236841c78ab2))
17 |
18 | # [0.11.0](https://github.com/simelation/simhub-plugins/compare/@simelation/simhub-sli-plugin@0.9.1...@simelation/simhub-sli-plugin@0.11.0) (2022-12-18)
19 |
20 | ### Bug Fixes
21 |
22 | - **simhub-sli-plugin:** added external WpfAutoGrid dependency. ([1275613](https://github.com/simelation/simhub-plugins/commit/1275613ea3bc73fd662b965fe4e126221a616068))
23 | - **simhub-sli-plugin:** ignore polled devices that the serial number can't be retrieved from. ([9d65802](https://github.com/simelation/simhub-plugins/commit/9d658028e1a88f430d3645b5e0872602f39b1316)), closes [#13](https://github.com/simelation/simhub-plugins/issues/13)
24 |
25 | ### Features
26 |
27 | - **simhub-sli-plugin:** bumped to .net 4.8 for SimHub 8. ([f062945](https://github.com/simelation/simhub-plugins/commit/f062945192ca04f8943d078bea01686c4c2d9c7c))
28 |
29 | # [0.10.0](https://github.com/simelation/simhub-plugins/compare/@simelation/simhub-sli-plugin@0.9.1...@simelation/simhub-sli-plugin@0.10.0) (2022-09-13)
30 |
31 | ### Bug Fixes
32 |
33 | - **simhub-sli-plugin:** ignore polled devices that the serial number can't be retrieved from. ([b248fc6](https://github.com/simelation/simhub-plugins/commit/b248fc665727f4257ec3c7c5b851ad195f757957)), closes [#13](https://github.com/simelation/simhub-plugins/issues/13)
34 |
35 | ### Features
36 |
37 | - **simhub-sli-plugin:** bumped to .net 4.8 for SimHub 8. ([9693ff1](https://github.com/simelation/simhub-plugins/commit/9693ff1034a1ad42ca1860af337602fa78bcc15e))
38 |
39 | ## [0.9.2](https://github.com/simelation/simhub-plugins/compare/@simelation/simhub-sli-plugin@0.9.1...@simelation/simhub-sli-plugin@0.9.2) (2022-09-12)
40 |
41 | **Note:** Version bump only for package @simelation/simhub-sli-plugin
42 |
43 | ## [0.9.1](https://github.com/simelation/simhub-plugins/compare/@simelation/simhub-sli-plugin@0.9.0...@simelation/simhub-sli-plugin@0.9.1) (2022-05-18)
44 |
45 | ### Bug Fixes
46 |
47 | - wrong help url. ([2c30fe2](https://github.com/simelation/simhub-plugins/commit/2c30fe27757ae1df6db1cd6980363a9d1b44e727)), closes [#11](https://github.com/simelation/simhub-plugins/issues/11)
48 |
49 | # [0.9.0](https://github.com/simelation/simhub-plugins/compare/@simelation/simhub-sli-plugin@0.8.0...@simelation/simhub-sli-plugin@0.9.0) (2021-07-11)
50 |
51 | ### Bug Fixes
52 |
53 | - **simhub-sli-plugin:** cope with no vjoy dlls in latest simhub. ([f718800](https://github.com/simelation/simhub-plugins/commit/f718800e61f7743e284f0c200e7abcab9f9d5ac0))
54 |
55 | ### Features
56 |
57 | - added speed and rpm segment displays. ([d2c6fe1](https://github.com/simelation/simhub-plugins/commit/d2c6fe1a3aed17080930e28a75a547261b710f77))
58 |
59 | # [0.8.0](https://github.com/simelation/simhub-plugins/compare/@simelation/simhub-sli-plugin@0.7.0...@simelation/simhub-sli-plugin@0.8.0) (2020-12-21)
60 |
61 | ### Features
62 |
63 | - **simhub-sli-plugin:** added ability to assign expressions to a configurable number of RPM LEDs. ([fcf958b](https://github.com/simelation/simhub-plugins/commit/fcf958bde9dc70017c7ebf65529a4b30e049f799))
64 |
65 | # [0.7.0](https://github.com/simelation/simhub-plugins/compare/@simelation/simhub-sli-plugin@0.6.0...@simelation/simhub-sli-plugin@0.7.0) (2020-12-14)
66 |
67 | ### Bug Fixes
68 |
69 | - **simhub-sli-plugin:** was always setting LED index 0 for status and external LEDs. ([2c54ccc](https://github.com/simelation/simhub-plugins/commit/2c54ccca80a6ae6db727db2e7e04dc00fdbe3acc))
70 |
71 | ### Features
72 |
73 | - **simhub-sli-plugin:** added support for mapping rotary switches to vJoy devices. ([a476b44](https://github.com/simelation/simhub-plugins/commit/a476b44ee2bca364404ef9d97590a229f04647c5))
74 | - **simhub-sli-plugin:** use FormulaPicker for LEDs. Javascript now works. ([d54318d](https://github.com/simelation/simhub-plugins/commit/d54318dc85aeeae5c1d0a299378704f75f0b914e))
75 |
76 | # 0.6.0 (2020-12-09)
77 |
78 | ### Features
79 |
80 | - **simhub-sli-plugin:** all sorts. ([2ef9405](https://github.com/simelation/simhub-plugins/commit/2ef94052c09f10350139d4c666f97414ee5f2ce3))
81 |
82 | # [0.5.0](https://github.com/simelation/simhub-plugins/compare/@simelation/simhub-slipro-plugin@0.4.0...@simelation/simhub-slipro-plugin@0.5.0) (2020-11-20)
83 |
84 | ### Bug Fixes
85 |
86 | - **simhub-slipro-plugin:** time & delta formatting. ([62c7a9c](https://github.com/simelation/simhub-plugins/commit/62c7a9c05e82ca720fc0311c0405a3a72fd191e6))
87 |
88 | ### Features
89 |
90 | - **simhub-slipro-plugin:** added a peek current segment display mode name function assignable to a button. ([724c34e](https://github.com/simelation/simhub-plugins/commit/724c34e0d5aa0780cc0abae5b9d17f148baf9b39))
91 | - **simhub-slipro-plugin:** added external LED support. ([626630c](https://github.com/simelation/simhub-plugins/commit/626630cdf5adb5a743ed24d531ae9e47ba81635a))
92 | - **simhub-slipro-plugin:** added feedback dialogs for rotary detection process. ([826628b](https://github.com/simelation/simhub-plugins/commit/826628bf1aa378a8ae45925e76bc1f2d0ad64a6f))
93 | - **simhub-slipro-plugin:** show device status in UI. ([8354147](https://github.com/simelation/simhub-plugins/commit/8354147eb7d208a8ab38767220af822e2c79c431))
94 |
95 | # [0.4.0](https://github.com/simelation/simhub-plugins/compare/@simelation/simhub-slipro-plugin@0.3.0...@simelation/simhub-slipro-plugin@0.4.0) (2020-11-01)
96 |
97 | ### Bug Fixes
98 |
99 | - **simhub-slipro-plugin:** don't check if a property has actually changed value before invoking OnPropertyChanged(). ([ac9ee02](https://github.com/simelation/simhub-plugins/commit/ac9ee0271c6d797d341b0de12d9492b4606c2c4f))
100 |
101 | ### Features
102 |
103 | - **simhub-slipro-plugin:** added support for blinking status LEDs. ([8f1a3d1](https://github.com/simelation/simhub-plugins/commit/8f1a3d1fbc8fc78f72b672ffc43b1b68bdc63efb))
104 | - **simhub-slipro-plugin:** ui tidy ups. ([680be9d](https://github.com/simelation/simhub-plugins/commit/680be9d121c630e207e8009516b4d88e2bc00266))
105 |
106 | # [0.3.0](https://github.com/simelation/simhub-plugins/compare/@simelation/simhub-slipro-plugin@0.2.0...@simelation/simhub-slipro-plugin@0.3.0) (2020-10-30)
107 |
108 | ### Bug Fixes
109 |
110 | - **simhub-slipro-plugin:** use absolute values for ahead/behind delta. ([88e1726](https://github.com/simelation/simhub-plugins/commit/88e17267568074aad41d38e13196990823bdbc9d))
111 |
112 | ### Features
113 |
114 | - **simhub-slipro-plugin:** added help link (to README) in UI. ([9bc6837](https://github.com/simelation/simhub-plugins/commit/9bc68374ee6bae7d0985ce5a0b049fac02dfd513))
115 | - **simhub-slipro-plugin:** added support for buttons to control segment displays. ([03a1971](https://github.com/simelation/simhub-plugins/commit/03a1971f21e49574a43e1ca483e8f13a8a776877))
116 | - **simhub-slipro-plugin:** show version in ui. ([e669799](https://github.com/simelation/simhub-plugins/commit/e669799ca311a84402d1fa2e8cda0e4ac68701b4))
117 | - **simhub-slipro-plugin:** use toggle for whether a rotary controls brightness, and slider for when not. ([7c16e3b](https://github.com/simelation/simhub-plugins/commit/7c16e3bf80382a8ac521e8d5aaa448d9558358b1))
118 |
119 | # 0.2.0 (2020-10-26)
120 |
121 | ### Bug Fixes
122 |
123 | - **simhub-slipro-plugin:** long messages across weren't split across the segment displays correctly by SliPro.SetTextMessage(). ([ca24e61](https://github.com/simelation/simhub-plugins/commit/ca24e6109e32aa4d9d864edd1e34b017a9da6b1e))
124 |
125 | ### Features
126 |
127 | - **simhub-slipro-plugin:** added UI control of segment display modes for when a rotary isn't available. ([a31302a](https://github.com/simelation/simhub-plugins/commit/a31302ae18fdd330253cdfd2afa5efbaccff6698))
128 |
129 | # 0.1.0 (2020-10-04)
130 |
131 | ### Features
132 |
133 | - **simhub-slipro-plugin:** initial commit of SLI-Pro SimHub plugin. ([78f45bc](https://github.com/simelation/simhub-plugins/commit/78f45bc959292a61fb4fdcc1d805ece3d0f25e92))
134 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/SimElation.SimHub.SliPlugin.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | x86
7 | 8.0
8 | {829D79D8-F56E-4F95-82E2-C385F7DFA5E5}
9 | Library
10 | Properties
11 | SimElation.Simhub.SliPlugin
12 | SimElation.SimHub.SliPlugin
13 | v4.8
14 | 512
15 |
16 |
17 |
18 | true
19 | full
20 | false
21 | bin\x86\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 | bin\x86\Debug\SimElation.SimHub.SliPlugin.xml
26 | x86
27 |
28 |
29 | pdbonly
30 | true
31 | bin\x86\Release\
32 | TRACE
33 | prompt
34 | 4
35 | bin\x86\Release\SimElation.SimHub.SliPlugin.xml
36 | x86
37 |
38 |
39 |
40 | .\SimHub\GameReaderCommon.dll
41 | False
42 |
43 |
44 | False
45 | .\SimHub\log4net.dll
46 | False
47 |
48 |
49 | False
50 | .\SimHub\MahApps.Metro.dll
51 | False
52 |
53 |
54 | False
55 | .\SimHub\MahApps.Metro.IconPacks.Core.dll
56 | False
57 |
58 |
59 | False
60 | .\SimHub\MahApps.Metro.IconPacks.Material.dll
61 | False
62 |
63 |
64 | False
65 | .\SimHub\MahApps.Metro.SimpleChildWindow.dll
66 | False
67 |
68 |
69 | False
70 | .\SimHub\Newtonsoft.Json.dll
71 | False
72 |
73 |
74 |
75 |
76 | False
77 | .\SimHub\SimHub.Logging.dll
78 | False
79 |
80 |
81 | .\SimHub\SimHub.Plugins.dll
82 | False
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | .\SimHub\vJoyInterfaceWrap.dll
96 | False
97 |
98 |
99 |
100 | packages\WpfAutoGrid.1.4.0.0\lib\net45\WpfAutoGrid.dll
101 |
102 |
103 |
104 |
105 |
106 |
107 | RotarySwitchMappingControl.xaml
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 | RpmLedsEditor.xaml
132 |
133 |
134 | SegmentDisplayControl.xaml
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 | SliPluginControl.xaml
149 |
150 |
151 | DeviceInstanceControl.xaml
152 |
153 |
154 |
155 | StatusLedArray.xaml
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 | Designer
164 | MSBuild:Compile
165 | true
166 |
167 |
168 | MSBuild:Compile
169 | Designer
170 |
171 |
172 | Designer
173 | MSBuild:Compile
174 |
175 |
176 | Designer
177 | MSBuild:Compile
178 |
179 |
180 | Designer
181 | MSBuild:Compile
182 |
183 |
184 | MSBuild:Compile
185 | Designer
186 |
187 |
188 | Designer
189 | MSBuild:Compile
190 |
191 |
192 | Designer
193 | MSBuild:Compile
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 | rem Good lord why can't the UI enable per-configuration settings. These people are monsters.
209 | if "$(Configuration)" == "Debug" (
210 | if not exist "$(ProjectDir)SimHub\$(TargetFileName)" (
211 | mklink "$(ProjectDir)SimHub\$(TargetFileName)" "$(TargetPath)"
212 | )
213 | )
214 |
215 |
216 |
223 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Controls/DeviceInstanceControl.xaml.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * Code-behind for DeviceInstanceControl.
3 | */
4 |
5 | using System;
6 | using System.Globalization;
7 | using System.Threading.Tasks;
8 | using System.Windows;
9 | using System.Windows.Controls;
10 | using System.Windows.Data;
11 | using System.Windows.Media;
12 | using MahApps.Metro.Controls.Dialogs;
13 | using SimElation.SliDevices;
14 |
15 | // ---------------------------------------------------------------------------------------------------------------------------------
16 |
17 | namespace SimElation.Simhub.SliPlugin
18 | {
19 | /// Interaction logic for DeviceInstanceControl.xaml.
20 | public partial class DeviceInstanceControl : UserControl
21 | {
22 | /// Constructor.
23 | public DeviceInstanceControl()
24 | {
25 | InitializeComponent();
26 |
27 | // TODO I'm sure this isn't the best way. DataContext is null after InitializeComponent() so handle its changed event.
28 | DataContextChanged +=
29 | (object sender, DependencyPropertyChangedEventArgs e) =>
30 | {
31 | m_deviceInstance = (DeviceInstance)DataContext;
32 |
33 | if (m_deviceInstance != null)
34 | {
35 | // TODO ditto. Using dialog:DialogParticipation.Register="{Binding}" in the xaml doesn't work, presumably
36 | // because {Binding} resolves to null at that point.
37 | DialogParticipation.SetRegister(this, this);
38 | }
39 | };
40 | }
41 |
42 | /// Get the vJoy button pulse length.
43 | public int? VJoyPulseButtonMs => m_deviceInstance?.DeviceSettings.VJoyButtonPulseMs;
44 |
45 | /// Remove a rotary switch -> vJoy mapping.
46 | ///
47 | public void RemoveRotarySwitchMapping(DeviceInstance.Settings.RotarySwitchMapping rotarySwitchMapping)
48 | {
49 | if (m_deviceInstance != null)
50 | m_deviceInstance.DeviceSettings.RotarySwitchMappings.Remove(rotarySwitchMapping);
51 | }
52 |
53 | /// Text to display when dll doesn't match driver version.
54 | public static String InvalidVersionString
55 | {
56 | get => String.Format("vJoy unavailable: driver version {0:x} doesn't match dll version {1:x}.",
57 | VJoyManager.Instance.DriverVersion, VJoyManager.Instance.DllVersion);
58 | }
59 |
60 | /// Text to display when dll version is bad and causes crashes.
61 | public static String BadVersionString
62 | {
63 | get => String.Format("vJoy unavailable: dll version {0:x} is known to cause crashes. Try vJoy package 2.1.8 or later.",
64 | VJoyManager.Instance.DllVersion);
65 | }
66 |
67 | private async void OnLeftSegmentRotaryClick(object sender, System.Windows.RoutedEventArgs e)
68 | {
69 | m_deviceInstance.DeviceSettings.LeftSegmentDisplayRotarySwitchIndex = await DetectOrForgetRotary(
70 | m_deviceInstance.DeviceSettings.LeftSegmentDisplayRotarySwitchIndex, "left segment display control");
71 | }
72 |
73 | private async void OnRightSegmentRotaryClick(object sender, System.Windows.RoutedEventArgs e)
74 | {
75 | m_deviceInstance.DeviceSettings.RightSegmentDisplayRotarySwitchIndex = await DetectOrForgetRotary(
76 | m_deviceInstance.DeviceSettings.RightSegmentDisplayRotarySwitchIndex, "right segment display control");
77 | }
78 |
79 | private async void OnBrightnessRotaryClick(object sender, System.Windows.RoutedEventArgs e)
80 | {
81 | m_deviceInstance.DeviceSettings.BrightnessRotarySwitchIndex = await DetectOrForgetRotary(
82 | m_deviceInstance.DeviceSettings.BrightnessRotarySwitchIndex, "brightness level");
83 | }
84 |
85 | private Task DetectOrForgetRotary(int rotarySwitchIndex, String type)
86 | {
87 | return (rotarySwitchIndex == RotarySwitchDetector.unknownIndex) ?
88 | DetectRotary(type) : Task.FromResult(RotarySwitchDetector.unknownIndex);
89 | }
90 |
91 | private async Task DetectRotary(String type)
92 | {
93 | int rotarySwitchIndex = RotarySwitchDetector.unknownIndex;
94 |
95 | try
96 | {
97 | var dialog = await DialogCoordinator.Instance.ShowProgressAsync(this,
98 | String.Format("Detecting rotary switch for {0}...", type),
99 | String.Format("Change the position of a rotary switch on {0}", m_deviceInstance.DeviceInfo.PrettyInfo), true,
100 | new MetroDialogSettings()
101 | {
102 | AnimateShow = false,
103 | AnimateHide = false
104 | });
105 |
106 | try
107 | {
108 | // TODO I suppose the actual rotary detection code should be cancelleable and cancelling the dialog should
109 | // trigger that really. For now we'll just close the dialog and the detection code will silently timeout.
110 | dialog.Canceled += async (sender, eventArgs) => await dialog.CloseAsync();
111 | dialog.SetIndeterminate();
112 |
113 | rotarySwitchIndex = await m_deviceInstance.ManagedDevice.DetectRotary();
114 |
115 | // Just ignore detection result if cancelled.
116 | if (!dialog.IsCanceled)
117 | {
118 | dialog.SetProgress(1);
119 | dialog.SetMessage((rotarySwitchIndex == RotarySwitchDetector.unknownIndex) ?
120 | "No rotary detected" : String.Format("Detected rotary switch {0}",
121 | RotarySwitchDetector.RotarySwitchIndexToUiValue(rotarySwitchIndex)));
122 |
123 | // Wait a bit to display detection feedback.
124 | await Task.Delay(2000);
125 | }
126 | }
127 | finally
128 | {
129 | if (dialog.IsOpen)
130 | _ = dialog.CloseAsync();
131 | }
132 | }
133 | catch (Exception)
134 | {
135 | }
136 |
137 | return rotarySwitchIndex;
138 | }
139 |
140 | private async void OnAddRotaryMappingClick(object sender, System.Windows.RoutedEventArgs e)
141 | {
142 | var rotarySwitchIndex = await DetectRotary("mapping to a vJoy device");
143 | if (RotarySwitchDetector.unknownIndex == rotarySwitchIndex)
144 | return;
145 |
146 | var rotarySwitchMapping = new DeviceInstance.Settings.RotarySwitchMapping() { RotarySwitchIndex = rotarySwitchIndex };
147 | m_deviceInstance.DeviceSettings.RotarySwitchMappings.Add(rotarySwitchMapping);
148 | }
149 |
150 | private void OnRefreshVJoyDevicesClick(object sender, System.Windows.RoutedEventArgs e)
151 | {
152 | // Just call through to the manager to get an up-to-date list. It's an ObservableCollection so the UI will update.
153 | _ = VJoyManager.Instance.DeviceIds;
154 | }
155 |
156 | private DeviceInstance m_deviceInstance;
157 | }
158 |
159 | /// Convert a product id to the appropriate image.
160 | public sealed class ProductIdToImageConverter : IValueConverter
161 | {
162 | ///
163 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
164 | {
165 | var productId = (int)value;
166 |
167 | switch (productId)
168 | {
169 | case SliDevices.Pro.Constants.CompileTime.productId:
170 | return "..\\..\\assets\\SLI-PRO-RevA.png";
171 |
172 | case SliDevices.F1.Constants.CompileTime.productId:
173 | return "..\\..\\assets\\SLI-F1.png";
174 |
175 | default:
176 | return "";
177 | }
178 | }
179 |
180 | ///
181 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
182 | {
183 | return DependencyProperty.UnsetValue;
184 | }
185 | }
186 |
187 | /// Convert a product id to the appropriate orientation of the status LEDs (pro - horizontal; f1 - vertical).
188 | public sealed class ProductIdToStatusLedOrientation : IValueConverter
189 | {
190 | ///
191 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
192 | {
193 | var productId = (int)value;
194 |
195 | switch (productId)
196 | {
197 | case SliDevices.F1.Constants.CompileTime.productId:
198 | return Orientation.Vertical;
199 |
200 | default:
201 | return Orientation.Horizontal;
202 | }
203 | }
204 |
205 | ///
206 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
207 | {
208 | return DependencyProperty.UnsetValue;
209 | }
210 | }
211 |
212 | /// Converter for rotary switch index to text for button.
213 | public sealed class RotarySwitchIndexConverter : IValueConverter
214 | {
215 | ///
216 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
217 | {
218 | if ((int)value == SliDevices.RotarySwitchDetector.unknownIndex)
219 | return "Learn rotary switch";
220 | else
221 | return String.Format("Forget rotary switch {0}", RotarySwitchDetector.RotarySwitchIndexToUiValue((int)value));
222 | }
223 |
224 | ///
225 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
226 | {
227 | return DependencyProperty.UnsetValue;
228 | }
229 | }
230 |
231 | /// Boolean converter for IsEnabled based off rotary switch control enabled.
232 | public sealed class IsRotarySwitchControlledConverter : IValueConverter
233 | {
234 | ///
235 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
236 | {
237 | int rotarySwitchIndex = (int)value;
238 |
239 | return rotarySwitchIndex != SliDevices.RotarySwitchDetector.unknownIndex;
240 | }
241 |
242 | ///
243 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
244 | {
245 | return DependencyProperty.UnsetValue;
246 | }
247 | }
248 |
249 | /// Boolean converter for IsEnabled based off rotary switch control NOT enabled.
250 | public sealed class IsNotRotarySwitchControlledConverter : IValueConverter
251 | {
252 | ///
253 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
254 | {
255 | int rotarySwitchIndex = (int)value;
256 |
257 | return rotarySwitchIndex == SliDevices.RotarySwitchDetector.unknownIndex;
258 | }
259 |
260 | ///
261 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
262 | {
263 | return DependencyProperty.UnsetValue;
264 | }
265 | }
266 |
267 | /// Boolean to LED color converter (for pit lane LEDs).
268 | public sealed class BoolToPitLaneLedColorConverter : IValueConverter
269 | {
270 | ///
271 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
272 | {
273 | return System.Convert.ToBoolean(value) ? Colors.DimGray.ToString() : Colors.Blue.ToString();
274 | }
275 |
276 | ///
277 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
278 | {
279 | return DependencyProperty.UnsetValue;
280 | }
281 | }
282 |
283 | /// Visibility of rotary switch -> vJoy mapping section. Needs vJoy installed!
284 | public sealed class IsVJoyInstalledToVisibilityConverter : IValueConverter
285 | {
286 | ///
287 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
288 | {
289 | var driverVersion = (uint)value;
290 |
291 | return (driverVersion > 0) ? Visibility.Visible : Visibility.Collapsed;
292 | }
293 |
294 | ///
295 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
296 | {
297 | return DependencyProperty.UnsetValue;
298 | }
299 | }
300 |
301 | /// If vJoy driver/dll versions don't match, return Visibility.Visible converter.
302 | public sealed class IsVJoyInvalidVersionToVisibilityConverter : IValueConverter
303 | {
304 | ///
305 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
306 | {
307 | var isValidVersion = (bool)value;
308 |
309 | return isValidVersion ? Visibility.Collapsed : Visibility.Visible;
310 | }
311 |
312 | ///
313 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
314 | {
315 | return DependencyProperty.UnsetValue;
316 | }
317 | }
318 | }
319 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/SliPlugin.cs:
--------------------------------------------------------------------------------
1 | /*
2 | * SimHub SLI plugin.
3 | */
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Collections.ObjectModel;
8 | using System.Linq;
9 | using System.Reflection;
10 | using System.Threading;
11 | using System.Threading.Tasks;
12 | using System.Windows.Data;
13 | using GameReaderCommon;
14 | using SimHub.Plugins;
15 | using SimHub.Plugins.OutputPlugins.Dash.TemplatingCommon;
16 | using Logging = SimHub.Logging;
17 |
18 | // ---------------------------------------------------------------------------------------------------------------------------------
19 |
20 | namespace SimElation.Simhub.SliPlugin
21 | {
22 | /// SimHub SLI plugin.
23 | ///
24 | /// Note the name of the class seems to form the base name for the settings file. A separate attribute would be nicer!
25 | ///
26 | [PluginDescription("SimElation SimHub Plugin for Leo Bodnar SLI-Pro and SLI-F1")]
27 | [PluginAuthor("SimElation")]
28 | [PluginName("SLI Plugin")]
29 | public sealed class SliPlugin : IPlugin, IDataPlugin, IWPFSettings
30 | {
31 | ///
32 | public PluginManager PluginManager { get; set; }
33 |
34 | /// Set of devices either being managed or plugged in but unmanaged.
35 | public ObservableCollection DeviceInstances { get => m_deviceInstances; }
36 |
37 | /// ncalc interpreter.
38 | public NCalcEngineBase Interpreter { get; } = new NCalcEngineBase();
39 |
40 | /// Called once after plugins startup. Plugins are rebuilt at game change.
41 | ///
42 | public void Init(PluginManager pluginManager)
43 | {
44 | Logging.Current.InfoFormat("{0}: initializing plugin in thread {1}", Assembly.GetExecutingAssembly().GetName().Name,
45 | Thread.CurrentThread.ManagedThreadId);
46 |
47 | BindingOperations.EnableCollectionSynchronization(m_deviceInstances, m_lock);
48 |
49 | // Load settings.
50 | var serializedSettings = this.ReadCommonSettings(settingsName, () => new SerializedSettings());
51 |
52 | foreach (var serializedDeviceInstance in serializedSettings.DeviceInstances)
53 | {
54 | serializedDeviceInstance.DeviceSettings.Fixup(
55 | SliPluginDeviceDescriptors.Instance.Dictionary[serializedDeviceInstance.DeviceInfo.ProductId]);
56 |
57 | DeviceInstances.Add(
58 | new DeviceInstance()
59 | {
60 | DeviceInfo = serializedDeviceInstance.DeviceInfo,
61 | DeviceSettings = serializedDeviceInstance.DeviceSettings,
62 | // Anything in serialized settings was managed.
63 | IsManaged = true
64 | });
65 |
66 | // Now we have to wait for the device polling to find this device and create a ManagedDevice for it.
67 | }
68 |
69 | // Initiate device polling.
70 | PollForDevices();
71 |
72 | Logging.Current.InfoFormat("{0}: initialization complete", Assembly.GetExecutingAssembly().GetName().Name);
73 | }
74 |
75 | ///
76 | /// Called at plugin manager stop, close/dispose anything needed here. Plugins are rebuilt at game change.
77 | ///
78 | ///
79 | public void End(PluginManager pluginManager)
80 | {
81 | Logging.Current.InfoFormat("{0}: shutting down plugin in thread {1}", Assembly.GetExecutingAssembly().GetName().Name,
82 | Thread.CurrentThread.ManagedThreadId);
83 |
84 | // Cancel device polling.
85 | if (m_devicePollTask != null)
86 | {
87 | m_devicePollTaskCancellation.Cancel();
88 | // Not disposing of m_devicePollTask as possibly we should await it after requesting cancellation,
89 | // but can't in Dispose().
90 | // TODO what is the correct thing to do?
91 | m_devicePollTask = null;
92 | }
93 |
94 | // Build serialized form of settings.
95 | var serialzedSettings = new SerializedSettings();
96 |
97 | // Dispose any managed devices and save settings for serialization.
98 | foreach (var deviceInstance in DeviceInstances)
99 | {
100 | if (deviceInstance.ManagedDevice != null)
101 | deviceInstance.ManagedDevice.Dispose();
102 |
103 | if (deviceInstance.IsManaged)
104 | {
105 | serialzedSettings.DeviceInstances.Add(
106 | new SerializedDeviceInstance()
107 | {
108 | DeviceInfo = deviceInstance.DeviceInfo,
109 | DeviceSettings = deviceInstance.DeviceSettings
110 | });
111 | }
112 | }
113 |
114 | // Save settings.
115 | this.SaveCommonSettings(settingsName, serialzedSettings);
116 |
117 | Logging.Current.InfoFormat("{0}: plugin shut down", Assembly.GetExecutingAssembly().GetName().Name);
118 | }
119 |
120 | ///
121 | public System.Windows.Controls.Control GetWPFSettingsControl(PluginManager pluginManager)
122 | {
123 | return new SliPluginControl(this);
124 | }
125 |
126 | /// Start managing a device. Called when the UI manage setting is toggled.
127 | ///
128 | public void AddManagedDevice(DeviceInstance deviceInstance)
129 | {
130 | // Sanity checking.
131 | if (-1 == DeviceInstances.IndexOf(deviceInstance))
132 | {
133 | Logging.Current.WarnFormat("{0}: {1} can't find device {2}", Assembly.GetExecutingAssembly().GetName().Name,
134 | nameof(AddManagedDevice), deviceInstance.DeviceInfo.SerialNumber);
135 | return;
136 | }
137 |
138 | if (deviceInstance.IsManaged)
139 | {
140 | Logging.Current.WarnFormat("{0}: {1} device {2} is already managed", Assembly.GetExecutingAssembly().GetName().Name,
141 | nameof(AddManagedDevice), deviceInstance.DeviceInfo.SerialNumber);
142 | return;
143 | }
144 |
145 | deviceInstance.IsManaged = true;
146 | CreateManagedDevice(deviceInstance);
147 | }
148 |
149 | /// Stop managing a device. Called when the UI manage setting is toggled.
150 | /// If the device is still plugged in, it will remain in the UI such that it can be managed again.
151 | ///
152 | public void RemoveManagedDevice(DeviceInstance deviceInstance)
153 | {
154 | // Sanity checking.
155 | if (-1 == DeviceInstances.IndexOf(deviceInstance))
156 | {
157 | Logging.Current.WarnFormat("{0}: {1} can't find device {2}", Assembly.GetExecutingAssembly().GetName().Name,
158 | nameof(AddManagedDevice), deviceInstance.DeviceInfo.SerialNumber);
159 | return;
160 | }
161 |
162 | if (!deviceInstance.IsManaged)
163 | {
164 | Logging.Current.WarnFormat("{0}: {1} device {2} is already unmanaged",
165 | Assembly.GetExecutingAssembly().GetName().Name, nameof(AddManagedDevice),
166 | deviceInstance.DeviceInfo.SerialNumber);
167 | return;
168 | }
169 |
170 | if (deviceInstance.ManagedDevice != null)
171 | {
172 | deviceInstance.ManagedDevice.Dispose();
173 | deviceInstance.ManagedDevice = null;
174 | }
175 |
176 | deviceInstance.IsManaged = false;
177 | }
178 |
179 | ///
180 | /// Called one time per game data update, contains all normalized game data,
181 | /// raw data are intentionally "hidden" under a generic object type (A plugin SHOULD NOT USE IT).
182 | ///
183 | /// This method is on the critical path, it must execute as fast as possible and avoid throwing any error.
184 | ///
185 | ///
186 | ///
187 | public void DataUpdate(PluginManager pluginManager, ref GameData gameData)
188 | {
189 | // TODO need locks over iterating Devices.
190 |
191 | if (gameData.GameRunning)
192 | {
193 | if (!gameData.GamePaused && (gameData.NewData != null))
194 | {
195 | // Fix up a few potentially missing things.
196 | m_normalizedData.Populate(pluginManager, gameData.NewData);
197 |
198 | foreach (var deviceInstance in DeviceInstances)
199 | {
200 | deviceInstance.ManagedDevice?.ProcessGameData(pluginManager, m_normalizedData);
201 | }
202 | }
203 | else
204 | {
205 | foreach (var deviceInstance in DeviceInstances)
206 | {
207 | deviceInstance.ManagedDevice?.ProcessPausedState(gameData, true);
208 | }
209 | }
210 | }
211 | else
212 | {
213 | foreach (var deviceInstance in DeviceInstances)
214 | {
215 | deviceInstance.ManagedDevice?.ProcessPausedState(gameData, false);
216 | }
217 | }
218 | }
219 |
220 | // Polling loop for devices.
221 | private async void PollForDevices()
222 | {
223 | while (true)
224 | {
225 | try
226 | {
227 | m_devicePollTask = Task.Delay(1000, m_devicePollTaskCancellation.Token);
228 | await m_devicePollTask;
229 | m_devicePollTask = null;
230 |
231 | PollForDevicesOnce();
232 | }
233 | catch (Exception e)
234 | {
235 | Logging.Current.InfoFormat("{0}: exception {1} in {2}", Assembly.GetExecutingAssembly().GetName().Name, e,
236 | nameof(PollForDevices));
237 | return;
238 | }
239 | }
240 | }
241 |
242 | // Poll for devices.
243 | private void PollForDevicesOnce()
244 | {
245 | Logging.Current.DebugFormat("{0}: polling for devices...", Assembly.GetExecutingAssembly().GetName().Name);
246 |
247 | var deviceInfoSet = SliDevices.DevicePoller.Poll(vendorId, SliDevices.DeviceDescriptors.Instance.ProductIds);
248 |
249 | // We might modify the DeviceInstances set, so don't walk it directly.
250 | foreach (var deviceInstance in DeviceInstances.ToList())
251 | {
252 | SliDevices.DeviceInfo deviceInfo;
253 | var isInDeviceSet = deviceInfoSet.TryGetValue(deviceInstance.DeviceInfo.SerialNumber, out deviceInfo);
254 |
255 | if (!deviceInstance.IsManaged)
256 | {
257 | // Remove any unmanaged and now unplugged devices from our known set.
258 | if (!isInDeviceSet)
259 | {
260 | Logging.Current.InfoFormat("{0}: unmanaged device {1} was unplugged",
261 | Assembly.GetExecutingAssembly().GetName().Name, deviceInstance.DeviceInfo.PrettyInfo);
262 |
263 | DeviceInstances.Remove(deviceInstance);
264 | continue;
265 | }
266 | }
267 |
268 | if (isInDeviceSet)
269 | {
270 | if (deviceInstance.DeviceInfo.HidDevice == null)
271 | {
272 | Logging.Current.InfoFormat("{0}: managed device {1} was plugged in",
273 | Assembly.GetExecutingAssembly().GetName().Name, deviceInstance.DeviceInfo.PrettyInfo);
274 |
275 | // This was a saved device that's just been plugged back in. Got a handle, can now create ManagedDevice.
276 | deviceInstance.DeviceInfo.HidDevice = deviceInfo.HidDevice;
277 | CreateManagedDevice(deviceInstance);
278 | }
279 |
280 | // No need to process this device below.
281 | deviceInfoSet.Remove(deviceInstance.DeviceInfo.SerialNumber);
282 | }
283 | }
284 |
285 | // Add new devices.
286 | foreach (var deviceInfo in deviceInfoSet.Values)
287 | {
288 | switch (deviceInfo.ProductId)
289 | {
290 | case SliDevices.Pro.Constants.CompileTime.productId:
291 | case SliDevices.F1.Constants.CompileTime.productId:
292 | Logging.Current.InfoFormat("{0}: unmanaged device {1} was plugged in",
293 | Assembly.GetExecutingAssembly().GetName().Name, deviceInfo.PrettyInfo);
294 |
295 | AddNewDevice(deviceInfo);
296 | break;
297 |
298 | default:
299 | // Shouldn't get here.
300 | Logging.Current.InfoFormat("{0}: found unknown device with product id {1}",
301 | Assembly.GetExecutingAssembly().GetName().Name, deviceInfo.ProductId);
302 | break;
303 | }
304 | }
305 | }
306 |
307 | private void AddNewDevice(SliDevices.DeviceInfo deviceInfo)
308 | {
309 | // Add to available set.
310 | DeviceInstances.Add(
311 | new DeviceInstance()
312 | {
313 | DeviceInfo = deviceInfo,
314 | DeviceSettings =
315 | new DeviceInstance.Settings(SliPluginDeviceDescriptors.Instance.Dictionary[deviceInfo.ProductId])
316 | });
317 | }
318 |
319 | private void CreateManagedDevice(DeviceInstance deviceInstance)
320 | {
321 | if (deviceInstance.ManagedDevice != null)
322 | {
323 | Logging.Current.WarnFormat("{0}: {1} device {2} already has a {3}", Assembly.GetExecutingAssembly().GetName().Name,
324 | nameof(AddManagedDevice), deviceInstance.DeviceInfo.SerialNumber, nameof(ManagedDevice));
325 | return;
326 | }
327 |
328 | deviceInstance.ManagedDevice = new ManagedDevice(this, deviceInstance);
329 | }
330 |
331 | private class SerializedDeviceInstance
332 | {
333 | public SliDevices.DeviceInfo DeviceInfo { get; set; }
334 | public DeviceInstance.Settings DeviceSettings { get; set; }
335 | }
336 |
337 | private class SerializedSettings
338 | {
339 | public List DeviceInstances { get; set; } = new List();
340 | }
341 |
342 | // Bodnar vendor id.
343 | private const int vendorId = 0x1dd2;
344 |
345 | // Base for settings file name.
346 | private const String settingsName = "Settings";
347 |
348 | // Set of devices.
349 | private readonly object m_lock = new object();
350 | private readonly ObservableCollection m_deviceInstances = new ObservableCollection();
351 |
352 | // Device polling.
353 | private readonly CancellationTokenSource m_devicePollTaskCancellation = new CancellationTokenSource();
354 | private Task m_devicePollTask = null;
355 |
356 | // "Fluffed up" data populated on a game update.
357 | private readonly NormalizedData m_normalizedData = new NormalizedData();
358 | }
359 | }
360 |
--------------------------------------------------------------------------------
/packages/simhub-sli-plugin/src/Controls/DeviceInstanceControl.xaml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
31 |
32 |
33 |
34 |
35 |
37 |
42 |
43 |
44 |
45 |
50 |
51 |
52 |
53 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
70 |
71 |
72 |
74 |
75 |
76 |
79 |
80 |
81 |
82 |
83 |
84 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
101 |
102 |
103 |
104 |
108 |
109 |
110 |
111 |
112 |
122 |
123 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
145 |
147 |
148 |
149 |
150 |
155 |
156 |
157 |
158 |
159 |
160 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
179 |
180 |
181 |
182 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
196 |
197 |
198 |
201 |
203 |
204 |
205 |
208 |
210 |
211 |
212 |
213 |
216 |
217 |
218 |
219 |
223 |
224 |
225 |
226 |
230 |
231 |
232 |
233 |
234 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
--------------------------------------------------------------------------------