Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support TimeSpan and String types also for "state" properties and not just for "display" properties #116

Merged
merged 2 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions StreamDeckSimHub.Plugin/PropertyLogic/PropertyComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
namespace StreamDeckSimHub.Plugin.PropertyLogic;

/// <summary>
/// Parses expressions like <c>some.property==5</c> and evaluates their resuls.
/// Parses expressions like <c>some.property==5</c> and evaluates their results.
/// </summary>
public class PropertyComparer
{
Expand Down Expand Up @@ -86,14 +86,10 @@ public bool Evaluate(PropertyType propertyType, IComparable? propertyValue, Cond
return false;
}

// When we arrive here with a SimHub property of type "object", it could be interpreted
// as "double" or "string" (see PropertyType.ParseFromSimHub and .ParseLiberally).
// Both values (property value and compare value) have to be of the same type, otherwise they are unequal.

var compareFunction = expression.Operator.CompareFunction();
if (expression.Operator != ConditionOperator.Between)
{
var compareValue = propertyType.ParseLiberally(expression.CompareValue);
var compareValue = propertyType.Parse(expression.CompareValue) ?? "";
if (propertyValue.GetType() != compareValue.GetType())
{
_logger.LogDebug("Property value and compare value are of different types, returning 'false'");
Expand All @@ -109,8 +105,8 @@ public bool Evaluate(PropertyType propertyType, IComparable? propertyValue, Cond
return false;
}

var compareValue1 = propertyType.ParseLiberally(values[0]);
var compareValue2 = propertyType.ParseLiberally(values[1]);
var compareValue1 = propertyType.Parse(values[0]) ?? "";
var compareValue2 = propertyType.Parse(values[1]) ?? "";
if (propertyValue.GetType() != compareValue1.GetType() || propertyValue.GetType() != compareValue2.GetType())
{
_logger.LogDebug("Property value and compare value are of different types, returning 'false'");
Expand Down
3 changes: 2 additions & 1 deletion StreamDeckSimHub.Plugin/SimHub/PropertyParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class PropertyParser
if (valueAsString == "(null)") valueAsString = null;

// See https://github.com/pre-martin/SimHubPropertyServer/blob/main/PropertyServer.Plugin/Property/SimHubProperty.cs
// Keep PropertyTypeTests.cs in sync with this list.
var type = typeAsString switch
{
"boolean" => PropertyType.Boolean,
Expand All @@ -37,7 +38,7 @@ public class PropertyParser
"object" => PropertyType.Object,
_ => PropertyType.Double // Should not happen. But best guess should always be "double".
};
var value = type.ParseFromSimHub(valueAsString);
var value = type.Parse(valueAsString);

return (name, type, value);
}
Expand Down
70 changes: 13 additions & 57 deletions StreamDeckSimHub.Plugin/SimHub/PropertyType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,68 +24,15 @@ public enum PropertyType
/// </summary>
public static class PropertyTypeEx
{
/// <summary>
/// Converts a string value into a typed value, which is returned as <c>IComparable</c>. The method assumes that the
/// string value was received from SimHub Property Plugin.
/// </summary>
public static IComparable? ParseFromSimHub(this PropertyType propertyType, string? propertyValue)
/// Converts a string value into a typed value, which is returned as <c>IComparable</c>. This method works for data
/// received from SimHub, but also for user supplied values, like compare values.
public static IComparable? Parse(this PropertyType propertyType, string? propertyValue)
{
if (propertyValue == null)
{
return null;
}

switch (propertyType)
{
case PropertyType.Boolean:
{
var result = bool.TryParse(propertyValue, out var boolResult);
return result ? boolResult : false;
}
case PropertyType.Integer:
{
var result = int.TryParse(propertyValue, out var intResult);
return result ? intResult : 0;
}
case PropertyType.Long:
{
var result = long.TryParse(propertyValue, out var longResult);
return result ? longResult : 0L;
}
case PropertyType.Double:
{
var result = double.TryParse(propertyValue, NumberStyles.Any, CultureInfo.InvariantCulture, out var doubleResult);
return result ? doubleResult : 0.0d;
}
case PropertyType.TimeSpan:
{
var result = TimeSpan.TryParse(propertyValue, CultureInfo.InvariantCulture, out var timeSpanResult);
return result ? timeSpanResult : null;
}
case PropertyType.String:
{
return propertyValue;
}
case PropertyType.Object:
{
// Try to parse as double.
var result = double.TryParse(propertyValue, NumberStyles.Any, CultureInfo.InvariantCulture, out var doubleResult);
if (result) return doubleResult;
// If not possible, return as string
return propertyValue;
}
default:
throw new ArgumentOutOfRangeException(nameof(propertyType), propertyType, null);
}
}

/// Converts a string value into a typed value, which is returned as <c>IComparable</c>. The method is more liberal
/// than <c>ParseFromSimHub</c> and accepts a wider range of property values.
/// <remarks>
/// This method should be used to parse user input.
/// </remarks>
public static IComparable ParseLiberally(this PropertyType propertyType, string propertyValue)
{
switch (propertyType)
{
case PropertyType.Boolean:
Expand Down Expand Up @@ -120,6 +67,15 @@ public static IComparable ParseLiberally(this PropertyType propertyType, string
var result = double.TryParse(propertyValue, NumberStyles.Any, CultureInfo.InvariantCulture, out var doubleResult);
return result ? doubleResult : 0.0d;
}
case PropertyType.TimeSpan:
{
var result = TimeSpan.TryParse(propertyValue, CultureInfo.InvariantCulture, out var timeSpanResult);
return result ? timeSpanResult : null;
}
case PropertyType.String:
{
return propertyValue;
}
case PropertyType.Object:
{
// Try to parse as double.
Expand All @@ -129,7 +85,7 @@ public static IComparable ParseLiberally(this PropertyType propertyType, string
return propertyValue;
}
default:
throw new ArgumentOutOfRangeException(nameof(propertyType), propertyType, null);
throw new ArgumentOutOfRangeException(nameof(propertyType), propertyType, "PropertyType parser not implemented for type");
}
}
}
38 changes: 28 additions & 10 deletions StreamDeckSimHub.Plugin/SimHub/SimHubConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,20 +141,31 @@ private async Task ConnectAsync()
{
Logger.Info($"Established connection to {Sanitize(line)}");
Connected = true;
foreach (var propertyName in _subscriptions.Keys)
{
await SendSubscribe(propertyName);
}

await ReadFromServer();
}
}
catch (Exception e)
{
Logger.Info($"Connection failed: {e.Message}");
Logger.Info($"Connection failed: {e}");
}

if (!Connected)
if (Connected)
{
Logger.Info("Sending queued subscriptions and starting poll loop");
try
{
foreach (var propertyName in _subscriptions.Keys)
{
await SendSubscribe(propertyName);
}
}
catch (Exception e)
{
Logger.Error(e, "Exception while subscribing queued subscriptions");
}

await ReadFromServer();
}
else
{
await Task.Delay(TimeSpan.FromSeconds(4));
}
Expand Down Expand Up @@ -346,7 +357,14 @@ private async Task ReadFromServer()
Logger.Debug($"Received from server: {Sanitize(line)}");
if (line.StartsWith("Property "))
{
await ParseProperty(line);
try
{
await ParseProperty(line);
}
catch (Exception e)
{
Logger.Error(e, $"Unhandled exception while processing data from server. Received line was: \"{Sanitize(line)}\"");
}
}
}

Expand All @@ -356,7 +374,7 @@ private async Task ReadFromServer()
catch (IOException ioe)
{
// IOException: Fall through to "CloseAndReconnect".
Logger.Warn($"Received IOException while waiting for data: {ioe.Message}");
Logger.Warn($"Received IOException while waiting for data: {ioe}");
}

await CloseAndReconnect();
Expand Down
18 changes: 8 additions & 10 deletions StreamDeckSimHub.PluginTests/Actions/HotkeyActionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,13 @@ public void TestOldComparisonInteger()
Assert.That(ce.Operator, Is.EqualTo(ConditionOperator.Gt));
Assert.That(ce.CompareValue, Is.EqualTo("0"));

// If "EngineStarted" would be an "Integer" property, the old logic was:
// If "EngineStarted" is an "Integer" property, the old logic was:
// - PropertyValue from SimHub > 0: True
// - else: False
// We simulate also that we have received the values from SimHub. This ensures the old logic is still valid.
var propValueOne = PropertyType.Integer.ParseFromSimHub("1");
var propValueTwo = PropertyType.Integer.ParseFromSimHub("2");
var propValueZero = PropertyType.Integer.ParseFromSimHub("0");
var propValueMinusOne = PropertyType.Integer.ParseFromSimHub("-1");
var propValueOne = PropertyType.Integer.Parse("1");
var propValueTwo = PropertyType.Integer.Parse("2");
var propValueZero = PropertyType.Integer.Parse("0");
var propValueMinusOne = PropertyType.Integer.Parse("-1");
Assert.That(_propertyComparer.Evaluate(PropertyType.Integer, propValueOne, ce), Is.True);
Assert.That(_propertyComparer.Evaluate(PropertyType.Integer, propValueTwo, ce), Is.True);
Assert.That(_propertyComparer.Evaluate(PropertyType.Integer, propValueZero, ce), Is.False);
Expand All @@ -46,12 +45,11 @@ public void TestOldComparisonBoolean()
Assert.That(ce.Operator, Is.EqualTo(ConditionOperator.Gt));
Assert.That(ce.CompareValue, Is.EqualTo("0"));

// If "EngineStarted" would be a "Boolean" property, the old logic was:
// If "EngineStarted" is a "Boolean" property, the old logic was:
// - PropertyValue from SimHub = "True": > True
// - else: False
// We simulate also that we have received the values from SimHub. This ensures the old logic is still valid.
var propValueTrue = PropertyType.Boolean.ParseFromSimHub("True");
var propValueFalse = PropertyType.Boolean.ParseFromSimHub("False");
var propValueTrue = PropertyType.Boolean.Parse("True");
var propValueFalse = PropertyType.Boolean.Parse("False");
Assert.That(_propertyComparer.Evaluate(PropertyType.Boolean, propValueTrue, ce), Is.True);
Assert.That(_propertyComparer.Evaluate(PropertyType.Boolean, propValueFalse, ce), Is.False);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ namespace StreamDeckSimHub.PluginTests.AttributeLogic;
public class PropertyComparerEvaluateTests
{
private PropertyComparer _propertyComparer;
private readonly IComparable? _propValueTrue = PropertyType.Boolean.ParseFromSimHub("True");
private readonly IComparable? _propValueFalse = PropertyType.Boolean.ParseFromSimHub("False");
private readonly IComparable? _propValueZero = PropertyType.Integer.ParseFromSimHub("0");
private readonly IComparable? _propValueOne = PropertyType.Integer.ParseFromSimHub("1");
private readonly IComparable? _propValueTwo = PropertyType.Integer.ParseFromSimHub("2");
private readonly IComparable? _propValueThree = PropertyType.Integer.ParseFromSimHub("3");
private readonly IComparable? _propValueLongZero = PropertyType.Long.ParseFromSimHub("0");
private readonly IComparable? _propValueLongOne = PropertyType.Long.ParseFromSimHub("1");
private readonly IComparable? _propValueLongTwo = PropertyType.Long.ParseFromSimHub("2");
private readonly IComparable? _propValueTrue = PropertyType.Boolean.Parse("True");
private readonly IComparable? _propValueFalse = PropertyType.Boolean.Parse("False");
private readonly IComparable? _propValueZero = PropertyType.Integer.Parse("0");
private readonly IComparable? _propValueOne = PropertyType.Integer.Parse("1");
private readonly IComparable? _propValueTwo = PropertyType.Integer.Parse("2");
private readonly IComparable? _propValueThree = PropertyType.Integer.Parse("3");
private readonly IComparable? _propValueLongZero = PropertyType.Long.Parse("0");
private readonly IComparable? _propValueLongOne = PropertyType.Long.Parse("1");
private readonly IComparable? _propValueLongTwo = PropertyType.Long.Parse("2");

[SetUp]
public void Init()
Expand Down Expand Up @@ -115,8 +115,8 @@ public void LongPropWithIntegerValue()
public void DoublePropWithDoubleValue()
{
var ce = _propertyComparer.Parse("acc.graphics.fuelEstimatedLaps>=3.5");
var propValue5Dot9 = PropertyType.Double.ParseFromSimHub("5.9");
var propValue3Dot4 = PropertyType.Double.ParseFromSimHub("3.4");
var propValue5Dot9 = PropertyType.Double.Parse("5.9");
var propValue3Dot4 = PropertyType.Double.Parse("3.4");
Assert.That(_propertyComparer.Evaluate(PropertyType.Double, propValue5Dot9, ce), Is.True);
Assert.That(_propertyComparer.Evaluate(PropertyType.Double, propValue3Dot4, ce), Is.False);
}
Expand Down Expand Up @@ -145,27 +145,27 @@ public void IntegerPropInvalidBetween()
public void ObjectProp()
{
var ce = _propertyComparer.Parse("DataCorePlugin.GameData.Gear>=3");
var propValue2 = PropertyType.Object.ParseFromSimHub("2");
var propValue3 = PropertyType.Object.ParseFromSimHub("3");
var propValue2 = PropertyType.Object.Parse("2");
var propValue3 = PropertyType.Object.Parse("3");
Assert.That(_propertyComparer.Evaluate(PropertyType.Object, propValue2, ce), Is.False);
Assert.That(_propertyComparer.Evaluate(PropertyType.Object, propValue3, ce), Is.True);

var propValueN = PropertyType.Object.ParseFromSimHub("N");
var propValueN = PropertyType.Object.Parse("N");
Assert.That(_propertyComparer.Evaluate(PropertyType.Object, propValueN, ce), Is.False);
}

[Test]
public void ObjectPropBetween()
{
var ce = _propertyComparer.Parse("DataCorePlugin.GameData.Gear~~3;4");
var propValue2 = PropertyType.Object.ParseFromSimHub("2");
var propValue3 = PropertyType.Object.ParseFromSimHub("3");
var propValue5 = PropertyType.Object.ParseFromSimHub("5");
var propValue2 = PropertyType.Object.Parse("2");
var propValue3 = PropertyType.Object.Parse("3");
var propValue5 = PropertyType.Object.Parse("5");
Assert.That(_propertyComparer.Evaluate(PropertyType.Object, propValue2, ce), Is.False);
Assert.That(_propertyComparer.Evaluate(PropertyType.Object, propValue3, ce), Is.True);
Assert.That(_propertyComparer.Evaluate(PropertyType.Object, propValue5, ce), Is.False);

var propValueN = PropertyType.Object.ParseFromSimHub("N");
var propValueN = PropertyType.Object.Parse("N");
Assert.That(_propertyComparer.Evaluate(PropertyType.Object, propValueN, ce), Is.False);
}
}
61 changes: 61 additions & 0 deletions StreamDeckSimHub.PluginTests/SimHub/PropertyTypeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (C) 2024 Martin Renner
// LGPL-3.0-or-later (see file COPYING and COPYING.LESSER)

using StreamDeckSimHub.Plugin.SimHub;

namespace StreamDeckSimHub.PluginTests.SimHub;

/// <summary>
/// Tests to ensure that all known property types are really parsed correctly.
/// </summary>
public class PropertyTypeTests
{
[Test]
public void TestBoolean()
{
var r1 = PropertyType.Boolean.Parse("True");
Assert.That(r1, Is.True);
}

[Test]
public void TestInteger()
{
var r1 = PropertyType.Integer.Parse("10");
Assert.That(r1, Is.EqualTo(10));
}

[Test]
public void TestLong()
{
var r1 = PropertyType.Long.Parse("10");
Assert.That(r1, Is.EqualTo(10));
}

[Test]
public void TestDouble()
{
var r1 = PropertyType.Double.Parse("10.1");
Assert.That(r1, Is.EqualTo(10.1));
}

[Test]
public void TestTimespan()
{
var r1 = PropertyType.TimeSpan.Parse("1:05:03");
Assert.That(r1, Is.EqualTo(TimeSpan.FromSeconds(1 * 3600 + 5 * 60 + 3)));
}

[Test]
public void TestString()
{
var r1 = PropertyType.String.Parse("Hello");
Assert.That(r1, Is.EqualTo("Hello"));
}

[Test]
public void TestObject()
{
var r1 = PropertyType.Object.Parse("Hello");
Assert.That(r1, Is.EqualTo("Hello"));
}
}
Loading