Skip to content

Commit

Permalink
Merge pull request #12 from cbries/cbries/entprellen
Browse files Browse the repository at this point in the history
debouncing
  • Loading branch information
cbries authored Jul 30, 2024
2 parents de0be52 + 9051ffc commit 2da3dc0
Show file tree
Hide file tree
Showing 17 changed files with 791 additions and 55 deletions.
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,28 @@ PIN Data from HSI-88-USB

The runtime can be configured by json-based configuration file named `EsuEcosMiddleman.json`, it lays side-by-side to the executable within the build directory, e.g. `bin\Release\EsuEcosMiddleman.exe`.

- `hsi/devicePath` is reqired to address you attached `HSI-88-USB` device
- `hsi/devicePath` is required to address you attached `HSI-88-USB` device
- `ecos/ip` is the ip address of your ECoS

## Debouncing

It has been shown that the HSI-88-USB detects any change, as well very small triggers. Sometimes very often when trains crosses a single detection area. This can slow down any railway control software because of status/command flooding and when several logging mechanism are switched on (e.g. `log4net` with `INFO` or `DEBUG` level).

To reduce this a debouncing functionality can help. When any approach exist in the hardware directly, just it, because it will be much faster and reliable. In any other cases you can configure global debouncing walltime in `EsuEcosMiddleware.json`.

The values are milliseconds, only real pin state changes after this walltime are handled and provided to connected clients.

```json
{
...
"debounce": {
"onMs": 20,
"offMs": 2000
},
...
}
```

# Runtime / Demonstration

The following screenshots show the software in action for a real model railway.
Expand Down
6 changes: 6 additions & 0 deletions Source/EsuEcosMiddleman.sln
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
..\README.md = ..\README.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTestProject1", "UnitTestProject1\UnitTestProject1.csproj", "{E49F5280-7C46-4ABB-B6AE-EDECF454EF9A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -20,6 +22,10 @@ Global
{4D6C82FD-2EEC-40DD-962D-86E5C8CE98E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4D6C82FD-2EEC-40DD-962D-86E5C8CE98E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4D6C82FD-2EEC-40DD-962D-86E5C8CE98E8}.Release|Any CPU.Build.0 = Release|Any CPU
{E49F5280-7C46-4ABB-B6AE-EDECF454EF9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E49F5280-7C46-4ABB-B6AE-EDECF454EF9A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E49F5280-7C46-4ABB-B6AE-EDECF454EF9A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E49F5280-7C46-4ABB-B6AE-EDECF454EF9A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
28 changes: 20 additions & 8 deletions Source/EsuEcosMiddleman/CfgRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@ internal class CfgHsi88 : ICfgHsi88
public string DevicePath { get; set; } = @"\\.\HsiUsb1";
}

internal interface ICfgDebounce
{
uint CheckInterval { get; set; }
uint On { get; set; }
uint Off { get; set; }
}

public class CfgDebounce : ICfgDebounce
{
[JsonProperty("checkIntervalMs")] public uint CheckInterval { get; set; }
[JsonProperty("onMs")] public uint On { get; set; }
[JsonProperty("offMs")] public uint Off { get; set; }
}

public interface IRuntimeConfiguration
{
}
Expand All @@ -45,6 +59,7 @@ internal interface ICfgRuntime
CfgServer CfgServer { get; set; }
CfgTargetEcos CfgTargetEcos { get; set; }
ICfgHsi88 CfgHsi88 { get; set; }
ICfgDebounce CfgDebounce { get; set; }
IRuntimeConfiguration RuntimeConfiguration { get; set; }
ICfgFilter Filter { get; set; }
}
Expand All @@ -53,15 +68,12 @@ internal class CfgRuntime : ICfgRuntime
{
[JsonIgnore]
public ILogger Logger { get; set; }
[JsonProperty("server")]
public CfgServer CfgServer { get; set; } = new();
[JsonProperty("ecos")]
public CfgTargetEcos CfgTargetEcos { get; set; } = new();
[JsonProperty("hsi")]
public ICfgHsi88 CfgHsi88 { get; set; } = new CfgHsi88();
[JsonProperty("runtime")]
public IRuntimeConfiguration RuntimeConfiguration { get; set; } = new RuntimeConfiguration();

[JsonProperty("server")] public CfgServer CfgServer { get; set; } = new();
[JsonProperty("ecos")] public CfgTargetEcos CfgTargetEcos { get; set; } = new();
[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("filter")] public ICfgFilter Filter { get; set; } = new CfgFilter();
}

