From 15b6cbe418b8ebe5ad1b69051ad12de9cb335b0f Mon Sep 17 00:00:00 2001 From: Harald Wolff Date: Wed, 14 Apr 2021 10:02:35 +0200 Subject: [PATCH] WIP --- .../EthercatProcessDataRecorder.cs | 154 ++++++++++++++++++ ln.ethercat.service/EthercatService.cs | 13 +- ln.ethercat.service/Program.cs | 10 +- .../api/v1/EthercatApiController.cs | 6 + .../api/v1/RecorderWebSocket.cs | 64 ++++++++ .../ln.ethercat.service.csproj | 9 +- ln.ethercat/ECMaster.cs | 4 + ln.ethercat/ln.ethercat.csproj | 10 +- 8 files changed, 250 insertions(+), 20 deletions(-) create mode 100644 ln.ethercat.service/EthercatProcessDataRecorder.cs create mode 100644 ln.ethercat.service/api/v1/RecorderWebSocket.cs diff --git a/ln.ethercat.service/EthercatProcessDataRecorder.cs b/ln.ethercat.service/EthercatProcessDataRecorder.cs new file mode 100644 index 0000000..dccf1b7 --- /dev/null +++ b/ln.ethercat.service/EthercatProcessDataRecorder.cs @@ -0,0 +1,154 @@ + +using System; +using System.Collections.Generic; +using System.Threading; +using ln.http.api; + +namespace ln.ethercat.service +{ + public delegate void RecordingWindowCompleted(EthercatProcessDataRecorder dataRecorder, EthercatProcessDataRecorder.RecordingWindow recordingWindow); + public delegate void ProcessDataRecordingStartStopDelegate(EthercatProcessDataRecorder dataRecorder); + + public class EthercatProcessDataRecorder : IDisposable + { + public ECMaster ECMaster { get; } + public event RecordingWindowCompleted OnRecordingWindowCompleted; + public event ProcessDataRecordingStartStopDelegate OnStartRecording; + public event ProcessDataRecordingStartStopDelegate OnStopRecording; + + public int RecordingWindowSize { get; set; } = 256; + public int RecordingWindowCount { get; set; } = 10; + + + PDO[] pdoMap; + public PDO[] CurrentPDOMap => pdoMap; + RecordingWindow[] recordingWindows; + int currentWindow; + + public EthercatProcessDataRecorder(ECMaster ecMaster) + { + ECMaster = ecMaster; + } + + [ESMethod] + public void Start() + { + lock (this) + { + pdoMap = ECMaster.GetPDOMap(); + SetupRamStorage(); + } + + OnStartRecording?.Invoke(this); + + ECMaster.OnStateChange += MasterStateChanged; + ECMaster.OnProcessDataExchanged += ProcessDataExchanged; + } + + [ESMethod] + public void Stop() + { + lock (this) + { + if (pdoMap != null) + { + ECMaster.OnProcessDataExchanged -= ProcessDataExchanged; + ECMaster.OnStateChange -= MasterStateChanged; + + OnStopRecording?.Invoke(this); + + pdoMap = null; + recordingWindows = null; + } + } + } + + void SetupRamStorage() + { + currentWindow = 0; + recordingWindows = new RecordingWindow[RecordingWindowCount]; + for (int window=0; window < RecordingWindowCount; window++) + recordingWindows[window] = new RecordingWindow(RecordingWindowSize, pdoMap.Length); + } + + + void ProcessDataExchanged(ECMaster ecMaster) + { + lock (this) + { + if (pdoMap != null) + { + RecordingWindow recordingWindow = recordingWindows[currentWindow]; + if (recordingWindow.GetNextRecordBuffer(out object[] currentRecord)) + { + for (int n = 0; n < pdoMap.Length; n++) + currentRecord[n] = pdoMap[n].SDOValue.GetValue(); + } + + if (recordingWindow.Completed) + { + currentWindow++; + if (currentWindow >= recordingWindows.Length) + currentWindow = 0; + + ThreadPool.QueueUserWorkItem((s)=>{ + OnRecordingWindowCompleted?.Invoke(this, recordingWindow); + }); + } + } + } + } + + void MasterStateChanged(ECMaster ecMaster, ECSlaveState newState) + { + if (newState != ECSlaveState.OPERATIONAL) + Stop(); + } + + public void Dispose() + { + Stop(); + } + + public class RecordingWindow + { + object[][] recordedData; + int nextRecord; + + public bool Completed => nextRecord >= recordedData.Length; + + public RecordingWindow(int windowSize,int pdomapSize) + { + recordedData = new object[windowSize][]; + for (int pos=0 ; pos < windowSize ; pos++) + { + recordedData[pos] = new object[pdomapSize]; + } + + Reset(); + } + + public void Reset() + { + nextRecord = 0; + } + + public object[][] GetRecords() => recordedData; + public object[] GetRecords(int record) => recordedData[record]; + + public bool GetNextRecordBuffer(out object[] recordBuffer) + { + if (nextRecord < recordedData.Length) + { + recordBuffer = recordedData[nextRecord++]; + return true; + } + recordBuffer = null; + return false; + } + + } + + } + +} \ No newline at end of file diff --git a/ln.ethercat.service/EthercatService.cs b/ln.ethercat.service/EthercatService.cs index 2c87820..aee3825 100644 --- a/ln.ethercat.service/EthercatService.cs +++ b/ln.ethercat.service/EthercatService.cs @@ -34,11 +34,18 @@ namespace ln.ethercat.service System.Timers.Timer timerWebsockets; - - public EthercatService(string interfaceName) - { + EthercatProcessDataRecorder processDataRecorder; + public EthercatProcessDataRecorder ProcessDataRecorder { + get { + if (processDataRecorder == null) + processDataRecorder = new EthercatProcessDataRecorder(ECMaster); + return processDataRecorder; + } } + + public EthercatService(){} + public void Initialize() { if (EthercatInterfaceName == null) diff --git a/ln.ethercat.service/Program.cs b/ln.ethercat.service/Program.cs index 815d285..9146402 100644 --- a/ln.ethercat.service/Program.cs +++ b/ln.ethercat.service/Program.cs @@ -1,13 +1,7 @@ -using System; -using System.Data; -using System.Text; -using System.Threading; +using System.Text; using ln.application; -using ln.ethercat.controller; -using ln.ethercat.controller.drives; using ln.ethercat.controller.remote; using ln.logging; -using ln.type; namespace ln.ethercat.service { @@ -25,7 +19,7 @@ namespace ln.ethercat.service Logging.Log(LogLevel.INFO, "ECMBind version: {0}", versionString.ToString()); - EthercatService ethercatService = new EthercatService(args[0]); + EthercatService ethercatService = new EthercatService(); ArgumentContainer argumentContainer = new ArgumentContainer(); argumentContainer.AddStaticOptions(); diff --git a/ln.ethercat.service/api/v1/EthercatApiController.cs b/ln.ethercat.service/api/v1/EthercatApiController.cs index 37b4226..1bd0d44 100644 --- a/ln.ethercat.service/api/v1/EthercatApiController.cs +++ b/ln.ethercat.service/api/v1/EthercatApiController.cs @@ -88,6 +88,12 @@ namespace ln.ethercat.service.api.v1 [GET("/socket")] HttpResponse OpenWebSocket(HttpRequest httpRequest) => new EthercatWebSocket(EthercatService); + + [GET("")] + HttpResponse OpenRecorderSocket(HttpRequest httpRequest) => new RecorderWebSocket(EthercatService.ProcessDataRecorder); + + + } } \ No newline at end of file diff --git a/ln.ethercat.service/api/v1/RecorderWebSocket.cs b/ln.ethercat.service/api/v1/RecorderWebSocket.cs new file mode 100644 index 0000000..4bd1ede --- /dev/null +++ b/ln.ethercat.service/api/v1/RecorderWebSocket.cs @@ -0,0 +1,64 @@ +using System.Net.Http; +using ln.http.api; +using ln.http.websocket; +using ln.json; +using ln.json.mapping; + +namespace ln.ethercat.service.api.v1 +{ + public class RecorderWebSocket : JSONEventWebSocketResponse + { + EthercatProcessDataRecorder DataRecorder; + + public RecorderWebSocket(EthercatProcessDataRecorder dataRecorder) + { + OnWebSocketStateChanged += (s,state)=>{ + switch (state) + { + case WebSocketState.OPEN: + dataRecorder.OnRecordingWindowCompleted += RecordingWindowCompleted; + dataRecorder.OnStartRecording += RecordingStarted; + dataRecorder.OnStopRecording += RecordingStopped; + break; + case WebSocketState.CLOSED: + dataRecorder.OnStartRecording -= RecordingStarted; + dataRecorder.OnStopRecording -= RecordingStopped; + dataRecorder.OnRecordingWindowCompleted -= RecordingWindowCompleted; + break; + } + }; + } + + + void RecordingStarted(EthercatProcessDataRecorder recorder) + { + JSONObject jsonMessage = new JSONObject(); + jsonMessage.Add("event","start"); + if (JSONMapper.DefaultMapper.Serialize(recorder.CurrentPDOMap, out JSONValue jsonValue)) + jsonMessage.Add("value", jsonValue); + + Send(jsonMessage); + } + + void RecordingStopped(EthercatProcessDataRecorder recorder) + { + JSONObject jsonMessage = new JSONObject(); + jsonMessage.Add("event","stop"); + jsonMessage.Add("value", JSONNull.Instance); + + Send(jsonMessage); + } + + void RecordingWindowCompleted(EthercatProcessDataRecorder recorder, EthercatProcessDataRecorder.RecordingWindow recordingWindow) + { + JSONObject jsonMessage = new JSONObject(); + jsonMessage.Add("event","window"); + if (JSONMapper.DefaultMapper.Serialize(recordingWindow.GetRecords(), out JSONValue jsonRecords)) + jsonMessage.Add("value", jsonRecords); + + Send(jsonMessage); + } + + + } +} \ No newline at end of file diff --git a/ln.ethercat.service/ln.ethercat.service.csproj b/ln.ethercat.service/ln.ethercat.service.csproj index 1a0686f..2a5b545 100644 --- a/ln.ethercat.service/ln.ethercat.service.csproj +++ b/ln.ethercat.service/ln.ethercat.service.csproj @@ -2,17 +2,18 @@ Exe + true netcoreapp3.1 - 0.1.1 + 0.1.2 - + - - + + diff --git a/ln.ethercat/ECMaster.cs b/ln.ethercat/ECMaster.cs index f0711bf..abb4344 100644 --- a/ln.ethercat/ECMaster.cs +++ b/ln.ethercat/ECMaster.cs @@ -39,6 +39,7 @@ namespace ln.ethercat } public delegate void ECStateChange(ECMaster sender,ECSlaveState newState); + public delegate void ProcessDataExchanged(ECMaster ecMaster); public class ECMaster { @@ -50,6 +51,7 @@ namespace ln.ethercat public event ECStateChange OnStateChange; + public event ProcessDataExchanged OnProcessDataExchanged; public string InterfaceName { get; } @@ -270,6 +272,8 @@ namespace ln.ethercat ScheduleRestart(); stopProcessData = true; } + } else { + OnProcessDataExchanged?.Invoke(this); } } Thread.Sleep(INTERVALL_PROCESSDATA); diff --git a/ln.ethercat/ln.ethercat.csproj b/ln.ethercat/ln.ethercat.csproj index b971037..b4145c5 100644 --- a/ln.ethercat/ln.ethercat.csproj +++ b/ln.ethercat/ln.ethercat.csproj @@ -3,7 +3,7 @@ netcoreapp3.1 true - 0.1.0-ci + 0.1.2 Harald Wolff-Thobaben l--n.de A simple ethercat master based on SOEM (https://openethercatsociety.github.io/) @@ -17,12 +17,12 @@ - + - - - + + +