├── .config └── dotnet-tools.json ├── .editorconfig ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .lintstagedrc.json ├── .prettierignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── commitlint.config.js ├── lerna.json ├── package.json ├── packages └── simhub-sli-plugin │ ├── .vscode │ └── tasks.json │ ├── CHANGELOG.md │ ├── Properties │ ├── AssemblyInfo.cs │ ├── DesignTimeResources.xaml │ ├── Styles.xaml │ └── ThemeProperties.cs │ ├── README.md │ ├── SimElation.SimHub.SliPlugin.csproj │ ├── SimElation.SimHub.SliPlugin.csproj.user │ ├── SimElation.SimHub.SliPlugin.sln │ ├── assets │ ├── SLI-F1.png │ └── SLI-PRO-RevA.png │ ├── package.json │ ├── packages.config │ └── src │ ├── Controls │ ├── DeviceInstanceControl.xaml │ ├── DeviceInstanceControl.xaml.cs │ ├── RotarySwitchMappingControl.xaml │ ├── RotarySwitchMappingControl.xaml.cs │ ├── RpmLedsEditor.xaml │ ├── RpmLedsEditor.xaml.cs │ ├── SegmentDisplayControl.xaml │ ├── SegmentDisplayControl.xaml.cs │ ├── SliPluginControl.xaml │ ├── SliPluginControl.xaml.cs │ ├── StatusLedArray.xaml │ └── StatusLedArray.xaml.cs │ ├── DeviceInstance.cs │ ├── Devices │ ├── Device.cs │ ├── DeviceDescriptor.cs │ ├── DeviceInfo.cs │ ├── DevicePoller.cs │ ├── IBrightnessReport.cs │ ├── IConstants.cs │ ├── IInputReport.cs │ ├── ILedStateReport.cs │ ├── RotarySwitchDetector.cs │ ├── SliF1 │ │ ├── SliF1Constants.cs │ │ └── SliF1ReportFormats.cs │ └── SliPro │ │ ├── SliProConstants.cs │ │ └── SliProReportFormats.cs │ ├── Led.cs │ ├── ManagedDevice.cs │ ├── NormalizedData.cs │ ├── SegmentDisplays │ ├── BrakeBiasSegmentDisplay.cs │ ├── DeltaSegmentDisplay.cs │ ├── Formatters │ │ ├── IOutputFormatters.cs │ │ ├── SliF1OutputFormatters.cs │ │ └── SliProOutputFormatters.cs │ ├── FuelSegmentDisplay.cs │ ├── LapCounterSegmentDisplay.cs │ ├── LapTimeSegmentDisplay.cs │ ├── LapsToGoSegmentDisplay.cs │ ├── PositionSegmentDisplay.cs │ ├── RpmSegmentDisplay.cs │ ├── SegmentDisplay.cs │ ├── SegmentDisplayManager.cs │ ├── SpeedSegmentDisplay.cs │ └── TempSegmentDisplay.cs │ ├── SliPlugin.cs │ ├── SliPluginDeviceDescriptor.cs │ ├── VJoyManager.cs │ └── VJoyWrap.cs └── yarn.lock /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | *.log 4 | .vs 5 | 6 | packages/**/lib 7 | packages/**/bin 8 | packages/**/obj 9 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # commit-msg. 4 | yarn --silent commitlint -e $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # pre-commit 4 | yarn --silent lint-staged -r 5 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .git 2 | node_modules 3 | 4 | packages/**/bin 5 | packages/**/lib 6 | packages/**/obj 7 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 132, 3 | "tabWidth": 4, 4 | "arrowParens": "always" 5 | } 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/.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/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/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/Properties/DesignTimeResources.xaml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/simhub-sli-plugin/Properties/Styles.xaml: -------------------------------------------------------------------------------- 1 |  2 | 6 | 7 | 8 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /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/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/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/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/assets/SLI-F1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simelation/simhub-plugins/b826c39b796a0f8897a795d7a5ecc75bd68957a4/packages/simhub-sli-plugin/assets/SLI-F1.png -------------------------------------------------------------------------------- /packages/simhub-sli-plugin/assets/SLI-PRO-RevA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simelation/simhub-plugins/b826c39b796a0f8897a795d7a5ecc75bd68957a4/packages/simhub-sli-plugin/assets/SLI-PRO-RevA.png -------------------------------------------------------------------------------- /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/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /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 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 70 | 71 | 72 | 74 | 75 | 76 | 79 | 80 | 81 | 82 | 83 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 110 | 111 | 112 | 122 | 123 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 148 | 149 | 150 | 155 | 156 | 157 | 158 | 159 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 188 | 189 | 190 | 191 | 192 | 193 | 196 | 197 | 198 | 201 | 203 | 204 | 205 | 208 | 210 | 211 | 212 | 213 | 216 | 217 | 218 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | -------------------------------------------------------------------------------- /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/Controls/RotarySwitchMappingControl.xaml: -------------------------------------------------------------------------------- 1 |  14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 54 | 55 | 56 | 57 | 58 | 59 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | 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/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/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/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/Controls/StatusLedArray.xaml: -------------------------------------------------------------------------------- 1 |  13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 35 | 36 | 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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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 | --------------------------------------------------------------------------------