From e20c2c14d07d0e0907b79b3386a033cd5ac13e21 Mon Sep 17 00:00:00 2001 From: Martin Renner Date: Wed, 18 Jan 2023 19:53:17 +0100 Subject: [PATCH] It is now possible to access all SimHub properties. Issue #13 --- .../Property/PropertyAccessor.cs | 12 ++-- .../Property/PropertySource.cs | 10 ++- .../Property/SimHubProperty.cs | 26 ++++++++ PropertyServer.Plugin/PropertyServerPlugin.cs | 3 + README.adoc | 62 +++++++++++++++++-- 5 files changed, 104 insertions(+), 9 deletions(-) diff --git a/PropertyServer.Plugin/Property/PropertyAccessor.cs b/PropertyServer.Plugin/Property/PropertyAccessor.cs index 54b86d2..982a9a0 100644 --- a/PropertyServer.Plugin/Property/PropertyAccessor.cs +++ b/PropertyServer.Plugin/Property/PropertyAccessor.cs @@ -43,9 +43,10 @@ public static async Task CreateProperty(string propertyName, Fun if (source == null) { - Log.Info($"Property {propertyName} does not start with a known source prefix"); - await errorCallback.Invoke($"Property {propertyName} does not start with a known source prefix"); - return null; + Log.Info($"Property {propertyName} does not start with a known source prefix. Treating as generic property."); + // We do not check if this property really exists (e.g. PluginManager.GetAllPropertyNames()), because properties + // can be added and removed dynamically. + return new SimHubPropertyGeneric(propertyName); } var simHubProperty = await CreateProperty(source.Value, propertyName, errorCallback); @@ -93,6 +94,7 @@ private static async Task CreateProperty(PropertySource source, // Is it a property of type "getter"? Log.Debug($"Trying to find property {name} in {source}"); var plainName = name.Contains('.') ? name.Substring(source.GetPropertyPrefix().Length + 1) : name; + var propertyInfo = source.GetPropertySourceType().GetProperty(plainName); if (propertyInfo != null) { @@ -145,7 +147,9 @@ public static IEnumerable GetAvailableProperties() { var result = new List(); - var sources = Enum.GetValues(typeof(PropertySource)).Cast(); + // Iterate over all known PropertySources and determine the accessible properties. + // But we really don't want to iterate on Generic properties. + var sources = Enum.GetValues(typeof(PropertySource)).Cast().Where(source => source != PropertySource.Generic); foreach (var source in sources) { var availableProperties = source.GetPropertySourceType().GetProperties(); diff --git a/PropertyServer.Plugin/Property/PropertySource.cs b/PropertyServer.Plugin/Property/PropertySource.cs index 3c55afc..797ef2c 100644 --- a/PropertyServer.Plugin/Property/PropertySource.cs +++ b/PropertyServer.Plugin/Property/PropertySource.cs @@ -29,8 +29,12 @@ public enum PropertySource /// /// Assetto Corsa Competizione - Rawdata "Physics" /// - AccPhysics + AccPhysics, + /// + /// Generic access to a property via PluginManager. + /// + Generic } public static class PropertySourceEx @@ -51,6 +55,8 @@ public static Type GetPropertySourceType(this PropertySource propertySource) return Type.GetType("ACSharedMemory.ACC.MMFModels.Graphics, ACSharedMemory"); case PropertySource.AccPhysics: return Type.GetType("ACSharedMemory.ACC.MMFModels.Physics, ACSharedMemory"); + case PropertySource.Generic: + return typeof(PluginManager); default: throw new ArgumentException($"Unknown PropertySource {propertySource}"); } @@ -71,6 +77,8 @@ public static string GetPropertyPrefix(this PropertySource propertySource) return "acc.graphics"; case PropertySource.AccPhysics: return "acc.physics"; + case PropertySource.Generic: + return ""; default: throw new ArgumentException($"Unknown PropertySource {propertySource}"); } diff --git a/PropertyServer.Plugin/Property/SimHubProperty.cs b/PropertyServer.Plugin/Property/SimHubProperty.cs index c9788d7..145a4c3 100644 --- a/PropertyServer.Plugin/Property/SimHubProperty.cs +++ b/PropertyServer.Plugin/Property/SimHubProperty.cs @@ -108,6 +108,8 @@ private string TypeToString(Type type) return "double"; case "System.Double": return "double"; + case "System.Object": + return "object"; default: return "(unknown)"; } @@ -185,4 +187,28 @@ protected override Type GetPropertyType() return _methodInfo.ReturnType; } } + + /// + /// This is a simple delegate that tries to retrieve the property value directly from the PluginManager. + /// + /// + /// This method is much slower than the other property implementations. Use it only if the property is not available through + /// the other implementations. + /// + public class SimHubPropertyGeneric : SimHubProperty + { + public SimHubPropertyGeneric(string propertyName) : base(PropertySource.Generic, propertyName) + { + } + + protected override object GetValue(object obj) + { + return obj is PluginManager pm ? pm.GetPropertyValue(Name) : null; + } + + protected override Type GetPropertyType() + { + return typeof(object); + } + } } \ No newline at end of file diff --git a/PropertyServer.Plugin/PropertyServerPlugin.cs b/PropertyServer.Plugin/PropertyServerPlugin.cs index f15f6ac..789ce21 100644 --- a/PropertyServer.Plugin/PropertyServerPlugin.cs +++ b/PropertyServer.Plugin/PropertyServerPlugin.cs @@ -141,6 +141,9 @@ private async void DataUpdateInternal(GameData data) case PropertySource.AccPhysics: await simHubProperty.UpdateFromObject(_rawDataManager.AccPhysics); break; + case PropertySource.Generic: + await simHubProperty.UpdateFromObject(PluginManager); + break; default: throw new ArgumentException($"Unknown PropertySource {simHubProperty.PropertySource}"); } diff --git a/README.adoc b/README.adoc index a4fe091..116fc83 100644 --- a/README.adoc +++ b/README.adoc @@ -1,5 +1,6 @@ = SimHub Property Server :toc: +:sectnums: ifdef::env-github[] :tip-caption: :bulb: endif::[] @@ -14,7 +15,7 @@ TIP: Always read the *correct version* of the documentation, which matches the v This is a plugin for https://www.simhubdash.com/[SimHub]. It allows access to SimHub properties via a tcp connection. -Clients can subscribe for property changes. They will then receive updates each time, when the value of a subscribed property changes. +Clients can subscribe for property changes. They will then receive updates each time, when the value of a subscribed property changes. One use case is the project https://github.com/pre-martin/StreamDeckSimHubPlugin[StreamDeckSimHubPlugin], which allows updating the state of Stream Deck keys via SimHub properties. @@ -25,11 +26,15 @@ Simply copy the file `PropertyServer.dll` into the root directory of your SimHub Optionally, the checkbox "_Show in left menu_" can be activated. This will show an entry named "_Property Server_" in the left menu bar, which allows to adjust the settings of the plugin. If "_Show in left menu_" is not enabled, the settings of the plugin can be found under "_Additional Plugins_ > _Property Server_". -After installation, the checkbox "_Show in left menu_" can be found under "_Settings_ > _Plugins_". +After installation, the checkbox "_Show in left menu_" can be found under "_Settings_ > _Plugins_". == Usage +If you are an end user, you can skip this section and jump to the https://github.com/pre-martin/StreamDeckSimHubPlugin[StreamDeckSimHubPlugin]. But make sure you read the section <>, because it contains important information about the SimHub properties you can use. + +=== Connection + Open a telnet connection to `localhost` on port `18082` (or whatever port has been set in the settings). The Property Server will respond with its name: ---- @@ -39,6 +44,8 @@ SimHub Property Server Now simply send `help` in order to receive a list of subscribable properties and a list of supported commands. +=== Example + The following shows an example of the communication. The characters `<` and `>` are not part of the communication, they are just used in this example to illustrate what has been sent by the server (`>`) and what has been sent by the client (`<`): ---- @@ -66,14 +73,61 @@ Property names follow the convention of SimHub, e.g. `[DataCorePlugin.GameRunnin At the moment, there are two limitations in effect: . The plugin will send data only at a rate of 10 Hz. -. Only properties of type `bool`, `int`, `long`, `float` and `double` are supported. No arrays are supported. +. Only properties of the types `bool`, `int`, `long`, `float` and `double` are supported. No compound types and no arrays. Limitation (1) was chosen because the plugin is not meant for real time communication. If real time is a requirement, then the UDP forwarding of SimHub should be used instead. Limitation (2) could be changed, if there are requirements to read other properties. It's just a matter of implementing other data types. -== "Help" +[#available-props-help] +== Available properties and "Help" + +=== Different sources of data + +This plugin allows access to SimHub properties of different sources. These sources are distinguished by property name prefixes: + +==== Prefix `dcp.*` + +This prefix is mapped to a subset of the SimHub properties found in SimHub under "Available properties" in the section "DataCorePlugin". The available properties are listed in the section <>. + +This is the best performing option and the properties are returned in a typed manner (e.g. as `int` or `double`). This option should be preferred over the "generic" properties (see below). + +To make the property names even shorter, the prefix `dcp.gd.\*` must be used to access properties which start with `DataCorePlugin.GameData.*`. + +- Example 1: `dcp.GameRunning` must be used to access the SimHub property `DataCorePlugin.GameRunning` + +- Example 2: `dcp.gd.Brake` must be used to access the SimHub property `DataCorePlugin.GameData.Brake` + +==== Prefix `acc.*` + +This prefix is mapped to raw data properties from Assetto Corsa Competizione. The available properties are listed below in the section <>. + +Properties with this prefix perform as well as properties with the prefix `dcp` and they are also typed - just as `dcp` properties. + +==== No prefix, also known as "Generic" properties + +If a property name does not start with one of the prefixes listed above, then a generic access is used. This allows access to almost all properties of SimHub. + +The number of available properties depends on the plugins that are enabled in SimHub, but in a usual setup there should be around 2000 properties available - including custom properties exported by NCalc scripts. + +These properties are not listed in the section <>! Just search the properties you are interested in in SimHub under "Available properties" and use the "Copy name" function from the context menu. + +The downsides of generic properties are: + +- Access is a little bit slower (but that shouldn't be a problem - it's fractions of a millisecond) +- These properties are not typed - they are just returned as `object`. + +The consequence of this second point is that client applications have to take care of the interpretation of the values themselves. Therefore the other prefixes should be used, if possible. + +- Example 1: `ShakeItWindPlugin.DynamicGain` allows access to just this property +- Example 2: `SystemInfoPlugin.CPULoadPercent` allows access to just this property +- Example 3: Of course, names like `DataCorePlugin.GameData.Brake` can be used. But consider using `dcp.gd.Brake` instead (for the reasons mentioned above) +- Example 4: Your SimHub installation contains an NCalc script example in the file `\NCalcScripts\samples.ini`, which exports the properties `DataCorePlugin.ExternalScript.BlinkingGear` and `DataCorePlugin.ExternalScript.BlinkingGearUP`. These custom properties can be accessed just by exactly these names. + + This allows you to convert values inside of SimHub with the help of NCalc for usage in external applications. + + +[#help,reftext=Help] +=== "Help" This is the current output of the command `help`: