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

improve feedback handling #15

Merged
merged 1 commit into from
Aug 20, 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
33 changes: 33 additions & 0 deletions Source/EsuEcosMiddleman/CfgRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,37 @@ public class CfgDebounce : ICfgDebounce
[JsonProperty("offMs")] public uint Off { get; set; }
}

internal interface ICfgDebug
{
bool Enabled { get; set; }

Dictionary<string, List<int>> Inputs { get; set; }
}

public class CfgDebug : ICfgDebug
{
[JsonProperty("enabled")] public bool Enabled { get; set; } = false;

// key: "port1", "port2", ...
// value: 1..16
[JsonProperty("inputs")]
public Dictionary<string, List<int>> Inputs { get; set; }

/// <summary>
/// Filters the number of a port for a specific input.
/// </summary>
/// <param name="inputName">e.g. `port1` or `port3`, depends on the naming in the configuration</param>
/// <returns></returns>
public static uint GetPortNumber(string inputName)
{
if (string.IsNullOrEmpty(inputName)) return 0;
var m = inputName.Replace("port", string.Empty).Trim();
if (uint.TryParse(m, out var res))
return res;
return 0;
}
}

public interface IRuntimeConfiguration
{
}
Expand All @@ -61,6 +92,7 @@ internal interface ICfgRuntime
ICfgHsi88 CfgHsi88 { get; set; }
ICfgDebounce CfgDebounce { get; set; }
IRuntimeConfiguration RuntimeConfiguration { get; set; }
ICfgDebug DebugConfiguration { get; set; }
ICfgFilter Filter { get; set; }
}

Expand All @@ -74,6 +106,7 @@ internal class CfgRuntime : ICfgRuntime
[JsonProperty("hsi")] public ICfgHsi88 CfgHsi88 { get; set; } = new CfgHsi88();
[JsonProperty("debounce")] public ICfgDebounce CfgDebounce { get; set; } = new CfgDebounce();
[JsonProperty("runtime")] public IRuntimeConfiguration RuntimeConfiguration { get; set; } = new RuntimeConfiguration();
[JsonProperty("debug")] public ICfgDebug DebugConfiguration { get; set; } = new CfgDebug();
[JsonProperty("filter")] public ICfgFilter Filter { get; set; } = new CfgFilter();
}

Expand Down
11 changes: 11 additions & 0 deletions Source/EsuEcosMiddleman/EsuEcosMiddleman.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@
"onMs": 25,
"offMs": 2000
},
"debug": {
"__doc": "In general, the number of ports is related to `hsi::left + hsi::middle + hsi::right` and should be the same count.",
"enabled": false,
"inputs": {
"port1": [],
"port2": [],
"port3": [],
"port4": [],
"port5": []
}
},
"filter": {
"enabled": true,
"objectIdsInfo": [
Expand Down
78 changes: 40 additions & 38 deletions Source/EsuEcosMiddleman/Middleman.HsiStateData.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace EsuEcosMiddleman;
Expand All @@ -14,9 +13,13 @@ internal partial class Middleman
public class HsiStateData
{
private readonly ICfgDebounce _cfgDebounce;

public const int ObjectIdOffset = 100; // offset of "100" because ecos starts the object id with 100 for s88 modules
public int ObjectId { get; } // ESU ECoS internal identifier
public int HsiDeviceId => ObjectId - ObjectIdOffset + 1;
public const int NumberOfPins = 16;
public const string ZeroHexValues = "0000";
public const char Bin1 = '1';
public const char Bin0 = '0';

private string _nativeHexData = ZeroHexValues;

Expand All @@ -28,8 +31,10 @@ public string NativeHexData

private readonly Dictionary<int, DateTime> _states = new();

public HsiStateData(ICfgDebounce cfgDebounce)
public HsiStateData(int objectId, ICfgDebounce cfgDebounce)
{
ObjectId = objectId;

_cfgDebounce = cfgDebounce;

for (var i = 0; i < NumberOfPins; ++i)
Expand All @@ -54,14 +59,13 @@ public bool Update(string dataset)

var sbin = new[]
{
'0', '0', '0', '0',
'0', '0', '0', '0',
'0', '0', '0', '0',
'0', '0', '0', '0'
Bin0, Bin0, Bin0, Bin0,
Bin0, Bin0, Bin0, Bin0,
Bin0, Bin0, Bin0, Bin0,
Bin0, Bin0, Bin0, Bin0
};

var res = false;

var changeCounter = 0;

for (var i = 0; i < NumberOfPins; ++i)
{
Expand All @@ -75,64 +79,62 @@ public bool Update(string dataset)

continue;
}

var stateTime = _states[i];
var deltaMs = (DateTime.Now - stateTime).TotalMilliseconds;

var bounceOn = _cfgDebounce.On;
var bounceOff = _cfgDebounce.Off;

if (cOld == '0') // was off, must wait bounceOff before update
if (cOld == Bin0) // was off, must wait bounceOff before update
{
res = deltaMs > bounceOn;

if (res)
if (deltaMs > bounceOn)
{
sbin[i] = '1';
sbin[i] = Bin1;

++changeCounter;
}
}
else if (cOld == '1') // was on, must wait bounceOn beforeUpdate
else if (cOld == Bin1) // was on, must wait bounceOn beforeUpdate
{
res = deltaMs > bounceOff;

if (res)
if (deltaMs > bounceOff)
{
sbin[i] = '0';
sbin[i] = Bin0;

++changeCounter;
}
}
}

NativeHexData = ToHex(new string(sbin));

return res;
return changeCounter > 0;
cbries marked this conversation as resolved.
Show resolved Hide resolved
}

#if DEBUG
public void ShowStates(int specificPin = -1, bool showHeadline = true)
public void ShowStates(uint portId, int specificPin = -1, bool showHeadline = true, Action<string> logCallback = null)
{
var recentBinary = ToBinary(NativeHexData);

if(showHeadline)
Trace.WriteLine("ShowStates");
if (showHeadline)
logCallback?.Invoke("ShowStates");

for (var i = 0; i < NumberOfPins; ++i)
if (specificPin == -1)
{
if (specificPin == -1)
for (var i = 0; i < NumberOfPins; ++i)
{
var dt = _states[i];
var delta = DateTime.Now - dt;
Trace.WriteLine($" {recentBinary[i]} {dt} {delta.TotalMilliseconds}");
}
else
{
if (specificPin == i + 1)
{
var dt = _states[i];
var delta = DateTime.Now - dt;
Trace.WriteLine($" {recentBinary[i]} {dt} {delta.TotalMilliseconds}");
}
logCallback?.Invoke($" {recentBinary[i]} {dt} {delta.TotalMilliseconds}");
}
}
else
{
var pinRight = NumberOfPins - specificPin;
var dt = _states[pinRight];
var delta = DateTime.Now - dt;
logCallback?.Invoke($"{portId}:{specificPin} {recentBinary[pinRight]} {dt} {delta.TotalMilliseconds}");
}
}
#endif

Expand All @@ -143,7 +145,7 @@ public void ShowStates(int specificPin = -1, bool showHeadline = true)
/// </summary>
/// <param name="hexValue"></param>
/// <returns></returns>
private string ToBinary(string hexValue)
public static string ToBinary(string hexValue)
{
return string.Join(string.Empty,
hexValue.Select(
Expand All @@ -158,7 +160,7 @@ private string ToBinary(string hexValue)
/// </summary>
/// <param name="binaryValue"></param>
/// <returns></returns>
private string ToHex(string binaryValue)
public static string ToHex(string binaryValue)
{
var hex = string.Join(string.Empty,
Enumerable.Range(0, binaryValue.Length / 8)
Expand Down
64 changes: 28 additions & 36 deletions Source/EsuEcosMiddleman/Middleman.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ public async Task RunAsync()
// init hsi state cache
// offset of "100" because ecos starts the object id with 100 for s88 modules
for (var idx = 0; idx < 32; ++idx)
_hsiStates[100 + idx] = new HsiStateData(_cfgRuntime.CfgDebounce);
{
var objectId = HsiStateData.ObjectIdOffset + idx;

_hsiStates[objectId] = new HsiStateData(objectId, _cfgRuntime.CfgDebounce);
}

_hsi88Device = new DeviceInterface();
_hsi88Device.Failed += Hsi88DeviceOnFailed;
Expand Down Expand Up @@ -199,19 +203,17 @@ private void Hsi88DeviceOnDataReceived(object sender, DeviceInterfaceData data)
_cfgRuntime.Logger?.Log.Debug($"{it.Key} => {it.Value}");

// ESU ECoS feedback device object ids starts at "100"
// HSI-88-USB sends "1"-based port ids
// HSI-88-USB sends "1"-based port ids => hsiDeviceId
// Finally: 99 + 1 => 100 (objId)
var objId = 99 + it.Key;

// TEST ONLY A SINGLE DEVICE
//if(objId != 101) continue;

//hsiPort.ShowStates(12, false);
var hsiDeviceId = it.Key;
var objId = 99 + hsiDeviceId;

var hsiPort = _hsiStates[objId];
var r = hsiPort.Update(it.Value);

//hsiPort.ShowStates(12, false);
#if DEBUG
if (r) ShowStates(hsiPort);
#endif

if (r)
{
Expand All @@ -222,37 +224,27 @@ private void Hsi88DeviceOnDataReceived(object sender, DeviceInterfaceData data)
}
}

#endregion

private readonly Random _rnd = new();

/// <summary>
///
/// </summary>
/// <param name="simdata">e.g. "i01022c05"</param>
internal void DoSimulation(string simdata = "")
#if DEBUG
private void ShowStates(HsiStateData hsiPort)
{
if (_cfgRuntime == null) return;

_cfgRuntime.Logger?.Log.Info("S88 Simulation");

if (string.IsNullOrEmpty(simdata))
{
// generate line
// e.g. "i01022c05"
if (hsiPort == null) return;
if (!_cfgRuntime.DebugConfiguration.Enabled) return;

simdata = "i";
simdata += _cfgRuntime.CfgHsi88.NumberMax.ToString("D2");
simdata += _rnd.Next(1, _cfgRuntime.CfgHsi88.NumberMax).ToString("D2");

var hexLeft = _rnd.Next(0, 255);
var hexRight = _rnd.Next(0, 255);

simdata += hexLeft.ToString("X2") + hexRight.ToString("X2");
foreach (var input in _cfgRuntime.DebugConfiguration.Inputs)
{
var portId = CfgDebug.GetPortNumber(input.Key);
if (portId == 0) continue;
if (portId != hsiPort.HsiDeviceId) continue;
foreach (var pin in input.Value)
hsiPort.ShowStates(portId, pin, false, (msg) =>
{
_cfgRuntime.Logger.Log.Info($"{msg}");
});
}

Hsi88DeviceOnDataReceived(this, new DeviceInterfaceData(simdata.Trim()));
}
#endif

#endregion

// filter for S88 commands
// all other commands, queries, etc.
Expand Down
Binary file not shown.
Loading