Expand Down
1 change: 1 addition & 0 deletions Source/EsuEcosMiddleman/EsuEcosMiddleman.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
<Compile Include="HSI88USB\NativeMethods.cs" />
<Compile Include="ILogger.cs" />
<Compile Include="Middleman.cs" />
<Compile Include="Middleman.HsiStateData.cs" />
<Compile Include="MiddlemanHandler.cs" />
<Compile Include="Network\CfgTargetEcos.cs" />
<Compile Include="Network\ConnectorFaster.cs" />
Expand Down
5 changes: 5 additions & 0 deletions Source/EsuEcosMiddleman/EsuEcosMiddleman.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
"right": 0,
"devicePath": "\\\\.\\HsiUsb1"
},
"debounce": {
"checkIntervalMs": 25,
"onMs": 25,
"offMs": 2000
},
"filter": {
"enabled": true,
"objectIdsInfo": [
Expand Down
79 changes: 47 additions & 32 deletions Source/EsuEcosMiddleman/HSI88USB/DeviceInterface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
using System.Threading;
using System.IO.Pipes;

namespace EsuEcosMiddleman.HSI88USB
{
Expand All @@ -28,6 +28,12 @@ public class DeviceInterfaceData(string data)
public DateTime Dt { get; set; } = DateTime.Now;
public string Data { get; set; } = data;

/// <summary>
/// When the information is based on "i00"-feedback
/// the value is `true`, otherwise `false`.
/// </summary>
public bool EventData => Data.Length > 0 && Data[0] == 'i';

/*
* Sample data:
* i01040506
Expand All @@ -38,17 +44,23 @@ public class DeviceInterfaceData(string data)
<Modulnummer> <HighByte> <LowByte>
<Modulnummer> <HighByte> <LowByte>
<CR>
-> i 01 02 2c05
*/
* Check sor "m00..." as well, e.g.
* m05019db60201c8036416043df6050000
* This line is the feedback of polling the current state.
* "i00" is an event information got when the device signals changes.
* These are two ways to get information from the S88-device.
*/

public int NumberOfModules
{
get
{
if (string.IsNullOrEmpty(Data)) return 0;
if (!Data.StartsWith("i")) return 0;
if (!Data.StartsWith("i") && !Data.StartsWith("m")) return 0;
var data = Data.TrimStart('i').Trim();
data = data.TrimStart('m').Trim();
var p0 = data.Substring(0, 2).Trim();
if (int.TryParse(p0, out var v))
return v;
Expand All @@ -67,7 +79,7 @@ public Dictionary<int, string> States
var res = new Dictionary<int, string>();
var noOfModules = NumberOfModules;
if (noOfModules == 0) return res;
var p = Data.Substring("i00".Length);
var p = Data.Substring("i00".Length); // as well "m00".Length
if (string.IsNullOrEmpty(p)) return res;
for (var idx = 0; idx < p.Length; idx += 6)
{
Expand Down Expand Up @@ -105,7 +117,7 @@ private void Open()
_handle = NativeMethods.CreateFile(
_cfgHsi88.DevicePath,
NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE,
0,
NativeMethods.FILE_SHARE_READ | NativeMethods.FILE_SHARE_WRITE,
IntPtr.Zero,
NativeMethods.OPEN_EXISTING,
0,
Expand Down Expand Up @@ -194,51 +206,54 @@ public async Task RunAsync()
}

// init terminal mode
Send("t\r");
Send("t1\r");
bytesRead = _fs.Read(buffer, 0, buffer.Length);
var d0 = new DeviceInterfaceData(Encoding.ASCII.GetString(buffer, 0, bytesRead));
DataReceived?.Invoke(this, d0);

// init number of shift registers
Send($"s{_cfgRuntime.CfgHsi88.NumberLeft:D2}{_cfgRuntime.CfgHsi88.NumberMiddle:D2}{_cfgRuntime.CfgHsi88.NumberRight:D2}\r");
bytesRead = _fs.Read(buffer, 0, buffer.Length);
var d1 = new DeviceInterfaceData(Encoding.ASCII.GetString(buffer, 0, bytesRead));
DataReceived?.Invoke(this, d1);

//// query current state
//Send("m\r");
//bytesRead = _fs.Read(buffer, 0, buffer.Length);
//var d2 = new DeviceInterfaceData(Encoding.ASCII.GetString(buffer, 0, bytesRead));
//DataReceived?.Invoke(this, d2);

// query device information/version
Send($"v\r");
bytesRead = _fs.Read(buffer, 0, buffer.Length);
var d2 = new DeviceInterfaceData(Encoding.ASCII.GetString(buffer, 0, bytesRead));
DataReceived?.Invoke(this, d2);

// init number of shift registers
Send($"s{_cfgRuntime.CfgHsi88.NumberLeft:D2}{_cfgRuntime.CfgHsi88.NumberMiddle:D2}{_cfgRuntime.CfgHsi88.NumberRight:D2}\r");
bytesRead = _fs.Read(buffer, 0, buffer.Length);
var d1 = new DeviceInterfaceData(Encoding.ASCII.GetString(buffer, 0, bytesRead));
DataReceived?.Invoke(this, d1);

var tkn = _cancellationToken.Token;

while (!tkn.IsCancellationRequested)
{
if (_fs.CanRead)
{
bytesRead = await _fs.ReadAsync(buffer, 0, buffer.Length, tkn);
Send("m\r");

if (bytesRead > 0)
bytesRead = _fs.Read(buffer, 0, buffer.Length);

if (bytesRead > 0)
{
var text = Encoding.UTF8.GetString(buffer, 0, bytesRead);
var lines = text.Split(new[] { '\r' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
var text = Encoding.UTF8.GetString(buffer, 0, bytesRead);
var lines = text.Split(new []{'\r'}, StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
if (string.IsNullOrEmpty(line)) continue;

DataReceived?.Invoke(this, new DeviceInterfaceData(line.Trim()));
}
if (string.IsNullOrEmpty(line)) continue;
var m = line.Trim();
var data = new DeviceInterfaceData(m);
if(!data.EventData)
DataReceived?.Invoke(this, data);

//Trace.WriteLine($"NumberOfModules: {data.NumberOfModules}");
//for (var i = 1; i <= data.NumberOfModules; ++i)
// Trace.Write($"{i} {data.States[i]} ");
//Trace.WriteLine(string.Empty);
//Trace.WriteLine(m);
}
}

Thread.Sleep(10);
var intervalMs = _cfgRuntime.CfgDebounce.CheckInterval;
if (intervalMs < 10) intervalMs = 50;
await Task.Delay(TimeSpan.FromMilliseconds(intervalMs));
}
});
}
Expand Down
3 changes: 3 additions & 0 deletions Source/EsuEcosMiddleman/HSI88USB/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ internal class NativeMethods
public const uint GENERIC_WRITE = 0x40000000;
public const uint OPEN_EXISTING = 3;

public const uint FILE_SHARE_WRITE = 2;
public const uint FILE_SHARE_READ = 1;

// Structures for SetupDi API
[StructLayout(LayoutKind.Sequential)]
public struct SP_DEVINFO_DATA
Expand Down
Loading

0 comments on commit 2da3dc0

Please sign in to comment.