From 2522d1da9fa11bc7dd1e61b07ffb146d18be2ed6 Mon Sep 17 00:00:00 2001 From: Michal Buzik Date: Wed, 18 Mar 2026 15:57:04 +0100 Subject: [PATCH] Add Genesis radio/opto communication and configuration components - Replace `OpthoHeadService` with `RadioService` to enhance Genesis communication handling via serial interface. - Introduce `OptoReceivedEventArgs` to manage event-driven communication. - Add foundational protocol implementations (`BaseProtocol`, `IProtocol`, etc.) for Genesis configuration and processing. - Implement `SirtConfig`, `MeterConfig`, and `ProcessConfig` classes for flexible Genesis configuration management. - Expand Genesis framework to support diagnostics, activity modes, and version retrieval. --- TBF/Properties/AssemblyInfo.cs | 4 +- .../GenesisRegReader/Factory.cs | 24 + .../GenesisRegReader/GenesisCfg.cs | 70 + .../GenesisRegReader/GenesisCfgCtrl.cs | 165 ++ .../GenesisCfgCtrl.designer.cs | 441 ++++ .../GenesisRegReader/GenesisCfgCtrl.resx | 120 ++ .../GenesisHeadTestCtrl.Designer.cs | 137 ++ .../GenesisRegReader/GenesisHeadTestCtrl.cs | 90 + .../GenesisRegReader/GenesisHeadTestCtrl.resx | 120 ++ .../GenesisRegReader/common/HexFormatter.cs | 175 ++ .../common/OptoTelegramRaw.cs | 353 ++++ .../communication/Genesis/Crc16Ccitt.cs | 227 ++ .../EventArguments/BaseDataEventArgs.cs | 33 + .../EventArguments/BendDetectDataEventArgs.cs | 20 + .../EventArguments/CalibDataEventArgs.cs | 20 + .../EventArguments/FlowDataEventArgs.cs | 20 + .../EventArguments/RegisterUpdateEventArgs.cs | 31 + .../RequestResponseDataEventArgs.cs | 23 + .../MeasurementRecords/BendDetectionRecord.cs | 114 + .../MeasurementRecords/CalibrationRecord.cs | 151 ++ .../MeasurementRecords/FlowTestRecord.cs | 32 + .../DataPackages/RadioConfigurationParams.cs | 49 + .../Genesis/DataPackages/RegisterToDb.cs | 29 + .../Genesis/DataPackages/TempTofCalc.cs | 27 + .../GenesisConfig/CommunicationConfig.cs | 88 + .../GenesisConfig/Const/MeterConfig.cs | 24 + .../Genesis/GenesisConfig/MeterConfig.cs | 86 + .../Genesis/GenesisConfig/ProccessConfig.cs | 114 + .../Genesis/GenesisConfig/ProcessConfig.cs | 111 + .../Genesis/GenesisConfig/SirtConfig.cs | 96 + .../EventArguments/BasePortDataEventArgs.cs | 46 + .../EventArguments/IPortDataEventArgs.cs | 48 + .../ListBytePortDataEventArgs.cs | 33 + .../EventArguments/StringPortDataEventArgs.cs | 34 + .../Interfaces/Ports/PortCore/IPort.cs | 63 + .../Interfaces/Ports/PortCore/PortConfig.cs | 63 + .../Interfaces/Ports/PortCore/SlotConfig.cs | 30 + .../Interfaces/Ports/PortCore/SlotType.cs | 17 + .../Ports/PortCore/TransmitPortSettings.cs | 109 + .../Ports/SerialPorts/BaseSerialPort.cs | 702 +++++++ .../Ports/SerialPorts/IrdaSerialPort.cs | 40 + .../Ports/SerialPorts/LedSerialPort.cs | 28 + .../Ports/SerialPorts/RfidSerialPort.cs | 600 ++++++ .../Ports/SerialPorts/UartSerialPort.cs | 17 + .../Protocols/ProtocolCore/BaseProtocol.cs | 156 ++ .../EventArguments/BaseDataEventArgs.cs | 33 + .../Protocols/ProtocolCore/IProtocol.cs | 43 + .../TransmitProtocol/BaseTransmitProtocol.cs | 31 + .../TransmitProtocol/ITransmitProtocol.cs | 38 + .../TransmitProtocol/IrdaTransmitProtocol.cs | 213 ++ .../TransmitProtocol/LedTransmitProtocol.cs | 60 + .../TransmitProtocol/RfidTransmitProtocol.cs | 52 + .../TransmitProtocol/UartTransmitProtocol.cs | 168 ++ .../communication/Genesis/ProgramConfig.cs | 53 + .../RequestProtocol/Consts/Commands.cs | 125 ++ .../RequestProtocol/Consts/ConfigExErrors.cs | 31 + .../RequestProtocol/Consts/HighLevelErrors.cs | 25 + .../RequestProtocol/Consts/LowLevelErrors.cs | 47 + .../Consts/RequestAcknowledgeState.cs | 48 + .../AuthorizationRequiredEventArgs.cs | 25 + .../Exceptions/RequestProtocolException.cs | 59 + .../RequestProtocol/RequestProtocol.cs | 767 +++++++ .../RequestProtocol/RequestRecord.cs | 114 + .../StreamingProtocol/StreamingDecoder.cs | 547 +++++ .../StreamingProtocol/StreamingProtocol.cs | 107 + .../communication/Genesis/Registers/Access.cs | 29 + .../Genesis/Registers/DataTypes/ByteArray.cs | 6 + .../Genesis/Registers/DataTypes/Enum8.cs | 8 + .../Genesis/Registers/DataTypes/Rpc.cs | 7 + .../Genesis/Registers/DataTypes/StaticType.cs | 67 + .../Genesis/Registers/DataTypes/StatusT.cs | 7 + .../Genesis/Registers/DataTypes/TimeT.cs | 117 ++ .../Genesis/Registers/DataTypes/UInt672.cs | 8 + .../Registers/DataTypes/st_radio_dewa.cs | 8 + .../Registers/DataTypes/st_radio_tfx.cs | 8 + .../Genesis/Registers/Json/AppSection.cs | 18 + .../Genesis/Registers/Json/AppsDictionary.cs | 8 + .../Genesis/Registers/Json/Build.cs | 11 + .../Genesis/Registers/Json/Details.cs | 19 + .../Genesis/Registers/Json/FwVersion.cs | 13 + .../Registers/Json/JsonVersionsConverter.cs | 32 + .../Genesis/Registers/Json/Privilege.cs | 48 + .../Genesis/Registers/Json/Register.cs | 12 + .../Genesis/Registers/Json/Status.cs | 13 + .../Genesis/Registers/Json/Value.cs | 13 + .../Genesis/Registers/MeterRegisters.cs | 135 ++ .../Properties/Resources.Designer.cs | 90 + .../Properties/Resources.de.Designer.cs | 0 .../Registers/Properties/Resources.de.resx | 110 + .../Registers/Properties/Resources.resx | 129 ++ .../Genesis/Registers/RecoveryRegisterItem.cs | 57 + .../Genesis/Registers/RecoverySettings.cs | 59 + .../Genesis/Registers/RegisterCheck.cs | 284 +++ .../Genesis/Registers/RegisterConverter.cs | 488 +++++ .../Genesis/Registers/RegisterDefinition.cs | 118 ++ .../Registers/RegisterRecoveryAccess.cs | 22 + .../Genesis/Registers/Registers.cs | 219 ++ .../communication/Genesis/StatusReturn.cs | 48 + .../communication/Genesis/ThreadWatcher.cs | 62 + .../communication/OptoHeadTest.cs | 370 ++++ .../communication/RadioService.cs | 319 +++ .../communication/Utils/ISerialDriver.cs | 9 + .../communication/Utils/SerialDriver.cs | 308 +++ .../Utils/SerialDriverBuilder.cs | 82 + .../implementations/CalibrationStruct.cs | 243 +++ .../implementations/CalibrationStructV4.cs | 259 +++ .../implementations/ConfigStruct.cs | 206 ++ .../GenesisRegReader/implementations/Enums.cs | 120 ++ .../implementations/FlowDirectionDetection.cs | 145 ++ .../GenesisImplHeadTestCtrl.cs | 197 ++ .../implementations/GenesisSmartReader.cs | 1842 +++++++++++++++++ .../implementations/OptoReceivedEventArgs.cs | 18 + .../implementations/ProcParams.cs | 201 ++ .../IPerlReader/IperlUniHeadTestCtrl.cs | 4 +- .../implementations/IPerlImplHeadTestCtrl.cs | 2 +- .../PoseidonReader/UniHeadTestCtrl.cs | 4 +- .../PoseidonImplHeadTestCtrl.cs | 3 +- .../IperlASICUniHeadTestCtrl.cs | 4 +- .../IPerlASICImplHeadTestCtrl.cs | 2 +- .../iPerlReaderUNI/IperlUniHeadTestCtrl.cs | 4 +- .../iPerlReaderUNI/common/IUniHeadTestCtrl.cs | 18 +- .../communication/OpthoHeadService.cs | 10 - TBF/TBF.csproj | 121 +- .../GenesisHeadBatchIntegrationTest.cs | 49 + TBFTests/TBFTests.csproj | 1 + 125 files changed, 14350 insertions(+), 30 deletions(-) create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/Factory.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/GenesisCfg.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/GenesisCfgCtrl.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/GenesisCfgCtrl.designer.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/GenesisCfgCtrl.resx create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/GenesisHeadTestCtrl.Designer.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/GenesisHeadTestCtrl.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/GenesisHeadTestCtrl.resx create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/common/HexFormatter.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/common/OptoTelegramRaw.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Crc16Ccitt.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/EventArguments/BaseDataEventArgs.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/EventArguments/BendDetectDataEventArgs.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/EventArguments/CalibDataEventArgs.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/EventArguments/FlowDataEventArgs.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/EventArguments/RegisterUpdateEventArgs.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/EventArguments/RequestResponseDataEventArgs.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/MeasurementRecords/BendDetectionRecord.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/MeasurementRecords/CalibrationRecord.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/MeasurementRecords/FlowTestRecord.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/RadioConfigurationParams.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/RegisterToDb.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/TempTofCalc.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/GenesisConfig/CommunicationConfig.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/GenesisConfig/Const/MeterConfig.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/GenesisConfig/MeterConfig.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/GenesisConfig/ProccessConfig.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/GenesisConfig/ProcessConfig.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/GenesisConfig/SirtConfig.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/EventArguments/BasePortDataEventArgs.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/EventArguments/IPortDataEventArgs.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/EventArguments/ListBytePortDataEventArgs.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/EventArguments/StringPortDataEventArgs.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/IPort.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/PortConfig.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/SlotConfig.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/SlotType.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/TransmitPortSettings.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/SerialPorts/BaseSerialPort.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/SerialPorts/IrdaSerialPort.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/SerialPorts/LedSerialPort.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/SerialPorts/RfidSerialPort.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/SerialPorts/UartSerialPort.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/ProtocolCore/BaseProtocol.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/ProtocolCore/EventArguments/BaseDataEventArgs.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/ProtocolCore/IProtocol.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/TransmitProtocol/BaseTransmitProtocol.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/TransmitProtocol/ITransmitProtocol.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/TransmitProtocol/IrdaTransmitProtocol.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/TransmitProtocol/LedTransmitProtocol.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/TransmitProtocol/RfidTransmitProtocol.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/TransmitProtocol/UartTransmitProtocol.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/ProgramConfig.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/Consts/Commands.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/Consts/ConfigExErrors.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/Consts/HighLevelErrors.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/Consts/LowLevelErrors.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/Consts/RequestAcknowledgeState.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/EventArguments/AuthorizationRequiredEventArgs.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/Exceptions/RequestProtocolException.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/RequestProtocol.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/RequestRecord.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/StreamingProtocol/StreamingDecoder.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/StreamingProtocol/StreamingProtocol.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Access.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/ByteArray.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/Enum8.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/Rpc.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/StaticType.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/StatusT.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/TimeT.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/UInt672.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/st_radio_dewa.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/st_radio_tfx.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/AppSection.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/AppsDictionary.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/Build.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/Details.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/FwVersion.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/JsonVersionsConverter.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/Privilege.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/Register.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/Status.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/Value.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/MeterRegisters.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Properties/Resources.Designer.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Properties/Resources.de.Designer.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Properties/Resources.de.resx create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Properties/Resources.resx create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/RecoveryRegisterItem.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/RecoverySettings.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/RegisterCheck.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/RegisterConverter.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/RegisterDefinition.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/RegisterRecoveryAccess.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Registers.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/StatusReturn.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/ThreadWatcher.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/OptoHeadTest.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/RadioService.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Utils/ISerialDriver.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Utils/SerialDriver.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/communication/Utils/SerialDriverBuilder.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/implementations/CalibrationStruct.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/implementations/CalibrationStructV4.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/implementations/ConfigStruct.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/implementations/Enums.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/implementations/FlowDirectionDetection.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisImplHeadTestCtrl.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/implementations/OptoReceivedEventArgs.cs create mode 100644 TBF/Rig/RegisterReaders/GenesisRegReader/implementations/ProcParams.cs delete mode 100644 TBF/Rig/TestMethods/iPerlCommunication/communication/OpthoHeadService.cs create mode 100644 TBFTests/Rig/TestMethods/GenesisCommunication/GenesisHead/GenesisHeadBatchIntegrationTest.cs diff --git a/TBF/Properties/AssemblyInfo.cs b/TBF/Properties/AssemblyInfo.cs index 7c2a535fb..f0fdd6294 100644 --- a/TBF/Properties/AssemblyInfo.cs +++ b/TBF/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ using System.Runtime.InteropServices; // Build Number // Revision // -[assembly: AssemblyVersion("3.9.3001.1")] -[assembly: AssemblyFileVersion("3.9.3001.1")] +[assembly: AssemblyVersion("3.9.3004.1")] +[assembly: AssemblyFileVersion("3.9.3004.1")] diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/Factory.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/Factory.cs new file mode 100644 index 000000000..1de2eaa95 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/Factory.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using TBF.Rig.Generic; +using TBF.Rig.RegisterReaders.iPerlASICReader.implementations; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader +{ + public class Factory: IComponentFactory + { + public string ClassName { get { return this.GetType().Namespace.Substring(8); } } + + public override string ToString() { return ClassName; } + + public IComponent DummyComponent() { return new SmartReader(); } + + public IComponent GetComponent(IComponentCfg cfg, IList components) { return new SmartReader(cfg); } + + public IComponentCfg DefaultConfig() { return new GenesisCfg(this); } + + public IComponentCfg CmpntCfgFromCmpntEntity(Config.Entities.Component component) + { + return ComponentCfgBase.CreateFromDbEntity(GenesisCfg.Serializer, component, this); + } + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/GenesisCfg.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/GenesisCfg.cs new file mode 100644 index 000000000..6e51bdf1b --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/GenesisCfg.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using System.Xml.Serialization; +using Common; +using Config.Entities; +using TBF.Rig.Generic; +using TBF.Rig.RegisterReaders.GenesisRegReader.implementations; + + +namespace TBF.Rig.RegisterReaders.GenesisRegReader +{ + public class GenesisCfg : ComponentCfgBase, Generic.IComponentCfg + { + public static XmlSerializer Serializer = XmlSerializer.FromTypes(new[] { typeof(GenesisCfg) })[0]; + public override XmlSerializer GetSerializer() { return Serializer; } + public IComponentCfgCtrl GetControl(IList cmpntEntities) + { + return new GenesisCfgCtrl(); + } + + + + /// + /// Serialized parameters + /// + public bool UseTcpIP; + public string OptoIPAddress; + public ushort OptoTcpipPortNr; + public int HeadCommunicationComPortNr; + public int OptoComPortNr; + public int RfidComPortNr; /// 0 = use MuxBoardNr + public int MuxBoardNr; /// 0 = use RfidComPort(Nr), otherwise mux. board nr. 1 .. 4 + public int Group; /// Number written to QuidoRS to connct the watermeter to RfidComPort, 1 .. 10 + public CommunicationInterface CommunicationInterface; /// Communication Interface: RFID or NFC + + /// Procedure parameters + [XmlIgnore] + public ProcParams ProcParams; + public override IParamsProvider GetRuntimeProcParamsProvider() { return ProcParams; } + public override IParamsProvider CreateProcParamsProvider() { return new ProcParams(true); } + + [XmlIgnore] + public MeterType MeterType { get { return (ProcParams != null) ? ProcParams.MeterType : MeterType.AutoDetect; } } + + + + /// Private parameterless constructor invoked by all other (public) constructors + GenesisCfg() + { + Name = "iPerl"; + ParentName = string.Empty; + OptoComPortNr = 10; + RfidComPortNr = 0; /// = use mux. board + MuxBoardNr = 1; + ProcParams = CreateProcParamsProvider() as ProcParams; + CommunicationInterface = CommunicationInterface.RFID; + HeadCommunicationComPortNr = 0; + } + + public GenesisCfg(IComponentFactory factory) + : this() + { + this.Factory = factory; + } + + public string ToString(int i) + { + return $"{Name} Group1 (mux#)={MuxBoardNr}, Group2={Group}, Opto=Com{OptoComPortNr}, {CommunicationInterface}=Com{RfidComPortNr}"; + } + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/GenesisCfgCtrl.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/GenesisCfgCtrl.cs new file mode 100644 index 000000000..3eea91d71 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/GenesisCfgCtrl.cs @@ -0,0 +1,165 @@ +/// +/// Copyright (c) 2015-2017 Sensus Metering Systems +/// + +using System; +using System.Net; +using System.Windows.Forms; +using Common; +using TBF.Resources; +using TBF.Rig.Generic; +using TBF.Rig.RegisterReaders.GenesisRegReader.implementations; + + +namespace TBF.Rig.RegisterReaders.GenesisRegReader +{ + public partial class GenesisCfgCtrl : UserControl, IComponentCfgCtrl + { + public bool ShowMore { get { return false; } } + + GenesisCfg config; + public IComponentCfg Config + { + get { return config as IComponentCfg; } + set + { + config = value as GenesisCfg; + Redraw(); + } + } + + public GenesisCfgCtrl() + { + InitializeComponent(); + } + + private void WaterMeterCfgCtrl_Load(object sender, EventArgs e) + { + nameLabel.Text = Strings.Name; + classNameLabel.Text = config.Factory.ClassName; + Redraw(); + } + + public void Closing() + { + } + + void Redraw() + { + if (config == null) return; /// Control was not loaded, settings were not changed + nameTextBox.Text = config.Name; + radioButton1.Checked = config.UseTcpIP; + radioButton2.Checked = !config.UseTcpIP; + ipAddressTextBox.Text = (config.OptoIPAddress != null) ? config.OptoIPAddress : "0.0.0.0"; + tcpipPortTextBox.Text = config.OptoTcpipPortNr.ToString(); + headPortNrTextBox.Text = config.HeadCommunicationComPortNr.ToString(); + optoSerialPortTextBox.Text = config.OptoComPortNr.ToString(); + rfidPortNrTextBox.Text = config.RfidComPortNr.ToString(); + muxBoardNrTextBox.Text = config.MuxBoardNr.ToString(); + groupTextBox.Text = config.Group.ToString(); + comboBoxCommunicationInterface.SelectedItem = config.CommunicationInterface.ToString(); + tabPage2.Controls.Add(new GenesisHeadTestCtrl(config)); + } + + public void Unlock() + { + nameTextBox.Enabled = true; + radioButton1.Enabled = true; + radioButton2.Enabled = true; + ipAddressTextBox.Enabled = true; + tcpipPortTextBox.Enabled = true; + optoSerialPortTextBox.Enabled = true; + rfidPortNrTextBox.Enabled = true; + headPortNrTextBox.Enabled = true; + muxBoardNrTextBox.Enabled = true; + groupTextBox.Enabled = true; + comboBoxCommunicationInterface.Enabled = true; + } + + public CfgUpdateFlags VerifyCfg(ref string message) + { + CfgUpdateFlags flags = CfgUpdateFlags.None; + + int dummy; + if (radioButton1.Checked) + { + IPAddress dummyIPAddress; + if (!IPAddress.TryParse(ipAddressTextBox.Text, out dummyIPAddress)) + { + flags |= CfgUpdateFlags.Error; + message += Environment.NewLine + "'IP address' is not valid"; + } + + ushort sdummy; + if (!ushort.TryParse(tcpipPortTextBox.Text, out sdummy)) + { + flags |= CfgUpdateFlags.Error; + message += Environment.NewLine + "'TCP/IP port nr.' is not valid"; + } + } + else + { + if (!int.TryParse(optoSerialPortTextBox.Text, out dummy) || dummy < 1 || dummy > 999) + { + flags |= CfgUpdateFlags.Error; + message += Environment.NewLine + "'Opto serial port nr.' is not valid"; + } + } + + if (!int.TryParse(rfidPortNrTextBox.Text, out dummy) || dummy < 0 || dummy > 999) + { + flags |= CfgUpdateFlags.Error; + message += Environment.NewLine + "'RFID serial port nr.' is not valid"; + } + + if (!int.TryParse(headPortNrTextBox.Text, out dummy) || dummy < 0 || dummy > 999) + { + flags |= CfgUpdateFlags.Error; + message += Environment.NewLine + "'Head communication serial port nr.' is not valid"; + } + + if (!int.TryParse(muxBoardNrTextBox.Text, out dummy) || dummy < 1 || dummy > 4) + { + flags |= CfgUpdateFlags.Error; + message += Environment.NewLine + string.Format(Strings.Invalid_0, muxBoardNrLabel.Text); + } + + if (!int.TryParse(groupTextBox.Text, out dummy) || dummy < 1 || dummy > 10) + { + flags |= CfgUpdateFlags.Error; + message += Environment.NewLine + string.Format(Strings.Invalid_0, groupLabel.Text); + } + + return flags; + } + + public CfgUpdateFlags UpdateCfg() + { + CfgUpdateFlags flags = CfgUpdateFlags.RestartRqrd; + + if (config == null) return CfgUpdateFlags.Error; /// Control was not loaded, settings were not changed + + config.Name = nameTextBox.Text; + + if (radioButton1.Checked) + { + config.UseTcpIP = true; + config.OptoIPAddress = ipAddressTextBox.Text; + config.OptoTcpipPortNr = ushort.Parse(tcpipPortTextBox.Text); + } + else + { + config.UseTcpIP = false; + config.OptoComPortNr = int.Parse(optoSerialPortTextBox.Text); + } + + config.RfidComPortNr = int.Parse(rfidPortNrTextBox.Text); + config.MuxBoardNr = int.Parse(muxBoardNrTextBox.Text); + config.Group = int.Parse(groupTextBox.Text); + config.CommunicationInterface = (CommunicationInterface)comboBoxCommunicationInterface.SelectedIndex; + config.HeadCommunicationComPortNr = int.Parse(headPortNrTextBox.Text); + + return flags; + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/GenesisCfgCtrl.designer.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/GenesisCfgCtrl.designer.cs new file mode 100644 index 000000000..3d454e096 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/GenesisCfgCtrl.designer.cs @@ -0,0 +1,441 @@ +/// +/// Copyright (c) 2015-2017 Sensus Metering Systems +/// +namespace TBF.Rig.RegisterReaders.GenesisRegReader +{ + partial class GenesisCfgCtrl + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.tabControl1 = new System.Windows.Forms.TabControl(); + this.tabPage1 = new System.Windows.Forms.TabPage(); + this.label4 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.comboBoxCommunicationInterface = new System.Windows.Forms.ComboBox(); + this.label1 = new System.Windows.Forms.Label(); + this.rfidPortNrTextBox = new System.Windows.Forms.TextBox(); + this.rfidSerialPortNrLabel = new System.Windows.Forms.Label(); + this.optoDataGroupBox = new System.Windows.Forms.GroupBox(); + this.tcpipPortLabel = new System.Windows.Forms.Label(); + this.tcpipPortTextBox = new System.Windows.Forms.TextBox(); + this.ipAddressLabel = new System.Windows.Forms.Label(); + this.ipAddressTextBox = new System.Windows.Forms.TextBox(); + this.radioButton1 = new System.Windows.Forms.RadioButton(); + this.radioButton2 = new System.Windows.Forms.RadioButton(); + this.optoSerialPortLabel = new System.Windows.Forms.Label(); + this.optoSerialPortTextBox = new System.Windows.Forms.TextBox(); + this.groupTextBox = new System.Windows.Forms.TextBox(); + this.groupLabel = new System.Windows.Forms.Label(); + this.muxBoardNrTextBox = new System.Windows.Forms.TextBox(); + this.muxBoardNrLabel = new System.Windows.Forms.Label(); + this.nameTextBox = new System.Windows.Forms.TextBox(); + this.nameLabel = new System.Windows.Forms.Label(); + this.classNameLabel = new System.Windows.Forms.Label(); + this.tabPage2 = new System.Windows.Forms.TabPage(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.label2 = new System.Windows.Forms.Label(); + this.headPortNrTextBox = new System.Windows.Forms.TextBox(); + this.tabControl1.SuspendLayout(); + this.tabPage1.SuspendLayout(); + this.groupBox1.SuspendLayout(); + this.optoDataGroupBox.SuspendLayout(); + this.groupBox2.SuspendLayout(); + this.SuspendLayout(); + // + // tabControl1 + // + this.tabControl1.Controls.Add(this.tabPage1); + this.tabControl1.Controls.Add(this.tabPage2); + this.tabControl1.Location = new System.Drawing.Point(3, 3); + this.tabControl1.Name = "tabControl1"; + this.tabControl1.SelectedIndex = 0; + this.tabControl1.Size = new System.Drawing.Size(611, 432); + this.tabControl1.TabIndex = 0; + // + // tabPage1 + // + this.tabPage1.Controls.Add(this.groupBox2); + this.tabPage1.Controls.Add(this.label4); + this.tabPage1.Controls.Add(this.label3); + this.tabPage1.Controls.Add(this.groupBox1); + this.tabPage1.Controls.Add(this.optoDataGroupBox); + this.tabPage1.Controls.Add(this.groupTextBox); + this.tabPage1.Controls.Add(this.groupLabel); + this.tabPage1.Controls.Add(this.muxBoardNrTextBox); + this.tabPage1.Controls.Add(this.muxBoardNrLabel); + this.tabPage1.Controls.Add(this.nameTextBox); + this.tabPage1.Controls.Add(this.nameLabel); + this.tabPage1.Controls.Add(this.classNameLabel); + this.tabPage1.Location = new System.Drawing.Point(4, 25); + this.tabPage1.Name = "tabPage1"; + this.tabPage1.Padding = new System.Windows.Forms.Padding(3); + this.tabPage1.Size = new System.Drawing.Size(603, 403); + this.tabPage1.TabIndex = 0; + this.tabPage1.Text = "Config"; + this.tabPage1.UseVisualStyleBackColor = true; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(208, 101); + this.label4.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(40, 16); + this.label4.TabIndex = 25; + this.label4.Text = "1 .. 10"; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(208, 72); + this.label3.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(33, 16); + this.label3.TabIndex = 24; + this.label3.Text = "1 .. 4"; + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.comboBoxCommunicationInterface); + this.groupBox1.Controls.Add(this.label1); + this.groupBox1.Controls.Add(this.rfidPortNrTextBox); + this.groupBox1.Controls.Add(this.rfidSerialPortNrLabel); + this.groupBox1.Location = new System.Drawing.Point(10, 259); + this.groupBox1.Margin = new System.Windows.Forms.Padding(4); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Padding = new System.Windows.Forms.Padding(4); + this.groupBox1.Size = new System.Drawing.Size(552, 68); + this.groupBox1.TabIndex = 23; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "RFID / NFC communication (in case mux. board is not used)"; + // + // comboBoxCommunicationInterface + // + this.comboBoxCommunicationInterface.Enabled = false; + this.comboBoxCommunicationInterface.FormattingEnabled = true; + this.comboBoxCommunicationInterface.Items.AddRange(new object[] { + "RFID", + "NFC"}); + this.comboBoxCommunicationInterface.Location = new System.Drawing.Point(201, 27); + this.comboBoxCommunicationInterface.Name = "comboBoxCommunicationInterface"; + this.comboBoxCommunicationInterface.Size = new System.Drawing.Size(71, 24); + this.comboBoxCommunicationInterface.TabIndex = 9; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(41, 30); + this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(153, 16); + this.label1.TabIndex = 8; + this.label1.Text = "Communication Interface"; + // + // rfidPortNrTextBox + // + this.rfidPortNrTextBox.Enabled = false; + this.rfidPortNrTextBox.Location = new System.Drawing.Point(439, 26); + this.rfidPortNrTextBox.Margin = new System.Windows.Forms.Padding(4); + this.rfidPortNrTextBox.Name = "rfidPortNrTextBox"; + this.rfidPortNrTextBox.Size = new System.Drawing.Size(44, 22); + this.rfidPortNrTextBox.TabIndex = 7; + // + // rfidSerialPortNrLabel + // + this.rfidSerialPortNrLabel.AutoSize = true; + this.rfidSerialPortNrLabel.Location = new System.Drawing.Point(321, 30); + this.rfidSerialPortNrLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.rfidSerialPortNrLabel.Name = "rfidSerialPortNrLabel"; + this.rfidSerialPortNrLabel.Size = new System.Drawing.Size(88, 16); + this.rfidSerialPortNrLabel.TabIndex = 6; + this.rfidSerialPortNrLabel.Text = "Serial port nr.:"; + // + // optoDataGroupBox + // + this.optoDataGroupBox.Controls.Add(this.tcpipPortLabel); + this.optoDataGroupBox.Controls.Add(this.tcpipPortTextBox); + this.optoDataGroupBox.Controls.Add(this.ipAddressLabel); + this.optoDataGroupBox.Controls.Add(this.ipAddressTextBox); + this.optoDataGroupBox.Controls.Add(this.radioButton1); + this.optoDataGroupBox.Controls.Add(this.radioButton2); + this.optoDataGroupBox.Controls.Add(this.optoSerialPortLabel); + this.optoDataGroupBox.Controls.Add(this.optoSerialPortTextBox); + this.optoDataGroupBox.Location = new System.Drawing.Point(10, 131); + this.optoDataGroupBox.Margin = new System.Windows.Forms.Padding(4); + this.optoDataGroupBox.Name = "optoDataGroupBox"; + this.optoDataGroupBox.Padding = new System.Windows.Forms.Padding(4); + this.optoDataGroupBox.Size = new System.Drawing.Size(552, 119); + this.optoDataGroupBox.TabIndex = 18; + this.optoDataGroupBox.TabStop = false; + this.optoDataGroupBox.Text = "Opto-data"; + // + // tcpipPortLabel + // + this.tcpipPortLabel.AutoSize = true; + this.tcpipPortLabel.Location = new System.Drawing.Point(41, 87); + this.tcpipPortLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.tcpipPortLabel.Name = "tcpipPortLabel"; + this.tcpipPortLabel.Size = new System.Drawing.Size(54, 16); + this.tcpipPortLabel.TabIndex = 4; + this.tcpipPortLabel.Text = "Port nr..:"; + // + // tcpipPortTextBox + // + this.tcpipPortTextBox.Enabled = false; + this.tcpipPortTextBox.Location = new System.Drawing.Point(143, 84); + this.tcpipPortTextBox.Margin = new System.Windows.Forms.Padding(4); + this.tcpipPortTextBox.Name = "tcpipPortTextBox"; + this.tcpipPortTextBox.Size = new System.Drawing.Size(51, 22); + this.tcpipPortTextBox.TabIndex = 5; + // + // ipAddressLabel + // + this.ipAddressLabel.AutoSize = true; + this.ipAddressLabel.Location = new System.Drawing.Point(41, 59); + this.ipAddressLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.ipAddressLabel.Name = "ipAddressLabel"; + this.ipAddressLabel.Size = new System.Drawing.Size(78, 16); + this.ipAddressLabel.TabIndex = 2; + this.ipAddressLabel.Text = "IP address.:"; + // + // ipAddressTextBox + // + this.ipAddressTextBox.Enabled = false; + this.ipAddressTextBox.Location = new System.Drawing.Point(143, 55); + this.ipAddressTextBox.Margin = new System.Windows.Forms.Padding(4); + this.ipAddressTextBox.Name = "ipAddressTextBox"; + this.ipAddressTextBox.Size = new System.Drawing.Size(129, 22); + this.ipAddressTextBox.TabIndex = 3; + // + // radioButton1 + // + this.radioButton1.AutoSize = true; + this.radioButton1.Checked = true; + this.radioButton1.Enabled = false; + this.radioButton1.Location = new System.Drawing.Point(29, 23); + this.radioButton1.Margin = new System.Windows.Forms.Padding(4); + this.radioButton1.Name = "radioButton1"; + this.radioButton1.Size = new System.Drawing.Size(99, 20); + this.radioButton1.TabIndex = 0; + this.radioButton1.TabStop = true; + this.radioButton1.Text = "Use TCP/IP"; + this.radioButton1.UseVisualStyleBackColor = true; + // + // radioButton2 + // + this.radioButton2.AutoSize = true; + this.radioButton2.Enabled = false; + this.radioButton2.Location = new System.Drawing.Point(312, 23); + this.radioButton2.Margin = new System.Windows.Forms.Padding(4); + this.radioButton2.Name = "radioButton2"; + this.radioButton2.Size = new System.Drawing.Size(115, 20); + this.radioButton2.TabIndex = 1; + this.radioButton2.Text = "Use serial port"; + this.radioButton2.UseVisualStyleBackColor = true; + // + // optoSerialPortLabel + // + this.optoSerialPortLabel.AutoSize = true; + this.optoSerialPortLabel.Location = new System.Drawing.Point(321, 55); + this.optoSerialPortLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.optoSerialPortLabel.Name = "optoSerialPortLabel"; + this.optoSerialPortLabel.Size = new System.Drawing.Size(88, 16); + this.optoSerialPortLabel.TabIndex = 6; + this.optoSerialPortLabel.Text = "Serial port nr.:"; + // + // optoSerialPortTextBox + // + this.optoSerialPortTextBox.Enabled = false; + this.optoSerialPortTextBox.Location = new System.Drawing.Point(439, 52); + this.optoSerialPortTextBox.Margin = new System.Windows.Forms.Padding(4); + this.optoSerialPortTextBox.Name = "optoSerialPortTextBox"; + this.optoSerialPortTextBox.Size = new System.Drawing.Size(44, 22); + this.optoSerialPortTextBox.TabIndex = 7; + // + // groupTextBox + // + this.groupTextBox.Enabled = false; + this.groupTextBox.Location = new System.Drawing.Point(153, 97); + this.groupTextBox.Margin = new System.Windows.Forms.Padding(4); + this.groupTextBox.Name = "groupTextBox"; + this.groupTextBox.Size = new System.Drawing.Size(44, 22); + this.groupTextBox.TabIndex = 22; + // + // groupLabel + // + this.groupLabel.AutoSize = true; + this.groupLabel.Location = new System.Drawing.Point(6, 101); + this.groupLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.groupLabel.Name = "groupLabel"; + this.groupLabel.Size = new System.Drawing.Size(54, 16); + this.groupLabel.TabIndex = 21; + this.groupLabel.Text = "Group 2"; + // + // muxBoardNrTextBox + // + this.muxBoardNrTextBox.Enabled = false; + this.muxBoardNrTextBox.Location = new System.Drawing.Point(153, 69); + this.muxBoardNrTextBox.Margin = new System.Windows.Forms.Padding(4); + this.muxBoardNrTextBox.Name = "muxBoardNrTextBox"; + this.muxBoardNrTextBox.Size = new System.Drawing.Size(44, 22); + this.muxBoardNrTextBox.TabIndex = 20; + // + // muxBoardNrLabel + // + this.muxBoardNrLabel.AutoSize = true; + this.muxBoardNrLabel.Location = new System.Drawing.Point(6, 72); + this.muxBoardNrLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.muxBoardNrLabel.Name = "muxBoardNrLabel"; + this.muxBoardNrLabel.Size = new System.Drawing.Size(131, 16); + this.muxBoardNrLabel.TabIndex = 19; + this.muxBoardNrLabel.Text = "Group 1 (mux. board)"; + // + // nameTextBox + // + this.nameTextBox.Enabled = false; + this.nameTextBox.Location = new System.Drawing.Point(153, 40); + this.nameTextBox.Margin = new System.Windows.Forms.Padding(4); + this.nameTextBox.Name = "nameTextBox"; + this.nameTextBox.Size = new System.Drawing.Size(160, 22); + this.nameTextBox.TabIndex = 17; + // + // nameLabel + // + this.nameLabel.AutoSize = true; + this.nameLabel.Location = new System.Drawing.Point(6, 44); + this.nameLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.nameLabel.Name = "nameLabel"; + this.nameLabel.Size = new System.Drawing.Size(44, 16); + this.nameLabel.TabIndex = 16; + this.nameLabel.Text = "Name"; + // + // classNameLabel + // + this.classNameLabel.AutoSize = true; + this.classNameLabel.Location = new System.Drawing.Point(149, 11); + this.classNameLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.classNameLabel.Name = "classNameLabel"; + this.classNameLabel.Size = new System.Drawing.Size(78, 16); + this.classNameLabel.TabIndex = 15; + this.classNameLabel.Text = "ClassName"; + // + // tabPage2 + // + this.tabPage2.Location = new System.Drawing.Point(4, 25); + this.tabPage2.Name = "tabPage2"; + this.tabPage2.Padding = new System.Windows.Forms.Padding(3); + this.tabPage2.Size = new System.Drawing.Size(603, 403); + this.tabPage2.TabIndex = 1; + this.tabPage2.Text = "Test"; + this.tabPage2.UseVisualStyleBackColor = true; + // + // groupBox2 + // + this.groupBox2.Controls.Add(this.headPortNrTextBox); + this.groupBox2.Controls.Add(this.label2); + this.groupBox2.Location = new System.Drawing.Point(10, 335); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(552, 50); + this.groupBox2.TabIndex = 26; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "Head Communication"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(321, 18); + this.label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(88, 16); + this.label2.TabIndex = 7; + this.label2.Text = "Serial port nr.:"; + // + // headPortNrTextBox + // + this.headPortNrTextBox.Enabled = false; + this.headPortNrTextBox.Location = new System.Drawing.Point(439, 15); + this.headPortNrTextBox.Margin = new System.Windows.Forms.Padding(4); + this.headPortNrTextBox.Name = "headPortNrTextBox"; + this.headPortNrTextBox.Size = new System.Drawing.Size(44, 22); + this.headPortNrTextBox.TabIndex = 8; + // + // IperlHeadCfgCtrl + // + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.tabControl1); + this.Margin = new System.Windows.Forms.Padding(4); + this.Name = "GenesisCfgCtrl"; + this.Size = new System.Drawing.Size(617, 438); + this.Load += new System.EventHandler(this.WaterMeterCfgCtrl_Load); + this.tabControl1.ResumeLayout(false); + this.tabPage1.ResumeLayout(false); + this.tabPage1.PerformLayout(); + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.optoDataGroupBox.ResumeLayout(false); + this.optoDataGroupBox.PerformLayout(); + this.groupBox2.ResumeLayout(false); + this.groupBox2.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.TabControl tabControl1; + private System.Windows.Forms.TabPage tabPage1; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.ComboBox comboBoxCommunicationInterface; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox rfidPortNrTextBox; + private System.Windows.Forms.Label rfidSerialPortNrLabel; + private System.Windows.Forms.GroupBox optoDataGroupBox; + private System.Windows.Forms.Label tcpipPortLabel; + private System.Windows.Forms.TextBox tcpipPortTextBox; + private System.Windows.Forms.Label ipAddressLabel; + private System.Windows.Forms.TextBox ipAddressTextBox; + private System.Windows.Forms.RadioButton radioButton1; + private System.Windows.Forms.RadioButton radioButton2; + private System.Windows.Forms.Label optoSerialPortLabel; + private System.Windows.Forms.TextBox optoSerialPortTextBox; + private System.Windows.Forms.TextBox groupTextBox; + private System.Windows.Forms.Label groupLabel; + private System.Windows.Forms.TextBox muxBoardNrTextBox; + private System.Windows.Forms.Label muxBoardNrLabel; + private System.Windows.Forms.TextBox nameTextBox; + private System.Windows.Forms.Label nameLabel; + private System.Windows.Forms.Label classNameLabel; + private System.Windows.Forms.TabPage tabPage2; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.TextBox headPortNrTextBox; + private System.Windows.Forms.Label label2; + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/GenesisCfgCtrl.resx b/TBF/Rig/RegisterReaders/GenesisRegReader/GenesisCfgCtrl.resx new file mode 100644 index 000000000..1af7de150 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/GenesisCfgCtrl.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/GenesisHeadTestCtrl.Designer.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/GenesisHeadTestCtrl.Designer.cs new file mode 100644 index 000000000..0c01a6698 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/GenesisHeadTestCtrl.Designer.cs @@ -0,0 +1,137 @@ +namespace TBF.Rig.RegisterReaders.GenesisRegReader +{ + partial class GenesisHeadTestCtrl + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.optoTestGroupBox = new System.Windows.Forms.GroupBox(); + this.optoListBox = new System.Windows.Forms.ListBox(); + this.rfidOutputListBox = new System.Windows.Forms.ListBox(); + this.RfidTestGroupBox = new System.Windows.Forms.GroupBox(); + this.label2 = new System.Windows.Forms.Label(); + this.rfidCommandComboBox = new System.Windows.Forms.ComboBox(); + this.commandTestButton = new System.Windows.Forms.Button(); + this.optoTestGroupBox.SuspendLayout(); + this.RfidTestGroupBox.SuspendLayout(); + this.SuspendLayout(); + // + // optoTestGroupBox + // + this.optoTestGroupBox.Controls.Add(this.optoListBox); + this.optoTestGroupBox.Location = new System.Drawing.Point(5, 4); + this.optoTestGroupBox.Name = "optoTestGroupBox"; + this.optoTestGroupBox.Size = new System.Drawing.Size(591, 161); + this.optoTestGroupBox.TabIndex = 2; + this.optoTestGroupBox.TabStop = false; + this.optoTestGroupBox.Text = "Opto-data"; + // + // optoListBox + // + this.optoListBox.FormattingEnabled = true; + this.optoListBox.ItemHeight = 16; + this.optoListBox.Location = new System.Drawing.Point(7, 22); + this.optoListBox.Name = "optoListBox"; + this.optoListBox.Size = new System.Drawing.Size(573, 132); + this.optoListBox.TabIndex = 0; + // + // rfidOutputListBox + // + this.rfidOutputListBox.FormattingEnabled = true; + this.rfidOutputListBox.ItemHeight = 16; + this.rfidOutputListBox.Location = new System.Drawing.Point(5, 54); + this.rfidOutputListBox.Name = "rfidOutputListBox"; + this.rfidOutputListBox.SelectionMode = System.Windows.Forms.SelectionMode.None; + this.rfidOutputListBox.Size = new System.Drawing.Size(575, 164); + this.rfidOutputListBox.TabIndex = 3; + // + // RfidTestGroupBox + // + this.RfidTestGroupBox.Controls.Add(this.rfidOutputListBox); + this.RfidTestGroupBox.Controls.Add(this.label2); + this.RfidTestGroupBox.Controls.Add(this.rfidCommandComboBox); + this.RfidTestGroupBox.Controls.Add(this.commandTestButton); + this.RfidTestGroupBox.Location = new System.Drawing.Point(5, 171); + this.RfidTestGroupBox.Name = "RfidTestGroupBox"; + this.RfidTestGroupBox.Size = new System.Drawing.Size(591, 224); + this.RfidTestGroupBox.TabIndex = 3; + this.RfidTestGroupBox.TabStop = false; + this.RfidTestGroupBox.Text = "RFID / NFC data"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(2, 25); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(69, 16); + this.label2.TabIndex = 2; + this.label2.Text = "Command"; + // + // rfidCommandComboBox + // + this.rfidCommandComboBox.FormattingEnabled = true; + this.rfidCommandComboBox.Location = new System.Drawing.Point(86, 19); + this.rfidCommandComboBox.Name = "rfidCommandComboBox"; + this.rfidCommandComboBox.Size = new System.Drawing.Size(341, 24); + this.rfidCommandComboBox.TabIndex = 1; + // + // commandTestButton + // + this.commandTestButton.Location = new System.Drawing.Point(449, 19); + this.commandTestButton.Name = "commandTestButton"; + this.commandTestButton.Size = new System.Drawing.Size(126, 24); + this.commandTestButton.TabIndex = 0; + this.commandTestButton.Text = "Send command"; + this.commandTestButton.UseVisualStyleBackColor = true; + this.commandTestButton.MouseClick += new System.Windows.Forms.MouseEventHandler(this.CommandTestButtonClick); + // + // IperlHeadTestCtrl + // + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.optoTestGroupBox); + this.Controls.Add(this.RfidTestGroupBox); + this.Name = "GenesisHeadTestCtrl"; + this.Size = new System.Drawing.Size(611, 432); + this.Load += new System.EventHandler(this.UserControl_Load); + this.optoTestGroupBox.ResumeLayout(false); + this.RfidTestGroupBox.ResumeLayout(false); + this.RfidTestGroupBox.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.GroupBox optoTestGroupBox; + private System.Windows.Forms.ListBox rfidOutputListBox; + private System.Windows.Forms.GroupBox RfidTestGroupBox; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.ComboBox rfidCommandComboBox; + private System.Windows.Forms.Button commandTestButton; + private System.Windows.Forms.ListBox optoListBox; + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/GenesisHeadTestCtrl.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/GenesisHeadTestCtrl.cs new file mode 100644 index 000000000..52b15e859 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/GenesisHeadTestCtrl.cs @@ -0,0 +1,90 @@ +using System; +using System.Linq; +using System.Threading; +using System.Windows.Forms; +using TBF.Rig.RegisterReaders.GenesisRegReader.implementations; +using TBF.Rig.RegisterReaders.iPerlReaderUNI.common; +using TBF.Rig.Sequences; +using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader +{ + public partial class GenesisHeadTestCtrl : UserControl + { + private IUniHeadTestCtrl _ctrl; + private IUniHeadTestCtrl Ctrl { get => _ctrl; } + + private GenesisCfg _genesisCfg; + private GenesisSmartReader _genesisSmartReader; + Thread optoThread; + private bool stopWorkerThread; + + public GenesisHeadTestCtrl(GenesisCfg config) + { + this._ctrl = new GenesisImplHeadTestCtrl(); + _genesisCfg = config; + Ctrl.config = config; + InitializeComponent(); + if (config == null) return; + + //SmartCommunicationForm.TestMethodCfg = new TestMethodCfg(null); // default values for iPerlCommunication + + foreach(var head in ProcessData.SmartHeadsUni) + { + if (head != null && head.Name == config.Name) { Ctrl.ISmartReader = head; } + } + + rfidCommandComboBox.DisplayMember = "Name"; + rfidCommandComboBox.ValueMember = "Value"; + var items = Ctrl.GetComboOperationsPairs(); + /* + // CTRL+ALT+double click - hidden poweruser menu + if (((Keyboard.ModifierKeys & Keys.Control) == Keys.Control) && ((Keyboard.ModifierKeys & Keys.Alt) == Keys.Alt) && Users.CurrentUser.AuthorizedAs == AuthorizedAs.PowerUser) + { + Array.Resize(ref items, items.Length + 1); + items[items.Length - 1] = new { Name = "Kluc", Value = "Kluc" }; + } + */ + rfidCommandComboBox.DataSource = items.Select(i => i.Name).ToArray(); + + Ctrl.Initialize(); + + Ctrl.OptoReceivedHandler += (EventHandler)((sndr, args) => + { + if (this.InvokeRequired) + this.Invoke((Delegate)new EventHandler(this.OnOptoReceived2), sndr, (object)args); + else + this.OnOptoReceived2(sndr, args); + }); + } + + public void OnOptoReceived2(object sender, OptoReceivedEventArgs args) + { + optoListBox.Items.Insert(0,args.Data); + } + + private void CommandTestButtonClick(object sender, MouseEventArgs e) + { + Ctrl.CommandTestButtonClick(sender, e, new Arguments() + { + ISmartReader = Ctrl.ISmartReader, + OptoListBox = optoListBox, + RfidCommandComboBox = rfidCommandComboBox, + RfidOutputListBox = rfidOutputListBox + }); + } + + + + private void UserControl_Load(object sender, EventArgs e) + { + this.ParentForm.FormClosing += new FormClosingEventHandler(ParentForm_FormClosing); + } + + void ParentForm_FormClosing(object sender, FormClosingEventArgs e) + { + //OnHandleDestroyed(new EventArgs()); + Ctrl.Destroy(); + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/GenesisHeadTestCtrl.resx b/TBF/Rig/RegisterReaders/GenesisRegReader/GenesisHeadTestCtrl.resx new file mode 100644 index 000000000..d58980a38 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/GenesisHeadTestCtrl.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/common/HexFormatter.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/common/HexFormatter.cs new file mode 100644 index 000000000..d7fef5f8b --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/common/HexFormatter.cs @@ -0,0 +1,175 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Text; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.common +{ + public static class HexFormatter + { + /// + /// Byte to hex string. + /// Formats a single byte as 0xNN. + /// Example: 0x0D + /// + public static string ToHex(byte value) + { + return "0x" + value.ToString("X2"); + } + + /// + /// int to byte - securely + /// + /// + /// + /// + public static byte ToHexByte(int value) + { + if (value < 0 || value > 255) + throw new ArgumentOutOfRangeException(nameof(value), + "Value must be between 0 and 255."); + + return (byte)value; + } + + /// + /// Formats a byte array as 0xNN 0xNN ... + /// + public static string ToHex(byte[] data) + { + if (data == null || data.Length == 0) + return ""; + + var sb = new System.Text.StringBuilder(); + + for (int i = 0; i < data.Length; i++) + { + if (i > 0) + sb.Append(' '); + + sb.Append("0x"); + sb.Append(data[i].ToString("X2")); + } + + return sb.ToString(); + } + + /// + /// Formats a byte array exactly as shown in serial terminals. + /// Example: "0D 04 08 01 00 1A" + /// + public static string ToSerialHex(byte[] data) + { + if (data == null || data.Length == 0) + return string.Empty; + + var sb = new System.Text.StringBuilder(); + + for (int i = 0; i < data.Length; i++) + { + if (i > 0) + sb.Append(' '); + + sb.Append(data[i].ToString("X2")); + } + + return sb.ToString(); + } + + + public static string ToHexWithAscii(byte value) + { + char c = (value >= 32 && value <= 126) ? (char)value : '.'; + return $"0x{value:X2} ('{c}')"; + } + + public static string ToSerialHexWithAscii(byte[] data) + { + if (data == null || data.Length == 0) + return string.Empty; + + var hex = new StringBuilder(data.Length * 3); + var ascii = new StringBuilder(data.Length); + + foreach (byte b in data) + { + hex.Append(b.ToString("X2")).Append(' '); + + // Printable ASCII range + if (b >= 32 && b <= 126) + { + ascii.Append((char)b); + } + // Binary numbers 0–9 -> show digit + else if (b <= 9) + { + ascii.Append((char)('0' + b)); + } + else + { + ascii.Append('.'); + } + } + + // remove last trailing space in hex + if (hex.Length > 0) + hex.Length--; + + return $"{hex} | {ascii}"; + } + + + + public static string ToHex(int value) + { + return $"0x{(byte)value:X2}"; + } + + public static byte[] IntToBytesBE(int value, int byteCount) + { + var result = new byte[byteCount]; + + for (int i = 0; i < byteCount; i++) + result[byteCount - 1 - i] = (byte)(value >> (8 * i)); + + return result; + } + + public static byte[] IntToBytesLE(int value, int byteCount) + { + var result = new byte[byteCount]; + + for (int i = 0; i < byteCount; i++) + result[i] = (byte)(value >> (8 * i)); + + return result; + } + + public static byte[] AsciiToBytes(string text) + { + return string.IsNullOrEmpty(text) + ? Array.Empty() + : System.Text.Encoding.ASCII.GetBytes(text); + } + + /// + /// Converts a hex string to a byte array. + /// Like: string hex = "3F 76 65 72 73 3A 20 48 61 72 72 79 20 54 3A 42 38 30 30 2C 20"; + /// + /// + /// + /// + public static byte[] HexStringToByteArray(string hex) + { + if (hex == null) + throw new ArgumentNullException(nameof(hex)); + + return hex + .Split(new[] { ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) + .Select(b => byte.Parse(b, NumberStyles.HexNumber, CultureInfo.InvariantCulture)) + .ToArray(); + } + + } + +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/common/OptoTelegramRaw.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/common/OptoTelegramRaw.cs new file mode 100644 index 000000000..7e8d2a8fc --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/common/OptoTelegramRaw.cs @@ -0,0 +1,353 @@ +/// +/// Copyright (c) 2015-2021 Sensus Metering Systems +/// + +using System; +using System.Globalization; + + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.common +{ + public enum OptoTelegramFlags : byte + { + OK = 0, + OK_TestStart, + OK_TestEnd, + InvalidTelegram, /// Wrong telegram format of checksum error + SyncError, + } + + public class OptoTelegramRaw + { + public static readonly int Length = 42; + private static CultureInfo culture; + + + /// + /// Strobed value + /// + public static decimal TestStartTimestampDec; + + /// + /// Stored values + /// + public OptoTelegramFlags Flags; + + public DateTime DateTime; /// From PC + public float RefFlow; /// [m3/h] + public int Counter; + + public Int32 EmfRaw; /// Signed EMF from iPerl opto data + public Int16 MagneticFieldRaw; + public Int16 FlowRaw; + public double VolumeRaw; + public double VolumeRawExt; + public Int16 Impedance; + public double Timestamp; + public double TimestampExt; + public byte CheckSum; + + /// + /// Calculated values + /// + public double EMF() + { + return 0.000000333 * (double)EmfRaw; + } + public double MagneticField() { return (double)MagneticFieldRaw; } + public double Flow(double scalingFactor) { return 0.225 * scalingFactor * (double)FlowRaw; } + public double Volume(double scalingFactor) { return 0.0000625 * scalingFactor * (double)VolumeRawExt; } + public Int32 FlipTime() { return Impedance; } + public decimal TimestampDec() { return (decimal)TimestampExt / (decimal)8192; } + public double VolumeDelta(double scalingFactor, OptoTelegramRaw previous) { return (previous == null) ? 0 : Volume(scalingFactor) - previous.Volume(scalingFactor); } + public decimal TimeDelta() { return TimestampDec() - TestStartTimestampDec; } + public string Label() + { + if (Flags == OptoTelegramFlags.OK_TestStart) return "#### start test ####"; + else if (Flags == OptoTelegramFlags.OK_TestEnd) return "#### end of test ####"; + else return string.Empty; + } + + + static OptoTelegramRaw() + { + culture = CultureInfo.CreateSpecificCulture("DE"); /// This is to use comma as decimal number separator + } + + public OptoTelegramRaw() + { + } + + /// + /// Parses optical telegram and returns OptoTelegramRaw object + /// + /// + /// Create a configuration structure from a complete byte array + /// + /// Telegram description: + /// + /// AAAAAA[tab]BBBB[tab]CCCC[tab]DDDDDD[tab]EEEE[tab]FFFFFFFF[tab]GG[cr][lf] (42 bytes) + /// + /// Data Comment Type Calculate to decimal + /// ---------------------------------------------------------------- + /// AAAAAA EMF Int24 Value * 0.000000333 + /// BBBB Magnetic field Int16 Value + /// CCCC Flow Int16 Value * 0.225 * Scalig factor + /// DDDDDD Volume Int24 Value / 16000 * Scaling factor + /// EEEE Impedance Int16 Value + /// FFFFFFFF Timestamp Uint32 Value / 8192 + /// GG Checksum Byte + /// ---------------------------------------------------------------- + /// + /// Example: + /// FFFFFE 51EA 0000 65324E 0087 F6319DFF 86 + /// FFDD3A 51F9 0000 65324E 0088 F631A60B 45 + /// ... + /// + /// A complete byte array data + /// true = telegram OK, false = telegram NOK + // public bool UpdateFromString(string telegram, int counter, float refFlow, ref Int64 volumeRawExtLast, ref Int64 timestampExtLast, bool isLog = false) + // { + // DateTime = DateTime.Now; + // Counter = counter; + // RefFlow = refFlow; + // + // if ((telegram == null) || (telegram.Length < Length) || + // (telegram[6] != '\t') || (telegram[11] != '\t') || (telegram[16] != '\t') || + // (telegram[23] != '\t') || (telegram[28] != '\t') || (telegram[37] != '\t') || + // (!isLog && (telegram[40] != '\r' || telegram[41] != '\n'))) + // { + // Flags = OptoTelegramFlags.InvalidTelegram; + // return false; + // } + // + // UInt32 uEmfRaw; + // bool f1 = UInt32.TryParse(telegram.Substring(0, 6), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out uEmfRaw); + // EmfRaw = (uEmfRaw > 0x7FFFFF) ? ((int)uEmfRaw - 0x1000000) : (int)uEmfRaw; + // + // bool f2 = Int16.TryParse(telegram.Substring(7, 4), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out MagneticFieldRaw); + // bool f3 = Int16.TryParse(telegram.Substring(12, 4), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out FlowRaw); + // bool f4 = UInt32.TryParse(telegram.Substring(17, 6), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out VolumeRaw); + // bool f5 = Int16.TryParse(telegram.Substring(24, 4), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out Impedance); + // bool f6 = UInt32.TryParse(telegram.Substring(29, 8), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out Timestamp); + // bool f7 = byte.TryParse(telegram.Substring(38, 2), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out CheckSum); + // + // byte calculatedCheckSum = 0; + // for (int i = 0; i < Length - 4; i++) + // { + // calculatedCheckSum += (byte)telegram[i]; + // } + // + // bool allOk = f1 && f2 && f3 && f4 && f5 && f6 && f7 && (calculatedCheckSum == CheckSum); + // + // if (allOk) + // { + // /// + // /// Cope with 'VolumeRaw' overflow + // /// + // Int64 uncorrected = (Int64)(((UInt64)volumeRawExtLast & 0xFFFFFFFFFF000000UL) | VolumeRaw); + // if (Math.Abs(uncorrected - volumeRawExtLast) <= 0x800000L) + // { + // VolumeRawExt = volumeRawExtLast = uncorrected; + // } + // else if (Math.Abs(uncorrected + 0x1000000L - volumeRawExtLast) <= 0x800000L) + // { + // VolumeRawExt = volumeRawExtLast = uncorrected + 0x1000000L; + // } + // else if (Math.Abs(uncorrected - 0x1000000L - volumeRawExtLast) <= 0x800000L) + // { + // VolumeRawExt = volumeRawExtLast = uncorrected - 0x1000000L; + // } + // else + // { + // VolumeRawExt = volumeRawExtLast = uncorrected; + // } + // + // /// + // /// Cope with 'Timestamp' overflow + // /// + // uncorrected = (Int64)(((UInt64)timestampExtLast & 0xFFFFFFFF00000000UL) | Timestamp); + // if (Math.Abs(uncorrected - timestampExtLast) <= 0x80000000L) + // { + // TimestampExt = timestampExtLast = uncorrected; + // } + // else if (Math.Abs(uncorrected + 0x100000000L - timestampExtLast) <= 0x80000000L) + // { + // TimestampExt = timestampExtLast = uncorrected + 0x100000000L; + // } + // else if (Math.Abs(uncorrected - 0x100000000L - timestampExtLast) <= 0x80000000L) + // { + // TimestampExt = timestampExtLast = uncorrected - 0x100000000L; + // } + // else + // { + // TimestampExt = timestampExtLast = uncorrected; + // } + // } + // + // Flags = allOk ? OptoTelegramFlags.OK : OptoTelegramFlags.InvalidTelegram; + // + // return allOk; + // } + + + // -------- TIMESTAMP (seconds) -------- + // bbbbbbbb – unsigned 32 bit ASIC time stamp in 8192 ticks per second– rolls over after 2^32 + private const double TS_TICKS_PER_SEC = 8192.0; + private const double TS_RANGE = 4294967296.0 / TS_TICKS_PER_SEC; // 2^32 / 8192 = 524288 sec + + // -------- VOLUME (liters) -------- + // vvvvvv is unsigned 24-bit, 1 tick = 1/4 ml = 0.00025 L + private const double VOL_LITERS_PER_TICK = 0.00025; // liters per tick + private const double VOL_RANGE = 16777216.0 * VOL_LITERS_PER_TICK; // 2^24 * 0.00025 = 4194.304 L + + // -------- VOLUME (liters) -------- + private const double GAL_TO_LITER = 3.785411784; + + public void UpdateFromSmart( + Object data, + int counter, + float refFlow, + ref double volumeRawExtLast, + ref double timestampExtLast) + { + DateTime = DateTime.Now; + Counter = counter; + RefFlow = refFlow; + + throw new NotImplementedException(); + + // FlowRaw = data.RawFlow; + // VolumeRaw = data.RawVolume; + // + // // ---- TIMESTAMP RAW (seconds, modulo TS_RANGE) ---- + // // If upstream conversion ever produced negative values, normalize them. + // double ts = data.AsicTimestamp; // already in seconds, but wraps every TS_RANGE + // ts = ts % TS_RANGE; + // if (ts < 0) ts += TS_RANGE; + // + // Timestamp = ts; + // + // // ---------- VOLUME UNWRAP ---------- + // double v = VolumeRaw; + // + // if (double.IsNaN(volumeRawExtLast)) + // { + // VolumeRawExt = volumeRawExtLast = v; + // } + // else + // { + // // nearest-lap unwrap + // //double k = Math.Round(volumeRawExtLast - v) / VOL_RANGE); + // if (v < volumeRawExtLast) + // { + // VolumeRawExt = volumeRawExtLast = v + VOL_RANGE; + // } + // else + // { + // VolumeRawExt = volumeRawExtLast = v; + // } + // } + // + // // ---------- TIMESTAMP UNWRAP (seconds) ---------- + // if (double.IsNaN(timestampExtLast)) + // { + // TimestampExt = timestampExtLast = ts; + // } + // else + // { + // // robust unwrap: choose the smallest jump across the modulo boundary + // double lastMod = timestampExtLast % TS_RANGE; + // if (lastMod < 0) lastMod += TS_RANGE; + // + // double delta = ts - lastMod; + // + // if (delta < -TS_RANGE / 2.0) delta += TS_RANGE; + // else if (delta > TS_RANGE / 2.0) delta -= TS_RANGE; + // + // TimestampExt = timestampExtLast = timestampExtLast + delta; + // } + + } + + + + /// + /// Alternative to UpdateFromString(...) when data are flushed + /// + public bool UpdateFromStringDummy(string telegram) + { + DateTime = DateTime.Now; + RefFlow = 0; + + if ((telegram == null) || (telegram.Length < Length) || + (telegram[6] != '\t') || (telegram[11] != '\t') || (telegram[16] != '\t') || + (telegram[23] != '\t') || (telegram[28] != '\t') || (telegram[37] != '\t') || + (telegram[40] != '\r') || (telegram[41] != '\n')) + { + Flags = OptoTelegramFlags.InvalidTelegram; + return false; + } + + bool f7 = byte.TryParse(telegram.Substring(38, 2), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out CheckSum); + + byte calculatedCheckSum = 0; + for (int i = 0; i < Length - 4; i++) + { + calculatedCheckSum += (byte)telegram[i]; + } + + bool allOk = f7 && (calculatedCheckSum == CheckSum); + + Flags = allOk ? OptoTelegramFlags.OK : OptoTelegramFlags.InvalidTelegram; + + return allOk; + } + + + public void SetFlags(OptoTelegramFlags flags) + { + this.Flags = flags; + } + + + public string ToString(double scalingFactor, OptoTelegramRaw previous) + { + if (Flags == OptoTelegramFlags.SyncError) + { + return "Sychronization error"; + } + else if (Flags == OptoTelegramFlags.InvalidTelegram) + { + return "Invalid telegram"; + } + else /// if (flags == OptoTelegramFlags.OK / OptoTelegramFlags.OK_TestStart / OptoTelegramFlags.OK_TestEnd) + { + return string.Format("{0}:{1}:{2}.{3}\t{4} :\t{5}\t{6}\t{7}\t{8}\t{9}\t{10}\t{11}\t{12}\t{13}\t{14}\t{15}\t{16}\t{17}\t{18}\t{19}\t{20}\t{21}\t{22}", + DateTime.Hour.ToString("D2"), + DateTime.Minute.ToString("D2"), + DateTime.Second.ToString("D2"), + DateTime.Millisecond.ToString("D4"), + Counter, + (EmfRaw & 0x00FFFFFF).ToString("X6"), + MagneticFieldRaw.ToString("X4"), + FlowRaw.ToString("X4"), + VolumeRaw.ToString("X6"), + Impedance.ToString("X4"), + Timestamp.ToString("X8"), + CheckSum.ToString("X2"), + EMF().ToString("F4", culture), + MagneticField().ToString("F0", culture), + Flow(scalingFactor).ToString("F2", culture), + Volume(scalingFactor).ToString("F4", culture), + FlipTime().ToString("F0", culture), + TimestampDec().ToString("F4", culture), + (RefFlow * 1000).ToString("F2", culture), + VolumeDelta(scalingFactor, previous).ToString("F4", culture), + TimeDelta().ToString("F3", culture), + scalingFactor.ToString("F1", culture), + Label()); + } + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Crc16Ccitt.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Crc16Ccitt.cs new file mode 100644 index 000000000..92e1a3f2d --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Crc16Ccitt.cs @@ -0,0 +1,227 @@ +using System; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis +{ + /// + /// Class for CRC16 CCITT calculation + /// + public static class Crc16Ccitt + { + /// + /// Initial CRC value + /// + private const UInt16 CrcSeedFfff = 0xFFFF; + /// + /// Initial CRC value + /// + private const UInt16 CrcSeed3791 = 0x3791; + + /// + /// Generator polynomial MagFlux - Modbus RTU + /// + private const UInt16 CrcGpA001 = 0xA001; + /// + /// Generator polynomial GENESIS + /// + private const UInt16 CrcGp1021 = 0x1021; + + /// + /// Generator polynomial RFID + /// + private const UInt16 CrcGp0408 = 0x0408; + + /// + /// Generator polynomial for reversed IrDA + /// + private const UInt16 CrcGp8408 = 0x8408; + + /// + /// Calculates the CRC (LSB first, reversed, CRC CCITT 0x8408) from a data array of bytes. + /// + /// data to be processed + /// Calculated CRC + public static UInt16 CalculateReversedLsb8408(Byte[] data) + { + var crc = CrcSeedFfff; + foreach (var t in data) + { + //copy data byte to lower word because of LSB will be XORed + var dataWord = (UInt16)(t & 0x00FF); + for (var i = 0; i < 8; i++) + { + //test if CRC lowest bit is XORed set to one + if (0x0001 == ((crc ^ dataWord) & 0x0001)) + { + crc >>= 1; + //apply generator polynomial + crc ^= CrcGp8408; + } + else + { + crc >>= 1; + } + + dataWord >>= 1; + } + } + return ((UInt16)~crc); + } + /// + /// Calculates the bitwise inverted CRC (MSB first, CRCCCITT 0x1021) + /// from a data array of bytes. + /// + /// data to be processed + /// Calculated CRC + public static UInt16 CalculateInvertedMsb1021(Byte[] data) + { + return (UInt16)~CalculateMsb1021(data); + } + + /// + /// Calculates the Modbus RTU CRC (LSB first, CRCCCITT 0xA001) + /// from a data array of bytes. + /// + /// data to be processed + /// Calculated CRC + public static UInt16 ModbusRtuLsbA001(Byte[] data) + { + var crc = CrcSeedFfff; + + for (var t = 0; t < data.Length; t++) + { + crc ^= data[t]; // XOR byte into least sig. byte of crc + + for (var i = 8; i != 0; i--) + { + if ((crc & 0x0001) != 0) + { + crc >>= 1; + crc ^= CrcGpA001; + } + else + { + crc >>= 1; + } + } + } + return crc; + } + + /// + /// Calculates the CRC (MSB first, CRCCCITT 0x1021) from a data array of bytes. + /// 18.07.2024 - optional parameters start and length added - for more flexibility by calculating a CRC of a message. + /// 18.07.2024 - length checks: if length less or equal 0 or grater than the data length: the data length is taken as length. + /// + /// optional, default = 0 + /// optional, default = 0 + /// data to be processed + /// Calculated CRC + public static UInt16 CalculateMsb1021(Byte[] data, Int32 start = 0, Int32 length = 0) + { + var dataLength = data.Length; + + if (length <= 0 || length > dataLength) + { + length = dataLength; + } + + var crc = CrcSeedFfff; + + for (var b = start; b < length; b++) + { + var t = data[b]; + + //copy data byte to higher word because of MSB will be XORed + var dataWord = (UInt16)((t << 8) & 0xFF00); + + for (var i = 0; i < 8; i++) + { + //test if CRC highest bit or data input highest bit is XORed set to one + if (0x8000 == ((crc ^ dataWord) & 0x8000)) //shifted in MSB first + { + //shift CRC high bit out + crc <<= 1; + //apply generator polynomial + crc ^= CrcGp1021; + } + else //CRC highest bit and data input highest bit is equal + { + crc <<= 1; + } + dataWord <<= 1; //MSB first shifted out + } + } + //foreach (var t in data) + //{ + // //copy data byte to higher word because of MSB will be XORed + // var dataWord = (UInt16)((t << 8) & 0xFF00); + // for (var i = 0; i < 8; i++) + // { + // //test if CRC highest bit or data input highest bit is XORed set to one + // if (0x8000 == ((crc ^ dataWord) & 0x8000)) //shifted in MSB first + // { + // //shift CRC high bit out + // crc <<= 1; + // //apply generator polynomial + // crc ^= CrcGp1021; + // } + // else //CRC highest bit and data input highest bit is equal + // { + // crc <<= 1; + // } + // dataWord <<= 1; //MSB first shifted out + // } + // } + return crc; + } + + public static Byte[] CalculateCRC1021LSBFirst(Byte[] bytes, Int32 start, Int32 length) + { + var crc = CalculateMsb1021(bytes, start, length); + + return BitConverter.GetBytes(crc); + } + + /// + /// Calculates the CRC (LSB first, CRCCCITT 0x0408) from a data array of bytes. + /// + /// data to be processed + /// Calculated CRC + public static UInt16 CalculateLsb0408(Byte[] data) + { + var crc = CrcSeed3791; + foreach (var t in data) + { + var dataByte = t; + for (var i = 0; i < 8; i++) + { + //test if CRC lowest bit is set to one + if (1 == (crc & 1)) + { + //shift CRC low bit out + crc >>= 1; + //test lowest bit of data word + if (1 == (dataByte & 1)) + crc |= 0x8000; + //invert CRC MSB + crc ^= 0x8000; + } + else //CRC lowest bit not set + { + //shift CRC low bit out + crc >>= 1; + //test lowest bit of data word + if (1 == (dataByte & 1)) + crc |= 0x8000; + } + if (0x8000 == (crc & 0x8000)) + //apply generator polynomial + crc ^= CrcGp0408; + + dataByte >>= 1; //LSB first shifted out + } + } + return crc; + } + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/EventArguments/BaseDataEventArgs.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/EventArguments/BaseDataEventArgs.cs new file mode 100644 index 000000000..7371a3a23 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/EventArguments/BaseDataEventArgs.cs @@ -0,0 +1,33 @@ +using System; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments +{ + + /// + /// + /// abstract for set structure for BaseDataEventArgs + /// + public abstract class BaseDataEventArgs : EventArgs + { + /// + /// 'Base' get event record form real child. + /// + /// + public Object GetData() + { + return GetEventData(); + } + + /// + /// get real Event record + /// + /// + public abstract Object GetEventData(); + + /// + /// Holds the record before decoding, for logging + /// + public String RawData; + } +} + diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/EventArguments/BendDetectDataEventArgs.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/EventArguments/BendDetectDataEventArgs.cs new file mode 100644 index 000000000..c19a76d41 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/EventArguments/BendDetectDataEventArgs.cs @@ -0,0 +1,20 @@ +using System; +using Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages.MeasurementRecords; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments +{ + /// + public class BendDetectDataEventArgs : BaseDataEventArgs + { + /// + /// new record from Stream + /// + public BendDetectionRecord NewData; + + /// + public override Object GetEventData() + { + return NewData; + } + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/EventArguments/CalibDataEventArgs.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/EventArguments/CalibDataEventArgs.cs new file mode 100644 index 000000000..a284932fb --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/EventArguments/CalibDataEventArgs.cs @@ -0,0 +1,20 @@ +using System; +using Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages.MeasurementRecords; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments +{ + /// + public class CalibDataEventArgs : BaseDataEventArgs + { + /// + /// Stream record for calibration on channel + /// + public CalibrationRecord CalibChl; + + /// + public override Object GetEventData() + { + return CalibChl; + } + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/EventArguments/FlowDataEventArgs.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/EventArguments/FlowDataEventArgs.cs new file mode 100644 index 000000000..b5119eb7e --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/EventArguments/FlowDataEventArgs.cs @@ -0,0 +1,20 @@ +using System; +using Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages.MeasurementRecords; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments +{ + /// + public class FlowDataEventArgs : BaseDataEventArgs + { + /// + /// new record from Stream + /// + public FlowTestRecord NewData; + + /// + public override Object GetEventData() + { + return NewData; + } + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/EventArguments/RegisterUpdateEventArgs.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/EventArguments/RegisterUpdateEventArgs.cs new file mode 100644 index 000000000..3ebbe43bd --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/EventArguments/RegisterUpdateEventArgs.cs @@ -0,0 +1,31 @@ +using System; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments +{ + /// + public class RegisterUpdatedEventArgs : EventArgs + { + /// + /// + /// Ctor with base record + /// + /// base record + /// base record + public RegisterUpdatedEventArgs(RegisterDefinition register, Byte[] value) + { + Register = register; + Value = value; + } + /// + /// Register witch has updated + /// + public RegisterDefinition Register; + + /// + /// New Value in Register + /// + public Byte[] Value; + + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/EventArguments/RequestResponseDataEventArgs.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/EventArguments/RequestResponseDataEventArgs.cs new file mode 100644 index 000000000..0dcdf26a7 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/EventArguments/RequestResponseDataEventArgs.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments +{ + /// + /// + /// EventArgs for Request Responses + /// + public class RequestResponseDataEventArgs : BaseDataEventArgs + { + /// + /// Request response from meter + /// + public List RequestResponseData; + + /// + public override Object GetEventData() + { + return RequestResponseData; + } + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/MeasurementRecords/BendDetectionRecord.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/MeasurementRecords/BendDetectionRecord.cs new file mode 100644 index 000000000..e52ca8761 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/MeasurementRecords/BendDetectionRecord.cs @@ -0,0 +1,114 @@ +using System; +using System.Text; +using Xylem.Common.Metrology.Measurements; + +namespace Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages.MeasurementRecords +{ + /// + /// + /// Streaming record for protocol M (information about bend detection and correction) + /// + public class BendDetectionRecord : MeasurementRecord + { + + /// + /// The status of the U0 detection for this measurement + /// + public enum StatusBendU0Enum + { + /// + /// Status okay + /// + OKAY = 0, + /// + /// Error code as defined by field name + /// + ERROR_GENESISFLOW_INSTALLATION_HIGH_TIME_DIFF = 0x0F41, + /// + /// Error code as defined by field name + /// + ERROR_GENESISFLOW_INSTALLATION_LOW_FLOW_IGNORE = 0x0F42, + /// + /// Error code as defined by field name + /// + ERROR_GENESISFLOW_INSTALLATION_BAD_CHANNELS = 0x0F43, + /// + /// Error code as defined by field name + /// + ERROR_GENESISFLOW_INSTALLATION_FIXED = 0x0F44 + } + + /// + /// An enum describing the installation type detected + /// + public enum InstallationTypeEnum + { + /// + /// Installation type code as defined by field name + /// + INSTALLATION_U0_180_360 = 0, + /// + /// Installation type code as defined by field name + /// + INSTALLATION_U0_90_270 = 1, + /// + /// Installation type code as defined by field name + /// + INSTALLATION_UNDISTURBED = 2 + } + + /// + /// Status of U0 Bend detection + /// + public StatusBendU0Enum StatusBendU0; + + /// + /// Installation type code + /// + public InstallationTypeEnum InstallationType; + + /// + /// The proportion of the installation correction to apply based on the detected + /// installation. 100% == 0x8000 + /// + public Double CorrectionFactor_percent; + + /// + /// The default proportion of the installation correction to apply based on the + /// detected installation. 100% == 0x8000 + /// + private const UInt32 DefaultCorrectionFactor = 0x8000; + + /// + /// Scale for correction factor to apply to convert it to percentage value 100% == 0x8000 + /// + public const Double CorrectionFactorScale = 100.0 / DefaultCorrectionFactor; + + /// + /// The volume in internal units before correction applied + /// + public Double PreCorrectionVolumeRaw; + + /// + /// The volume in internal units after correction applied + /// + public Double PostCorrectionVolumeRaw; + + + /// + /// Get result as string + /// + /// + public override String ToString() + { + var sb = new StringBuilder(); + sb.Append($"Status={StatusBendU0}").Append(",") + .Append($"Installation={InstallationType}").Append(",") + .Append($"Factor={CorrectionFactor_percent}").Append(",") + .Append($"PreVolume={PreCorrectionVolumeRaw}").Append(",") + .Append($"PostVolume={PostCorrectionVolumeRaw}"); + + return sb.ToString(); + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/MeasurementRecords/CalibrationRecord.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/MeasurementRecords/CalibrationRecord.cs new file mode 100644 index 000000000..5211dff81 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/MeasurementRecords/CalibrationRecord.cs @@ -0,0 +1,151 @@ +using System; +using System.Text; +using Xylem.Common.Metrology.Measurements; + +namespace Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages.MeasurementRecords +{ + /// + /// + /// hold streaming record for protocol H (contains calibration record) + /// + public class CalibrationRecord : MeasurementRecord + { + + /// + /// record validation + /// + public UInt16 Validation; + + /// + /// total time of flight in seconds + /// + public Double TotalTimeOfFlightS; + + /// + /// delta time of flight in seconds + /// + public Double DeltaTimeOfFlightS; + + + /// + /// total time of flight in cordonel units + /// + public Int32 RawTotalTimeOfFlight; + + /// + /// delta time of flight in cordonel units + /// + public Int32 RawDeltaTimeOfFlight; + + /// + /// volume scale (default: 1024) means + /// 1024digits = 1ml + /// + public Double VolumeScaleRawPerMl; + + /// + /// volume factor to convert raw to m³ + /// uses 1E-6 (ml to m³) / VolumeScaleRawPerMl + /// + public Double VolumeFactorRawToQm; + + /// + /// raw volume between two samples + /// + public Double DeltaVolumeRaw; + + /// + /// calculated out of dRawVolume * VolumeFactorRawToQm . + /// in cubic meters + /// + public Double DeltaVolumeQm; + + /// + /// accumulated raw volume + /// + public Double AccuVolumeRaw; + + /// + /// sample interval between two samples in seconds + /// + public Double SampleIntervalS; + + /// + /// high threshold amplitude in V + /// + public Double AmplitudeUpV; + + /// + /// low threshold amplitude in V + /// + public Double AmplitudeDownV; + + /// + /// high ratio for pulse width + /// + public Double PulseWidthRatioUp; + + /// + /// low ratio for pulse width + /// + public Double PulseWidthRatioDown; + + /// + /// raw temperature + /// + public Double TemperatureRaw; + + /// + /// temperature scale + /// + public Double TemperaturePowFactor; + + /// + /// calculated temperature in degree C + /// + public Double TemperatureDegC; + + /// + /// Get result as string + /// + /// + public override String ToString() + { + var sb = new StringBuilder(); + sb.Append($"Channel={Channel}").Append(",") + .Append($"Validation={Validation}").Append(",") + .Append($"TotalTimeOfFlightS={TotalTimeOfFlightS}").Append(",") + .Append($"DeltaTimeOfFlightS={DeltaTimeOfFlightS}").Append(",") + .Append($"DeltaVolumeRaw={DeltaVolumeRaw}").Append(",") + + .Append($"VolumeScaleRawPerMl={VolumeScaleRawPerMl}").Append(",") + .Append($"VolumeFactorRawToQm={VolumeFactorRawToQm}").Append(",") + .Append($"DeltaVolumeRaw={DeltaVolumeRaw}").Append(",") + .Append($"DeltaVolumeQm={DeltaVolumeQm}").Append(",") + .Append($"AccuVolumeRaw={AccuVolumeRaw}").Append(",") + .Append($"VolumeCm={VolumeCm}").Append(",") + .Append($"AccuDutOverflowVolumeCm={OverflowVolumeCm}").Append(",") + + .Append($"SampleIntervalS={SampleIntervalS}").Append(",") + .Append($"AmplitudeUpV={AmplitudeUpV}").Append(",") + .Append($"AmplitudeDownV={AmplitudeDownV}").Append(",") + .Append($"PulseWidthRatioUp={PulseWidthRatioUp}").Append(",") + .Append($"PulseWidthRatioDown={PulseWidthRatioDown}").Append(",") + + .Append($"TemperatureRaw={TemperatureRaw}").Append(",") + .Append($"TemperaturePowFactor={TemperaturePowFactor}").Append(",") + .Append($"TemperatureDegC={TemperatureDegC}").Append(",") + + .Append($"TimeS={TimeS}").Append(",") + .Append($"OverflowTimeS={OverflowTimeS}").Append(",") + + .Append($"CRC={Crc}").Append(",") + .Append($"IsValid={IsValid}").Append(",") + + .Append($"ReceivedTimeUtc={ReceivedTime}").Append(",") + .Append($"DecodedTimeUtc={DecodedTime}").Append(",") + .Append($"SyncMarkRecord={SyncMarkRecord}"); + return sb.ToString(); + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/MeasurementRecords/FlowTestRecord.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/MeasurementRecords/FlowTestRecord.cs new file mode 100644 index 000000000..17242528d --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/MeasurementRecords/FlowTestRecord.cs @@ -0,0 +1,32 @@ +using System; +using System.Text; +using Xylem.Common.Metrology.Measurements; + +namespace Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages.MeasurementRecords +{ + /// + /// + /// struct to hold Led record for protocol F (contains measurement record) + /// + public class FlowTestRecord : MeasurementRecord + { + /// + /// Get result as string + /// + /// + public override String ToString() + { + var sb = new StringBuilder(); + sb = sb.Append($"VolumeCm={VolumeCm}").Append(",") + .Append($"OverflowVolumeCm={OverflowVolumeCm}").Append(",") + .Append($"TimeS={TimeS}").Append(",") + .Append($"OverflowTimeS={OverflowTimeS}").Append(",") + .Append($"CRC={Crc}").Append(",") + .Append($"IsValid={IsValid}").Append(",") + .Append($"ReceivedTimeUtc={ReceivedTime}").Append(",") + .Append($"DecodedTimeUtc={DecodedTime}").Append(",") + .Append($"SyncMarkRecord={SyncMarkRecord}"); + return sb.ToString(); + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/RadioConfigurationParams.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/RadioConfigurationParams.cs new file mode 100644 index 000000000..1d96b541f --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/RadioConfigurationParams.cs @@ -0,0 +1,49 @@ +using System; + +namespace Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages +{ + + /// + /// All parameters needed for radio setup + /// + public class RadioConfigurationParams + { + /// + /// Pcb identification + /// + public String PcbId; + /// + /// Serial number + /// + public UInt32 SerialNumber; + /// + /// Radio address + /// + public UInt32 RadioAddress; + /// + /// Power level + /// + public UInt32 PowerLevel; + /// + /// Power level option + /// + public UInt32 PowerLevelOption; + /// + /// New code for impedance + /// + public UInt32 ImpedanceCodeNew; + /// + /// Impedance code option + /// + public UInt32 ImpedanceCodeOption; + /// + /// Encryption key + /// + public Byte[] EncryptionKey; + + /// + /// Frequency offset + /// + public Int16? FrqOffset { get; set; } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/RegisterToDb.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/RegisterToDb.cs new file mode 100644 index 000000000..cdd4d56b2 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/RegisterToDb.cs @@ -0,0 +1,29 @@ +using System; + +namespace Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages +{ + /// + /// + /// + public class RegisterToDb + { + /// + /// Register address and value to DB + /// + /// + /// + public RegisterToDb(Byte[] adresse, Byte[] value) + { + Adresse = adresse; + Value = value; + } + /// + /// Address + /// + public Byte[] Adresse; + /// + /// Value + /// + public Byte[] Value; + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/TempTofCalc.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/TempTofCalc.cs new file mode 100644 index 000000000..ef9ffabef --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/DataPackages/TempTofCalc.cs @@ -0,0 +1,27 @@ +using System; + +namespace Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages +{ + /// + /// Temperature Time Of Flight calculation + /// + public struct TempTofCalc + { + private DateTime _refDate; + private Double _refTemp; + private Double _tof; + + /// + /// Reference data + /// + public DateTime RefDate { get => _refDate; set => _refDate = value; } + /// + /// Reference temperature + /// + public Double RefTemp { get => _refTemp; set => _refTemp = value; } + /// + /// Time Of Flight + /// + public Double Tof { get => _tof; set => _tof = value; } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/GenesisConfig/CommunicationConfig.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/GenesisConfig/CommunicationConfig.cs new file mode 100644 index 000000000..ef8d43867 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/GenesisConfig/CommunicationConfig.cs @@ -0,0 +1,88 @@ +using System; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.GenesisConfig +{ + /// + /// Setup of communication timeouts and retries for request and streaming protocol, + /// this timeout starts the retry. + /// + /// The individual timings of the hardware will be covert by the + /// + /// injected by the different transmit protocols: + /// + /// + /// + /// + /// + public static class CommunicationConfig + { + private static Int32 _requestRetries = DefaultRequestRetries; + + /// + /// Maximum retries for one command at request protocol + /// at missing response. + /// + public static Int32 RequestRetries + { + get => _requestRetries; + set => _requestRetries = value > MaxRequestRetries ? MaxRequestRetries : value; + } + + /// + /// Acceptable error code as valid answer to skip retries, + /// this might be 0x0004 for not installed application. + /// + public const UInt16 SkipRetryErrorCode = 0x0004; + + /// + /// Default retries for one command at request protocol + /// at missing response. + /// + public const Int32 DefaultRequestRetries = 2; + + /// + /// Limit retries to this value. + /// + public const Int32 MaxRequestRetries = 6; + + /// + /// Send delay between records in milliseconds before + /// new record is going to be sent or between retries, + /// this time is independent of the timeout, it is + /// used to delay the next send record after successful + /// response from meter. + /// + public const Int32 InterRecordSendDelayMs = 5; + //public const Int32 InterRecordSendDelayMs = 50; + + /// + /// Timeout before retry will be initiated in milliseconds + /// for the response of the request protocol. This is an + /// offset value, the transmission specific timing will be + /// added from the transmit protocol timeout. This timeout + /// will be multiplied with the (retry + 1)! + /// + public const Int32 ResponseTimeoutMs = 250; +// public const Int32 ResponseTimeoutMs = 100; + + /// + /// Timeout before retry will be initiated if the meter is + /// not ready indicated by a wakeup-message or decoding error. + /// + public const Int32 BusyTimeoutMs = 500; +// public const Int32 BusyTimeoutMs = 1000; + + /// + /// Time to avoid automatic lock off of genesis device due to + /// missing communication with request protocol in seconds + /// + public const Int32 KeepSessionTimeS = 60 * 3; + + /// + /// Time to update intermediate record for overflow detection + /// during a measurement and update of short-term measurement + /// in seconds + /// + public const Int32 MeasurementUpdateTimeS = 10; + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/GenesisConfig/Const/MeterConfig.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/GenesisConfig/Const/MeterConfig.cs new file mode 100644 index 000000000..e9236bbb5 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/GenesisConfig/Const/MeterConfig.cs @@ -0,0 +1,24 @@ +using System; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.GenesisConfig.Const +{ + public static class MeterConfig + { + /// + /// Calibration Factor on init from meter + /// Is also the factor to convert CalibrationFactors to readable number, + /// this is the default if the ProccessConfig.json file cannot be read. + /// + public const UInt16 CalibrationDefault = 15625; + + /// + /// Channels for calibration + /// + public const Int32 CalibChannels = 3; + + /// + /// Maximum calibration value in percent + /// + public const Double DefaultMaxCalibFactorTolerancePercent = 100.0; + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/GenesisConfig/MeterConfig.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/GenesisConfig/MeterConfig.cs new file mode 100644 index 000000000..e844324fe --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/GenesisConfig/MeterConfig.cs @@ -0,0 +1,86 @@ +using System; +using System.IO; +using Newtonsoft.Json; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.GenesisConfig +{ + /// + /// Configuration of meter + /// + public class MeterConfig + { + /// + /// ctor + /// + public MeterConfig() + { + UseRegisterWatchService = false; + UseErrorLogger = false; + } + + /// + public MeterConfig(String file) + { + var configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(Xylem.Common.Hardware.WaterMeter.Genesis), file); + + if (!File.Exists(configFile)) + { + configFile = Path.Combine(file); + if (!File.Exists(configFile)) + { + UseRegisterWatchService = false; + UseErrorLogger = false; + return; + } + } + + using (var tr = new StreamReader(configFile)) + { + var meterConfig = JsonConvert.DeserializeObject(tr.ReadToEnd()); + UseRegisterWatchService = meterConfig.UseRegisterWatchService; + RegisterWatchServiceUrl = meterConfig.RegisterWatchServiceUrl; + UseMinMaxCheck = meterConfig.UseMinMaxCheck; + UseErrorLogger = meterConfig.UseErrorLogger; + ErrorLoggerServiceUrl = meterConfig.ErrorLoggerServiceUrl; + UseCalibrationLogger = meterConfig.UseCalibrationLogger; + CalibrationLoggerServiceUrl = meterConfig.CalibrationLoggerServiceUrl; + } + + } + /// + /// + /// + public Boolean UseRegisterWatchService { get; set; } + + /// + /// + /// + public String RegisterWatchServiceUrl { get; set; } + + /// + /// + /// + public Boolean UseMinMaxCheck { get; set; } + + /// + /// + /// + public Boolean UseErrorLogger { get; set; } + + /// + /// + /// + public String ErrorLoggerServiceUrl { get; set; } + + /// + /// + /// + public Boolean UseCalibrationLogger { get; set; } + + /// + /// + /// + public String CalibrationLoggerServiceUrl { get; set; } + + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/GenesisConfig/ProccessConfig.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/GenesisConfig/ProccessConfig.cs new file mode 100644 index 000000000..8918688ba --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/GenesisConfig/ProccessConfig.cs @@ -0,0 +1,114 @@ +using System; +using System.ComponentModel; +using System.IO; +using Newtonsoft.Json; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.GenesisConfig +{ + /// + /// Configuration of meter + /// + public class ProccessConfig + { + /// + /// ctor + /// + public ProccessConfig() + { + UseRegisterWatchService = false; + UseErrorLogger = false; + AutoUpdateFiles = false; + ProductionMode = true; + } + + /// + public ProccessConfig(String file) + { + var configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(Xylem.Common.Hardware.WaterMeter.Genesis), file); + + if (!File.Exists(configFile)) + { + configFile = Path.Combine(file); + if (!File.Exists(configFile)) + { + UseRegisterWatchService = false; + UseErrorLogger = false; + return; + } + } + + using (var tr = new StreamReader(configFile)) + { + var meterConfig = JsonConvert.DeserializeObject(tr.ReadToEnd()); + UseRegisterWatchService = meterConfig.UseRegisterWatchService; + RegisterWatchServiceUrl = meterConfig.RegisterWatchServiceUrl; + UseMinMaxCheck = meterConfig.UseMinMaxCheck; + UseErrorLogger = meterConfig.UseErrorLogger; + ErrorLoggerServiceUrl = meterConfig.ErrorLoggerServiceUrl; + UseCalibrationLogger = meterConfig.UseCalibrationLogger; + CalibrationLoggerServiceUrl = meterConfig.CalibrationLoggerServiceUrl; + AutoUpdateFiles = meterConfig.AutoUpdateFiles; + ProductionMode = meterConfig.ProductionMode; + + } + + } + + public void Update(String file) + { + var configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(Xylem.Common.Hardware.WaterMeter.Genesis), file); + + if (!File.Exists(configFile)) + { + configFile = Path.Combine(file); + } + + File.WriteAllText(file, JsonConvert.SerializeObject(this)); + } + + /// + /// + /// + public Boolean UseRegisterWatchService { get; set; } + + /// + /// + /// + public String RegisterWatchServiceUrl { get; set; } + + /// + /// + /// + public Boolean UseMinMaxCheck { get; set; } + + /// + /// + /// + public Boolean UseErrorLogger { get; set; } + + /// + /// + /// + public String ErrorLoggerServiceUrl { get; set; } + + /// + /// + /// + public Boolean UseCalibrationLogger { get; set; } + + /// + /// + /// + public String CalibrationLoggerServiceUrl { get; set; } + + /// + /// + /// + public bool AutoUpdateFiles { get; set; } + + [DefaultValue(true)] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + + public bool ProductionMode { get; set; } = true; + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/GenesisConfig/ProcessConfig.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/GenesisConfig/ProcessConfig.cs new file mode 100644 index 000000000..7d7808685 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/GenesisConfig/ProcessConfig.cs @@ -0,0 +1,111 @@ +using System; +using System.ComponentModel; +using System.IO; +using Newtonsoft.Json; + + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.GenesisConfig +{ + /// + /// Configuration of meter + /// + public class ProcessConfig + { + /// + /// Load configuration file from AppRoaming or working directory + /// + public Boolean ReadProcessConfig() + { + // Check for AppRoaming + var configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + nameof(Xylem.Common.Hardware.WaterMeter.Genesis), ProgramConfig.MeterConfigFileName); + + if (!File.Exists(configFile)) + { + // Take actual working directory + configFile = Path.Combine(ProgramConfig.MeterConfigFileName); + if (!File.Exists(configFile)) + { + UseRegisterWatchService = false; + UseErrorLogger = false; + AutoUpdateFiles = false; + ProductionMode = true; + return false; + } + } + + using (var tr = new StreamReader(configFile)) + { + var meterConfig = JsonConvert.DeserializeObject(tr.ReadToEnd()); + UseRegisterWatchService = meterConfig.UseRegisterWatchService; + RegisterWatchServiceUrl = meterConfig.RegisterWatchServiceUrl; + UseMinMaxCheck = meterConfig.UseMinMaxCheck; + UseErrorLogger = meterConfig.UseErrorLogger; + ErrorLoggerServiceUrl = meterConfig.ErrorLoggerServiceUrl; + UseCalibrationLogger = meterConfig.UseCalibrationLogger; + CalibrationLoggerServiceUrl = meterConfig.CalibrationLoggerServiceUrl; + AutoUpdateFiles = meterConfig.AutoUpdateFiles; + ProductionMode = meterConfig.ProductionMode; + } + + return true; + } + + /// + /// Write file to AppRoaming and backup it to actual working directory + /// + public void Update() + { + var configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + nameof(Xylem.Common.Hardware.WaterMeter.Genesis), ProgramConfig.MeterConfigFileName); + + File.WriteAllText(configFile, JsonConvert.SerializeObject(this)); + File.WriteAllText(ProgramConfig.MeterConfigFileName, JsonConvert.SerializeObject(this)); + } + + /// + /// + /// + public Boolean UseRegisterWatchService { get; set; } + + /// + /// + /// + public String RegisterWatchServiceUrl { get; set; } + + /// + /// + /// + public Boolean UseMinMaxCheck { get; set; } + + /// + /// + /// + public Boolean UseErrorLogger { get; set; } + + /// + /// + /// + public String ErrorLoggerServiceUrl { get; set; } + + /// + /// + /// + public Boolean UseCalibrationLogger { get; set; } + + /// + /// + /// + public String CalibrationLoggerServiceUrl { get; set; } + + /// + /// + /// + public Boolean AutoUpdateFiles { get; set; } + + [DefaultValue(true)] + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + + public Boolean ProductionMode { get; set; } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/GenesisConfig/SirtConfig.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/GenesisConfig/SirtConfig.cs new file mode 100644 index 000000000..211ae3b3e --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/GenesisConfig/SirtConfig.cs @@ -0,0 +1,96 @@ +using System; +using System.IO; +using Newtonsoft.Json; + + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.GenesisConfig +{ + /// + /// Configuration of SIRT + /// + public class SirtConfig + { + /// + /// Load SIRT configuration file from AppRoaming or working directory + /// + public Boolean ReadSirtConfig() + { + // Check for AppRoaming + var configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + nameof(Xylem.Common.Hardware.WaterMeter.Genesis), ProgramConfig.SirtConfigFileName); + + if (!File.Exists(configFile)) + { + // Take actual working directory + configFile = Path.Combine(ProgramConfig.SirtConfigFileName); + if (!File.Exists(configFile)) + { + SirtComport433MHz = null; + SirtComport868MHz = null; + ServiceSirtComport868MHz = null; + ServiceSirtComport433MHz = null; + SirtBoxNo = null; + StationId = null; + return false; + } + } + + using (var tr = new StreamReader(configFile)) + { + var sirtConfig = JsonConvert.DeserializeObject(tr.ReadToEnd()); + SirtComport433MHz = sirtConfig.SirtComport433MHz; + SirtComport868MHz = sirtConfig.SirtComport868MHz; + ServiceSirtComport433MHz = sirtConfig.ServiceSirtComport433MHz; ; + ServiceSirtComport868MHz = sirtConfig.ServiceSirtComport868MHz; ; + SirtBoxNo = sirtConfig.SirtBoxNo; + StationId = sirtConfig.StationId; + } + + return true; + } + + /// + /// Write file to AppRoaming and backup it to actual working directory + /// + public void Update() + { + var configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + nameof(Xylem.Common.Hardware.WaterMeter.Genesis), ProgramConfig.SirtConfigFileName); + + File.WriteAllText(configFile, JsonConvert.SerializeObject(this)); + File.WriteAllText(ProgramConfig.SirtConfigFileName, JsonConvert.SerializeObject(this)); + } + + /// + /// Comport for SIRT interface with 433MHz radio frequency + /// + public String SirtComport433MHz { get; set; } + + /// + /// Comport for SIRT interface with 868MHz radio frequency + /// + public String SirtComport868MHz { get; set; } + + /// + /// Box number for calibration settings + /// + public Int32? SirtBoxNo { get; set; } + + /// + /// Station ID where the SIRT is used + /// + public Int32? StationId { get; set; } + + + /// + /// Service Comport for SIRT interface with 433MHz radio frequency + /// + public String ServiceSirtComport433MHz { get; set; } + + /// + /// Service Comport for SIRT interface with 868MHz radio frequency + /// + public String ServiceSirtComport868MHz { get; set; } + + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/EventArguments/BasePortDataEventArgs.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/EventArguments/BasePortDataEventArgs.cs new file mode 100644 index 000000000..062795661 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/EventArguments/BasePortDataEventArgs.cs @@ -0,0 +1,46 @@ +using System; +using Xylem.Common.CommonCore.Consts; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments +{ + /// + public abstract class BasePortDataEventArgs : EventArgs, IPortDataEventArgs + { + /// + /// Marker for incoming record at time of the PC + /// + private DateTimeOffset _receivedTime = default(DateTimeOffset); + + /// + public abstract Object GetData(); + + /// + public abstract void SetData(Object data); + + /// + public void SetReceivedTime(DateTimeOffset receivedTime) + { + _receivedTime = receivedTime; + } + + /// + public DateTimeOffset GetReceivedTime() + { + return _receivedTime; + } + + private SyncMarkRecord _syncMarkRecord; + + /// + public void SetSyncMarkRecord(SyncMarkRecord syncMarkRecord) + { + _syncMarkRecord = syncMarkRecord; + } + + /// + public SyncMarkRecord GetSyncMarkRecord() + { + return _syncMarkRecord; + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/EventArguments/IPortDataEventArgs.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/EventArguments/IPortDataEventArgs.cs new file mode 100644 index 000000000..aa40e28ca --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/EventArguments/IPortDataEventArgs.cs @@ -0,0 +1,48 @@ +using System; +using Xylem.Common.CommonCore.Consts; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments +{ + + /// + /// Interface for port data event arguments + /// + public interface IPortDataEventArgs + { + /// + /// Read data + /// + /// + Object GetData(); + + /// + /// Write data + /// + /// + void SetData(Object data); + + /// + /// Reading received time + /// + /// + DateTimeOffset GetReceivedTime(); + + /// + /// Writing received time + /// + /// + void SetReceivedTime(DateTimeOffset receivedTime); + + /// + /// Setting the data marker + /// + /// + void SetSyncMarkRecord(SyncMarkRecord syncMarkRecord); + + /// + /// Getting the data marker + /// + /// + SyncMarkRecord GetSyncMarkRecord(); + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/EventArguments/ListBytePortDataEventArgs.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/EventArguments/ListBytePortDataEventArgs.cs new file mode 100644 index 000000000..88a49fc45 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/EventArguments/ListBytePortDataEventArgs.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using Xylem.Common.CommonCore.Consts; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments +{ + /// + public class ListBytePortDataEventArgs : BasePortDataEventArgs + { + private List _data; + + /// + public ListBytePortDataEventArgs(List data, DateTimeOffset readTimeStampPc = default(DateTimeOffset), + SyncMarkRecord syncMarkRecord = SyncMarkRecord.SkipDecoding) + { + _data = data; + SetSyncMarkRecord(syncMarkRecord); + SetReceivedTime(readTimeStampPc); + } + + /// + public override Object GetData() + { + return _data; + } + + /// + public override void SetData(Object data) + { + _data = (List)data; + } + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/EventArguments/StringPortDataEventArgs.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/EventArguments/StringPortDataEventArgs.cs new file mode 100644 index 000000000..6b35e146c --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/EventArguments/StringPortDataEventArgs.cs @@ -0,0 +1,34 @@ +using System; +using Xylem.Common.CommonCore.Consts; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments +{ + /// + public class StringPortDataEventArgs : BasePortDataEventArgs + { + private String _data; + + /// + public StringPortDataEventArgs(String data, DateTimeOffset readTimeStampPc = default(DateTimeOffset), + SyncMarkRecord syncMarkRecord = SyncMarkRecord.SkipDecoding) + { + _data = data; + SetSyncMarkRecord(syncMarkRecord); + SetReceivedTime(readTimeStampPc); + } + + /// + public override Object GetData() + { + return _data; + } + + /// + public override void SetData(Object data) + { + _data = (String)data; + } + + + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/IPort.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/IPort.cs new file mode 100644 index 000000000..ad7a026a9 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/IPort.cs @@ -0,0 +1,63 @@ +using System; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments; +using Xylem.Common.CommonCore.Consts; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore +{ + /// + /// Interface for Ports + /// + public interface IPort + { + /// + /// event for record received, either bytes or string + /// + event EventHandler OnRawRecordReceived; + + /// + /// event for record received, either bytes or string + /// + event EventHandler OnRawRecordSendOut; + /// + /// set specific mark + /// and flush or delete all incoming byte from buffer when SyncMarkRecord is or + /// + /// + /// + void SynchronizeReceiveBuffer(SyncMarkRecord syncMarkRecord); + + /// + /// if the port needs stuff to open, always open for better logical handling + /// + void Open(); + + /// + /// close and dispose all connections + /// + void Dispose(); + + /// + /// indicates if the Port is open (also on Ports that did not have an open state) + /// + /// + Boolean IsOpen(); + + /// + /// Write byte[] to the Stream/Port on Child Class + /// wrapped with base class error handling + /// + /// Array of bytes to write + void PortWrite(Byte[] data); + + /// + /// discard all buffers + /// + void Clear(); + + /// + /// Return the port name + /// + /// name of the port as string + String GetPortName(); + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/PortConfig.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/PortConfig.cs new file mode 100644 index 000000000..08f6558c1 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/PortConfig.cs @@ -0,0 +1,63 @@ +using System; +using System.IO.Ports; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore +{ + /// + /// Collection of port settings + /// + public struct PortConfig + { + private String _portName; + /// + /// Assigns a port name and creates the serial port object + /// Port name e.g. "COM2" + /// + public String PortName + { + get => + //if the serial port object does not exists + GetSerialPort() == null ? "NA" : _portName; + set + { + if (value == null) return; + + _portName = value; + //create a serial port object + if (GetSerialPort() == null) + { + SetSerialPort(new SerialPort(value)); + } + else + { + GetSerialPort().PortName = value; + } + } + + } + + /// + /// setup of port type from name (referenced as "Type" in config file) + /// + public String Type { get; set; } + + + private SerialPort _serialPort; + + /// + /// using dotNets for connection + /// + public SerialPort GetSerialPort() + { + return _serialPort; + } + + /// + /// using dotNets for connection + /// + private void SetSerialPort(SerialPort value) + { + _serialPort = value; + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/SlotConfig.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/SlotConfig.cs new file mode 100644 index 000000000..21647bc22 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/SlotConfig.cs @@ -0,0 +1,30 @@ +using System; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore +{ + /// + /// Container for port configuration + /// + public class SlotConfig + { + /// + /// Slot number + /// + public Int32 Slot { get; set; } + + /// + /// Request port settings + /// + public PortConfig Request { get; set; } + + /// + /// Streaming port settings + /// + public PortConfig Streaming { get; set; } + + /// + /// Streaming port settings + /// + public SlotType Type { get; set; } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/SlotType.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/SlotType.cs new file mode 100644 index 000000000..e57d0bd95 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/SlotType.cs @@ -0,0 +1,17 @@ +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore +{ + /// + /// Definition of type for slot + /// + public enum SlotType + { + /// + /// Cordonel used as DUT with two serial ports + /// + DutMeter, + /// + /// Cordonel used as temperature meter with one serial port streaming the temperature + /// + TemperatureMeter, + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/TransmitPortSettings.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/TransmitPortSettings.cs new file mode 100644 index 000000000..13efaf6f8 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/PortCore/TransmitPortSettings.cs @@ -0,0 +1,109 @@ +using System; +using System.IO.Ports; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore +{ + /// + /// Store Port settings set most likely from transmit protocol + /// + public struct TransmitPortSettings + { + /// + /// The transmission protocol needs to inform the communication port how to set up, + /// this is the data container. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// - String delimiter for ASCII to support others than LF "\n", + /// - Flexible DataBits, + /// - Flexible parity. + /// + public TransmitPortSettings(Byte? protSyncByte, UInt16? protLengthIndex, UInt16 protAddLength, Int32 responseTimeoutMs, + UInt32 baudRate, UInt32? receiveBufferFlushThreshold, Boolean doubleSyncByte = false, Char stringDelimiter = '\n', + Int32 dataBits = 8, Parity parity = Parity.None ) + { + ProtSyncByte = protSyncByte; + ProtLengthIndex = protLengthIndex; + ProtAddLength = protAddLength; + ResponseTimeoutMs = responseTimeoutMs; + BaudRate = baudRate; + ReceiveBufferFlushThreshold = receiveBufferFlushThreshold; + DoubleSyncByte = doubleSyncByte; + StringDelimiter = stringDelimiter; + DataBits = dataBits; + Parity = parity; + } + /// + /// String delimiter for ASCII to support others than LF "\n" + /// + public readonly Char StringDelimiter; + + /// + /// Flexible DataBits to support 7 bits. + /// + public readonly Int32 DataBits; + + /// + /// Flexible parity + /// + public readonly Parity Parity; + + /// + /// start of receiving synchronization byte at byte receive routine + /// syncByte == null: use the readLine routine (ASCII) and NOT the BYTE routine, + /// lengthPosition and additionalLength are not used + /// + public readonly Byte? ProtSyncByte; + + /// + /// position of length information field in received BYTE record + /// lengthIndex == null: take the constant receive length of additionalLength + /// because the record doesn't contain length information + /// + public readonly UInt16? ProtLengthIndex; + + /// + /// additional record length NOT covert by the record length information field + /// lengthIndex == null: constant length for received record + /// Being used for the BYTE records indicated by a valid syncByte, + /// not being used for ASCII records. + /// + public readonly UInt16 ProtAddLength; + + /// + /// Response time out in milliseconds + /// + public readonly Int32 ResponseTimeoutMs; + + /// + /// BaudRate for Port + /// + public readonly UInt32 BaudRate; + + /// + /// lower threshold for buffer flushing if dataMarker != SkipDecoding + /// receiveBufferFlushThreshold == null: never flush the communication buffer + /// receiveBufferFlushThreshold == 0: flush always the communication buffer + /// receiveBufferFlushThreshold == x: flush communication buffer if it exceeds x Byte + /// waste old records if an up-to-date record is being needed for start/stop synchronization + /// of a measurement. This is the level at which the old data have to be flushed because the + /// data have been dammed up in the communication port input buffer which means they are to + /// old for synchronization purposes. + /// /// + public readonly UInt32? ReceiveBufferFlushThreshold; + + /// + /// a special implementation may require the doubling of the sync byte to re-synchronize + /// + public readonly Boolean DoubleSyncByte; + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/SerialPorts/BaseSerialPort.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/SerialPorts/BaseSerialPort.cs new file mode 100644 index 000000000..d9f2163d2 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/SerialPorts/BaseSerialPort.cs @@ -0,0 +1,702 @@ +using System; +using System.Collections.Generic; +using System.IO.Ports; +using System.Threading; +using log4net; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments; +using Xylem.Common.CommonCore.Consts; + + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.SerialPorts +{ + /// + /// All Ports must use BasePort as an base class + /// its support some wrapping event/function handling + /// + public abstract class BaseSerialPort : IPort, IDisposable + { + private static readonly ILog _byteDataLogger = LogManager.GetLogger(typeof(BaseSerialPort)); + internal ILog AsciiDataLogger = LogManager.GetLogger("LedRawData"); + + /// + public virtual event EventHandler OnRawRecordReceived; + + /// + /// The port is not assigned. + /// + public const String PortNotAssigned = "NA"; + + /// + public virtual event EventHandler OnRawRecordSendOut; + + //initially do not signal event + private readonly AutoResetEvent _onSyncReceiveThread = new AutoResetEvent(false); + private readonly Thread _receiveThread; + private readonly CancellationTokenSource _receiveToken = new CancellationTokenSource(); + + /// + /// Delay between bytes if receiving has started in milliseconds + /// + private const Int32 InterByteReadDelayMs = 50; + + /// + /// internal SerialPort class + /// + private readonly SerialPort _serialPort; + + + /// + /// Activate raw record recording + /// + public Boolean RecordStreamingRawData = false; + //public Boolean RecordStreamingRawData + //{ + // get => _recordStreamingRawData; + // set => _recordStreamingRawData = value; + //} + + /// + /// date and time of incoming first date interrupt on serial IO to set the + /// PC time-stamp to the record + /// + private DateTimeOffset _receiveTimeStampPc; + + /// + /// Individual port settings depending on transmit protocol + /// + protected TransmitPortSettings PortSettingsForTransmitProtocol; + + /// + public String GetPortName() + { + return _serialPort.PortName; + } + + /// + /// store a identification of a port, this contains the Slot, Port, Protocol and Type + /// e.g. "Slot:1, Port:COM4, Protocol:Request, Type:IrDA -" + /// + public String Ident; + + /// + /// ctor for SerialComPort with and full constructed properties + /// + /// set port as unique + /// Class for communication, all parameters for serial communication needs to be set + /// Class for store all parameters for serial communication needs to be set came from transmit protocol + protected BaseSerialPort(String ident, SerialPort serialPort, TransmitPortSettings setting) + { + Ident = ident; + + //communication port settings + _serialPort = serialPort; + _serialPort.BaudRate = (Int32)setting.BaudRate; + + PortSettingsForTransmitProtocol = setting; + + //assign thread to loop + _receiveThread = new Thread(ReadingThreadLoop) { Name = $"{Ident} Reading thread" }; + + } + + + public void RefreshLogger() + { + + } + + + + /// + /// received bytes in buffer of SerialComPort, calls base function + /// + /// number of bytes in Rx buffer + protected Int32 BytesToRead() + { + try + { + return _serialPort.BytesToRead; + } + catch (Exception ex) + { + _byteDataLogger.Error(ex); + return 0; + + } + + } + + /// + /// read single byte from Rx buffer of SerialComPort, calls base function + /// + /// + protected Int32 ReadByte() + { + + try + { + return _serialPort.ReadByte(); + } + catch (Exception ex) + { + _byteDataLogger.Error(ex); + return 0; + + } + } + + /// + public void PortWrite(Byte[] record) + { + try + { + SpecificPortWrite(record); + } + catch (Exception ex) + { + throw new SystemException($"{Ident} Communication error while sending data to port", ex); + } + } + + /// + /// + /// flush Rx and Tx buffer of SerialComPort, calls base functions + /// + public void Clear() + { + try + { + if (!_serialPort.IsOpen) + { + return; + } + + _serialPort.DiscardInBuffer(); + _serialPort.DiscardOutBuffer(); + } + catch (Exception ex) + { + _byteDataLogger.Error(ex); + + } + } + + /// + /// Dispose serial port + /// + /// + /// - Dispose procedure changed. + /// + public void Dispose() + { + try + { + + //remove registration for receive delegate + _serialPort.DataReceived -= PhysicalDataReceived; + + //Cancel send and receive tokens + _receiveToken.Cancel(); + + //Run thread again to notice CancellationToken has changed + _onSyncReceiveThread.Set(); + + // dispose directly called from here to overcome glitches caused by USB to serial interface + _serialPort.Dispose(); + + } + catch (Exception ex) + { + _byteDataLogger.Error(ex); + } + } + + + /// + public void Open() + { + try + { + //set receive interrupt threshold for immediate execution + _serialPort.ReceivedBytesThreshold = 1; + _serialPort.DataReceived += PhysicalDataReceived; + + //start reading thread, will be immediately put to waitSleepJoin in ReadingThreadLoop + //to avoid side effects with RFID communication + + ThreadWatcher.Instance.Start(_receiveThread); + + if (!_serialPort.IsOpen) + { + _serialPort.Open(); + if (!_serialPort.IsOpen) + { + _byteDataLogger.Error($"{Ident} Port cannot be opened"); + throw new ApplicationException($"{Ident} Port cannot be opened"); + } + _byteDataLogger.Info($"{Ident} Port is opened"); + } + else + { + _byteDataLogger.Warn($"{Ident} Port was already opened"); + } + } + catch (Exception ex) + { + _byteDataLogger.Error($"{Ident} {ex.Message}"); + throw new ApplicationException($"{Ident} {ex.Message}"); + } + } + + /// + /// Getting the base port + /// + protected SerialPort GetBaseComport() + { + return _serialPort; + } + + /// + /// Fill FIFO with received data + /// + /// + /// - Initial + /// + /// + /// - Time stamp added + /// + /// + /// - Timeout moved from to PhysicalDataReceived to avoid + /// of serial port while waiting on first incoming + /// data. + /// + /// + /// - Inter byte read delay used instead of response delay!!! + /// + + /// + /// + public virtual void PhysicalDataReceived(Object sender, SerialDataReceivedEventArgs e) + { + if (!_receiveToken.IsCancellationRequested) + { + //remove event delegate to avoid repeated execution during one record + _serialPort.DataReceived -= PhysicalDataReceived; + + //put the actual time stamp to this record being able to assign it correctly + //even if the decoding is delayed. This time stamp will be used to do the first + //synchronization at start and stop of the measurement. Therefore, the serial buffer + //has to be flushed in advance to avoid wrong time stamp to "old" records + _receiveTimeStampPc = DateTimeOffset.UtcNow; + + //start timeout for hanging read communication called inter byte delay + _serialPort.ReadTimeout = InterByteReadDelayMs; + // _serialPort.ReadTimeout = PortSettingsForTransmitProtocol.ResponseTimeoutMs; + + //wake up the reading threat from JoinWaitSleep + _onSyncReceiveThread.Set(); + } + } + + /// + /// + /// + private SyncMarkRecord _syncMarkRecord; + + /// + public void SynchronizeReceiveBuffer(SyncMarkRecord syncMarkRecord) + { + try + { + + //not in test bench situation only for temp logging + if (syncMarkRecord == SyncMarkRecord.FlushBuffer) + { + FlushBuffer(); + return; + } + //DecodeEveryPackage is ongoing and SkipDecoding is requested = do nothing + if (_syncMarkRecord == SyncMarkRecord.DecodeEveryPackage && + syncMarkRecord == SyncMarkRecord.SkipDecoding) + { + return; + } + //deny intermediate record when a start or end record is requested + if (syncMarkRecord == SyncMarkRecord.DecodeIntermediate && + (_syncMarkRecord == SyncMarkRecord.SyncEnd || + _syncMarkRecord == SyncMarkRecord.SyncStart)) + { + return; + } + //being able to detect the first incoming record after a sync is requested, the buffer is going to be flushed + //if the measurement will be started or stopped + if (syncMarkRecord == SyncMarkRecord.SyncEnd || + syncMarkRecord == SyncMarkRecord.SyncStart || + syncMarkRecord == SyncMarkRecord.DecodeIntermediate) + { + FlushBuffer(); + } + + _byteDataLogger.Info($"{Ident} Mark next incoming record as {syncMarkRecord} (was {_syncMarkRecord})"); + _syncMarkRecord = syncMarkRecord; + + + + } + catch (Exception ex) + { + _byteDataLogger.Error($"{Ident} {ex.Message}"); + } + } + + private void FlushBuffer() + { + //check if flushing is allowed + if (null != PortSettingsForTransmitProtocol.ReceiveBufferFlushThreshold) + { + //flush buffer above threshold to get an accurate actual record with next record coming in + if (_serialPort.IsOpen && _serialPort.BytesToRead > PortSettingsForTransmitProtocol.ReceiveBufferFlushThreshold) + { + _byteDataLogger.Warn($"{Ident} Flushed receive buffer({_serialPort.BytesToRead}Byte)"); + _serialPort.DiscardInBuffer(); + } + } + } + + /// + /// Reading thread loop getting data from serial port + /// + /// + /// - Initial + /// + /// + /// - Reading will be executed and repeated until buffer is empty or timeout + /// + /// + /// - Receive byte protocol more dynamically on position of length information in record + /// and additional length, + /// - decoding of data controlled by dataSyncMarker!= SyncMarkRecord.SkipDecoding to speed up recording + /// + /// + /// - Directly invoked onRawRecord Received event, + /// - Doubling of sync byte implemented + /// + /// + /// - Timeout moved from ReadingThreadLoop to to avoid + /// of serial port while waiting on first incoming + /// data and flush receive buffer at timeout to force task to enter JoinWaitSleep state. + /// + /// + /// - Hide data in logging for e.g. passwords + /// + /// + /// - Raw record recording for missing sync-byte issues. + /// + /// + /// - Try to recover raw record on missing SYNC byte by adding the SYNC upfront and sending + /// the record to the decoding thread which will detect if just the SYNC byte had been + /// missed, then the CRC will match and the record can be decoded. + /// + /// + /// - Improved recovering of data sets at byte records with preceding SYNC byte: + /// - used payload length == 0 to skip decoding, it makes no sense to decode something where + /// the payload is empty, + /// - wait a certain time to complete incoming data. + /// + /// + /// - Inter byte read delay used instead of response delay. + /// + /// + /// - Flush buffer if received length is 0. + /// + /// + /// - Flush buffer (DiscardInBuffer) removed as some bytes are missing from time to time. + /// The IrDA sniffer showed these on the communication line, but they are incompletely received. + /// Assuming the DiscardInBuffer may be delayed, so the incoming data will be scrapped. + /// + /// + /// - Dispose procedure changed. + /// + /// + /// - Replaced 'ReadLine' with 'ReadTo' using a string delimiter to support others than LF "\n". + /// + private void ReadingThreadLoop() + { + //put the receive thread to JoinWaitSleep to avoid reading and logger output for timeout on + //RFID communication, because RFID will handle the physical receive by itself + //here the assignment of the _synReadingThreadEvent to the reading thread is being done + _onSyncReceiveThread.WaitOne(); + + try + { + while (!_receiveToken.IsCancellationRequested) + { + try + { + //remind data marker for this Thread execution time slice + var dataSyncMarkThisRun = _syncMarkRecord; + + //read line of ASCII indicated by syncByte == null + if (null == PortSettingsForTransmitProtocol.ProtSyncByte) + { + //exit is timeout from serialPort or received line + var rxStringRecord = _serialPort.ReadLine(); + + if (!string.IsNullOrEmpty(rxStringRecord)) + { + //decode optional at synchronized data SyncStart, SyncEnd or DecodeIntermediate + //todo remove after air problem + if (SyncMarkRecord.SkipDecoding != dataSyncMarkThisRun) + { + OnRawRecordReceived?.Invoke(this, new StringPortDataEventArgs(rxStringRecord, + _receiveTimeStampPc, dataSyncMarkThisRun)); + } + + if (RecordStreamingRawData) + { + AsciiDataLogger.Debug($"{rxStringRecord}"); + } + } + } + //byte record with preceding SYNC byte + else + { + //normally decoding is required + dataSyncMarkThisRun = SyncMarkRecord.DecodeEveryPackage; + + //this is the record for decoding + var rxByteRecord = new List(); + + //this is the informational record for logging on undetected SYNC byte + var rxRawRecord = new List(); + + //each byte protocol has to start with a syncByte, this has to be detected first + Byte rxByte; + do + { + rxByte = (Byte)_serialPort.ReadByte(); + //record raw data stream for output on missing sync-byte to investigate this issue + rxRawRecord.Add(rxByte); + } while (_serialPort.BytesToRead > 0 && PortSettingsForTransmitProtocol.ProtSyncByte != rxByte); + + // if syncByte has not been detected skip read loop and wait for next incoming record + if (PortSettingsForTransmitProtocol.ProtSyncByte == rxByte) + { + //save received SYNC byte + rxByteRecord.Add(rxByte); + + //to assemble the length it has to be extracted first from the record at given index, + //the payload length index cannot be 0 because at this position is always the SYNC byte, + //the overall length includes the SYNC byte! + var length = PortSettingsForTransmitProtocol.ProtLengthIndex + 1; + + //if the protLengthIndex is null the protAddLength equals the entire record length + if (null == PortSettingsForTransmitProtocol.ProtLengthIndex) + { + length = PortSettingsForTransmitProtocol.ProtAddLength; + } + + //read until length index to extract the length from the record + //the length index cannot be at position 0, because this is always the syncByte + do + { + rxByte = (Byte)_serialPort.ReadByte(); + //read the next byte if doubled sync byte detected and required by transmit protocol settings + if (PortSettingsForTransmitProtocol.DoubleSyncByte) + { + if (rxByte.Equals(PortSettingsForTransmitProtocol.ProtSyncByte) && + rxByteRecord[rxByteRecord.Count - 1] + .Equals(PortSettingsForTransmitProtocol.ProtSyncByte)) + { + //skips this byte and read the next one + rxByte = (Byte)_serialPort.ReadByte(); + } + } + + //add byte to receive result buffer + rxByteRecord.Add(rxByte); + + //capture the length at given index and readjust the record length + if (null != PortSettingsForTransmitProtocol.ProtLengthIndex + && rxByteRecord.Count - 1 == PortSettingsForTransmitProtocol.ProtLengthIndex) + { + //if the received length indicates empty payload, then nothing is to decode + if (rxByte == 0) + { + //exit this loop + dataSyncMarkThisRun = SyncMarkRecord.SkipDecoding; + FlushBuffer(); + } + else + { + //build the new length to continue this receive loop + length = (UInt16)(PortSettingsForTransmitProtocol.ProtAddLength + rxByte); + } + } + + //wait a certain time to let the data stream coming in + if (length - rxByteRecord.Count - 1 > _serialPort.BytesToRead) + { + Thread.Sleep(2); + } + + } while (rxByteRecord.Count < length && dataSyncMarkThisRun != SyncMarkRecord.SkipDecoding); + + //all data received or skip decoding marked, skip decoding will clear the response timeout + //these protocols have always to be decoded because the base is a request protocol + OnRawRecordReceived?.Invoke(this, new ListBytePortDataEventArgs(rxByteRecord, + _receiveTimeStampPc, dataSyncMarkThisRun)); + } + else + { + //output actual byte and recorded raw data stream to investigate missed sync-byte + _byteDataLogger.Warn($"{Ident} Response SYNC Byte missing. Raw Byte Received: " + + $"{BitConverter.ToString(rxRawRecord.ToArray())}"); + } + } + } + catch (ThreadAbortException) + { + _byteDataLogger.Info($"{Ident} Thread abort exception fired!"); + } + catch (TimeoutException) + { + //ATTENTION: This "_serialPort.DiscardInBuffer()" caused a lot of trouble as it flushes a few incoming + //bytes and therefore destroys the already started data stream. Here it had been left in to indicate + //this critical issue! + /*-------------------------------DO NOT ACTIVATE-----------------------------------------------------*/ + //flush receive buffer at timeout to force task to enter JoinWaitSleep state in finally + //if (_serialPort.IsOpen) + //{ + // _serialPort.DiscardInBuffer(); + //} + /*---------------------------------------------------------------------------------------------------*/ + + _byteDataLogger.Debug($"{Ident} Read timeout({InterByteReadDelayMs}ms)"); + //_byteDataLogger.Trace($"{Ident} Read timeout({PortSettingsForTransmitProtocol.ResponseTimeoutMs}ms)"); + + } + catch (Exception ex) + { + if (_receiveThread.ThreadState == ThreadState.Aborted + || _receiveThread.ThreadState == ThreadState.AbortRequested) + { + _byteDataLogger.Debug( $"{Ident} ThreadState is Aborted or AbortRequested but an " + + "error occurred while reading records from serial port", ex); + } + else + { + _byteDataLogger.Error( $"{Ident} Error while reading records from serial port", ex); + } + } + finally + { + if (_serialPort.IsOpen && !_receiveToken.IsCancellationRequested) + { + //it makes no sense to put the thread to sleep if there is something to read + if (0 == _serialPort.BytesToRead) + { + //restore event delegate to activate handle for incoming records + _serialPort.DataReceived += PhysicalDataReceived; + + //put this thread (receive thread) to JainWaitSleep + _onSyncReceiveThread.WaitOne(); + } + } + } + } //while (!_tokenReadData.IsCancellationRequested) + } // over hole reading thread loop + catch (ThreadAbortException) + { + _byteDataLogger.Info($"{Ident} Thread abort exception fired!"); + } + catch (Exception ex) + { + _byteDataLogger.Error($"{Ident} {ex.Message}"); + } + //finally + //{ + // //var retries = 5; + // //while (_serialPort != null && _serialPort.IsOpen && retries-- > 0) + // //{ + // // _serialPort.Close(); + // // if (_serialPort.IsOpen) + // // { + // // _byteDataLogger.Info($"{Ident} Port closing delay!"); + // // Thread.Sleep(500); + // // } + // //} + // //if (_serialPort != null && _serialPort.IsOpen) + // // _byteDataLogger.Info($"{Ident} Port unable to close"); + // //else + // // _byteDataLogger.Info($"{Ident} Port is closed"); + //} + } + + /// + public Boolean IsOpen() + { + return _serialPort != null && _serialPort.IsOpen; + } + + /// + /// Serial port specific write routine + /// + /// + /// + /// - Hide data in logging for e.g. passwords + /// + protected virtual void SpecificPortWrite(Byte[] txBuffer) + { + PhysicalWrite(txBuffer); + } + + /// + /// Physically sending to the serial port + /// + /// + /// - Initial + /// + /// + /// - Doubling of sync byte in protocol behind sync byte itself implemented + /// + /// + /// - Flush buffer as MOXA sometimes takes two messages and combines these to one! + /// As the Genesis needs a separated wake-up message with a following delay before the + /// real payload message, this causes a lot of trouble. + /// + protected void PhysicalWrite(Byte[] txBuffer) + { + try + { + //don't double the sync byte itself + var txByteList = new List { txBuffer[0] }; + for (var byteCtr = 1; byteCtr < txBuffer.Length; byteCtr++) + { + txByteList.Add(txBuffer[byteCtr]); + + //write the doubled sync byte again if required by transmit protocol settings + if (PortSettingsForTransmitProtocol.DoubleSyncByte) + { + if (txBuffer[byteCtr].Equals(PortSettingsForTransmitProtocol.ProtSyncByte)) + { + txByteList.Add(txBuffer[byteCtr]); + } + } + } + + _serialPort.Write(txByteList.ToArray(), 0, txByteList.Count); + + // flush the buffer for MOXA, to avoid two subsequent communications assembled to one communication! + _serialPort.BaseStream.Flush(); + + OnRawRecordSendOut?.Invoke(this, new ListBytePortDataEventArgs(txByteList, DateTimeOffset.UtcNow)); + } + catch (Exception ex) + { + _byteDataLogger.Error($"{Ident} {ex.Message}"); + } + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/SerialPorts/IrdaSerialPort.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/SerialPorts/IrdaSerialPort.cs new file mode 100644 index 000000000..7a9df3748 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/SerialPorts/IrdaSerialPort.cs @@ -0,0 +1,40 @@ +using System; +using System.IO.Ports; +using System.Threading; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.SerialPorts +{ + /// + public class IrdaSerialPort : BaseSerialPort + { + /// + public IrdaSerialPort(String ident, SerialPort serialPort, TransmitPortSettings setting) + : base(ident, serialPort, setting) + { + } + + /// + /// + /// - Initial + /// + /// + /// - wakeup burst pattern changed from 2 times 0xF0 to 4 times 0x01, + /// - required delay from 500µs to 1ms between wakeup burst and data. + /// + /// + /// - Removed comments and one delay (was at 2 x 1 ms). + /// + protected override void SpecificPortWrite(Byte[] tx) + { + //send wake up burst + var wakeUpBurst = new Byte[] { 0x01, 0x01, 0x01, 0x01 }; + PhysicalWrite(wakeUpBurst); + //delay at least 500 µs + Thread.Sleep(1); + + //send the IrDA record + PhysicalWrite(tx); + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/SerialPorts/LedSerialPort.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/SerialPorts/LedSerialPort.cs new file mode 100644 index 000000000..c35f38856 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/SerialPorts/LedSerialPort.cs @@ -0,0 +1,28 @@ +using System; +using System.IO.Ports; +using log4net; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.SerialPorts +{ + /// + public class LedSerialPort : BaseSerialPort + { + /// + public LedSerialPort(String ident, SerialPort serialPort, TransmitPortSettings setting) : base(ident, serialPort, setting) + { + } + + /// + protected override void SpecificPortWrite(Byte[] tx) + { + throw new NotSupportedException("Led port does not support writing"); + } + + public ILog GetRawLogger() + { + return AsciiDataLogger; + + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/SerialPorts/RfidSerialPort.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/SerialPorts/RfidSerialPort.cs new file mode 100644 index 000000000..8870e39f5 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/SerialPorts/RfidSerialPort.cs @@ -0,0 +1,600 @@ +using System; +using System.IO.Ports; +using System.Linq; +using System.Threading; +using log4net; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.SerialPorts +{ + /// + public class RfidSerialPort : BaseSerialPort + { + /// + public override event EventHandler OnRawRecordReceived; + + private const Byte RfidFrameStartId = 0x01; + private const Byte RfidFrameTxLength = 0x12; + private const Byte RfidFrameRxLength = 0x0C; + private const Byte RfidFrameExpectedRxLength = 0x0A; + private const Byte RfidFramePayloadDataMarker = 0x7D; + private const Byte RfidRxModeMarker = 0x7E; + private const Byte RfidFramePollingByte = 0x03; + + private const Int32 RfidRxStartSyncPosition = 0; + private const Int32 RfidRxLengthPosition = 1; + private const Int32 RfidRxModePosition = 4; + private const Int32 RfidRxDataPosition = 5; + private const Int32 RfidRxDataMarkerPosition = 11; + private const Int32 RfidRxCrcLowPosition = 12; + private const Int32 RfidRxCrcHighPosition = 13; + private const Int32 RfidRxBccPosition = 14; + private const Int32 RfidCommRetries = 4; + private const Int32 RfidStartPatternLength = 10; + //additional frame length for start, length and BCC added to frame length + //being in the length itself + private const Int32 RfidProtAddLength = 3; + private const Int32 RfidRxLength = RfidFrameRxLength + RfidProtAddLength; + private const Int32 RfidTxLength = RfidFrameTxLength + RfidProtAddLength; + + //first byte is fixed to 0x7D (data mode) at the Tx or the last byte behind + //the payload at Rx, the others are from the device protocol (the payload), + //this byte needs to be included into the CRC calculation + private const Int32 RfidPayLoadLength = 7; + private const Int32 RfidRawPayLoadLength = 6; + + //the raw payload to add to RFID transmit buffer, it has a start Id, a length position + //and some additional length needed to add to the raw Rx length + private Byte[] _rawTxPayLoad; + private Int32 _rawRxLength; + private Byte[] _rawRxPayLoad; + + + //create RFID payload + private Byte[] _rfidTxPayLoad; + private Int32 _payLoadTxCounterPosition; + private Int32 _payLoadRxCounterPosition; + //remind send state + private Boolean _txIsActive; + //marker for first protocol containing the start id and the length + private Boolean _waitRawStartSyncPattern; + //this is the RFID raw buffer being sent to the serial port + private Byte[] _rfidTxBuffer = new Byte[RfidTxLength]; + private Byte[] _rfidRxBuffer = new Byte[RfidRxLength]; + private Int32 _rfidCommRetryCounter = RfidCommRetries; + private RfidRxState _rfidRxState; + + private static readonly ILog _logger = LogManager.GetLogger(typeof(RfidSerialPort)); + + //communication counter for debug for application layer + private Int32 _commCounter; + + /// + /// + /// - Initial + /// + public RfidSerialPort(String ident, SerialPort serialPort, TransmitPortSettings setting) + : base(ident, serialPort, setting) + { + + } + + /// + /// + /// - Not needed and therefor deactivated by overwriting + /// + public override void PhysicalDataReceived(Object sender, SerialDataReceivedEventArgs e) + { + //remove registration of delegate + GetBaseComport().DataReceived -= PhysicalDataReceived; + } + + /// + /// + /// Overwritten routine, kicks off the first communication, + /// handles the communication state machine + /// + /// + /// + /// - Not needed and therefor deactivated by overwriting + /// + /// + /// - communication scheduler removed, call directly the stats machine + /// + /// + /// - Hide data in logging for e.g. passwords + /// + protected override void SpecificPortWrite(Byte[] tx) + { + //raw protocol before wrapped into RFID + SendDataViaRfid(tx); + + RfidRxState rfidState; + + //call state machine until ready + do + { + rfidState = RfidCommStateMachine(); + + } while (RfidRxState.CommunicationFinished != rfidState && + RfidRxState.CommunicationFailed != rfidState); + + } + + /// + /// Send data via RFID + /// + /// data to be transferred via RFID + /// + /// - Initial + /// + /// + /// - Receive size removed, will be automatically extracted from underlay-protocol (raw) + /// + /// + /// - Raw buffer increased to communication retries * raw packet size. + /// + public void SendDataViaRfid(Byte[] data) + { + if (data.Length < RfidRawPayLoadLength) return; + //length is unknown before receiving the first raw data, setup to x packets size for + //receive routine + _rawRxLength = RfidRawPayLoadLength * _rfidCommRetryCounter; + _rawRxPayLoad = new Byte[_rawRxLength]; + _waitRawStartSyncPattern = true; + //start with sending of data + _txIsActive = true; + //allow retries during communication + _rfidCommRetryCounter = RfidCommRetries; + //reset running counters + _payLoadTxCounterPosition = 0; + _payLoadRxCounterPosition = 0; + _commCounter = 0; + + //copy part of the huge payload buffer (containing the entire data) to small + //6 byte chunks being able to transmit within one RFID communication + _rfidTxPayLoad = null; + _rfidTxPayLoad = new Byte[RfidRawPayLoadLength]; + _rawTxPayLoad = new Byte[data.Length]; + _rawTxPayLoad = data; + for (var i = 0; i < RfidRawPayLoadLength; i++) + { + _rfidTxPayLoad[i] = _rawTxPayLoad[_payLoadTxCounterPosition]; + _payLoadTxCounterPosition++; + } + //assemble the RFID transmit buffer + PrepareRfidTxBuffer(ref _rfidTxBuffer, _rfidTxPayLoad); + //kick off first communication + _rfidRxState = RfidComm(ref _rfidRxBuffer, _rfidTxBuffer); + } + /// + /// RFID Tx buffer content + /// + /// array of RFID Tx buffer content + /// + /// - Initial + /// + public Byte[] GetRfidTxBuffer() + { + return _rfidTxBuffer; + } + /// + /// RFID Rx buffer content + /// + /// array of RFID Rx buffer content + /// + /// - Initial + /// + public Byte[] GetRfidRxBuffer() + { + return _rfidRxBuffer; + } + + /// + /// read out the received data from RFID + /// + /// + /// + /// - Initial + /// + public Byte[] GetDecodedDataFromRfid() + { + return _rawRxPayLoad; + } + /// + /// feedback of communication counter + /// + /// number of RFID communications + /// + /// - Initial + /// + public Int32 GetRfidComCounter() + { + return _commCounter; + } + + /// + /// communication state machine + /// + /// + /// + /// - Initial + /// + /// + /// - Extract length information from raw protocol + /// + /// + /// - Return of communication failed on missing start sync pattern of raw protocol + /// + /// + /// - New modes implemented to differ between sending / waiting for Rx start + /// and receiving + /// + /// + /// - Time stamp added + /// + /// + /// - Hide data in logging for e.g. passwords + /// + public RfidRxState RfidCommStateMachine() + { + //check the receive status + switch (_rfidRxState) + { + case RfidRxState.EchoRxProtocolOk: + //clear payload buffer to force filling with polling pattern + _rfidTxPayLoad = null; + //send loop + if (_txIsActive) + { + if (_payLoadTxCounterPosition < _rawTxPayLoad.Length) + { + //assign new Tx buffer + Int32 arraySize; + if (_payLoadTxCounterPosition + RfidRawPayLoadLength < + _rawTxPayLoad.Length) + arraySize = RfidRawPayLoadLength; + else + arraySize = _rawTxPayLoad.Length - _payLoadTxCounterPosition; + _rfidTxPayLoad = new Byte[arraySize]; + //fill payload buffer with remaining payload bytes + for (var i = 0; i < arraySize; i++) + { + if (_payLoadTxCounterPosition >= _rawTxPayLoad.Length) continue; + _rfidTxPayLoad[i] = _rawTxPayLoad[_payLoadTxCounterPosition]; + _payLoadTxCounterPosition++; + } + } + else + { + //switch receive loop active + _txIsActive = false; + } + } + //reset retry counter + _rfidCommRetryCounter = RfidCommRetries; + //assemble new RFID transmit buffer + PrepareRfidTxBuffer(ref _rfidTxBuffer, _rfidTxPayLoad); + //start communication + _rfidRxState = RfidComm(ref _rfidRxBuffer, _rfidTxBuffer); + break; + + case RfidRxState.DataRxProtocolOk: + //receive loop with polling pattern sending + for (var i = 0; i < RfidRawPayLoadLength; i++) + { + //avoid out of bounce access + if (_payLoadRxCounterPosition >= _rawRxLength) continue; + _rawRxPayLoad[_payLoadRxCounterPosition] = + _rfidRxBuffer[i + RfidRxDataPosition]; + _payLoadRxCounterPosition++; + } + //check for entire message received + if (_payLoadRxCounterPosition >= _rawRxLength) + { + //assign time stamp of received record + var readDateTimePc = DateTimeOffset.UtcNow; + + //communication finished, return data to caller + //RawRecordReceived(this, _rawRxPayLoad, readDateTimePc); + var byteList = _rawRxPayLoad.ToList(); + OnRawRecordReceived?.Invoke(this, new ListBytePortDataEventArgs(byteList, readDateTimePc)); + + //log the RFID payload, RFID protocol is removed + //_logger.Trace($"{Ident} Read({BitConverter.ToString(_rawRxPayLoad)})"); + + return _waitRawStartSyncPattern ? RfidRxState.CommunicationFailed : + RfidRxState.CommunicationFinished; + } + //reset retry counter + _rfidCommRetryCounter = RfidCommRetries; + //start communication with polling pattern, has been assembled in last send run + _rfidRxState = RfidComm(ref _rfidRxBuffer, _rfidTxBuffer); + break; + + case RfidRxState.Idle: + break; + + default: + //RFID or device is not ready or protocol error, start retry + if (_rfidCommRetryCounter > 0) + { + _logger.Debug($"{Ident} Internal RFID retry"); + _rfidCommRetryCounter--; + + //send the same RFID message again + _rfidRxState = RfidComm(ref _rfidRxBuffer, _rfidTxBuffer); + } + else + { + //communication failed because of exceeded internal RFID retry counter + _rfidRxState = RfidRxState.CommunicationFailed; + _logger.Debug($"{Ident} Internal RFID communication failed, RFID retry counter exceeded"); + } + break; + } + return _rfidRxState; + } + /// + /// send and receive + /// + /// + /// + /// RfidRxState + /// + /// - Initial + /// + /// + /// - Communication timeout activated. + /// + /// + /// - Hide data in logging for e.g. passwords + /// + private RfidRxState RfidComm(ref Byte[] rxBuffer, Byte[] txBuffer) + { + var rfidRxState = RfidRxState.CommPortError; + + if (!IsOpen()) return rfidRxState; + + Clear(); + //_logger.Trace($"{Ident} RFID raw sent({BitConverter.ToString(txBuffer)})"); + PhysicalWrite(txBuffer); + _commCounter++; + //set timeout for receive exit, take 5 records of 6 byte chunks as base + var commTimeoutMs = PortSettingsForTransmitProtocol.ResponseTimeoutMs / 5; + while (commTimeoutMs > 0 && BytesToRead() < RfidRxLength) + { + Thread.Sleep(1); + commTimeoutMs--; + } + var i = 0; + //test if something is in input buffer + if (RfidRxLength > BytesToRead()) + { + _logger.Debug($"{Ident} Internal RFID read timeout({PortSettingsForTransmitProtocol.ResponseTimeoutMs}ms)"); + return RfidRxState.RxTimeout; + } + while (BytesToRead() > 0 && i < RfidRxLength) + { + rxBuffer[i] = (Byte)ReadByte(); + i++; + } + if (i != RfidRxLength) return rfidRxState; + + //all expected bytes received + //_logger.Trace($"{Ident} RFID raw read({BitConverter.ToString(rxBuffer)})"); + rfidRxState = CheckRfidRxBuffer(rxBuffer); + + return rfidRxState; + } + /// + /// check the received buffer CRC, BCC and + /// + /// + /// + /// - Initial + /// + /// + /// - New modes implemented to differ between sending / waiting for Rx start + /// and receiving + /// + /// RfidRxState + private RfidRxState CheckRfidRxBuffer(Byte[] rfidRxBuffer) + { + //retry required by start sync error + if (RfidFrameStartId != rfidRxBuffer[RfidRxStartSyncPosition]) + return RfidRxState.StartSyncError; + //retry required by length error + if (RfidFrameRxLength != rfidRxBuffer[RfidRxLengthPosition]) + return RfidRxState.LengthError; + + //data marked as useful data + if (RfidRxModeMarker != rfidRxBuffer[RfidRxModePosition] || + RfidFramePayloadDataMarker != rfidRxBuffer[RfidRxDataMarkerPosition]) + return RfidRxState.RetryRequired; + //BCC check transfer entire receive buffer, this will automatically handled + if (rfidRxBuffer[RfidRxBccPosition] != BuildRfidBcc(rfidRxBuffer)) + return RfidRxState.BccError; + + //extract the 6 data (payload) bytes and the data frame marker + var testBuffer = new Byte[RfidPayLoadLength]; + for (var i = 0; i < RfidPayLoadLength; i++) + testBuffer[i] = rfidRxBuffer[i + RfidRxDataPosition]; + //CRC with LSB first + var buildCrc = Crc16Ccitt.CalculateLsb0408(testBuffer); + UInt16 receivedCrc = rfidRxBuffer[RfidRxCrcHighPosition]; + receivedCrc <<= 8; + receivedCrc &= 0xFF00; + receivedCrc += rfidRxBuffer[RfidRxCrcLowPosition]; + if (receivedCrc != buildCrc) return RfidRxState.DataCrcError; + //send loop is active + if (_txIsActive) return RfidRxState.EchoRxProtocolOk; + //if the start sync byte has been detected the normal receive mode is active + if (!_waitRawStartSyncPattern) return RfidRxState.DataRxProtocolOk; + //search for raw protocol start sync Id to extract length information, + //if start Id hasn't been found the wait for Rx start mode is active + //forcing the retry loop being active (default switch) + if (PortSettingsForTransmitProtocol.ProtSyncByte != _rfidRxBuffer[RfidRxDataPosition]) + return RfidRxState.WaitRxStartProtocolOk; + //here the Rx mode is going to be activated, first data received including + //start sync Id and length information + _waitRawStartSyncPattern = false; + _rawRxLength = _rfidRxBuffer[RfidRxDataPosition + + (PortSettingsForTransmitProtocol.ProtLengthIndex.HasValue ? + PortSettingsForTransmitProtocol.ProtLengthIndex.Value : 0)] + + PortSettingsForTransmitProtocol.ProtAddLength; + _rawRxPayLoad = new Byte[_rawRxLength]; + return RfidRxState.DataRxProtocolOk; + } + /// + /// assemble the RFID transmit buffer as bytes + /// + /// + /// + /// + /// - Initial + /// + private static void PrepareRfidTxBuffer(ref Byte[] rfidTxBuffer, Byte[] payLoad) + { + RfidTxProtocol rfidTxStruct; + rfidTxStruct.StartPattern = new Byte[] + { + RfidFrameStartId, //start identifier 0x01 + RfidFrameTxLength, //length, start behind length without BCC + 0xE8, 0x90, 0x00, //CMD 1, 2, 3 + 0x00, 0x32, 0x00, 0x11, //Power 1 and 2 + 0x48 //TX bits + }; + //the default data is the polling pattern 0x03 + rfidTxStruct.PayLoad = new[] { RfidFramePayloadDataMarker, + RfidFramePollingByte, RfidFramePollingByte, RfidFramePollingByte, + RfidFramePollingByte, RfidFramePollingByte, RfidFramePollingByte}; + + //fill the payload with real data + var i = 0; + if (payLoad != null) + { + //put payload behind RFID frame payload data marker + for (; i < payLoad.Length; i++) + rfidTxStruct.PayLoad[i + 1] = payLoad[i]; + } + rfidTxStruct.ExpectedRxBytes = RfidFrameExpectedRxLength; + + //fill transmit buffer with constant start pattern for RFID communication + i = 0; + for (; i < RfidStartPatternLength; i++) + { + rfidTxBuffer[i] = rfidTxStruct.StartPattern[i]; + } + //add payload to buffer, first byte is constant 0x7D (data mode) + var c = 0; + for (; i < RfidStartPatternLength + RfidPayLoadLength; i++) + { + rfidTxBuffer[i] = rfidTxStruct.PayLoad[c]; + c++; + } + //add data CRC, LSB first + rfidTxStruct.DataCrc = Crc16Ccitt.CalculateLsb0408(rfidTxStruct.PayLoad); + rfidTxBuffer[i] = (Byte)(rfidTxStruct.DataCrc & 0xFF); + i++; + rfidTxBuffer[i] = (Byte)((rfidTxStruct.DataCrc & 0xFF00) >> 8); + //add expected receive bytes + i++; + rfidTxBuffer[i] = rfidTxStruct.ExpectedRxBytes; + //add BCC + rfidTxStruct.Bcc = BuildRfidBcc(rfidTxBuffer); + i++; + rfidTxBuffer[i] = rfidTxStruct.Bcc; + } + + /// + /// build simple byte by byte XOR'ed checksum called BCC for RFID + /// + /// + /// BCC code + /// + /// - Initial + /// + private static Byte BuildRfidBcc(Byte[] rfidRawBuffer) + { + //remove the "start byte" and the BCC itself + var maxCounts = rfidRawBuffer.Length - 1; + //start behind the "start byte" + var i = 1; + Byte bcc = rfidRawBuffer[i]; + //take the second value + i++; + for (; i < maxCounts; i++) + { + bcc ^= rfidRawBuffer[i]; + } + return bcc; + } + + private struct RfidTxProtocol + { + public Byte[] StartPattern; + public Byte[] PayLoad; + public UInt16 DataCrc; + public Byte ExpectedRxBytes; + public Byte Bcc; + } + } + + public enum RfidRxState + { + /// + /// No communication is ongoing + /// + Idle, + /// + /// Echo of sent protocol is received successfully back + /// + EchoRxProtocolOk, + /// + /// Waiting for switch from transmitting to receiving + /// + WaitRxStartProtocolOk, + /// + /// Received data message is valid + /// + DataRxProtocolOk, + /// + /// Communication port assignment error + /// + CommPortError, + /// + /// Start of synchronization error + /// + StartSyncError, + /// + /// Length error + /// + LengthError, + /// + /// Retry required + /// + RetryRequired, + /// + /// CRC error of data in payload field + /// + DataCrcError, + /// + /// BCC (special checksum) error of RFID + /// + BccError, + /// + /// Receive timeout + /// + RxTimeout, + /// + /// Communication to RFID failed, record cannot be assembled + /// + CommunicationFailed, + /// + /// Communication to RFID successfully executed + /// + CommunicationFinished + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/SerialPorts/UartSerialPort.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/SerialPorts/UartSerialPort.cs new file mode 100644 index 000000000..3ec46b90e --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Ports/SerialPorts/UartSerialPort.cs @@ -0,0 +1,17 @@ +using System; +using System.IO.Ports; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.SerialPorts +{ + /// + public class UartSerialPort : BaseSerialPort + { + /// + public UartSerialPort(String ident, SerialPort serialPort, TransmitPortSettings setting) : base(ident, serialPort, setting) + { + + } + } + +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/ProtocolCore/BaseProtocol.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/ProtocolCore/BaseProtocol.cs new file mode 100644 index 000000000..7c0709dfe --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/ProtocolCore/BaseProtocol.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.ProtocolCore.EventArguments; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.TransmitProtocol; +using BaseDataEventArgs = TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments.BaseDataEventArgs; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.ProtocolCore +{ + /// + /// + /// Base for all protocol types + /// + public abstract class BaseProtocol : IProtocol + { + private readonly CancellationTokenSource _decodingToken = new CancellationTokenSource(); + + /// + public abstract event EventHandler OnRecordIsDecoded; + + /// + public virtual event EventHandler OnRecordReadyToSend; + + private readonly ConcurrentQueue _decodingFifo = new ConcurrentQueue(); + + //initially do not signal event + private readonly AutoResetEvent _onSyncDecodingThread = new AutoResetEvent(false); + private readonly Thread _decodingThread; + + /// + /// Ident is a combined string of Slot, Port, Protocol and Type + /// + protected readonly String Ident; + private ITransmitProtocol _transmitProtocol; + /// + /// Starting decoding thread + /// + /// + /// - Initial + /// + protected BaseProtocol(String ident) + { + Ident = ident; + //assign thread to loop + _decodingThread = new Thread(DecodingThreadLoop) { Name = $"{Ident} Decoding thread" }; + //start DecodingThread + ThreadWatcher.Instance.Start(_decodingThread); + + } + + /// + /// + /// Kill the decoding thread + /// + /// + /// - Initial + /// + /// + /// - try catch block. + /// + public virtual void Dispose() + { + try + { + //Cancel receive tokens + _decodingToken.Cancel(); + + //Run thread again to notice CancellationToken has changed + _onSyncDecodingThread.Set(); + + //this timeout counter is being used for dispose only + var timeoutCounter = 100; + + while (_decodingThread.ThreadState != ThreadState.Stopped && timeoutCounter > 0) + { + Thread.Sleep(1); + timeoutCounter -= 1; + //Run thread again to notice CancellationToken has changed + _onSyncDecodingThread.Set(); + } + + if (_decodingThread.ThreadState != ThreadState.Stopped) + { + //if thread still running, he stuck so try to abort + // try to avoid abort (takes age to run and is not safe) + _decodingThread.Abort(); + } + } + catch (Exception ex) + { + throw new ApplicationException(ex.Message); + } + } + + /// + /// Fill FIFO with received data + /// + /// + /// - Initial + /// + public void FillDecodingBuffer(IPortDataEventArgs data) + { + if (_decodingToken.IsCancellationRequested) return; + + //put data to FIFO + _decodingFifo.Enqueue(data); + + //put DecodingThread state from WaitSleepJoin to Running + _onSyncDecodingThread.Set(); + } + + /// + /// Decoding thread loop calling the individual decoding + /// + /// + /// - Initial + /// + private void DecodingThreadLoop() + { + try + { + while (!_decodingToken.IsCancellationRequested) + { + while (_decodingFifo.TryDequeue(out var receivedRecord)) + { + DecodeRecord(receivedRecord); + } + + //put DecodingThread to WaitSleepJoin until next data arrived + _onSyncDecodingThread.WaitOne(); + } + } + catch (ThreadAbortException) + { } + } + + /// + /// The decoding routine + /// + /// + protected abstract void DecodeRecord(IPortDataEventArgs data); + + /// + public ITransmitProtocol GetTransmitProtocol() + { + return _transmitProtocol; + } + + /// + public void SetTransmitProtocol(ITransmitProtocol transmitProtocol) + { + _transmitProtocol = transmitProtocol; + } + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/ProtocolCore/EventArguments/BaseDataEventArgs.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/ProtocolCore/EventArguments/BaseDataEventArgs.cs new file mode 100644 index 000000000..2569c3985 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/ProtocolCore/EventArguments/BaseDataEventArgs.cs @@ -0,0 +1,33 @@ +using System; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.ProtocolCore.EventArguments +{ + + /// + /// + /// abstract for set structure for BaseDataEventArgs + /// + public abstract class BaseDataEventArgs : EventArgs + { + /// + /// 'Base' get event record form real child. + /// + /// + public Object GetData() + { + return GetEventData(); + } + + /// + /// get real Event record + /// + /// + public abstract Object GetEventData(); + + /// + /// Holds the record before decoding, for logging + /// + public String RawData; + } +} + diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/ProtocolCore/IProtocol.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/ProtocolCore/IProtocol.cs new file mode 100644 index 000000000..1467dae16 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/ProtocolCore/IProtocol.cs @@ -0,0 +1,43 @@ +using System; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.ProtocolCore.EventArguments; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.TransmitProtocol; +using BaseDataEventArgs = TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments.BaseDataEventArgs; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.ProtocolCore +{ + /// + /// + /// Protocol interface + /// + public interface IProtocol : IDisposable + { + /// + /// Fill the buffer with data ready for decoding + /// + /// + void FillDecodingBuffer(IPortDataEventArgs rawData ); + + /// + /// Record is ready to send including all protocols + /// + event EventHandler OnRecordReadyToSend; + + /// + /// Record is successfully decoded and ready for further processing + /// + event EventHandler OnRecordIsDecoded; + + /// + /// Return of transmit protocol settings + /// + /// + ITransmitProtocol GetTransmitProtocol(); + + /// + /// Assignment of transmit protocol + /// + /// + void SetTransmitProtocol(ITransmitProtocol transmitProtocol); + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/TransmitProtocol/BaseTransmitProtocol.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/TransmitProtocol/BaseTransmitProtocol.cs new file mode 100644 index 000000000..65207d516 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/TransmitProtocol/BaseTransmitProtocol.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using log4net; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.TransmitProtocol +{ + public abstract class BaseTransmitProtocol : ITransmitProtocol + { + public static readonly ILog Logger = LogManager.GetLogger(typeof(BaseTransmitProtocol)); + + + protected BaseTransmitProtocol(String port) + { + + } + + public abstract TransmitPortSettings GetTransmitPortSettings(); + public abstract void SetResponseTimeout(Int32 responseTimeoutMs); + + public abstract void SetDefaultResponseTimeout(); + + public abstract List DecodeDataForPhysicalLayer(String ident, Byte command, Byte[] payload, Boolean hideDataInLog = false); + + public abstract List DecodeDataForLogicLayer(String ident, List rawData, Boolean hideDataInLog = false); + + public abstract List DecodeDataForPhysicalLayerUI1236(String ident, Byte[] payload, Boolean hideDataInLog = false); + + } + +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/TransmitProtocol/ITransmitProtocol.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/TransmitProtocol/ITransmitProtocol.cs new file mode 100644 index 000000000..08ff8a2a5 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/TransmitProtocol/ITransmitProtocol.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.TransmitProtocol +{ + public interface ITransmitProtocol + { + /// + /// Struct of settings being needed for transmit port + /// + /// + TransmitPortSettings GetTransmitPortSettings(); + + /// + /// Overwrite the default response timeout + /// + void SetResponseTimeout(Int32 responseTimeoutMs); + + /// + /// Set timeout to the default response time + /// + void SetDefaultResponseTimeout(); + + // ReSharper disable once InconsistentNaming UI1236 is a naming forced by the caller + List DecodeDataForPhysicalLayerUI1236(String ident, Byte[] payload, Boolean hideDataInLog = false); + + /// + /// Send out data (including transport protocol specific data like CRC) to physical port (like UART or IrDA) + /// + List DecodeDataForPhysicalLayer(String ident, Byte command , Byte[] payload, Boolean hideDataInLog = false); + + /// + /// Received data have to be checked and converted to request protocol + /// + List DecodeDataForLogicLayer(String ident, List rawData, Boolean hideDateInLog = false); + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/TransmitProtocol/IrdaTransmitProtocol.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/TransmitProtocol/IrdaTransmitProtocol.cs new file mode 100644 index 000000000..e8e00485a --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/TransmitProtocol/IrdaTransmitProtocol.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.TransmitProtocol +{ + /// + /// IrDA transmission protocol + /// + public class IrdaTransmitProtocol : BaseTransmitProtocol + { + // Format of IrDA transmit protocol: + // IrdaSyncByte|IrdaSend/ReceiveHeader|IrdaPayLoadLength|IrdaCommand/Message| + // IrdaPayLoad|IrdaCrc LSB|IrdaCrc MSB + private const Byte IrdaSyncByte = 0x9B; + + // Encoding of IrdaSendHeader (LAT: listen after talk, LAT 10b: 500ms) + // Bits: 0| 0| 10| 0| 000 + // standard frame|adapter to register|LAT|reserved|optical command + private const Byte IrdaSendHeader = 0x20; + + // Encoding of IrdaReceiveHeader (LAT: listen after talk, LAT 10b: 500ms) + // Bits: 0| 1| 10| 0| 001 + // standard frame|register to adapter|LAT|reserved|optical message + private const Byte IrdaReceiveHeader = 0x61; + private const Byte IrdaWakeupHeader = 0x41; + + // Mask out the LAT and the reserved bit for the IrdaReceiveHeader + private const Byte IrdaReceiveHeaderMask = 0xC7; + + // data from adapter (software) to register (water meter) referred as optical command + private const Byte IrdaCommand = 0x02; + + // data from register (water meter) to adapter (software) referred as optical message + private const Byte IrdaMessageId = 0x03; + private const Byte IrdaWakeupId = 0x04; + + // IrdaSyncByte|IrdaReceiveHeader|IrdaPayLoadLength|IrdaMessageId|CRC LSB|CRC MSB + private const Int32 IrdaProtocolFrameLength = 6; + + // Indexes of IrDA optical protocol + private const Int32 IrdaSyncByteIndex = 0; + private const Int32 IrdaHeaderIndex = 1; + private const Int32 IrdaPayLoadLengthIndex = 2; + private const Int32 IrdaMessageIndex = 3; + private const Int32 IrdaPayLoadIndex = 4; + + //the IrDA length covers the payload length only + private const UInt16 ProtAddLength = IrdaProtocolFrameLength; + private const Int32 DefaultResponseTimeoutMs = 250; + //private const Int32 DefaultResponseTimeoutMs = 150; + private Int32 _responseTimeoutMs = DefaultResponseTimeoutMs; + + private const UInt32 BaudRate = 115200; + + private readonly UInt32? _receiveBufferFlushThreshold = 1; + + /// + public override void SetResponseTimeout(Int32 responseTimeoutMs) + { + _responseTimeoutMs = responseTimeoutMs; + } + /// + public override void SetDefaultResponseTimeout() + { + _responseTimeoutMs = DefaultResponseTimeoutMs; + } + /// + public IrdaTransmitProtocol(String portName) : base(portName) + { + + } + + /// + public override TransmitPortSettings GetTransmitPortSettings() + { + return new TransmitPortSettings(IrdaSyncByte, IrdaPayLoadLengthIndex, ProtAddLength, + _responseTimeoutMs, BaudRate, _receiveBufferFlushThreshold); + } + + /// + /// + /// - Initial + /// + /// + /// - Removed IrdaMessageId identifier check as it was observed receiving a 0x03 or a 0x04 in this field. + /// + /// + /// - Wakeup message detection reported to log-file, + /// - Message error text changed. + /// + /// + /// - Hide data in log introduced. + /// + public override List DecodeDataForLogicLayer(String ident, List irdaRecord, Boolean hideDataInLog = false) + { + // avoid logging of passwords or other sensitive data + Logger.Info(hideDataInLog + ? $"{ident} DecodeDataForLogicalLayer Cordonel->PC(*****)" + : $"{ident} DecodeDataForLogicalLayer Cordonel->PC({BitConverter.ToString(irdaRecord.ToArray())})"); + + // check the record length and start of frame information + if (irdaRecord.Count < IrdaProtocolFrameLength + irdaRecord[IrdaPayLoadLengthIndex] + || irdaRecord[IrdaSyncByteIndex] != IrdaSyncByte + || (((irdaRecord[IrdaHeaderIndex] & IrdaReceiveHeaderMask) != (IrdaReceiveHeader & IrdaReceiveHeaderMask) + && (irdaRecord[IrdaHeaderIndex] != IrdaWakeupHeader)) + || ((irdaRecord[IrdaMessageIndex] != IrdaMessageId) + && irdaRecord[IrdaMessageIndex] != IrdaWakeupId))) + { + var error = new ApplicationException($"{ident} Reply from IrDA is invalid."); + Logger.Error(error.Message, error); + + return new List(); + } + + // extract the received CRC first the MSB at last position + UInt16 receivedCrc = irdaRecord[irdaRecord.Count - 1]; + receivedCrc <<= 8; + // add the LSB from before last position, the LSB of the CRC will be sent first + receivedCrc += irdaRecord[irdaRecord.Count - 2]; + + // the CRC is being built from IrdaReceiveHeader to end of IrdaPayload, + // subtract irdaSyncByteLength and irdaCrcLength + var irdaCrcInputBuffer = new List(); + irdaCrcInputBuffer.AddRange(irdaRecord.GetRange(IrdaHeaderIndex, irdaRecord.Count - 3)); + + var calculatedCrc = Crc16Ccitt.CalculateReversedLsb8408(irdaCrcInputBuffer.ToArray()); + + if (receivedCrc != calculatedCrc) + { + var error = new ApplicationException($"{ident} Reply CRC failure. Decoding of transmit layer failed."); + Logger.Error(error.Message, error); + return new List(); + } + + // build the return value witch equals the IrDA payload + var irdaPayLoad = new List(); + irdaPayLoad.AddRange(irdaRecord.GetRange(IrdaPayLoadIndex, irdaRecord.Count - IrdaProtocolFrameLength)); + + return irdaPayLoad; + } + + /// + /// + /// - Initial + /// + /// + /// - Hide data in log forwarded to DecodeDateForPhysicalLayer to hide passwords in log files. + /// + public override List DecodeDataForPhysicalLayer(String ident, Byte requestProtocolCommand, Byte[] requestProtocolPayload, + Boolean hideDataInLog = false) + { + var irdaCrcInputBuffer = new List + { + // build CRC calculation buffer without sync byte + // IrdaSendHeader|IrdaPayLoadLength|IrdaCommand|requestProtocolCommand|requestProtocolPayload + IrdaSendHeader, + + // IrdaPayloadLength is the requestProtocolCommandLength + requestProtocolPayloadLength + (Byte)(requestProtocolPayload.Length + 1), + IrdaCommand, + + // add the IrdaPayload witch is the requestProtocolCommand + requestProtocolPayload + requestProtocolCommand + }; + irdaCrcInputBuffer.AddRange(requestProtocolPayload); + + var crcResult = Crc16Ccitt.CalculateReversedLsb8408(irdaCrcInputBuffer.ToArray()); + + // assemble result buffer + // IrdaSyncByte|crcInputBuffer|IrdaCrc LSB|IrdaCrc MSB + var irdaRecord = new List { IrdaSyncByte }; + irdaRecord.AddRange(irdaCrcInputBuffer); + irdaRecord.Add((Byte)(crcResult & 0xFF)); + irdaRecord.Add((Byte)(crcResult >> 8)); + + // avoid logging of passwords or other sensitive data + Logger.Info(hideDataInLog + ? $"{ident} DecodeDataForPhysicalLayer PC->Cordonel(*****)" + : $"{ident} DecodeDataForPhysicalLayer PC->Cordonel({BitConverter.ToString(irdaRecord.ToArray())})"); + + return irdaRecord; + } + + /// + /// + /// - Initial + /// + public override List DecodeDataForPhysicalLayerUI1236(String ident, Byte[] requestProtocolPayload, + Boolean hideDataInLog = false) + { + var collection = new List + { + 35, + (Byte) (requestProtocolPayload.Length / 2) + }; + collection.AddRange(requestProtocolPayload); + var crcResult = Crc16Ccitt.CalculateReversedLsb8408(collection.ToArray()); + var byteList = new List + { + 155 + }; + byteList.AddRange(collection); + byteList.Add((Byte)(crcResult & 0xFF)); + byteList.Add((Byte)(crcResult >> 8)); + // avoid logging of passwords or other sensitive data + Logger.Info(hideDataInLog + ? $"{ident} DecodeDataForPhysicalLayerUI1236 PC->Cordonel(*****)" + : $"{ident} DecodeDataForPhysicalLayerUI1236 PC->Cordonel({BitConverter.ToString(byteList.ToArray())})"); + return byteList; + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/TransmitProtocol/LedTransmitProtocol.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/TransmitProtocol/LedTransmitProtocol.cs new file mode 100644 index 000000000..9abe87c25 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/TransmitProtocol/LedTransmitProtocol.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.TransmitProtocol +{ + public class LedTransmitProtocol : BaseTransmitProtocol + { + private readonly Byte? _protSyncByte = null; + private const UInt16 ProtAddLength = 0; + private readonly UInt16? _protLengthIndex = null; + private const Int32 DefaultResponseTimeoutMs = 50; + private Int32 _responseTimeoutMs = DefaultResponseTimeoutMs; + + private const UInt32 BaudRate = 115200; + + private readonly UInt32? _receiveBufferFlushThreshold = 1000; + + /// + public override void SetResponseTimeout(Int32 responseTimeoutMs) + { + _responseTimeoutMs = responseTimeoutMs; + } + /// + public override void SetDefaultResponseTimeout() + { + _responseTimeoutMs = DefaultResponseTimeoutMs; + } + public LedTransmitProtocol(String portName) : base (portName) + { + + } + public override TransmitPortSettings GetTransmitPortSettings() + { + return new TransmitPortSettings(_protSyncByte, _protLengthIndex, ProtAddLength, _responseTimeoutMs, BaudRate, + _receiveBufferFlushThreshold); + } + + public TransmitPortSettings GetTransmitPortSettings(UInt32 baudRate) + { + return new TransmitPortSettings(_protSyncByte, _protLengthIndex, ProtAddLength, _responseTimeoutMs, baudRate, + _receiveBufferFlushThreshold); + } + + /// + public override List DecodeDataForPhysicalLayer(String ident, Byte command, Byte[] payload, Boolean hideDataInLog = false) + { + throw new ApplicationException("Streaming port cannot send data"); + } + + /// + public override List DecodeDataForLogicLayer(String ident, List rawData, Boolean hideDataInLog = false) + { + return rawData; + } + + public override List DecodeDataForPhysicalLayerUI1236(String ident, Byte[] payload, Boolean hideDataInLog = false) + => throw new ApplicationException("Streaming port cannot send data"); + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/TransmitProtocol/RfidTransmitProtocol.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/TransmitProtocol/RfidTransmitProtocol.cs new file mode 100644 index 000000000..63678c426 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/TransmitProtocol/RfidTransmitProtocol.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.TransmitProtocol +{ + public class RfidTransmitProtocol : UartTransmitProtocol + { + //use the UART transmit protocol sync byte wrapped to RFID protocol + private const Byte ProtSyncByte = 0x5B; + private const UInt16 ProtAddLength = 1; + private readonly UInt16? _protLengthIndex = 1; + //this will be used if RFID protocol has been extracted from RfidComPort + //private const Byte ProtSyncByte = 0x01; + //private const UInt16 ProtAddLength = 1; + //private readonly UInt16? _protLengthIndex = 3; + + private const Int32 DefaultResponseTimeoutMs = 1550; + private Int32 _responseTimeoutMs = DefaultResponseTimeoutMs; + + /// + public override void SetResponseTimeout(Int32 responseTimeoutMs) + { + _responseTimeoutMs = responseTimeoutMs; + } + /// + public override void SetDefaultResponseTimeout() + { + _responseTimeoutMs = DefaultResponseTimeoutMs; + } + private const UInt32 BaudRate = 9600; + private readonly UInt32? _receiveBufferFlushThreshold = null; + + public RfidTransmitProtocol(String portName) : base (portName) + { + + } + public override TransmitPortSettings GetTransmitPortSettings() + { + return new TransmitPortSettings(ProtSyncByte, _protLengthIndex, ProtAddLength, + _responseTimeoutMs, BaudRate, _receiveBufferFlushThreshold); + } + public override List SpecificCrcCalc() + { + //returns an empty list of bytes to avoid filling of CRC in advance to the CRC calculation, + //because the RFID does not use the CRC filled up with 0x00, 0x00 to calculate itself + return new List(); + } + } +} + + diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/TransmitProtocol/UartTransmitProtocol.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/TransmitProtocol/UartTransmitProtocol.cs new file mode 100644 index 000000000..53d1e1bbb --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Interfaces/Protocols/TransmitProtocol/UartTransmitProtocol.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.TransmitProtocol +{ + public class UartTransmitProtocol : BaseTransmitProtocol + { + // Format of UART transmit protocol: + // UartSyncByte|UartLength LSB|UartLength MSB|requestProtocolCommand| + // NextRequestProtocolCommand|UartCrc LSB|UartCrc MSB|requestProtocolPayload + // UartLength MSB will NOT be used, it is always 0x00 + // NextRequestProtocolCommand will NOT be used, it is always 0x00 + + //Indexes of UART transmit protocol + private const Int32 UartSyncByteIndex = 0; + private const Int32 UartLengthIndex = 1; + private const Int32 UartCommandIndex = 3; + private const Int32 UartCrcIndex = 5; + private const Int32 UartPayloadIndex = 7; + + //length of UART transmit protocol fields + private const Int32 UartSyncByteLength = 1; //length of syncByte + private const Int32 UartHeaderLength = 4; //length excluding syncByte and CRC + private const Int32 UartCrcLength = 2; + private const Int32 UartProtocolFrameLength = UartSyncByteLength + UartHeaderLength + UartCrcLength; + + private const Byte UartSyncByte = 0x5B; + //The UART length covers all bytes excluding the syncByte length + private const Int32 DefaultResponseTimeoutMs = 50; + private Int32 _responseTimeoutMs = DefaultResponseTimeoutMs; + private const UInt16 ProtAddLength = UartSyncByteLength; + private const UInt32 BaudRate = 9600; + private readonly UInt32? _receiveBufferFlushThreshold = null; + private const Boolean UartDoubleSyncByte = true; + /// + public override void SetResponseTimeout(Int32 responseTimeoutMs) + { + _responseTimeoutMs = responseTimeoutMs; + } + /// + public override void SetDefaultResponseTimeout() + { + _responseTimeoutMs = DefaultResponseTimeoutMs; + } + public UartTransmitProtocol(String portName) : base (portName) + { + + } + public override TransmitPortSettings GetTransmitPortSettings() + { + return new TransmitPortSettings(UartSyncByte, UartLengthIndex, ProtAddLength, + _responseTimeoutMs, BaudRate, _receiveBufferFlushThreshold, UartDoubleSyncByte); + } + + public override List DecodeDataForLogicLayer(String ident, List uartRecord, Boolean hideDataInLog = false) + { + // check the record length and start of frame information + if (uartRecord.Count < uartRecord[UartLengthIndex] + UartSyncByteLength + || UartSyncByte != uartRecord[UartSyncByteIndex]) + { + var error = new ApplicationException($"{ident} Reply from UART is invalid. Decoding of transmit layer failed."); + Logger.Error(error.Message, error); + return new List(); + } + + //extract the header without syncByte and the payload, call CRC calculation + var calculatedCrc = CrcCalc(uartRecord.GetRange(UartLengthIndex, UartHeaderLength), + uartRecord.GetRange(UartPayloadIndex, uartRecord.Count - UartProtocolFrameLength)); + + //extract the received CRC, first the MSB + UInt16 receivedCrc = uartRecord[UartCrcIndex + 1]; + receivedCrc <<= 8; + receivedCrc += uartRecord[UartCrcIndex]; + + if(receivedCrc != calculatedCrc) + { + var error = new ApplicationException($"{ident} Reply CRC failure. Decoding of transmit layer failed."); + Logger.Error(error.Message, error); + return new List(); + } + + //the request protocol needs the requestProtocolCommand and the requestProtocolPayload + var ret = new List {uartRecord[UartCommandIndex]}; + //add the requestProtocolCommand + //add the request protocol payload + ret.AddRange(uartRecord.GetRange(UartPayloadIndex, uartRecord.Count - UartProtocolFrameLength)); + return ret; + } + + public virtual List SpecificCrcCalc() + { + //for the pure UART transmit protocol the CRC fields will be set + //to 0x00 and used for the CRC calculation + return new List() { 0x00, 0x00 }; + } + public UInt16 CrcCalc(List header, List payload) + { + //the CRC calculation excludes the syncByte and optional includes the + //CRC fields filled up with 0x00 + var tmpArr = new List(); + tmpArr.AddRange(header); + + //this adds the optional CRC fields + tmpArr.AddRange(SpecificCrcCalc()); + tmpArr.AddRange(payload); + + return Crc16Ccitt.CalculateMsb1021(tmpArr.ToArray()); + } + + /// + public override List DecodeDataForPhysicalLayer(String ident, Byte requestProtocolCommand, + Byte[] requestProtocolPayload, Boolean hideDataInLog = false) + { + var uartHeader = new List(); + var uartPayload = requestProtocolPayload.ToList(); + var uartRecord = new List(); + + //first assemble the list being used for CRC calculation, + //this list does NOT contain the syncByte and optional + //the CRC LSB and MSB if set in SpecificCrcCalc() + + //the length of length LSB and MSB: 2 + //add the length of the requestProtocolCommand: 1 + //the nextRequestProtocolCommand length: 1 + //the length of the CRC MSB and LSB: 2 + uartHeader.Add((Byte)(requestProtocolPayload.Length + UartHeaderLength + UartCrcLength)); + //add the length MSB, always 0x00 + uartHeader.Add(0x00); + uartHeader.Add(requestProtocolCommand); + //add the nextRequestProtocolCommand, always 0x00 + uartHeader.Add(0x00); + var crcResult = CrcCalc(uartHeader, uartPayload); + + //assemble the entire record for sending via the communication port + uartRecord.Add(UartSyncByte); + uartRecord.AddRange(uartHeader); + uartRecord.Add((Byte)(crcResult & 0xFF)); + uartRecord.Add((Byte)(crcResult >> 8)); + uartRecord.AddRange(uartPayload); + + return uartRecord; + } + + /// + /// + /// - Initial + /// + public override List DecodeDataForPhysicalLayerUI1236(String ident, Byte[] requestProtocolPayload, + Boolean hideDataInLog = false) + { + var uartHeader = new List(); + var uartPayload = requestProtocolPayload.ToList(); + var uartRecord = new List(); + uartHeader.Add((Byte)(requestProtocolPayload.Length + 4 + 2)); + uartHeader.Add(0); + uartHeader.Add(0); + var crcResult = CrcCalc(uartHeader, uartPayload); + uartRecord.Add(91); + uartRecord.AddRange(uartHeader); + uartRecord.Add((Byte)(crcResult & 0xFF)); + uartRecord.Add((Byte)(crcResult >> 8)); + uartRecord.AddRange(uartPayload); + return uartRecord; + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/ProgramConfig.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/ProgramConfig.cs new file mode 100644 index 000000000..3d32dc6c8 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/ProgramConfig.cs @@ -0,0 +1,53 @@ +using System; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis +{ + public static class ProgramConfig + { + /// + /// Name of register definition file + /// + public const String RegisterDefinitionFileName = "configuration.json"; + + /// + /// Name of serial configuration file + /// + public const String FM2014ConfigFileName = "FM2014Config.json"; + + /// + /// Name of serial configuration file + /// + public const String SerialConfigFileName = "SerialConfig.json"; + + /// + /// Name for NLog config + /// + public const String NlogConfig = "NlogConfig.xml"; + + /// + /// Name for meter config + /// + public const String MeterConfigFileName = "MeterConfig.json"; + + /// + /// Name for SIRT config + /// + public const String SirtConfigFileName = "SirtConfig.json"; + + /// + /// Name for offline information e.g. Passwords + /// + public const String OfflineInfoFile = "OfflineFile.json"; + + /// + /// Path for data logger. + /// + public const String BaseLoggingPath = "C:\\GenesisLog\\"; + + /// + /// Sub folder for genesis contents. + /// + public const String GenesisBaseFolder = "Genesis\\"; + + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/Consts/Commands.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/Consts/Commands.cs new file mode 100644 index 000000000..2cf8da454 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/Consts/Commands.cs @@ -0,0 +1,125 @@ +using System; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.RequestProtocol.Consts +{ + + /// + /// Commands that the genesis meter support over the request protocol. + /// Functional Specification Breeze Core and Applications Revision:3.03(12748DOC11 - functional spec ICOE472.pdf) + /// + public static class Commands + { + /// + /// 9.2.5 + ///Command 0x00 (NOP) + ///This command will do nothing, and will have no response. + /// + public const Byte Nop = 0x00; + /// + /// 9.2.6 + /// Command 0x01 (Query capabilities) + /// This command will be used by the external computer to discover the protocol parameters that may be varied. + /// These can then be compared with the external computer’s capabilities and the best match selected. + /// + public const Byte QueryCaps = 0x01; + /// + /// response on + /// + public const Byte QueryCapsReply = 0x02; + /// + /// 9.2.7 + /// Command 0x03 (Set capabilities) + /// Used to finalize the baud rate and packet settings after negotiation. + /// The reply will be sent at the currently selected baud rate and packet length, + /// after which the settings will take effect + /// + public const Byte SetCaps = 0x03; + /// + /// response on + /// + public const Byte SetCapsReply = 0x04; + /// + /// 9.2.8 + /// Command 0x05 (Train) + ///This command will perform target driven data training, that is, where the target is in control of the data flow. + ///See also command 0x11. + /// + public const Byte Train = 0x05; + /// + /// response on + /// + public const Byte TrainReply = 0x06; + /// + /// 9.2.9 + /// Command 0x07 (Repeat last) + /// Used by the external computer to request the last response to be resent, for example if it was found to be corrupted. + /// Note that the response 0x08 will never be sent, the reply to command 0x07 will be a verbatim resend of the last response. + /// + public const Byte RepeatLast = 0x07; + /// + /// response on + /// + public const Byte RepeatLastReply = 0x08; + /// + /// 9.2.10 + ///Command 0x09 (Read data) + ///This command makes reads of any random selection of configuration registers, up to the maximum packet size negotiated. + /// + public const Byte ReadData = 0x09; + /// + /// response on + /// + public const Byte ReadDataReply = 0x0A; + /// + /// 9.2.11 + /// Command 0x0B (Write data) + /// This command makes writes to any random selection of configuration registers, up to the maximum packet size negotiated. + /// + public const Byte WriteData = 0x0B; + /// + /// response on + /// + public const Byte WriteDataReply = 0x0C; + /// + /// 9.2.12 + ///Command 0x0D (Multiple read data) + ///This command makes reads of one register multiple times, which will be more efficient than performing successive reads using command 0x09. + ///This command will be available from protocol version 0.40, for earlier protocol versions command 0x09 should be used. + /// + public const Byte MultipleReadData = 0x0D; + /// + /// response on + /// + public const Byte MultipleReadDataReply = 0x0E; + /// + /// 9.2.13 + /// Command 0x0F (Multiple write data) + /// This command makes writes to one register multiple times, which will be more efficient than performing successive reads using command 0x0B. + /// This command will be available from protocol version 0.40, for earlier protocol versions command 0x0B should be used. + /// + public const Byte MultipleWriteData = 0x0F; + /// + /// response on + /// + public const Byte MultipleWriteDataReply = 0x10; + /// + /// 9.2.14 + /// Command 0x11 (Set level) + /// This command will perform external driven data training, that is, where the external computer is in control of the data flow. + /// This command will be available from protocol version 0.42, for earlier protocol versions command 0x05 should be used. + /// + public const Byte SetLevel = 0x11; + /// + /// response on + /// + public const Byte SetLevelReply = 0x12; + + //todo remove? + //public const byte SetBitrateLsb = 0x03; + //public const byte SetBitrateMsb = 0x05; + //public const byte SetPacketSizesLsb = 0x04; + //public const byte SetPacketSizesMsb = 0x05; + //public const byte SetProtocolLsb = 0x02; + //public const byte SetProtocolMsb = 0x05; + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/Consts/ConfigExErrors.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/Consts/ConfigExErrors.cs new file mode 100644 index 000000000..0c6dff4d5 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/Consts/ConfigExErrors.cs @@ -0,0 +1,31 @@ +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.RequestProtocol.Consts +{ + /// + /// Config Exchange error codes. + /// + public enum ConfigExErrors + { +#pragma warning disable CS1591 //as a matter of course + Base = 0x04, + LockedOut = 0x00, + AuthenticationFail = 0x01, + AccessDenied = 0x02, + UnknownParameter = 0x03, + InUse = 0x04, + SizesDoesNotMatch = 0x05, + CantReadCfgFile = 0x06, + UserNotKnown = 0x07, + CantCreateCfgFile = 0x08, + StoringFailed = 0x09, + StoreCorrupt = 0x0A, + ExpectedWrite = 0x0B, + ExpectedRead = 0x0C, + StopCycling = 0x0D, + TooManyOpen = 0x0E, + NeverOpened = 0x0F, + FileProtected = 0x1F, + PartialRecall = 0x2F, + DefaultPasswordUsed = 0x3F, +#pragma warning restore + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/Consts/HighLevelErrors.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/Consts/HighLevelErrors.cs new file mode 100644 index 000000000..f1bd064e0 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/Consts/HighLevelErrors.cs @@ -0,0 +1,25 @@ +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.RequestProtocol.Consts +{ + /// + /// Transport errors for request protocol + /// + public enum HighLevelErrors + { +#pragma warning disable CS1591 //as a matter of course + Base = 0x05, + PayloadCount = 0x00, + InvalidSubReason = 0x01, + InvalidBaudrate = 0x02, + InvalidBufferSize = 0x03, + CrcFailure = 0x04, + UnrecognizedCmd = 0x05, + Framing = 0x06, + Overflow = 0x07, + PacketTimeout = 0x08, + InvalidEscape = 0x09, + UnknownParameter = 0x0A, + TrainingFailed = 0x0B, + NoBreak = 0x0C, +#pragma warning restore + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/Consts/LowLevelErrors.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/Consts/LowLevelErrors.cs new file mode 100644 index 000000000..09cb5fb0e --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/Consts/LowLevelErrors.cs @@ -0,0 +1,47 @@ +using System; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.RequestProtocol.Consts +{ + /// + /// List of constance for error codes from genesis meter + /// + public static class LowLevelErrors + { + /// + /// everything is fine + /// + public const Int16 NoError = 0; + /// + /// Port is not open + /// + public const Int16 SerialPortClosed = -2; + /// + /// fails to write to serial port + /// + public const Int16 SerialPortWriteFailure = -3; + /// + /// fails to read from serial port + /// + public const Int16 SerialPortReadFailure = -4; + /// + /// Command is to long + /// + public const Int16 MaxDataLength = -5; + /// + /// deeper exception, check out log if this happen + /// + public const Int16 Exception = -6; + /// + /// wrong CRC + /// + public const Int16 CrcCalcError = -7; + /// + /// something strange + /// + public const Int16 Unknown = -8; + /// + /// Timeout occur + /// + public const Int16 Timeout = -9; + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/Consts/RequestAcknowledgeState.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/Consts/RequestAcknowledgeState.cs new file mode 100644 index 000000000..5ff6e4eec --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/Consts/RequestAcknowledgeState.cs @@ -0,0 +1,48 @@ +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.RequestProtocol.Consts +{ + /// + /// Acknowledge feedback from meter after communication + /// + public enum RequestAcknowledgeState + { + /// + /// Acknowledge code for unassigned command + /// + CommandNotAssigned, + /// + /// Will be used initially as the record is not sent + /// + Unsent, + /// + /// Response missing + /// + NoResponse, + /// + /// Response command not match the required command + /// + CommandError, + /// + /// Lost connection (logged out from meter), a re-authorization is required + /// to access this command and/or register + /// + AuthorizationRequired, + /// + /// Meter error received, a retry may be useful + /// + MeterError, + /// + /// The response record couldn't be decoded + /// + DecodingError, + /// + /// The meter sent a wakeup message instead of the required data + /// + WakeupMessage, + /// + /// Valid meter response record + /// + Ok + + + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/EventArguments/AuthorizationRequiredEventArgs.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/EventArguments/AuthorizationRequiredEventArgs.cs new file mode 100644 index 000000000..e28e18c9c --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/EventArguments/AuthorizationRequiredEventArgs.cs @@ -0,0 +1,25 @@ +using System; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.RequestProtocol.EventArguments +{ + /// + /// + /// if session is gone and a re authorization is necessary + /// + public class AuthorizationRequiredEventArgs : EventArgs + { + /// + /// + /// Ctor + /// + /// this command will be resend after authorization + public AuthorizationRequiredEventArgs(RequestRecord command = null) + { + Command = command; + } + /// + /// Command to resend + /// + public RequestRecord Command; + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/Exceptions/RequestProtocolException.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/Exceptions/RequestProtocolException.cs new file mode 100644 index 000000000..248f6c9b2 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/Exceptions/RequestProtocolException.cs @@ -0,0 +1,59 @@ +using System; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.RequestProtocol.Exceptions +{ + /// + /// Holds all errors can occur on the request protocol + /// + public class RequestProtocolException : Exception + { + /// + /// Exception occurred on this command + /// + public Object Command; + /// + /// Exception occurred while receiving this data + /// + public Object ReceivedData; + + /// + /// Error is interpreted and has a define error code + /// if its not null is Genesis.Protocols.Request.Const.ConfigExErrors + /// + public Int32? ConfigExErrorCode; + /// + /// Error is interpreted and has a define error code + /// if its not null is Genesis.Protocols.Request.Const.HighLevelError + /// + public Int32? HighLevelErrorCode; + + /// + /// Ctor with only message + /// + /// error message + public RequestProtocolException(String message) : base(message) + { + } + /// + /// Ctor with message and + /// + /// error message + /// + public RequestProtocolException(String message, Object command) : base(message) + { + Command = command; + ReceivedData = null; + } + /// + /// Ctor with message, and + /// + /// error message + /// + /// + public RequestProtocolException(String message, Object command, Object receivedData) : base(message) + { + Command = command; + ReceivedData = receivedData; + } + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/RequestProtocol.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/RequestProtocol.cs new file mode 100644 index 000000000..23fe03a6c --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/RequestProtocol.cs @@ -0,0 +1,767 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using log4net; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.GenesisConfig; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.ProtocolCore; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.RequestProtocol.Consts; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers; + + + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.RequestProtocol +{ + /// + /// a bidirectional protocol support read and write genesis meter registers + /// needed for + /// + public class RequestProtocol : BaseProtocol + { + //All read and write data sets (payload size) are organized in 4 byte chunks + + //Format of RequestProtocol read data + //Request CMD | Register LSB | Register MSB | 0x00 | 0x00 | 0x00 | 0x00 + //Response CMD | ErrCode Reason | ErrCode Base | DATA0 | DATA1 | DATA2 | DATA3 + + //Format of RequestProtocol write data + //Request CMD | Register LSB | Register MSB | DATA0 | DATA1 | DATA2 | DATA3 + //Response CMD | ErrCode Reason | ErrCode Base | UNDEF0 | UNDEF1 | UNDEF2 | UNDEF3 + + //Format of RequestProtocol multiple read data + //Request CMD | Register LSB | Register MSB | RequestChunks LSB | RequestChunks MSB + //Response CMD | ErrCode Reason | ErrCode Base | ErrPosition LSB | ErrPosition MSB | + //DATA0 | DATA1 | DATA2 | DATA3.... (repeat for number of chunks) + + //Format of RequestProtocol multiple read data + //Request CMD | Register LSB | Register MSB | WriteChunks LSB | WriteChunks MSB | + //DATA0 | DATA1 | DATA2 | DATA3.... (repeat for number of chunks) + //Response CMD | ErrCode Reason | ErrCode Base | ErrPosition LSB | ErrPosition MSB + + //Indexes of the request protocol read/write data + //private const Int32 RequestCommandIndex = 0; + //private const Int32 RequestRegisterIndex = 1; + //private const Int32 RequestDataIndex = 3; + + //Indexes for response protocol read/write data + private const Int32 ResponseCommandIndex = 0; + private const Int32 ResponseErrorCodeIndex = 1; + private const Int32 ResponseDataIndex = 3; + + //Indexes of the request protocol multiple read/write data + //private const Int32 RequestChunksIndex = 3; + //private const Int32 RequestMultiDataIndex = 5; + + //Indexes of the response protocol multiple read/write data + private const Int32 ResponseMultiErrorPosition = 3; + private const Int32 ResponseMultiDataIndex = 5; + + //Indexes of error code + private const Int32 ErrorCodeReasonIndex = 0; + private const Int32 ErrorCodeBaseIndex = 1; + + //Multiple response header size is 1 byte command 2 bytes error code and 2 bytes error position + private const Int32 ResponseMultiHeaderSize = 5; + + //fill byte + private const Byte FillByte = 0x00; + + private static readonly ILog _logger = LogManager.GetLogger(typeof(RequestProtocol)); + + // wakeup from register (water meter) to adapter + private static readonly Byte[] WakeupMessage = { 0x00, 0xFF, 0xFF }; + + /// + /// FIFO of records to be send next + /// + private readonly ConcurrentQueue _recordsSendFifo = new ConcurrentQueue(); + + /// + /// This is the actual record in the send loop + /// + private RequestRecord _recordInProcess; + + /// + /// Last communication time for session refresh + /// + public DateTimeOffset? LastCommTime; + + /// + public override event EventHandler OnRecordReadyToSend; + + /// + public override event EventHandler OnRecordIsDecoded; + + /// + /// Occurs when a meter Response for write password is good + /// + public event EventHandler OnAuthorizationGrant; + + /// + /// Event after a register entries changed + /// + public event EventHandler OnMeterRegisterUpdated; + + /// + /// User adjustable additional retry timeout. This is 0 ms for standard operation. + /// + public Int32 AdditionalRetryTimeoutMs; + + private Int32 _maxTimeOutMs; + + private readonly String _ident; + + /// + public RequestProtocol(String ident) : base(ident) + { + _ident = ident; + } + + /// + /// Process all records needed to be sent, this routine has to be called + /// to kick-off the communication of all RequestRecords saved to the send + /// FIFO + /// + /// + /// - Initial + /// + /// + /// - Logging of raw RequestProtocol at time of sending, + /// - Logging of retries. + /// + /// + /// - Added timeout from transmit protocol. + /// + /// + /// - Added reorder FIFO to bring login at first position + /// + /// + /// - Hide data in logging for e.g. passwords + /// + /// + /// - Retry counter reset if authorization required and this record will be put back into FIFO, + /// - Retry delay corrected for all errors. + /// + /// + /// - Dynamic retry delay: ResponseTimeoutMs * Retry counter. + /// + /// + /// - Skip retries on specific error mask to speed up communication on functional errors + /// or informational feed backs (e.g FW not installed 0x0004) + /// + /// + /// + /// - User adjustable additional retry timeout. + /// + /// + /// - Response timeout message output, + /// - Initialize acknowledge code before communication to NoResponse. + /// + /// + /// - Response timeout deviated from system time. + /// + /// + /// - Avoid enqueue of _recordInProcess if retry counter is 0. + /// + /// + /// - Additional DEBUG information included about FIFO and loops. + /// + /// + /// - Command not assigned response for recordInProcess == null. + /// + /// + /// - Initial request acknowledge state changed from NotDecoded to NoResponse. + /// + public RequestAcknowledgeState ProcessRecordList() + { + _logger.Debug($"{_ident} Entry to ProcessRecordList, FIFO contains ({_recordsSendFifo.Count}) records"); + + //The inter record send delay has to be hold before trying to communicate again + const Int32 interRecordSendDelayMs = CommunicationConfig.InterRecordSendDelayMs; + + //remind counter for FIFO and therefore execution loops + var loopCounter = 0; + + //check if FIFO is empty and get the actual record to process out of it + while (_recordsSendFifo.TryDequeue(out var internalRecord)) + { + // Starting with record number 1 + loopCounter++; + _logger.Debug($"{_ident} Record({loopCounter}) - Dequeued from FIFO"); + + //pointer to new record + _recordInProcess = internalRecord; + if (_recordInProcess == null) + return RequestAcknowledgeState.CommandNotAssigned; + + do + { + //mark record as answer outstanding + _recordInProcess.Acknowledge = RequestAcknowledgeState.NoResponse; + + _logger.Debug($"{_ident} Record({loopCounter}) - Processing"); + //log retries + if (_recordInProcess.RetryCtr > 0) + { + _logger.Debug($"{_ident} Retry({_recordInProcess.RetryCtr})"); + _logger.Debug($"{_ident} Record({loopCounter}) - Retry({_recordInProcess.RetryCtr})"); + } + + //log request protocol content + _logger.Debug(_recordInProcess.HideDataInLog + ? $"{_ident} SentData(*****)" + : $"{_ident} SentData({BitConverter.ToString(_recordInProcess.RequestProtocolData.ToArray())})"); + + //remind time for keep-session-active test to deny automatic logout of meter + LastCommTime = DateTimeOffset.UtcNow; + + //set timeout for one communication trial adding the transmit protocol specific timeout and + //increase the timeout with each retry + _maxTimeOutMs = _recordInProcess.ResponseTimeoutMs + AdditionalRetryTimeoutMs + + CommunicationConfig.ResponseTimeoutMs * (_recordInProcess.RetryCtr + 1); + //start initial communication or retry + OnRecordReadyToSend?.Invoke(this, new ListBytePortDataEventArgs(_recordInProcess.EncodedRequestData)); + + //time reminder of request record + var requestTimeUtc = DateTimeOffset.UtcNow; + Int32 actualResponseWaitTimeMs; + + //wait for communication acknowledge or until timeout, this also handles the inter record send delay + do + { + //minimum delay is the inter record send delay + Thread.Sleep(interRecordSendDelayMs); + var actualTimeUtc = DateTimeOffset.UtcNow; + //actual time difference from request to now + var timeSpan = actualTimeUtc - requestTimeUtc; + //avoid total milliseconds below zero at time overflow + if (timeSpan.TotalMilliseconds < 0) + { + requestTimeUtc = DateTimeOffset.UtcNow; + } + actualResponseWaitTimeMs = (Int32)timeSpan.TotalMilliseconds; + + } while (_recordInProcess.Acknowledge != RequestAcknowledgeState.Ok && + _recordInProcess.SkipRetryErrorCode != _recordInProcess.ResponseErrorCode && + _maxTimeOutMs > actualResponseWaitTimeMs); + + //if timeout value reaches zero, the response hasn't been received or the delay until + //next communication needed to be hold + if (_maxTimeOutMs <= actualResponseWaitTimeMs) + { + _logger.Error(_recordInProcess.Acknowledge == RequestAcknowledgeState.NoResponse + ? $"{_ident} Response timeout({actualResponseWaitTimeMs}ms)" + : $"{_ident} Communication delay({actualResponseWaitTimeMs}ms)"); + } + + //skip loop to handle re-authorization + if (_recordInProcess.Acknowledge != RequestAcknowledgeState.AuthorizationRequired) + continue; + + //reset retry counter for this record, it will be dispatched after re-authorization + _recordInProcess.RetryCtr = 0; + + if (CommunicationConfig.RequestRetries > 0) + { + //add the recordInProcess to the top of the FIFO + _recordsSendFifo.Enqueue(_recordInProcess); + } + //call login + return _recordInProcess.Acknowledge; + + } while (_recordInProcess.Acknowledge != RequestAcknowledgeState.Ok && + _recordInProcess.RetryCtr++ < CommunicationConfig.RequestRetries && + _recordInProcess.SkipRetryErrorCode != _recordInProcess.ResponseErrorCode); + + } + + return _recordInProcess.Acknowledge; + } + + /// + /// Assemble record with transmit protocol and put it to record-send-FIFO, + /// backup the ready-to-send, which is the dataEncodedWithTransmitProtocol, + /// to the RequestRecord object for sending including the retry capability. + /// + /// Data package with all details like CRC, etc + /// Command identifier + /// which is intend + /// hiding data in log file to avoid spying of passwords + /// error mask to skip retries + /// the assembled record for the send FIFO + /// + /// - Initial + /// + /// + /// - Logging of raw data (RequestProtocol) moved to ProcessRecordList. + /// + /// + /// - Added timeout from transmit protocol. + /// + /// + /// - Hide data in logging for e.g. passwords + /// + /// + /// - Skip retries on specific error mask to speed up communication on functional errors + /// or informational feed backs (e.g FW not installed 0x0004) + /// + /// + /// + /// - Initial skip retry error code set to 0x0004 (e.g FW not installed 0x0004). + /// + /// + /// - Hide data in log forwarded to DecodeDateForPhysicalLayer to hide passwords in log files. + /// + private RequestRecord AddRecordToSendFifo(Byte cmd, Byte[] payload, RegisterDefinition register = null, + Boolean hideDataInLog = false, UInt16 skipRetryErrorCode = CommunicationConfig.SkipRetryErrorCode) + { + var requestProtocolData = new List { cmd }; + requestProtocolData.AddRange(payload); + + //encode request protocol data with transmit protocol + var transmit = GetTransmitProtocol(); + var dataEncodedWithTransmitProtocol = transmit.DecodeDataForPhysicalLayer(_ident, cmd, payload, hideDataInLog); + + //remind port specific response timeout + var transmitPortSettings = transmit.GetTransmitPortSettings(); + + var recordForSendFifo = new RequestRecord(cmd, requestProtocolData, dataEncodedWithTransmitProtocol, + transmitPortSettings.ResponseTimeoutMs, register, hideDataInLog, skipRetryErrorCode); + + _recordsSendFifo.Enqueue(recordForSendFifo); + + return recordForSendFifo; + } + + + /// + /// Record dispatcher to send FIFO of UI1236 command + /// + /// + /// - Initial + /// + /// string for logging of slot + /// Data package with all details like CRC, etc + /// which is intend + /// hiding data in log file to avoid spying of passwords + /// error mask to skip retries + /// the assembled record for the send FIFO + // ReSharper disable once InconsistentNaming UI1236 is a naming forced by the caller + public RequestRecord AddRecordToSendFifoUI1236(String ident, Byte[] payload, RegisterDefinition register = null, + Boolean hideDataInLog = false, UInt16 skipRetryErrorCode = CommunicationConfig.SkipRetryErrorCode) + { + const Byte maxValue = byte.MaxValue; + var requestProtocolData = new List + { + maxValue + }; + requestProtocolData.AddRange(payload); + var transmitProtocol = GetTransmitProtocol(); + var encodedRequestData = transmitProtocol.DecodeDataForPhysicalLayerUI1236(ident, payload, hideDataInLog); + var transmitPortSettings = transmitProtocol.GetTransmitPortSettings(); + var sendFifoUi1236 = new RequestRecord(maxValue, requestProtocolData, encodedRequestData, + transmitPortSettings.ResponseTimeoutMs, register, hideDataInLog, skipRetryErrorCode); + _recordsSendFifo.Enqueue(sendFifoUi1236); + return sendFifoUi1236; + } + + /// + /// Called after successful response. + /// Decode and check record, handle Errors and dispatch result. + /// Invokes if somebody is listening + /// + /// + /// - Initial + /// + /// + /// - First part reworked to extract the response protocol information + /// + /// + /// - Hide data in logging for e.g. passwords + /// + /// + /// - Error code extraction changed for + /// + /// + /// - Wakeup-message will avoid further timeout (during FW update this is in the range + /// of 15000ms). + /// - Allow one additional retry on decoding error, which is often the wakeup-message. + /// + /// + /// - Send time reminder for timeout time calculation as output in log-file, + /// - OnRecordIsDecoded?.Invoke moved before return to assure that the Acknowledge status is set. + /// + /// + /// - Avoid activation of wakeup retry if record is meanwhile acknowledged. + /// + /// + /// - Wakeup message handling changed, + /// - Multiple replies on wakeup message allowed. + /// + /// + /// - On wakeup message 5 retires are allowed to avoid an infinite loop. + /// + /// + /// - On wakeup message exit this routine. + /// + /// + /// - Early exit on _recordInProcess == null, + /// - Wakeup message retries from 5 to 2, + /// - Removed error base from error code decision as error base is only the AppId + /// + /// + /// - HideDataInLog. + /// + /// + /// - Ignore wakeup if message already acknowledged (avoid to set + /// "_recordInProcess.Acknowledge = RequestAcknowledgeState.NotDecoded"), + /// - Avoid to overwrite "_recordInProcess.Acknowledge = RequestAcknowledgeState.Acknowledge" with + /// "RequestAcknowledgeState.WakeupMessage". + /// + protected override void DecodeRecord(IPortDataEventArgs data) + { + if (_recordInProcess == null) + return; + //the request response record covers the entire request protocol + var responseRecord = GetTransmitProtocol().DecodeDataForLogicLayer(_ident, (List)data.GetData(), + _recordInProcess.HideDataInLog); + + //on protocol decoding failure + if (responseRecord.Count == 0) + { + _recordInProcess.Acknowledge = RequestAcknowledgeState.DecodingError; + _logger.Debug($"{_ident} Message decoding error."); + //reset timeout to a normal value if timeout is extremely high but response received + if (_maxTimeOutMs > CommunicationConfig.BusyTimeoutMs) + _maxTimeOutMs = CommunicationConfig.BusyTimeoutMs; + return; + } + + if (responseRecord.Count == WakeupMessage.Length) + { + // check for wakeup message + var wakeUp = true; + for (var i = 0; i < WakeupMessage.Length; i++) + { + if (responseRecord[i] != WakeupMessage[i]) + wakeUp = false; + } + + if (wakeUp) + { + //avoid wakeup retry on acknowledged record + if (RequestAcknowledgeState.Ok != _recordInProcess.Acknowledge) + _recordInProcess.Acknowledge = RequestAcknowledgeState.WakeupMessage; + //initiate a single retry on wakeup message response + if (_recordInProcess.WakeupMessageRetryCtr < 2) + { + _recordInProcess.WakeupMessageRetryCtr++; + _logger.Debug( + $"{_ident} Wakeup message({_recordInProcess.WakeupMessageRetryCtr}) received"); + } + } + else + { + //if it is not identified as valid wakeup message it is something unknown + _recordInProcess.Acknowledge = RequestAcknowledgeState.DecodingError; + _logger.Debug($"{_ident} Message decoding error."); + } + + // reset retry counter to get all required retries + _recordInProcess.RetryCtr = -1; + //reset timeout to a normal value if timeout is extremely high but response received + if (_maxTimeOutMs > CommunicationConfig.BusyTimeoutMs) + _maxTimeOutMs = CommunicationConfig.BusyTimeoutMs; + return; + } + + //log request protocol content + _logger.Debug(_recordInProcess.HideDataInLog + ? $"{_ident} DecodedRecord(*****)" + : $"{_ident} DecodedRecord({BitConverter.ToString(responseRecord.ToArray())})"); + + //extract information command + var replyCmd = responseRecord[ResponseCommandIndex]; + + //extract position of error for multiple access + if (replyCmd == Commands.MultipleReadDataReply || replyCmd == Commands.MultipleWriteDataReply) + { + _recordInProcess.ResponseErrorPosition = + (UInt16)((responseRecord[ResponseMultiErrorPosition] & 0x00FF) | + (UInt16)((responseRecord[ResponseMultiErrorPosition + 1] << 8) & 0xFF00)); + } + + //extract the error codes from response record to _record in process + _recordInProcess.ResponseErrorBase = responseRecord[ResponseErrorCodeIndex + ErrorCodeBaseIndex]; + _recordInProcess.ResponseErrorReason = responseRecord[ResponseErrorCodeIndex + ErrorCodeReasonIndex]; + //combine error base and error reason + _recordInProcess.ResponseErrorCode = (UInt16)(_recordInProcess.ResponseErrorReason | + (_recordInProcess.ResponseErrorBase << 8)); + + _recordInProcess.ResponsePayload = new List(); + switch (replyCmd) + { + case Commands.MultipleReadDataReply: + _recordInProcess.ResponsePayload.AddRange( + responseRecord.GetRange(ResponseMultiDataIndex, + responseRecord.Count - ResponseMultiHeaderSize)); + break; + case Commands.ReadDataReply: + _recordInProcess.ResponsePayload.AddRange(responseRecord.GetRange(ResponseDataIndex, + RegisterDefinition.ChunkSize)); + break; + } + + //Deny access if reply does not match + if (_recordInProcess.ResponseCommand != replyCmd) + { + _logger.Fatal($"{_ident} Wrong command received({replyCmd:X2})," + + $" expected command({_recordInProcess.ResponseCommand:X2})"); + _recordInProcess.Acknowledge = RequestAcknowledgeState.CommandError; + OnRecordIsDecoded?.Invoke(this, + new RequestResponseDataEventArgs { RequestResponseData = responseRecord }); + return; + } + + //examine error code, if base (AppId) is not 0 the reason 0 may be an error!!!! + if (/*_recordInProcess.ResponseErrorBase != 0 ||*/ _recordInProcess.ResponseErrorReason != 0) + { + _logger.Warn($"{_ident} Error code received(0x{_recordInProcess.ResponseErrorBase:X2}" + + $"{_recordInProcess.ResponseErrorReason:X2})"); + + _recordInProcess.Acknowledge = RequestAcknowledgeState.MeterError; + CheckErrorCode(); + OnRecordIsDecoded?.Invoke(this, + new RequestResponseDataEventArgs { RequestResponseData = responseRecord }); + return; + } + + //register password acknowledge + if (_recordInProcess.Register.GetIdent() == Register.Configexchange.Password) + { + OnAuthorizationGrant?.Invoke(null, null); + } + + //dispatch data + if (_recordInProcess.ResponsePayload != null) + { + OnMeterRegisterUpdated?.Invoke(this, new RegisterUpdatedEventArgs(_recordInProcess.Register, + _recordInProcess.ResponsePayload.ToArray())); + } + + _recordInProcess.Acknowledge = RequestAcknowledgeState.Ok; + OnRecordIsDecoded?.Invoke(this, + new RequestResponseDataEventArgs { RequestResponseData = responseRecord }); + } + + /// + /// Method to send basic Commands to Meter. Creates an integer of 4 bytes for the payload. + /// Supported Commands are: ,, + /// , and . + /// Calculate CRCs and Command length and check if command is valid. + /// Use to push data to Port/Meter. + /// + /// + /// Supported ,, + /// , and . + /// + /// Register to Read or Write, null for + /// + /// Data to push into the . + /// must be null on Read Commands ( ,) + /// and not null on WriteData ( and ) + /// + /// + /// hiding data in log file to avoid spying of passwords + /// mask to skip reties on error + /// an new command just send to port/meter + /// + /// - Initial + /// + /// + /// - Reworked to zero pad payload with chunks of 4 bytes, the caller needn't take care of the size + /// + /// + /// - Corrected payload content in request protocol + /// + /// + /// - Hide data in logging for e.g. passwords + /// + /// + /// - Skip retries on specific error mask to speed up communication on functional errors + /// or informational feed backs (e.g FW not installed 0x0004) + /// + /// + /// + /// - Multiple read for string split to simple reads. + /// + /// + /// - Default set. + /// + /// + /// - MultipleReadData based on data size. + /// + /// + /// - Exit multiple read date if retries exceeded. + /// + /// + /// - Exit multiple read date if retries exceeded increased to . + /// + /// + /// - Rewound to version from 06.09.2023 14:44:33 before Commit 8c9d7704. + /// + /// + /// - Removed useless too short comments as every string is going to be delimited by a 0 and usually won't match + /// to a 4 byte chunk. + /// + /// + /// - Removed redundant 'return cRecord'. + /// + public RequestRecord CommandToMeter(Byte command, RegisterDefinition meterRegister = null, + Byte[] payload = null, Int32? expectedLength = null, Boolean hideDataInLog = false, + UInt16 skipRetryErrorCode = CommunicationConfig.SkipRetryErrorCode) + { + if (command == Commands.MultipleReadData && meterRegister != null && meterRegister.DataType == typeof(String)) + { + var completeResponse = new List(); + while (true) + { + var cRecord = CommandToMeter(Commands.ReadData, meterRegister, payload, expectedLength, + hideDataInLog, skipRetryErrorCode); + var rest = ProcessRecordList(); + if (rest == RequestAcknowledgeState.Ok && cRecord?.ResponsePayload != null) + { + completeResponse.AddRange(cRecord.ResponsePayload); + if (!cRecord.ResponsePayload.Contains(0)) continue; + + cRecord.ResponsePayload = completeResponse; + OnMeterRegisterUpdated?.Invoke(this, + new RegisterUpdatedEventArgs(meterRegister, completeResponse.ToArray())); + } + + return cRecord; + } + } + + //the minimum payload is one 4 byte chunk + var chunkSize = RegisterDefinition.ChunkSize; + if (payload == null) + { + payload = new Byte[chunkSize]; + for (var i = 0; i < chunkSize; i++) + { + payload[i] = FillByte; + } + } + + //real payload based on 4 byte chunks + var requestPayload = new List(); + requestPayload.AddRange(payload); + //fill-byte padding + if (payload.Length % chunkSize != 0) + { + for (var i = 0; i < chunkSize - payload.Length % chunkSize; i++) + { + requestPayload.Add(FillByte); + } + } + + //request protocol excluding the command + var requestProtocol = new List(); + + if (meterRegister != null) + { + requestProtocol.Add(meterRegister.RegisterAddress[1]); + requestProtocol.Add(meterRegister.RegisterAddress[0]); + + if (command == Commands.MultipleWriteData) + { + //set counter to inform meter writes are expected + //the payload size is already padded to 4 byte chunks + var numberOfChunks = (UInt16)(requestPayload.Count / chunkSize); + requestProtocol.Add((Byte)(numberOfChunks & 0xFF)); + requestProtocol.Add((Byte)((numberOfChunks >> 8) & 0xFF)); + } + + if (command == Commands.MultipleReadData) + { + //TODO THW create expected size out of meterRegister.DataType + if (!expectedLength.HasValue) + { + if ((meterRegister.DataType == typeof(UInt64) || meterRegister.DataType == typeof(Int64))) + { + expectedLength = 8; + } + else + { + expectedLength = 24; + } + } + + var numberOfChunks = (UInt16)(expectedLength.Value / chunkSize); + if (expectedLength % chunkSize != 0) + numberOfChunks++; + requestProtocol.Add((Byte)(numberOfChunks & 0xFF)); + requestProtocol.Add((Byte)((numberOfChunks >> 8) & 0xFF)); + } + + //add always the payload to the request protocol + requestProtocol.AddRange(requestPayload); + } + + return AddRecordToSendFifo(command, requestProtocol.ToArray(), meterRegister, + hideDataInLog, skipRetryErrorCode); + } + + /// + /// Analyzes the error code + /// + private void CheckErrorCode() + { + if (_recordInProcess.ResponseErrorBase == (Byte)ConfigExErrors.Base) + { + if (_recordInProcess.ResponseErrorReason == (Byte)ConfigExErrors.AuthenticationFail || + _recordInProcess.ResponseErrorReason == (Byte)ConfigExErrors.AccessDenied || + _recordInProcess.ResponseErrorReason == (Byte)ConfigExErrors.LockedOut) + { + _recordInProcess.Acknowledge = RequestAcknowledgeState.AuthorizationRequired; + } + } + } + + /// + /// Reorders the record list so that first entry is the defined topRegister + /// + /// + public void ReorderRecordList(String topRegister) + { + _recordsSendFifo.Enqueue(_recordInProcess); + //reorder FIFO to bring login at first position + for (var i = 0; i < _recordsSendFifo.Count; i++) + { + _recordsSendFifo.TryPeek(out var tmpRecord); + if (tmpRecord == null || tmpRecord.Register.GetIdent() == topRegister) + { + break; + } + + _recordsSendFifo.TryDequeue(out tmpRecord); + _recordsSendFifo.Enqueue(tmpRecord); + } + } + + /// + /// Checks the active register to access + /// + /// + /// + public Boolean ContainsRegisterIdent(String registerName) + { + return _recordsSendFifo.Any(a => string.Equals(a.Register.GetIdent(), + registerName, StringComparison.CurrentCultureIgnoreCase)); + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/RequestRecord.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/RequestRecord.cs new file mode 100644 index 000000000..f7a196c10 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/RequestProtocol/RequestRecord.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.RequestProtocol.Consts; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers; +using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisConfig; +using CommunicationConfig = TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.GenesisConfig.CommunicationConfig; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.RequestProtocol +{ + /// + /// Holds request commands with detail parameters to see processing state + /// + public class RequestRecord + { + /// + /// Indicates the base command + /// + public readonly Byte ResponseCommand; + + /// + /// the register the command refers to. + /// needed to set Register dictionary to link response with dictionary key + /// + public readonly RegisterDefinition Register; + + /// + /// Command acknowledged + /// + public RequestAcknowledgeState Acknowledge = RequestAcknowledgeState.Unsent; + + /// + /// Retry counter + /// + public Int32 RetryCtr = 0; + + /// + /// Wakeup-message retry counter + /// + public Int32 WakeupMessageRetryCtr = 0; + + /// + /// indicates error base on + /// + public Byte ResponseErrorBase = 0xFF; + + /// + /// indicates error reason on + /// + public Byte ResponseErrorReason = 0xFF; + + /// + /// Combined error code of base and reason + /// + public UInt16 ResponseErrorCode = 0xFFFF; + + /// + /// Chunk position of error on multiple read/write access + /// + public UInt16 ResponseErrorPosition = 0; + + /// + /// Data containing the request protocol + /// + public readonly List RequestProtocolData; + + /// + /// Encoded with transmit protocol, ready to stream to port as is + /// + public List EncodedRequestData { get; } + + /// + /// Extracted payload of response + /// + public List ResponsePayload; + + /// + /// Extracted payload of response + /// + public readonly Int32 ResponseTimeoutMs; + + /// + /// avoid logging for e.g. password + /// + public readonly Boolean HideDataInLog; + + /// + /// error mask to skip retries for functional errors + /// + public readonly UInt16 SkipRetryErrorCode; + + /// + /// Ctor for an base command + /// + /// as an byte + /// Request protocol data for logging + /// Ready to send data encoded with transmit protocol retries + /// Timeout for response + /// to do command with + /// hiding data in log file to avoid spying of passwords + /// error mask to skip retries + public RequestRecord(Byte command, List requestProtocolData = null, List encodedRequestData = null, + Int32 responseTimeoutMs = 100, RegisterDefinition register = null, Boolean hideDataInLog = false, + UInt16 skipRetryErrorCode = CommunicationConfig.SkipRetryErrorCode) + { + ResponseCommand = (Byte)(command + 1); + Register = register; + RequestProtocolData = requestProtocolData; + EncodedRequestData = encodedRequestData; + ResponseTimeoutMs = responseTimeoutMs; + HideDataInLog = hideDataInLog; + SkipRetryErrorCode = skipRetryErrorCode; + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/StreamingProtocol/StreamingDecoder.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/StreamingProtocol/StreamingDecoder.cs new file mode 100644 index 000000000..19c99d042 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/StreamingProtocol/StreamingDecoder.cs @@ -0,0 +1,547 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages.MeasurementRecords; + + +// ReSharper disable UnusedMember.Local + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.StreamingProtocol +{ + /// + /// Data fields and definitions for GENESIS streaming protocol + /// + public class StreamingDecoder + { + private const Double MilliLitersToCmFactor = 1.0E-6; + private const Double CpuTimeToSecondsFactor = 1.0 / 0x10000; + private const Double CpuTimeOverflowS = 0x100000000 * CpuTimeToSecondsFactor; + private const Double LitersPerSecondToCmPerHourFactor = 3600.0 / 1000.0; + + private const Double DefaultVolumeScaleRawPerMl = 1024.0; + private const Double DefaultVolumeFactorRawToCm = MilliLitersToCmFactor / DefaultVolumeScaleRawPerMl; + private const Double MaxGenesisAccuVolumeRaw = UInt32.MaxValue; //0x100000000; //2^32 + private const Double DefaultAccuDutOverflowVolumeCm = MaxGenesisAccuVolumeRaw * DefaultVolumeFactorRawToCm; + + private const Double DisplayMlSetupDutOverflowVolumeCm = 1000.0; //overflow of LCD if set to ml + private const Double TofToSecondsFactor38Bit = 1.0 / 0x4000000000; // 2^38 + private const Double AmplitudeToVoltFactor = 1.0 / 0x400000 / 1000.0; // 2^22 100 0000 0000 0000 0000 0000b + private const Double PulseWidthToRelFactor = 1.0 / 0x100; // 2^8 + + /// + /// Default data for bend detection tests of Genesis + /// + private readonly BendDetectionRecord _bendDetectionDefault = new BendDetectionRecord + { + StatusBendU0 = BendDetectionRecord.StatusBendU0Enum.OKAY, + InstallationType = BendDetectionRecord.InstallationTypeEnum.INSTALLATION_UNDISTURBED, + CorrectionFactor_percent = 0.0, + PreCorrectionVolumeRaw = 0.0, + PostCorrectionVolumeRaw = 0.0, + TimeS = 0.0, + OverflowTimeS = CpuTimeOverflowS, + Crc = 0xFFFF, + IsValid = false + }; + + /// + /// Default data for flow tests of Genesis + /// + private readonly FlowTestRecord _dataDefault = new FlowTestRecord + { + VolumeCm = 0.0, + OverflowVolumeCm = DisplayMlSetupDutOverflowVolumeCm, + TimeS = 0.0, + OverflowTimeS = CpuTimeOverflowS, + Crc = 0xFFFF, + IsValid = false + }; + + /// + /// Default data for calibration of Genesis + /// + private readonly CalibrationRecord _rawDataDefault = new CalibrationRecord + { + Channel = 0, + Validation = 0xFFFF, + TotalTimeOfFlightS = 0.0, + DeltaTimeOfFlightS = 0.0, + + VolumeScaleRawPerMl = DefaultVolumeScaleRawPerMl, + VolumeFactorRawToQm = DefaultVolumeFactorRawToCm, + DeltaVolumeRaw = 0.0, + DeltaVolumeQm = 0.0, + AccuVolumeRaw = 0.0, + VolumeCm = 0.0, + OverflowVolumeCm = DefaultAccuDutOverflowVolumeCm, + + SampleIntervalS = 0.0, + AmplitudeUpV = 0.0, + AmplitudeDownV = 0.0, + PulseWidthRatioUp = 0.0, + PulseWidthRatioDown = 0.0, + TemperatureRaw = 20.0, + TemperaturePowFactor = 1.0, + TemperatureDegC = 20.0, + TimeS = 0.0, + OverflowTimeS = CpuTimeOverflowS, + Crc = 0xFFFF, + IsValid = false + }; + + private CalibrationRecord _dataCalibRec; + private FlowTestRecord _dataFlowTestRec; + private BendDetectionRecord _dataBendDetectRec; + private readonly Boolean _ignoreCorruptedData; + + /// + /// Constructor initializes all decoded members with default values + /// + public StreamingDecoder(Boolean ignoreCorruptedData = true) + { + _dataFlowTestRec = _dataDefault; + _dataCalibRec = _rawDataDefault; + _dataBendDetectRec = _bendDetectionDefault; + _ignoreCorruptedData = ignoreCorruptedData; + } + + /// + /// Calibration data + /// + public CalibrationRecord DataCalib + { + get; private set; + } + + /// + /// Flow test data + /// + public FlowTestRecord DataFlowTest + { + get; private set; + } + + /// + /// Bend detection test data + /// + public BendDetectionRecord DataBendDetectTest + { + get; private set; + } + + /// + /// Decoding the raw message + /// + /// message received as one line delimited with LF + /// true if decoding was successful and data has been validated + /// + /// - Modified using common CRC check before branching to the protocol specific decoder. + /// + /// + /// - Introduced protocol 'm' for bending detection. + /// + public Boolean DecodeMsg(String rawMsg) + { + var rawRecordIsValid = false; + try + { + // save the raw message for CRC calculation before separation to fields + //_rawMsgForCrc = rawMsg; + // extract message and split it to fields + + //DN50 + //2022-07-21 07:22:06.9871 | @f 8497D 062E4216 9B2A + //2022-07-21 07:22:06.9871 | @h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331 + //2022-07-21 07:22:07.0171 | @h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD + //rawMsg = "@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E"; + + //DN80 + //2022-04-28 15:19:54.9167 | @f AA754B 4D0CEE78 5D89 + //2022-04-28 15:19:54.9337 | @h 1 0 0EF4A130 0002AAB8 000DAC8C 02B98FBD 00000200 00000FFC 643BCF30 63C9CFF6 00015096 0C 4D0CF3CC 3E87 + //2022-04-28 15:19:54.9497 | @h 2 0 0EFA3000 000283E7 000CE580 E3D5EFFA 00000200 00001000 6DC0A84E 6CD462C2 00015096 0C 4D0CF922 1646 + //2022-04-28 15:19:54.9627 | @h 3 0 0EFF7F91 0002AC39 000DB43D FE1C0254 00000200 00000FFE 6C932DE6 6D20005D 00015096 0C 4D0CFE76 868A + //2022-04-28 15:19:54.9787 | @f AA7C01 4D0CFE76 B08F + // rawMsg = "@h 3 0 0EFF7F91 0002AC39 000DB43D FE1C0254 00000200 00000FFE 6C932DE6 6D20005D 00015096 0C 4D0CFE76 868A "; + + // The received message is a string with a line delimiter. + rawMsg = rawMsg.Replace('\n', ' '); + + // The raw message fields are the separated values from the received string with a blank as field separator + var rawMsgFields = rawMsg.Split(' '); + + // The last element is the CRC, the CRC can be separated, calculated and validated before trying to decode the content + var rawRecordForCrc = ""; + // get all fields excluding the CRC (length - 1) + for (var x = 0; x < rawMsgFields.Length - 1; x++) + { + rawRecordForCrc += rawMsgFields[x]; + // add the field delimiter from raw data + rawRecordForCrc += " "; + } + + // extract bytes of raw message for CRC calculation each character, CRC field is already removed + var byteArraySize = rawRecordForCrc.Length; + var byteArray = new Byte[byteArraySize]; + for (var i = 0; i < byteArraySize; i++) + { + byteArray[i] = (Byte)rawRecordForCrc[i]; + } + + // calculate the CRC from the received data + var calculatedCrc = Crc16Ccitt.CalculateMsb1021(byteArray); + + // extract received CRC + var receivedCrc = UInt16.Parse(rawMsgFields[rawMsgFields.Length - 1], NumberStyles.HexNumber); + + // compare received with calculated CRC and remind valid decoding + rawRecordIsValid = calculatedCrc == receivedCrc; + + switch (rawMsgFields[0]) + { + case "@m": + _dataBendDetectRec.IsValid = rawRecordIsValid; + if (rawRecordIsValid || !_ignoreCorruptedData) + { + DecodeProtocolM(ref _dataBendDetectRec, rawMsgFields); + DataBendDetectTest = _dataBendDetectRec; + } + break; + + case "@f": + _dataFlowTestRec.IsValid = rawRecordIsValid; + if (rawRecordIsValid || !_ignoreCorruptedData) + { + DecodeProtocolF(ref _dataFlowTestRec, rawMsgFields); + DataFlowTest = _dataFlowTestRec; + } + break; + + case "@g": + _dataCalibRec.IsValid = rawRecordIsValid; + if (rawRecordIsValid || !_ignoreCorruptedData) + { + DecodeProtocolG(ref _dataCalibRec, rawMsgFields); + DataCalib = _dataCalibRec; + } + break; + + case "@h": + _dataCalibRec.IsValid = rawRecordIsValid; + if (rawRecordIsValid || !_ignoreCorruptedData) + { + DecodeProtocolH(ref _dataCalibRec, rawMsgFields); + DataCalib = _dataCalibRec; + } + break; + } + } + catch (Exception) + { + // ignored + } + return rawRecordIsValid; + } + + /// + /// Extracting message from string fields for protocol 'm' + /// + /// reference to bend detection test record + /// Separated fields containing the measurement as string + /// + /// + /// - Modified using common CRC check in advance. + /// + private static void DecodeProtocolM(ref BendDetectionRecord dataBendTestRec, IList fields) + { + // time stamp attachment + dataBendTestRec.DecodedTime = DateTimeOffset.UtcNow; + + // extract received CRC + dataBendTestRec.Crc = ushort.Parse(fields[(Int32)ProtMsubString.Crc], + NumberStyles.HexNumber); + // extract the status + dataBendTestRec.StatusBendU0 = + (BendDetectionRecord.StatusBendU0Enum)UInt32.Parse(fields[(Int32)ProtMsubString.Status], + NumberStyles.AllowHexSpecifier); + // extract the installation type + dataBendTestRec.InstallationType = + (BendDetectionRecord.InstallationTypeEnum)UInt32.Parse(fields[(Int32)ProtMsubString.Installation], + NumberStyles.AllowHexSpecifier); + // extract the correction factor in percent + dataBendTestRec.CorrectionFactor_percent = + UInt32.Parse(fields[(Int32)ProtMsubString.Factor], + NumberStyles.AllowHexSpecifier) * BendDetectionRecord.CorrectionFactorScale; + + // build result values, the volume before and after correction can be positive or negative! + dataBendTestRec.PreCorrectionVolumeRaw = Int32.Parse(fields[(Int32)ProtMsubString.PreVolume], + NumberStyles.AllowHexSpecifier); + // build result values, the volume before and after correction can be positive or negative! + dataBendTestRec.PostCorrectionVolumeRaw = Int32.Parse(fields[(Int32)ProtMsubString.PostVolume], + NumberStyles.AllowHexSpecifier); + } + + /// + /// Extracting message from string fields for protocol 'f' + /// + /// reference to flow test record + /// Separated fields containing the measurement as string + /// + /// + /// - Modified using common CRC check in advance. + /// + private static void DecodeProtocolF(ref FlowTestRecord dataFlowTestRec, IList fields) + { + // time stamp attachment + dataFlowTestRec.DecodedTime = DateTimeOffset.UtcNow; + + // extract received CRC + dataFlowTestRec.Crc = ushort.Parse(fields[(Int32)ProtFsubString.Crc], + NumberStyles.HexNumber); + // build result values, the display volume can be positive or negative! + dataFlowTestRec.VolumeCm = int.Parse(fields[(Int32)ProtFsubString.DisplayVolume], + NumberStyles.AllowHexSpecifier) * MilliLitersToCmFactor; + dataFlowTestRec.TimeS = uint.Parse(fields[(Int32)ProtFsubString.CpuTime], + NumberStyles.AllowHexSpecifier) * CpuTimeToSecondsFactor; + } + + /// + /// Extracting message from string fields to individual raw channel for protocol 'g' + /// + /// Reference to result structure for raw data for one channel + /// Separated fields containing the measurement as string + /// true if protocol is valid + /// + /// - Usage of VolumeFactorRawToQm and calculation of AccuDutOverflowVolumeCm + /// + /// + /// - Modified using common CRC check in advance. + /// + private static void DecodeProtocolG(ref CalibrationRecord dataProtGRec, IList fields) + { + // time stamp attachment + dataProtGRec.DecodedTime = DateTimeOffset.UtcNow; + + // extract received CRC + dataProtGRec.Crc = ushort.Parse(fields[(Int32)ProtGsubString.Crc], + NumberStyles.HexNumber); + + dataProtGRec.Channel = ushort.Parse(fields[(Int32)ProtGsubString.ChanNo], + NumberStyles.HexNumber); + dataProtGRec.Validation = ushort.Parse(fields[(Int32)ProtGsubString.Validation], + NumberStyles.HexNumber); + + // Delta time of flight + dataProtGRec.DeltaTimeOfFlightS = int.Parse(fields[(Int32)ProtGsubString.Dtof], + NumberStyles.AllowHexSpecifier) * TofToSecondsFactor38Bit; + + // Delta raw volume between last sample + dataProtGRec.DeltaVolumeRaw = uint.Parse(fields[(Int32)ProtGsubString.RawDVolume], + NumberStyles.AllowHexSpecifier); + + // volume scaling + var volumeRawScale = uint.Parse(fields[(Int32)ProtGsubString.VolumeScale], + NumberStyles.AllowHexSpecifier); + + dataProtGRec.VolumeScaleRawPerMl = volumeRawScale != 0 ? volumeRawScale : DefaultVolumeScaleRawPerMl; + dataProtGRec.VolumeFactorRawToQm = MilliLitersToCmFactor / dataProtGRec.VolumeScaleRawPerMl; + dataProtGRec.OverflowVolumeCm = MaxGenesisAccuVolumeRaw * dataProtGRec.VolumeFactorRawToQm; + + // Calculate volume in cubic meters out of the raw volume + dataProtGRec.DeltaVolumeQm = dataProtGRec.DeltaVolumeRaw * dataProtGRec.VolumeFactorRawToQm; + + // accumulated volume for each channel received from water meter scaled with volumeScale + // the volume can just be positive + dataProtGRec.AccuVolumeRaw = uint.Parse(fields[(Int32)ProtGsubString.AccuVolume], + NumberStyles.AllowHexSpecifier); + dataProtGRec.VolumeCm = dataProtGRec.AccuVolumeRaw * dataProtGRec.VolumeFactorRawToQm; + + // Sample interval + dataProtGRec.SampleIntervalS = uint.Parse(fields[(Int32)ProtGsubString.SampleInterval], + NumberStyles.AllowHexSpecifier) * CpuTimeToSecondsFactor; + + // amplitude for high threshold in V + dataProtGRec.AmplitudeUpV = uint.Parse(fields[(Int32)ProtGsubString.AmplitudeUp], + NumberStyles.AllowHexSpecifier) * AmplitudeToVoltFactor; + + // amplitude for low threshold in V + dataProtGRec.AmplitudeDownV = uint.Parse(fields[(Int32)ProtGsubString.AmplitudeDown], + NumberStyles.AllowHexSpecifier) * AmplitudeToVoltFactor; + + // pulse width ratio high threshold + dataProtGRec.PulseWidthRatioUp = uint.Parse(fields[(Int32)ProtGsubString.PulseWidthRatioUp], + NumberStyles.AllowHexSpecifier) * PulseWidthToRelFactor; + + // pulse width ratio low threshold + dataProtGRec.PulseWidthRatioDown = uint.Parse(fields[(Int32)ProtGsubString.PulseWidthRatioDown], + NumberStyles.AllowHexSpecifier) * PulseWidthToRelFactor; + + // raw temperature + dataProtGRec.TemperatureRaw = int.Parse(fields[(Int32)ProtGsubString.RawTemperature], + NumberStyles.AllowHexSpecifier); + + // temperature scaling + dataProtGRec.TemperaturePowFactor = uint.Parse(fields[(Int32)ProtGsubString.TemperatureScale], + NumberStyles.AllowHexSpecifier); + + // Calculate temperature + dataProtGRec.TemperatureDegC = dataProtGRec.TemperatureRaw / + Math.Pow(2.0, dataProtGRec.TemperaturePowFactor); + + // absolute CPU time, started at LED mode 3 activation + dataProtGRec.TimeS = uint.Parse(fields[(Int32)ProtGsubString.CpuTime], + NumberStyles.AllowHexSpecifier) * CpuTimeToSecondsFactor; + } + + /// + /// Extracting message from string fields to individual raw channel for protocol 'g' + /// + /// Reference to result structure for raw data for one channel + /// Separated fields containing the measurement as string + /// true if protocol is valid + /// + /// - Usage of VolumeFactorRawToQm and calculation of AccuDutOverflowVolumeCm + /// + /// + /// - Modified using common CRC check in advance. + /// + private static void DecodeProtocolH(ref CalibrationRecord dataProtHRec, IList fields) + { + // time stamp attachment + dataProtHRec.DecodedTime = DateTimeOffset.UtcNow; + + // extract received CRC + dataProtHRec.Crc = ushort.Parse(fields[(Int32)ProtHsubString.Crc], + NumberStyles.HexNumber); + + dataProtHRec.Channel = ushort.Parse(fields[(Int32)ProtHsubString.ChanNo], + NumberStyles.HexNumber); + dataProtHRec.Validation = ushort.Parse(fields[(Int32)ProtHsubString.Validation], + NumberStyles.HexNumber); + + // Total time of flight + dataProtHRec.TotalTimeOfFlightS = int.Parse(fields[(Int32)ProtHsubString.Ttof], + NumberStyles.AllowHexSpecifier) * TofToSecondsFactor38Bit; + // Delta time of flight + dataProtHRec.DeltaTimeOfFlightS = int.Parse(fields[(Int32)ProtHsubString.Dtof], + NumberStyles.AllowHexSpecifier) * TofToSecondsFactor38Bit; + + dataProtHRec.RawTotalTimeOfFlight = + int.Parse(fields[(Int32)ProtHsubString.Ttof], NumberStyles.AllowHexSpecifier); + dataProtHRec.RawDeltaTimeOfFlight = int.Parse(fields[(Int32)ProtHsubString.Dtof], NumberStyles.AllowHexSpecifier); + + // Delta raw volume between two samples + dataProtHRec.DeltaVolumeRaw = uint.Parse(fields[(Int32)ProtHsubString.RawDVolume], + NumberStyles.AllowHexSpecifier); + + // volume scaling + var volumeRawScale = uint.Parse(fields[(Int32)ProtHsubString.VolumeScale], + NumberStyles.AllowHexSpecifier); + + dataProtHRec.VolumeScaleRawPerMl = volumeRawScale != 0 ? volumeRawScale : DefaultVolumeScaleRawPerMl; + dataProtHRec.VolumeFactorRawToQm = MilliLitersToCmFactor / dataProtHRec.VolumeScaleRawPerMl; + dataProtHRec.OverflowVolumeCm = MaxGenesisAccuVolumeRaw * dataProtHRec.VolumeFactorRawToQm; + + // Calculate volume in cubic meters out of the raw volume + dataProtHRec.DeltaVolumeQm = dataProtHRec.DeltaVolumeRaw * dataProtHRec.VolumeFactorRawToQm; + + // accumulated volume for each channel received from water meter scaled with volumeScale + // the volume can just be positive + dataProtHRec.AccuVolumeRaw = uint.Parse(fields[(Int32)ProtHsubString.AccuVolume], + NumberStyles.AllowHexSpecifier); + dataProtHRec.VolumeCm = dataProtHRec.AccuVolumeRaw * dataProtHRec.VolumeFactorRawToQm; + + // Sample interval + dataProtHRec.SampleIntervalS = uint.Parse(fields[(Int32)ProtHsubString.SampleInterval], + NumberStyles.AllowHexSpecifier) * CpuTimeToSecondsFactor; + + // amplitude for high threshold in V + dataProtHRec.AmplitudeUpV = uint.Parse(fields[(Int32)ProtHsubString.AmplitudeUp], + NumberStyles.AllowHexSpecifier) * AmplitudeToVoltFactor; + + // amplitude for low threshold in V + dataProtHRec.AmplitudeDownV = uint.Parse(fields[(Int32)ProtHsubString.AmplitudeDown], + NumberStyles.AllowHexSpecifier) * AmplitudeToVoltFactor; + + // raw temperature + dataProtHRec.TemperatureRaw = int.Parse(fields[(Int32)ProtHsubString.RawTemperature], + NumberStyles.AllowHexSpecifier); + + // temperature scaling + dataProtHRec.TemperaturePowFactor = uint.Parse(fields[(Int32)ProtHsubString.TemperatureScale], + NumberStyles.AllowHexSpecifier); + + // Calculate temperature + dataProtHRec.TemperatureDegC = dataProtHRec.TemperatureRaw / ( + Math.Pow(2.0, dataProtHRec.TemperaturePowFactor)); + + // absolute CPU time, started at LED mode 3 activation + dataProtHRec.TimeS = uint.Parse(fields[(Int32)ProtHsubString.CpuTime], + NumberStyles.AllowHexSpecifier) * CpuTimeToSecondsFactor; + } + + /// field position in protocol 'f' + private enum ProtFsubString + { + //do not remove this needed for position in record + ProtType, + DisplayVolume, + CpuTime, + Crc + } + /// field position in protocol 'm' + private enum ProtMsubString + { + //do not remove this needed for position in record + ProtType, + Status, + Installation, + Factor, + PreVolume, + PostVolume, + Crc + } + + /// field position in protocol 'g' + private enum ProtGsubString + { + //do not remove this needed for position in record + ProtType, + ChanNo, + Validation, + Dtof, + RawDVolume, + AccuVolume, + VolumeScale, + SampleInterval, + AmplitudeUp, + AmplitudeDown, + PulseWidthRatioUp, + PulseWidthRatioDown, + RawTemperature, + TemperatureScale, + CpuTime, + Crc + } + + /// field position in protocol 'h' + private enum ProtHsubString + { + //do not remove this needed for position in record + ProtType, + ChanNo, + Validation, + Ttof, + Dtof, + RawDVolume, + AccuVolume, + VolumeScale, + SampleInterval, + AmplitudeUp, + AmplitudeDown, + RawTemperature, + TemperatureScale, + CpuTime, + Crc + } + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/StreamingProtocol/StreamingProtocol.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/StreamingProtocol/StreamingProtocol.cs new file mode 100644 index 000000000..fc4a88de4 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Protocols/StreamingProtocol/StreamingProtocol.cs @@ -0,0 +1,107 @@ +using System; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.ProtocolCore; + + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.StreamingProtocol +{ + /// + /// + /// Layer between CRC16CCITT handler and Events + /// block trashy telegrams + /// + public class StreamingProtocol : BaseProtocol + { + + /// + /// Decode data and hold results + /// + private StreamingDecoder _streamingDecode; + private readonly Boolean _ignoreCorruptedData; + + /// + public override event EventHandler OnRecordIsDecoded; + + + /// + protected override void DecodeRecord(IPortDataEventArgs dataArgs) + { + var data = (String)dataArgs.GetData(); + + //Data decoding + _streamingDecode = new StreamingDecoder(_ignoreCorruptedData); + _streamingDecode.DecodeMsg(data); + + //Event for new data + if (_streamingDecode.DataFlowTest != null) + { + _streamingDecode.DataFlowTest.SyncMarkRecord = dataArgs.GetSyncMarkRecord(); + _streamingDecode.DataFlowTest.ReceivedTime = dataArgs.GetReceivedTime(); + + if (_streamingDecode.DataFlowTest.IsValid) + { + OnRecordIsDecoded?.Invoke(this, + new FlowDataEventArgs { NewData = _streamingDecode.DataFlowTest, RawData = data }); + } + else if (!_ignoreCorruptedData) + { + OnRecordIsDecoded?.Invoke(this, + new FlowDataEventArgs { NewData = _streamingDecode.DataFlowTest, RawData = data }); + } + + } + else if (_streamingDecode.DataCalib != null) + { + _streamingDecode.DataCalib.SyncMarkRecord = dataArgs.GetSyncMarkRecord(); + _streamingDecode.DataCalib.ReceivedTime = dataArgs.GetReceivedTime(); + if (_streamingDecode.DataCalib.IsValid) + { + OnRecordIsDecoded?.Invoke(this, new CalibDataEventArgs + { + CalibChl = _streamingDecode.DataCalib, + RawData = data + }); + } + else if (!_ignoreCorruptedData) + { + OnRecordIsDecoded?.Invoke(this, new CalibDataEventArgs + { + CalibChl = _streamingDecode.DataCalib, + RawData = data + }); + } + + } + else if (_streamingDecode.DataBendDetectTest != null) + { + _streamingDecode.DataBendDetectTest.SyncMarkRecord = dataArgs.GetSyncMarkRecord(); + _streamingDecode.DataBendDetectTest.ReceivedTime = dataArgs.GetReceivedTime(); + if (_streamingDecode.DataBendDetectTest.IsValid) + { + OnRecordIsDecoded?.Invoke(this, new BendDetectDataEventArgs + { + NewData = _streamingDecode.DataBendDetectTest, + RawData = data + }); + } + else if (!_ignoreCorruptedData) + { + OnRecordIsDecoded?.Invoke(this, new BendDetectDataEventArgs + { + NewData = _streamingDecode.DataBendDetectTest, + RawData = data + }); + } + } + + //toDo: handle trashy telegrams + } + + /// + public StreamingProtocol(String ident, Boolean ignoreCorruptedData = true) : base(ident) + { + _ignoreCorruptedData = ignoreCorruptedData; + } + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Access.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Access.cs new file mode 100644 index 000000000..becc20366 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Access.cs @@ -0,0 +1,29 @@ +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers +{ + /// + /// Enumeration for all known access level + /// + public enum Access + { + /// + /// Not set, means no valid input/information + /// + NS, + /// + /// No Access + /// + NA, + /// + /// read only + /// + RO, + /// + /// Write only + /// + WO, + /// + /// Read and Write + /// + RW + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/ByteArray.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/ByteArray.cs new file mode 100644 index 000000000..92a66d441 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/ByteArray.cs @@ -0,0 +1,6 @@ +namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes +{ + public struct ByteArray + { + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/Enum8.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/Enum8.cs new file mode 100644 index 000000000..ccd1f56e6 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/Enum8.cs @@ -0,0 +1,8 @@ +namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes +{ + // internal struct Enum8 + public struct Enum8 + { + //todo find out struct definition and implement it + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/Rpc.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/Rpc.cs new file mode 100644 index 000000000..3b6b7fa05 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/Rpc.cs @@ -0,0 +1,7 @@ +namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes +{ + public struct Rpc + { + //todo find out struct definition and implement it + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/StaticType.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/StaticType.cs new file mode 100644 index 000000000..935412ac5 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/StaticType.cs @@ -0,0 +1,67 @@ +using System; + +namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes +{ + /// + /// Static type to define restore capability + /// + public struct StaticType + { + /// + /// The value of the static type qualifier + /// + public String StaticTypeValue; + + /// + /// Field identifier in configuration.json + /// + public const String StaticTypeFieldIdentifier = "statictype"; + + /// + /// Unknown to define restore capability + /// + // ReSharper disable once UnusedMember.Local + private const String UnknownAccess = null; + + /// + /// Static type to define restore capability + /// + public const String RestoreRequired = "static"; + + /// + /// Static type approximate informs that the read back value may be not identical + /// to the written value and is approximated. + /// + public const String ReadBackApproximated = "approximate"; + + /// + /// Denied restore capability + /// + // ReSharper disable once UnusedMember.Local + public const String RestoreDenied = "dynamic"; + + /// + /// Unpredictable restore capability + /// + // ReSharper disable once UnusedMember.Local + public const String RestoreUnpredictable = "infrequentlyupdated"; + + /// + /// Check restore capability + /// + /// true if restore required + public Boolean CheckRestoreCapability() + { + return StaticTypeValue == RestoreRequired || StaticTypeValue == ReadBackApproximated; + } + + /// + /// Ctor + /// + /// input of initial type + public StaticType(String staticType) + { + StaticTypeValue = staticType; + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/StatusT.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/StatusT.cs new file mode 100644 index 000000000..1f1ea26ac --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/StatusT.cs @@ -0,0 +1,7 @@ +namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes +{ + public struct StatusT + { + //todo find out struct definition and implement it + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/TimeT.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/TimeT.cs new file mode 100644 index 000000000..da8e20ea3 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/TimeT.cs @@ -0,0 +1,117 @@ +using System; + +namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes +{ + + /// + /// Class for Genesis TimeT + /// + public class TimeT + { + /// + /// Ctor: + /// - Takes actual date time as preset value + /// + public TimeT() + { + DateTimeUtc = DateTime.UtcNow; + } + /// + /// 1. Get seconds since 01. Jan 2000 00:00:00 UTC previously set by any call by + /// or + /// or directly by this routine delivering the seconds e.g. read from meter, + /// 2. Set UTC seconds to analyze the corresponding time in UTC and use the ToString to + /// read the converted result. + /// + /// + /// - Used to calculate time in UTC based on 01. Jan 2000. + /// + /// + /// - Setting of seconds and conversion to corresponding UTC sine 2000 or return seconds + /// from set DataTimeUtc. + /// + public Int32 SecondsSince2000 + { + set + { + var secondsToTimeSpan = TimeSpan.FromSeconds(value); + DateTimeUtc = _fixedDateTime2000.Add(secondsToTimeSpan); + } + // DateTimeUtc has to be set in advance with seconds or by the call + // of UtcNowToSecondsSince2000 or UtcAnyToSecondsSince2000 + get => UtcToSecondsSince2000(); + } + + /// + /// Get actual seconds (UTC now) since 01. Jan 2000 00:00:00 UTC. + /// + /// + /// - Initial. + /// + public Int32 UtcNowToSecondsSince2000 + { + get + { + DateTimeUtc = DateTime.UtcNow; + return UtcToSecondsSince2000(); + } + } + + /// + /// Common routine to convert given UTC timestamp to seconds since 01. Jan 2000 00:00:00 UTC. + /// + /// + /// - Initial. + /// + private Int32 UtcToSecondsSince2000() + { + var timeSince2000Utc = DateTimeUtc - _fixedDateTime2000; + var secondsSince2000 = Convert.ToInt32(timeSince2000Utc.TotalSeconds); + return secondsSince2000; + } + + /// + /// Builds the seconds for a given timestamp based on 01.Jan.2000 00:00:00 UTC + /// + /// time stamp dateTime for conversion + /// + /// - Optional input of dateTime to convert this to the time in UTC based on 01. Jan 2000. + /// + public Int32 UtcAnyToSecondsSince2000(DateTime dateTimeUtc) + { + try + { + // limit dateTime to 01. Jan 2000 00:00:00 UTC + DateTimeUtc = dateTimeUtc < _fixedDateTime2000 ? _fixedDateTime2000 : dateTimeUtc; + return UtcToSecondsSince2000(); + } + catch (Exception) + { + return 0; + } + } + + /// + /// The date and time of 01.Jan 2000 00:00:00 UTC + /// + private static readonly DateTime _fixedDateTime2000 = new DateTime(2000, 1, 1, 0, 0, 0); + + /// + /// Converted date and time to UTC using the FixedDateTime2000 + /// and the SecondsSince2000 + /// + public DateTime DateTimeUtc + { + private set; + get; + } + + /// + /// Get string of UTC date and time universal language with 24 hours format + /// + public override String ToString() + { + return $@"{DateTimeUtc:yyyy-MM-dd HH:mm:ss} UTC"; + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/UInt672.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/UInt672.cs new file mode 100644 index 000000000..7538afa2a --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/UInt672.cs @@ -0,0 +1,8 @@ +using System; + +namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes +{ + public struct UInt672 + { + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/st_radio_dewa.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/st_radio_dewa.cs new file mode 100644 index 000000000..e62556c84 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/st_radio_dewa.cs @@ -0,0 +1,8 @@ +namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes +{ + // ReSharper disable once InconsistentNaming + internal class st_radio_dewa + { + //todo find out struct definition and implement it + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/st_radio_tfx.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/st_radio_tfx.cs new file mode 100644 index 000000000..ad92ed7fc --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/DataTypes/st_radio_tfx.cs @@ -0,0 +1,8 @@ +namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes +{ + // ReSharper disable once InconsistentNaming + internal class st_radio_tfx + { + //todo find out struct definition and implement it + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/AppSection.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/AppSection.cs new file mode 100644 index 000000000..890888ddb --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/AppSection.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.Json +{ + public class AppSection + { + public UInt16 Id { get; set; } + + public FwVersion Version { get; set; } + + public Dictionary> Builds { get; set; } + + public IDictionary Registers { get; set; } + + public IDictionary Status { get; set; } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/AppsDictionary.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/AppsDictionary.cs new file mode 100644 index 000000000..3f182edfd --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/AppsDictionary.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.Json +{ + public class AppsDictionary : Dictionary + { + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/Build.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/Build.cs new file mode 100644 index 000000000..4ee7d32a0 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/Build.cs @@ -0,0 +1,11 @@ +using System; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.Json +{ + public class Build + { + public Int32 Id { get; set; } + + public String FW { get; set; } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/Details.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/Details.cs new file mode 100644 index 000000000..7cf128ece --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/Details.cs @@ -0,0 +1,19 @@ +using System; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.Json +{ + public class Details + { + public String Type { get; set; } + + public Privilege Privilege { get; set; } + + public String Description { get; set; } + + public FwVersion Version { get; set; } + + public String StaticType { get; set; } + + public Value Values { get; set; } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/FwVersion.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/FwVersion.cs new file mode 100644 index 000000000..a6027cede --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/FwVersion.cs @@ -0,0 +1,13 @@ +using System; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.Json +{ + public class FwVersion + { + public Int32? First { get; set; } + + public Int32? Last { get; set; } + + public Int32[] Exclude { get; set; } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/JsonVersionsConverter.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/JsonVersionsConverter.cs new file mode 100644 index 000000000..986b8ff34 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/JsonVersionsConverter.cs @@ -0,0 +1,32 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.Json +{ + public class JsonVersionsConverter : JsonConverter + { + public override FwVersion ReadJson(JsonReader reader, Type objectType, FwVersion existingValue, Boolean hasExistingValue, JsonSerializer serializer) + { + var jobject = JToken.Load(reader); + + if (jobject.Type != JTokenType.Object) + { + if (int.TryParse($"{jobject}", out var version)) + { + return new FwVersion + { + First = version + }; + } + } + + return jobject.ToObject(); + } + + public override void WriteJson(JsonWriter writer, FwVersion value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/Privilege.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/Privilege.cs new file mode 100644 index 000000000..ac0ab51b8 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/Privilege.cs @@ -0,0 +1,48 @@ +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.Json +{ + /// + /// Representing the 8 access levels meter register can have + /// + public class Privilege + { + /// + /// Value for first level + /// + public Access Lvl1 { get; set; } + + /// + /// Value for second level + /// + public Access Lvl2 { get; set; } + + /// + /// Value for third level + /// + public Access Lvl3 { get; set; } + + /// + /// Value for fourth level + /// + public Access Lvl4 { get; set; } + + /// + /// Value for fifth level + /// + public Access Lvl5 { get; set; } + + /// + /// Value for sixth level + /// + public Access Lvl6 { get; set; } + + /// + /// Value for seventh level + /// + public Access Lvl7 { get; set; } + + /// + /// Value for eighth level + /// + public Access Lvl8 { get; set; } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/Register.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/Register.cs new file mode 100644 index 000000000..28e0828d6 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/Register.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.Json +{ + public class Register + { + public Byte Id { get; set; } + + public IEnumerable
Details { get; set; } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/Status.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/Status.cs new file mode 100644 index 000000000..3112785f6 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/Status.cs @@ -0,0 +1,13 @@ +using System; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.Json +{ + public class Status + { + public Byte Id { get; set; } + + public String Action { get; set; } + + public String Description { get; set; } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/Value.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/Value.cs new file mode 100644 index 000000000..9d5d66bc1 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Json/Value.cs @@ -0,0 +1,13 @@ +using System; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.Json +{ + public class Value + { + public Object Minimum { get; set; } + + public Object Maximum { get; set; } + + public Object Default { get; set; } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/MeterRegisters.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/MeterRegisters.cs new file mode 100644 index 000000000..0e652331d --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/MeterRegisters.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using Xylem.Common.Hardware.WaterMeter.WaterMeterRegisters; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers +{ + /// + /// Representing all registers from one meter. + /// create empty list on construction. + /// updated after read register + /// + public class MeterRegisters : IEnumerable + { + /// + /// Dictionary of meter registers + /// + public readonly ConcurrentDictionary MeterRegisterDic; + + /// + /// create empty list on construction. + /// + public MeterRegisters() + { + MeterRegisterDic = new ConcurrentDictionary(); + } + + /// + /// add empty registers + /// + /// + public void AddRegistersDefinitions(List adds) + { + foreach (var add in adds) + { + try + { + if (add is RegisterDefinition addGenesisRegister) + { + MeterRegisterDic.TryAdd(addGenesisRegister, null); + } + } + catch (Exception) + { + // ignored + } + } + } + + /// + /// Search register referenced by name as combination of application name and register name + /// Example: GENSISFLOW_LedMode + /// + /// + /// + public RegisterDefinition GetRegisterDefinitionByName(String name) + { + if (MeterRegisterDic == null || !MeterRegisterDic.Any()) + { + throw new ApplicationException($"Register {name} is not available. Registers are not loaded!"); + } + + var regDef = MeterRegisterDic.Where(f => f.Key.GetIdent().ToLower() == name.ToLower()).ToList(); + + if (regDef.Count == 1) + { + return regDef.First().Key; + } + var r = regDef.Count >= 1 ? regDef.Last().Key : new RegisterDefinition(); + + if (!string.IsNullOrEmpty(r.AppName)) + { + return r; + } + // if no register is available throw ex + throw new ApplicationException($"Register {name} is not available. Check the configuration.json for latest version!"); + } + + /// + /// Get current value of register in program. + /// ATTENTION: this is not the current meter register! + /// for meter register use read register in advance + /// + /// register to get + /// current value + public Byte[] Get(String register) + { + var reg = GetRegisterDefinitionByName(register); + if (!MeterRegisterDic.ContainsKey(reg)) + return null; + if (MeterRegisterDic.Count(f => f.Key.GetIdent() == register) > 1) + { + if (MeterRegisterDic.Any(f => f.Key.GetIdent() == register && f.Value != null)) + { + var regDs = MeterRegisterDic.Last(f => f.Key.GetIdent() == register && f.Value != null); + return regDs.Value; + + } + } + var regD = MeterRegisterDic.First(f => f.Key.GetIdent() == register); + return regD.Value; + } + + /// + /// Set register in program + /// ATTENTION: this is not stored to the meter! + /// to store to meter register use write register! + /// + /// Register to set + /// Value to set + public void Set(RegisterDefinition register, Byte[] value) + { + if (MeterRegisterDic.ContainsKey(register)) + { + MeterRegisterDic[register] = value; + } + else if (register.DataType != null && register.RegisterDetail != null) + { + throw new ApplicationException("unknown register"); + } + } + + /// + /// Unimplemented + /// + /// + /// + public IEnumerator GetEnumerator() + { + throw new NotImplementedException(); + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Properties/Resources.Designer.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Properties/Resources.Designer.cs new file mode 100644 index 000000000..27bae83f6 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Properties/Resources.Designer.cs @@ -0,0 +1,90 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Xylem.Common.Hardware.WaterMeter.Genesis.Registers.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to ERROR: Register data type unknown. + /// + internal static string StrRegisterDataTypeUnknown { + get { + return ResourceManager.GetString("StrRegisterDataTypeUnknown", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ERROR: Register value is out of range. + /// + internal static string StrRegisterValueOutOfRange { + get { + return ResourceManager.GetString("StrRegisterValueOutOfRange", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to WARNING: Register value is set to default. + /// + internal static string StrSetRegisterToDefault { + get { + return ResourceManager.GetString("StrSetRegisterToDefault", resourceCulture); + } + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Properties/Resources.de.Designer.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Properties/Resources.de.Designer.cs new file mode 100644 index 000000000..e69de29bb diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Properties/Resources.de.resx b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Properties/Resources.de.resx new file mode 100644 index 000000000..36a2b24ef --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Properties/Resources.de.resx @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + WARNUNG: Registerwert auf Standardwert gesetzt + + + FEHLER: Registerwert außerhalb des zulässigen Bereiches + + + FEHLER: Registerdatentyp ist unbekannt! + + \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Properties/Resources.resx b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Properties/Resources.resx new file mode 100644 index 000000000..0e66da421 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Properties/Resources.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + WARNING: Register value is set to default + + + ERROR: Register value is out of range + + + ERROR: Register data type unknown + + \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/RecoveryRegisterItem.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/RecoveryRegisterItem.cs new file mode 100644 index 000000000..327d8bdc9 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/RecoveryRegisterItem.cs @@ -0,0 +1,57 @@ +using System; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers +{ + /// + /// Recovery of registers in the field + /// + public class RecoveryRegisterItem + { + /// + /// Ctor + /// + public RecoveryRegisterItem() + { + } + + /// + /// Ctor for write access parameter setup + /// + public RecoveryRegisterItem(String registerIdent, Byte[] writeValue, Byte[] readBackValue = null) + { + RegisterIdent = registerIdent; + WriteValue = writeValue; + ReadBackValue = readBackValue; + } + + + /// + /// Register name, use the latest configuration.json to make sure that the address will be correct + /// + public String RegisterIdent { get; set; } + + /// + /// Content to write + /// + public Byte[] WriteValue { get; set; } + + /// + /// check if the written value is sett like this + /// Null or not set if a read back is not needed + /// + public Byte[] ReadBackValue { get; set; } + + /// + /// Mark Update as failed if write was not successful + /// + public Boolean IsCritical { get; set; } + + /// + /// Condition when the register will be overridden + /// + public RegisterRecoveryAccess Access { get; set; } + + } +} + + diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/RecoverySettings.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/RecoverySettings.cs new file mode 100644 index 000000000..fd842dad1 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/RecoverySettings.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers; + +namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers +{ + /// + /// + /// + [Serializable] + public class RecoverySettings + { + + /// + /// Null if check in not necessary if is an number check it against the pcbid from meter + /// + public String PcbId; + + /// + /// Radio frequency in MHz referenced a SENSUSRADIO AppId 0x10, Register FrequencyIndicator Id 0x05 + /// values: default 433, minimum 433, maximum 868 or null if region is "NA" or if for all frequencies + /// + public Int32? RadioFrequencyMhz; + + /// + /// Null till meter handles regions + /// + public String Region; + + /// + /// Release string which is the FLEXNETVERSION + /// + public String Release; + + /// + /// User name + /// + public String ApproverName; + + /// + /// Null means not approved + /// + public DateTimeOffset? ApprovalDate; + + /// + /// List of registers to recover + /// + public List RecoveryRegisters; + + /// + /// File name for this recovery register set + /// + /// + public String GenerateFileName() + { + return $"{PcbId}_{Region}_{Release}.recovery"; + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/RegisterCheck.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/RegisterCheck.cs new file mode 100644 index 000000000..0a7c87e06 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/RegisterCheck.cs @@ -0,0 +1,284 @@ +using System; +using Xylem.Common.Hardware.WaterMeter.Genesis.Registers; +using Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers +{ + /// + /// Check the register value for Out Of Boundaries (min, max) and optional if set to Default . + /// + public static class RegisterCheck + { + private enum RangeCheck + { + MinimumExceeded, + MaximumExceeded, + DefaultValue, + NotConvertible + }; + private static String OutOfRangeMsg(RangeCheck rangeCheck, String value, String range, String name) + { + var msg = ""; + switch (rangeCheck) + { + case RangeCheck.DefaultValue: + msg = $"{Xylem.Common.Hardware.WaterMeter.Genesis.Registers.Properties.Resources.StrSetRegisterToDefault} {name}: ({value})"; + break; + case RangeCheck.MaximumExceeded: + msg = $"{Xylem.Common.Hardware.WaterMeter.Genesis.Registers.Properties.Resources.StrRegisterValueOutOfRange} {name}: ({value}) - Maximum ({range})"; + break; + case RangeCheck.MinimumExceeded: + msg = $"{Xylem.Common.Hardware.WaterMeter.Genesis.Registers.Properties.Resources.StrRegisterValueOutOfRange} {name}: ({value}) - Minimum ({range})"; + break; + case RangeCheck.NotConvertible: + msg = $"{Xylem.Common.Hardware.WaterMeter.Genesis.Registers.Properties.Resources.StrRegisterDataTypeUnknown} {name}"; + break; + } + return msg; + + } + + /// + /// Check the registers for ot of range (below minimum or above maximum) and return empty string + /// if impossible to check (unset limits) or in range. + /// + /// + /// + /// feedback message + /// forces a warning if value is set to default + /// + /// if default and check for this is required + /// if the value is in range or has no range defined + /// if value exceeds the limits + /// + /// + /// - Init. + /// + /// + /// - Modified with try catch block. + /// + /// + /// - Returns warning on default, else error or okay. + /// + /// + /// - Ignore TimeT. + /// + public static StatusReturn CheckRange(RegisterDefinition regDef, Byte[] value, out String msg, + Boolean checkIfValueIsDefault = false) + { + msg = ""; + // for all registers without min and max or if type is of TimeT + if ((!regDef.Minimum.HasValue && !regDef.Maximum.HasValue) || regDef.DataType == typeof(TimeT)) + { + return StatusReturn.Okay; + } + + try + { + var type = regDef.DataType; + if (type == typeof(UInt16)) + { + var checkValue = RegisterConverter.ByteArrayToValue(value); + if (regDef.Minimum.HasValue && checkValue < regDef.Minimum.Value) + { + msg = OutOfRangeMsg(RangeCheck.MinimumExceeded, checkValue.ToString(), + regDef.Minimum.Value.ToString(), regDef.GetIdent()); + return StatusReturn.Failed; + } + + if (regDef.Maximum.HasValue && checkValue > regDef.Maximum.Value) + { + msg = OutOfRangeMsg(RangeCheck.MaximumExceeded, checkValue.ToString(), + regDef.Maximum.Value.ToString(), regDef.GetIdent()); + return StatusReturn.Failed; + } + if (checkIfValueIsDefault && regDef.Default.HasValue && checkValue == regDef.Default.Value) + { + msg = OutOfRangeMsg(RangeCheck.DefaultValue, checkValue.ToString(), + regDef.Default.Value.ToString(), regDef.GetIdent()); + return StatusReturn.Warning; + } + } + else if (type == typeof(UInt32)) + { + var checkValue = RegisterConverter.ByteArrayToValue(value); + if (regDef.Minimum.HasValue && checkValue < regDef.Minimum.Value) + { + msg = OutOfRangeMsg(RangeCheck.MinimumExceeded, checkValue.ToString(), + regDef.Minimum.Value.ToString(), regDef.GetIdent()); + return StatusReturn.Failed; + } + + if (regDef.Maximum.HasValue && checkValue > regDef.Maximum.Value) + { + msg = OutOfRangeMsg(RangeCheck.MaximumExceeded, checkValue.ToString(), + regDef.Maximum.Value.ToString(), regDef.GetIdent()); + return StatusReturn.Failed; + } + if (checkIfValueIsDefault && regDef.Default.HasValue && checkValue == regDef.Default.Value) + { + msg = OutOfRangeMsg(RangeCheck.DefaultValue, checkValue.ToString(), + regDef.Default.Value.ToString(), regDef.GetIdent()); + return StatusReturn.Warning; + } + } + else if (type == typeof(UInt64)) + { + var checkValue = RegisterConverter.ByteArrayToValue(value); + if (regDef.Minimum.HasValue && checkValue < (UInt64)regDef.Minimum.Value) + { + msg = OutOfRangeMsg(RangeCheck.MinimumExceeded, checkValue.ToString(), + regDef.Minimum.Value.ToString(), regDef.GetIdent()); + return StatusReturn.Failed; + } + + if (regDef.Maximum.HasValue && checkValue > (UInt64)regDef.Maximum.Value) + { + msg = OutOfRangeMsg(RangeCheck.MaximumExceeded, checkValue.ToString(), + regDef.Maximum.Value.ToString(), regDef.GetIdent()); + return StatusReturn.Failed; + } + if (checkIfValueIsDefault && regDef.Default.HasValue && 0 == checkValue.CompareTo(regDef.Default.Value)) + { + msg = OutOfRangeMsg(RangeCheck.DefaultValue, checkValue.ToString(), + regDef.Default.Value.ToString(), regDef.GetIdent()); + return StatusReturn.Warning; + } + } + else if (type == typeof(Int16)) + { + var checkValue = RegisterConverter.ByteArrayToValue(value); + if (regDef.Minimum.HasValue && checkValue < regDef.Minimum.Value) + { + msg = OutOfRangeMsg(RangeCheck.MinimumExceeded, checkValue.ToString(), + regDef.Minimum.Value.ToString(), regDef.GetIdent()); + return StatusReturn.Failed; + } + + if (regDef.Maximum.HasValue && checkValue > regDef.Maximum.Value) + { + msg = OutOfRangeMsg(RangeCheck.MaximumExceeded, checkValue.ToString(), + regDef.Maximum.Value.ToString(), regDef.GetIdent()); + return StatusReturn.Failed; + } + if (checkIfValueIsDefault && regDef.Default.HasValue && checkValue == regDef.Default.Value) + { + msg = OutOfRangeMsg(RangeCheck.DefaultValue, checkValue.ToString(), + regDef.Default.Value.ToString(), regDef.GetIdent()); + return StatusReturn.Warning; + } + } + else if (type == typeof(Int32)) + { + var checkValue = RegisterConverter.ByteArrayToValue(value); + if (regDef.Minimum.HasValue && checkValue < regDef.Minimum.Value) + { + msg = OutOfRangeMsg(RangeCheck.MinimumExceeded, checkValue.ToString(), + regDef.Minimum.Value.ToString(), regDef.GetIdent()); + return StatusReturn.Failed; + } + + if (regDef.Maximum.HasValue && checkValue > regDef.Maximum.Value) + { + msg = OutOfRangeMsg(RangeCheck.MaximumExceeded, checkValue.ToString(), + regDef.Maximum.Value.ToString(), regDef.GetIdent()); + return StatusReturn.Failed; + } + if (checkIfValueIsDefault && regDef.Default.HasValue && checkValue == regDef.Default.Value) + { + msg = OutOfRangeMsg(RangeCheck.DefaultValue, checkValue.ToString(), + regDef.Default.Value.ToString(), regDef.GetIdent()); + return StatusReturn.Warning; + } + } + else if (type == typeof(Int64)) + { + var checkValue = RegisterConverter.ByteArrayToValue(value); + if (regDef.Minimum.HasValue && checkValue < regDef.Minimum.Value) + { + msg = OutOfRangeMsg(RangeCheck.MinimumExceeded, checkValue.ToString(), + regDef.Minimum.Value.ToString(), regDef.GetIdent()); + return StatusReturn.Failed; + } + + if (regDef.Maximum.HasValue && checkValue > regDef.Maximum.Value) + { + msg = OutOfRangeMsg(RangeCheck.MaximumExceeded, checkValue.ToString(), + regDef.Maximum.Value.ToString(), regDef.GetIdent()); + return StatusReturn.Failed; + } + if (checkIfValueIsDefault && regDef.Default.HasValue && checkValue == regDef.Default.Value) + { + msg = OutOfRangeMsg(RangeCheck.DefaultValue, checkValue.ToString(), + regDef.Default.Value.ToString(), regDef.GetIdent()); + return StatusReturn.Warning; + } + } + else if (type == typeof(Byte) || type == typeof(Enum8)) + { + var checkValue = RegisterConverter.ByteArrayToValue(value); + if (regDef.Minimum.HasValue && checkValue < regDef.Minimum.Value) + { + msg = OutOfRangeMsg(RangeCheck.MinimumExceeded, checkValue.ToString(), + regDef.Minimum.Value.ToString(), regDef.GetIdent()); + return StatusReturn.Failed; + } + + if (regDef.Maximum.HasValue && checkValue > regDef.Maximum.Value) + { + msg = OutOfRangeMsg(RangeCheck.MaximumExceeded, checkValue.ToString(), + regDef.Maximum.Value.ToString(), regDef.GetIdent()); + return StatusReturn.Failed; + } + if (checkIfValueIsDefault && regDef.Default.HasValue && checkValue == regDef.Default.Value) + { + msg = OutOfRangeMsg(RangeCheck.DefaultValue, checkValue.ToString(), + regDef.Default.Value.ToString(), regDef.GetIdent()); + return StatusReturn.Warning; + } + } + else if (type == typeof(SByte)) + { + var checkValue = RegisterConverter.ByteArrayToValue(value); + if (regDef.Minimum.HasValue && checkValue < regDef.Minimum.Value) + { + msg = OutOfRangeMsg(RangeCheck.MinimumExceeded, checkValue.ToString(), + regDef.Minimum.Value.ToString(), regDef.GetIdent()); + return StatusReturn.Failed; + } + + if (regDef.Maximum.HasValue && checkValue > regDef.Maximum.Value) + { + msg = OutOfRangeMsg(RangeCheck.MaximumExceeded, checkValue.ToString(), + regDef.Maximum.Value.ToString(), regDef.GetIdent()); + return StatusReturn.Failed; + } + if (checkIfValueIsDefault && regDef.Default.HasValue && checkValue == regDef.Default.Value) + { + msg = OutOfRangeMsg(RangeCheck.DefaultValue, checkValue.ToString(), + regDef.Default.Value.ToString(), regDef.GetIdent()); + return StatusReturn.Warning; + } + } + else if (type == typeof(Boolean)) + { + // call function to force exception if out of range + RegisterConverter.ByteArrayToValue(value); + } + else + { + msg = OutOfRangeMsg(RangeCheck.NotConvertible, "", "", regDef.GetIdent()); + return StatusReturn.Failed; + } + } + catch (Exception e) + { + msg = e.Message; + return StatusReturn.Failed; + } + + // the value is in range and not on default + return StatusReturn.Okay; + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/RegisterConverter.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/RegisterConverter.cs new file mode 100644 index 000000000..8dba1d758 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/RegisterConverter.cs @@ -0,0 +1,488 @@ +using System; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using Xylem.Common.Hardware.WaterMeter.Genesis.Registers; +using Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers +{ + /// + /// Helper to convert genesis register values store in bytes to data types and back + /// + public static class RegisterConverter + { + /// + /// Convert single register from Genesis and return a message of the result. The value input is a byte array + /// with the LSB at the index 0 and the MSB at the highest index. A string is sorted with the first character + /// at index 0, this does not need to be reversed as it is readable by default. + /// The raw value is swapped byte-wise to get a human-readable value with MSB at leftmost and LSB at rightmost. + /// The message contains the register name, the swapped raw value and on the converted value. + /// + /// + /// the raw byte array is always % 4 0 padded for unused elements, the LSB is + /// on index 0 (little endian, LSB first) + /// message with register name, swapped raw value and converted value + /// + /// - Initial. + /// + /// + /// - Reworked. + /// + /// + /// - Avoid logging of encryption key. + /// + /// + /// - Hash password and encryption key with SHA256. + /// + public static String GetRegisterContentText(RegisterDefinition registerDefinition, Byte[] regRawByteArray) + { + String msg; + String registerName = "?"; + try + { + registerName = registerDefinition.GetIdent(); + // getting a result value as text of the data type e.g. UInt32 + var strValue = ConvertToText(regRawByteArray, registerDefinition.DataType); + // remove non-printable char of the converted result + var strValueResult = Regex.Replace(strValue, @"\p{C}+", string.Empty); + + // use hashed output being able to compare written and read back values + String strRawResult; + if (registerName.Contains("EncryptionKey") || registerName.Contains("Password")) + { + strRawResult = GetRegisterRawText(registerDefinition, regRawByteArray, encryptData: true); + msg = $"{registerName}: ({strRawResult})h"; + } + else + { + strRawResult = GetRegisterRawText(registerDefinition, regRawByteArray); + msg = $"{registerName}: ({strRawResult})h ({strValueResult})"; + } + } + catch (Exception) + { + throw new ApplicationException($"Cannot convert register {registerName}"); + } + + return msg; + } + + /// + /// Convert single register from Genesis and return a message of the result. The value input is a byte array + /// with the LSB at the index 0 and the MSB at the highest index. A string is sorted with the first character + /// at index 0, this does not need to be reversed as it is readable by default. + /// The raw value is swapped byte-wise to get a human-readable value with MSB at leftmost and LSB at rightmost. + /// The message contains the swapped raw value. + /// + /// + /// the raw byte array is always % 4 0 padded for unused elements, the LSB is + /// on index 0 (little endian, LSB first) + /// data encryption for data required SHA256 hash instead of clear text + /// message with swapped raw value + /// + /// - Initial. + /// + /// + /// - Hash password and encryption key with SHA256. + /// + /// + /// - Data size introduced including the new 'ByteArray' type and size to get rid of new defined 'uintxx_t' + /// data types. + /// + public static String GetRegisterRawText(RegisterDefinition registerDefinition, Byte[] regRawByteArray, + Boolean encryptData = false) + { + String msg; + var strRawResult = "?"; + try + { + // inverse the raw value to get a readable byte order MSB left and LSB last right position + var tmpList = regRawByteArray.ToList(); + + // reverse the list if it is not a string or the UInt48, UInt72, UInt88,UInt96 or UInt128 which + // will be used as placeholder for a string + if (registerDefinition.DataType != typeof(String) && + registerDefinition.DataType != typeof(ByteArray)) + { + tmpList.Reverse(); + } + + // use hashed output being able to compare written and read back values + if (encryptData) + { + var hash = SHA256.Create().ComputeHash(regRawByteArray); + strRawResult = BitConverter.ToString(hash.ToArray()); + msg = $"SHA256 - {strRawResult}"; + } + else + { + strRawResult = BitConverter.ToString(tmpList.ToArray()); + msg = $"{strRawResult}"; + } + } + catch (Exception) + { + msg = $"{strRawResult}"; + } + + return msg; + } + + /// + /// Preset the raw byte array with the default value. + /// + /// + /// + /// true if value could be set + /// + /// - Initial. + /// + public static Boolean SetToDefault(RegisterDefinition registerDefinition, out Byte[] regRawByteArray) + { + if (registerDefinition.Default.HasValue) + { + // will always return an Int64 value Byte[8] ! + regRawByteArray = ValueToByteArray(registerDefinition.Default); + return true; + } + + regRawByteArray = new Byte[] { 0 }; + return false; + } + + /// + /// Convert a value (T) into byte[] + /// + /// Type to convert from + /// value to convert + /// byte[] like it store in genesis meter + /// + /// - Init. + /// + /// + /// - Used to calculate time in UTC based on 01. Jan 2000 + /// and the given offset in seconds. + /// + /// + /// - Data size introduced including the new 'ByteArray' type and size to get rid of new defined 'uintxx_t' + /// data types. + /// + public static Byte[] ValueToByteArray(T value) + { + var converted = new Byte[] { 0 }; + if (value == null) + return converted; + + if (value.GetType() == typeof(TimeT)) + { + var teaTime = value as TimeT; + if (teaTime != null) + { + // the value contains always the seconds since 01.Jan 2000 + var intValue = Convert.ToInt32(teaTime.SecondsSince2000); + return BitConverter.GetBytes(intValue); + } + } + + if (value is Boolean) + { + var tmp = Convert.ToBoolean(value); + return BitConverter.GetBytes(tmp); + } + + if (value is UInt16) + { + var tmp = Convert.ToUInt16(value); + return BitConverter.GetBytes(tmp); + } + + if (value is UInt32) + { + var tmp = Convert.ToUInt32(value); + return BitConverter.GetBytes(tmp); + } + + if (value is UInt64) + { + var tmp = Convert.ToUInt64(value); + return BitConverter.GetBytes(tmp); + } + + if (value is Int16) + { + var intValue = Convert.ToInt16(value); + return BitConverter.GetBytes(intValue); + } + + if (value is Int32) + { + var intValue = Convert.ToInt32(value); + return BitConverter.GetBytes(intValue); + } + + if (value is Int64) + { + var tmp = Convert.ToInt64(value); + return BitConverter.GetBytes(tmp); + } + + if (value is String) + { + return Encoding.ASCII.GetBytes(value.ToString()); + } + + if (value is Byte || value is Enum8) + { + converted[0] = Convert.ToByte(value); + } + else if (value is SByte) + { + return BitConverter.GetBytes(Convert.ToSByte(value)); + } + + else if (value is Byte[] || value is ByteArray) + { + return (Byte[])Convert.ChangeType(value, typeof(Byte[])); + } + + else + { + throw new ApplicationException($"Data type {typeof(T)} is unknown!"); + } + + return converted; + } + + /// + /// Build the result of the array as text. + /// + /// byte value for conversion + /// type of the result + /// + /// + /// - Init. + /// + /// + /// - Used to calculate time in UTC based on 01. Jan 2000 + /// and the given offset in seconds. + /// + /// + /// - Data size introduced including the new 'ByteArray' type and size to get rid of new defined 'uintxx_t' + /// data types. + /// + public static String ConvertToText(Byte[] rawByteArray, Type type) + { + try + { + if (type == typeof(Boolean)) + { + return ByteArrayToValue(rawByteArray).ToString(); + } + + if (type == typeof(TimeT)) + { + var teaTime = ByteArrayToValue(rawByteArray); + return teaTime.ToString(); + } + if (type == typeof(UInt16)) + { + return ByteArrayToValue(rawByteArray).ToString(); + } + if (type == typeof(UInt32)) + { + return ByteArrayToValue(rawByteArray).ToString(); + } + if (type == typeof(UInt64)) + { + return ByteArrayToValue(rawByteArray).ToString(); + } + if (type == typeof(Int16)) + { + return ByteArrayToValue(rawByteArray).ToString(); + } + if (type == typeof(Int32)) + { + return ByteArrayToValue(rawByteArray).ToString(); + } + if (type == typeof(Int64)) + { + return ByteArrayToValue(rawByteArray).ToString(); + } + if (type == typeof(Byte) || type == typeof(Enum8)) + { + return ByteArrayToValue(rawByteArray).ToString(); + } + if (type == typeof(ByteArray) || type == typeof(Byte[])) + { + return ""; + } + if (type == typeof(SByte)) + { + return ByteArrayToValue(rawByteArray).ToString(); + } + + return ByteArrayToValue(rawByteArray); + } + catch (Exception) + { + return ""; + } + + } + + + /// + /// convert form byte (meter) to data type + /// + /// Type to cast in + /// byte value for conversion + /// converted value + /// + /// - Init. + /// + /// + /// - Used to calculate time in UTC based on 01. Jan 2000 + /// and the given offset in seconds. + /// - String removed from first zero in string until the end to avoid ghost signs! + /// + /// + /// - Return default (T) on input == null; + /// + /// + /// - Padding bytes if input doesn't fit the required size; + /// + /// + /// - Data size introduced including the new 'ByteArray' type and size to get rid of new defined 'uintxx_t' + /// data types. + /// + public static T ByteArrayToValue(Byte[] rawByteArray) + { + if (rawByteArray == null) + return default(T); + + try + { + Object convertedObj = null; + var type = typeof(T); + + if (type == typeof(Byte) || type == typeof(Enum8)) + { + convertedObj = rawByteArray[0]; + } + else if (type == typeof(Boolean)) + { + convertedObj = BitConverter.ToBoolean(rawByteArray, 0); + } + else if (type == typeof(SByte)) + { + // set all others to 0 as only the LSB is of interest + for (var idx = 1; idx < rawByteArray.Length; idx++) + rawByteArray[idx] = 0; + convertedObj = Convert.ToSByte((SByte)rawByteArray[0]); + } + else if (type == typeof(Boolean)) + { + convertedObj = Convert.ToBoolean(rawByteArray[0]); + } + else if (type == typeof(String)) + { + // the value may contain zeros at the beginning of the value-record, these have to be removed + var zeroPaddingCounter = 0; + while (rawByteArray.Length > zeroPaddingCounter && rawByteArray[zeroPaddingCounter] == 0) + zeroPaddingCounter += 1; + var trimmedValue = new Byte[rawByteArray.Length - zeroPaddingCounter]; + Int32 i; + for (i = 0; i < rawByteArray.Length - zeroPaddingCounter; i++) + trimmedValue[i] = rawByteArray[i + zeroPaddingCounter]; + + // as a string is terminated with a zero all following elements after including the initial + // zero have to be removed. This is caused due to the 4 byte chunks which will be sent. + var validCharCtr = 0; + // find the first zero in the trimmed value array + while (trimmedValue.Length > validCharCtr && trimmedValue[validCharCtr] != 0) + validCharCtr += 1; + // reserve space for valid chr plus the trailing 0 which will be added if + var rawResult = new Byte[validCharCtr + 1]; + for (i = 0; i < validCharCtr; i++) + rawResult[i] = trimmedValue[i]; + if (validCharCtr < trimmedValue.Length) + { + rawResult[i] = 0; + convertedObj = Encoding.ASCII.GetString(rawResult); + } + else + { + convertedObj = Encoding.ASCII.GetString(trimmedValue); + } + } + else if (type == typeof(TimeT)) + { + // the value contains always the seconds since 01.Jan 2000 + convertedObj = new TimeT { SecondsSince2000 = ByteArrayToValue(rawByteArray) }; + } + + if (convertedObj != null) + { + return (T)Convert.ChangeType(convertedObj, type); + } + + // Padding byte array to required byte size will be used for numerical values + var requiredSize = Marshal.SizeOf(typeof(T)); + var paddedRawByteArray = new Byte[requiredSize]; + var inputArraySize = rawByteArray.Length; + for (var ctr = 0; ctr < requiredSize; ctr++) + { + if (inputArraySize > 0) + { + paddedRawByteArray[ctr] = rawByteArray[ctr]; + inputArraySize--; + } + else + { + paddedRawByteArray[ctr] = 0x00; + } + } + + // For all numerical values the padded raw byte array will be used + if (type == typeof(UInt16)) + { + convertedObj = BitConverter.ToUInt16(paddedRawByteArray, 0); + } + else if (type == typeof(UInt32)) + { + convertedObj = BitConverter.ToUInt32(paddedRawByteArray, 0); + } + else if (type == typeof(UInt64)) + { + convertedObj = BitConverter.ToUInt64(paddedRawByteArray, 0); + } + else if (type == typeof(Int16)) + { + convertedObj = BitConverter.ToInt16(paddedRawByteArray, 0); + } + else if (type == typeof(Int32)) + { + convertedObj = BitConverter.ToInt32(paddedRawByteArray, 0); + } + else if (type == typeof(Int64)) + { + convertedObj = BitConverter.ToInt64(paddedRawByteArray, 0); + } + else + { + throw new ApplicationException($"Data type {type} not implemented"); + } + + return (T)Convert.ChangeType(convertedObj, type); + } + catch (Exception ex) + { + throw new ApplicationException($"Data type {typeof(T)} not implemented. Message: {ex.Message}"); + //return default(T); + } + } + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/RegisterDefinition.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/RegisterDefinition.cs new file mode 100644 index 000000000..fba1e0716 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/RegisterDefinition.cs @@ -0,0 +1,118 @@ +using System; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.Json; +using Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes; +using Xylem.Common.Hardware.WaterMeter.WaterMeterRegisters; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers +{ + /// + /// describe a genesis register. + /// need for read and write + /// all request protocols uses RegisterDefinition + /// + public class RegisterDefinition : IRegister + { + /// + /// name of the application which uses the register + /// + public String AppName; + + /// + /// address of application which is the base address for the register + /// + /// + /// - AppAddress from Byte to UInt16 including typecast for Byte[] return. To external, it will be used as Byte + /// as before this change. But being able to parse the configuration.json with OPTICALINTERFACE using the + /// AppAddress 256, which exceeds the byte range as this isn't a real application, but will be used to identify + /// the interface (configuration.json) version. + /// + public UInt16 AppAddress; + + /// + /// address of the register in this application + /// + public Byte RegAddressInApp; + + /// + /// data type of register + /// + public Type DataType; + + /// + /// size of the data to support the SizeOf implementation + /// + public Int32 DataSize; + + /// + /// length of one chunk during communication + /// + /// returns the expected length of the data from register + public const Int32 ChunkSize = 4; + + /// + /// name of the register + /// + public String RegisterName; + + /// + /// Indicated if the register is a for this meter + /// + public Boolean IsAvailable; + + /// + public String GetIdent() + { + return $"{AppName}_{RegisterName}"; + } + + /// + /// address combined of application and register in this application + /// + /// + /// - AppAddress from Byte to UInt16 including typecast for Byte[] return. To external, it will be used as Byte + /// as before this change. But being able to parse the configuration.json with OPTICALINTERFACE using the + /// AppAddress 256, which exceeds the byte range as this isn't a real application, but will be used to identify + /// the interface (configuration.json) version. + /// + public Byte[] RegisterAddress + { + get { return new[] { (Byte)AppAddress, RegAddressInApp }; } + set + { + AppAddress = value[0]; + RegAddressInApp = value[1]; + } + } + + /// + /// describe the accessibility for different login levels + /// + public Details RegisterDetail { get; set; } + + /// + /// default value + /// + public Int64? Default { get; set; } + + /// + /// minimum value + /// + public Int64? Minimum { get; set; } + + /// + /// maximum value + /// + public Int64? Maximum { get; set; } + + /// + /// Read restore capability for firmware updates + /// + public StaticType RestoreCapability; + + /// + public Int32 CompareTo(Object obj) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/RegisterRecoveryAccess.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/RegisterRecoveryAccess.cs new file mode 100644 index 000000000..e1e02ea31 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/RegisterRecoveryAccess.cs @@ -0,0 +1,22 @@ +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers +{ + /// + /// Recovery rules + /// + public enum RegisterRecoveryAccess + { + /// + /// Set this register always + /// + SetAlways, + /// + /// Set register if it has the default value + /// + SetIfDefault, + /// + /// Set the register if it is zero + /// + SetIfZero + + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Registers.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Registers.cs new file mode 100644 index 000000000..241cca068 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/Registers/Registers.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Generic; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers +{ + public static class Register + { + public static class System + { + public static readonly String CheckFwPresence = "SYSTEM_CheckPresence"; + public static readonly String CheckFwCrc = "SYSTEM_CRC"; + public static readonly String TriggerFwUpgrade = "SYSTEM_TriggerUpgrade"; + public static readonly String CoreRevision = "SYSTEM_CoreRevision"; + public static readonly String MonotonicSeconds = "SYSTEM_MonotonicSeconds"; + public static readonly String MetrologyUpgradePermission = "SYSTEM_UpgradePermissions"; + } + + public static class Customer + { + public static readonly String AlarmStatus0 = "CUSTOMER_AlarmStatus0"; + public static readonly String AlarmStatus1 = "CUSTOMER_AlarmStatus1"; + public static readonly String TriggerAlarmCancel = "CUSTOMER_TriggerAlarmCancel"; + public static readonly String AlarmStatus2 = "CUSTOMER_AlarmStatus2"; + public static readonly String AlarmStatus3 = "CUSTOMER_AlarmStatus3"; + public static readonly String AlarmStatus4 = "CUSTOMER_AlarmStatus4"; + public static readonly String AlarmStatus5 = "CUSTOMER_AlarmStatus5"; + public static readonly String AlarmStatus6 = "CUSTOMER_AlarmStatus6"; + public static readonly String AlarmStatus7 = "CUSTOMER_AlarmStatus7"; + public static readonly String StoreConfiguration = "CUSTOMER_StoreConfiguration"; + }; + + + public static class Configexchange + { + public static readonly String Privilege = "CONFIGEXCHANGE_Privilege"; + public static readonly String Password = "CONFIGEXCHANGE_Password"; + public static readonly String PcbSerialNumber = "CONFIGEXCHANGE_PCBSerialNumber"; + public static readonly String FileOpen = "CONFIGEXCHANGE_FOpen"; + public static readonly String FileClose = "CONFIGEXCHANGE_FClose"; + public static readonly String FileWrite = "CONFIGEXCHANGE_FWrite"; + public static readonly String FileRead = "CONFIGEXCHANGE_FRead"; + public static readonly String GetFilePointerOffset = "CONFIGEXCHANGE_FTell"; + public static readonly String SetFilePointerOffset = "CONFIGEXCHANGE_FSeek"; + public static readonly String FileRemove = "CONFIGEXCHANGE_Remove"; + public static readonly String Catalogue = "CONFIGEXCHANGE_Catalogue"; + }; + + public static class Genesisflow + { + public static readonly String SampleRate = "GENESISFLOW_SampleRate"; + public static readonly String MeterSize = "GENESISFLOW_MeterSize"; + public static readonly String CalFactor1 = "GENESISFLOW_CalFactor1"; + public static readonly String CalFactor2 = "GENESISFLOW_CalFactor2"; + public static readonly String CalFactor3 = "GENESISFLOW_CalFactor3"; + public static readonly String ZeroOffset1 = "GENESISFLOW_ZeroOffset1"; + public static readonly String ZeroOffset2 = "GENESISFLOW_ZeroOffset2"; + public static readonly String ZeroOffset3 = "GENESISFLOW_ZeroOffset3"; + public static readonly String ResetAccumulators = "GENESISFLOW_ResetAccumulators"; + public static readonly String ForwardArrow = "GENESISFLOW_ForwardArrow"; + public static readonly String LedMode = "GENESISFLOW_LedMode"; + public static readonly String StoreCalibration = "GENESISFLOW_StoreCalibration"; + public static readonly String TriggerActive = "GENESISFLOW_TriggerActive"; + public static readonly String TriggerIdle = "GENESISFLOW_TriggerIdle"; + public static readonly String FirstHitPercent1 = "GENESISFLOW_FirstHitPercent1"; + public static readonly String FirstHitPercent2 = "GENESISFLOW_FirstHitPercent2"; + public static readonly String FirstHitPercent3 = "GENESISFLOW_FirstHitPercent3"; + public static readonly String FirstHitShift = "GENESISFLOW_FirstHitShift"; + public static readonly String FirstHitUpdatePeriod = "GENESISFLOW_FirstHitUpdatePeriod"; + public static readonly String ToFTempOffset1 = "GENESISFLOW_ToFTempOffset1"; + public static readonly String ToFTempOffset2 = "GENESISFLOW_ToFTempOffset2"; + public static readonly String ToFTempOffset3 = "GENESISFLOW_ToFTempOffset3"; + public static readonly String ToFTempCalibrate = "GENESISFLOW_ToFTempCalibrate"; + public static readonly String StoreConfiguration = "GENESISFLOW_StoreConfiguration"; + public static readonly String AmplitudePeakDetectEnd = "GENESISFLOW_AmplitudePeakDetectEnd"; + public static readonly String FirstHitLvlDown1 = "GENESISFLOW_FirstHitLvlDown1"; + public static readonly String FirstHitLvlDown2 = "GENESISFLOW_FirstHitLvlDown2"; + public static readonly String FirstHitLvlDown3 = "GENESISFLOW_FirstHitLvlDown3"; + public static readonly String FirstHitLvlUp1 = "GENESISFLOW_FirstHitLvlUp1"; + public static readonly String FirstHitLvlUp2 = "GENESISFLOW_FirstHitLvlUp2"; + public static readonly String FirstHitLvlUp3 = "GENESISFLOW_FirstHitLvlUp3"; + public static readonly String StartHit = "GENESISFLOW_StartHit"; + public static readonly String NumFirePulses = "GENESISFLOW_NumFirePulses"; + public static readonly String LookupFileCrc = "GENESISFLOW_LookupFileCrc"; + public static readonly String DisplayUnits = "GENESISFLOW_DisplayUnits"; + public static readonly String DisplayPow10 = "GENESISFLOW_DisplayPow10"; + public static readonly String SealDisplay = "GENESISFLOW_SealDisplay"; + }; + + public static class Powermon + { + public static readonly String BatteryVoltage = "POWERMON_BatteryVoltage"; + public static readonly String BatteryQuantity = "POWERMON_BatteryQuantity"; + public static readonly String BatteryDrainedLoad = "POWERMON_TotalUsedCharge"; + public static readonly String BatteryInitialLoad = "POWERMON_BatteryMilliAHrRating"; + public static readonly String BatteryExceededSeconds = "POWERMON_TotalUsedSeconds"; + public static readonly String StoreConfiguration = "POWERMON_StoreConfiguration"; + public static readonly String RemainingSeconds = "POWERMON_RemainingSeconds"; + } + + + public static class Sensusradio + { + public static readonly String FrequencyIndicator = "SENSUSRADIO_FrequencyIndicator"; + public static readonly String WakeupInterval = "SENSUSRADIO_WakeupInterval"; + public static readonly String SystemState = "SENSUSRADIO_SystemState"; + public static readonly String StoreConfiguration = "SENSUSRADIO_StoreConfiguration"; + public static readonly String EncryptionKey = "SENSUSRADIO_EncryptionKey"; + } + + public static class Metrologyasst + { + public static readonly String PulseEvenDistribution = "METROLOGYASST_PulseEvenDistribution"; + public static readonly String PulseMode = "METROLOGYASST_PulseMode"; + public static readonly String PressurePresent = "METROLOGYASST_PressurePresent"; + public static readonly String StoreConfiguration = "METROLOGYASST_StoreConfiguration"; + public static readonly String FlowUnits = "METROLOGYASST_FlowUnits"; + } + + public static class Irda + { + public static readonly String PulseSequence = "IRDA_PulseSequence"; + public static readonly String AdapterId = "IRDA_AdapterID"; + public static readonly String StoreConfiguration = "IRDA_StoreConfiguration"; + } + + public static class Logger + { + public static readonly String StoreConfiguration = "LOGGER_StoreConfiguration"; + } + + public static List GetAvoidLogRegisters() + { + return new List() + { + Configexchange.Privilege, + Configexchange.Password, + Configexchange.FileOpen, + Configexchange.FileClose, + Configexchange.FileWrite, + Configexchange.FileRead, + Configexchange.Catalogue, + Configexchange.GetFilePointerOffset, + Configexchange.SetFilePointerOffset, + Configexchange.FileRemove, + + //Sensusradio.EncryptionKey + }; + } + public static List GetMinRequiredRegisters() + { + return new List() + { + System.CheckFwPresence, + System.CheckFwCrc, + System.TriggerFwUpgrade, + System.CoreRevision, + System.MonotonicSeconds, + System.MetrologyUpgradePermission, + + Customer.AlarmStatus0, + Customer.AlarmStatus1, + Customer.TriggerAlarmCancel, + Customer.AlarmStatus2, + Customer.AlarmStatus3, + Customer.AlarmStatus4, + Customer.AlarmStatus5, + Customer.AlarmStatus6, + Customer.AlarmStatus7, + Customer.StoreConfiguration, + + Configexchange.Privilege, + Configexchange.Password, + Configexchange.PcbSerialNumber, + Configexchange.FileOpen, + Configexchange.FileClose, + Configexchange.FileWrite, + Configexchange.FileRead, + Configexchange.Catalogue, + Configexchange.GetFilePointerOffset, + Configexchange.SetFilePointerOffset, + Configexchange.FileRemove, + + Genesisflow.TriggerActive, + Genesisflow.TriggerIdle, + Genesisflow.LedMode, + Genesisflow.SampleRate, + Genesisflow.MeterSize, + Genesisflow.LookupFileCrc, + Genesisflow.StoreCalibration, + Genesisflow.StoreConfiguration, + + Powermon.BatteryVoltage, + Powermon.BatteryQuantity, + Powermon.BatteryDrainedLoad, + Powermon.BatteryInitialLoad, + Powermon.BatteryExceededSeconds, + Powermon.StoreConfiguration, + + Sensusradio.WakeupInterval, + Sensusradio.FrequencyIndicator, + Sensusradio.SystemState, + Sensusradio.StoreConfiguration, + + Metrologyasst.PulseMode, + Metrologyasst.PulseEvenDistribution, + Metrologyasst.StoreConfiguration, + + Irda.AdapterId, + Irda.PulseSequence, + Irda.StoreConfiguration, + + Logger.StoreConfiguration + + }; + + } + } + +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/StatusReturn.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/StatusReturn.cs new file mode 100644 index 000000000..cf9f33e95 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/StatusReturn.cs @@ -0,0 +1,48 @@ +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis +{ + /// + /// Returns of status from routines + /// + public enum StatusReturn + { + /// + /// undefined status does not fit in any other status defined below + /// + Unknown, + + /// + /// skipped test and therefore the return identifies this + /// + Skipped, + + /// + /// successfully executed without any restrictions + /// + Okay, + + /// + /// execution failed + /// + Failed, + + /// + /// inspection needed as warning returns + /// + Warning, + + /// + /// the measurement is 'in-range' for threshold cheks + /// + MeasurementInRange, + + /// + /// the measurement is out of range for threshold checks + /// + MeasurementOutOfRange, + + /// + /// The setup value is out of range + /// + SetupOutOfRange + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/ThreadWatcher.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/ThreadWatcher.cs new file mode 100644 index 000000000..32125e89c --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Genesis/ThreadWatcher.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis +{ + + public sealed class TaskWatcher + { + #region Singleton + private static readonly Lazy + Lazy = + new Lazy + (() => new TaskWatcher()); + + public static TaskWatcher Instance => Lazy.Value; + + #endregion + private TaskWatcher() + { + _tasks = new ConcurrentDictionary(); + } + + + private readonly ConcurrentDictionary _tasks; + public void Add(Task t) + { + _tasks.TryAdd(Guid.NewGuid(), t); + } + + } + + public sealed class ThreadWatcher + { + #region Singleton + private static readonly Lazy + Lazy = + new Lazy + (() => new ThreadWatcher()); + + public static ThreadWatcher Instance => Lazy.Value; + + #endregion + private ThreadWatcher() + { + _threads = new ConcurrentDictionary(); + } + + private readonly ConcurrentDictionary _threads; + + + public void Start(Thread t) + { + + t.Start(); + + _threads.TryAdd(Guid.NewGuid(), t); + } + + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/OptoHeadTest.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/OptoHeadTest.cs new file mode 100644 index 000000000..bccf6601a --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/OptoHeadTest.cs @@ -0,0 +1,370 @@ +using System; +using System.IO.Ports; +using Common; +using log4net; +using TBF.Rig.RegisterReaders.GenesisRegReader.implementations; +using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed; +using TBF.Rig.TestMethods.iPerlCommunication.communication.Utils; + + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication +{ + public class OptoHeadTest : IDisposable + { + //protected static readonly ILog rfidDataLogger = LogManager.GetLogger("RfidData"); + private static readonly ILog log = LogManager.GetLogger(typeof(OptoHeadTest)); + + private GenesisSmartReader genesisHead; + private SerialDriver serialDriver; + + public static SerialDriver BuildConnection(GenesisSmartReader iHead) + { + return new SerialDriverBuilder() + .WithPort($"COM{iHead.RfidComPortNr}") + .WithBaudRate(2400) + .WithDataBits(8) + .WithParity(Parity.None) + .WithStopBits(StopBits.One) + .WithTimeouts(4000, 2000) + .BuildAndConnect(); + + } + + public OptoHeadTest(GenesisSmartReader genesisHead) + { + this.genesisHead = genesisHead; + } + + public void CloseConnection() + { + if (serialDriver != null) + serialDriver.CloseConnection(); + serialDriver = null; + } + + public bool ReadSerialNr() + { + try + { + if (genesisHead != null) + { + if (serialDriver == null) + serialDriver = BuildConnection(genesisHead); + + log.Debug("ReadSerialNr called for iHead: " + genesisHead.ToString() + " serialDriver: " + serialDriver); + RadioService headService = new RadioService(serialDriver); + string serialNo = headService.ReadRequest_PCB(ref genesisHead); + if (!string.IsNullOrEmpty(serialNo)) + { + log.Info($"Success Serial No: {serialNo} on COM{genesisHead.RfidComPortNr} serialDriver: {serialDriver}"); + return true; + } + } + } + catch (Exception ex) + { + log.Error($"ReadSerialNr(COM{genesisHead.RfidComPortNr}) - Exception:" + ex.Message); + } + + return false; + } + + public string ReadRequest_PCB() + { + if (genesisHead.DebugLevel == DebugMode.Simulate) + { + return "-OK Simulated response-"; + } + + try + { + if (genesisHead != null) + { + if (serialDriver == null) + serialDriver = BuildConnection(genesisHead); + + RadioService headService = new RadioService(serialDriver); + string serialNo = headService.ReadRequest_PCB(ref genesisHead); + log.Info($"PCB Number: {serialNo} on COM{genesisHead.RfidComPortNr} serialDriver: {serialDriver}"); + return serialNo; + } + } + catch (Exception ex) + { + log.Error("ReadRequest_PCB() - Exception:" + ex.StackTrace); + return (ex.Message.ToString()); + } + + return ""; + } + + /// + /// Set Test mode + /// + /// + /// + public bool SetTestMode() + { + log.Debug("SetTestMode called for iHead: " + genesisHead.ToString()); + bool activityModeActive = SetActivityMode_Active(); + bool optActiveMode = SetOptTestMode(); + + log.Debug("SetTestMode result: optoMod-> " + optActiveMode + " meterModeActive ->" + activityModeActive); + return (optActiveMode && activityModeActive); + } + + /// + /// Set Active mode + /// + /// + /// + public bool SetActiveMode() + { + log.Debug("SetActiveMode called for iHead: " + genesisHead.ToString()); + bool optActiveMode = SetOptActiveMode(genesisHead); + //bool activityModeIdle = SetActivityMode_Idle(); + + return optActiveMode; + } + + /// + /// Set Idle mode - only + /// + /// + /// + public bool SetIdleMode() + { + log.Debug("SetIdleMode called for iHead: " + genesisHead.ToString()); + bool activityModeIdle = SetActivityMode_Idle(); + + return activityModeIdle; + } + + /// + /// Set Test mode - string response + /// + /// + /// + /// + public string SetTestMode(ref bool isTestModeSuccessful) + { + if (genesisHead.DebugLevel == DebugMode.Simulate) + { + isTestModeSuccessful = true; + return "-OK Simulated response-"; + } + + try + { + bool testMode = SetTestMode(); + isTestModeSuccessful = testMode; + return testMode ? "Set Test Mode - OK" : "Set Test Mode - FAILED"; + }catch (Exception ex) + { + log.Error("SetTestMode() - Exception:" + ex.StackTrace); + return "Set Test Mode - Exception"; + } + } + + + + /// + /// Set Optical -> Test mode + /// + /// + /// + /// + private bool SetOptTestMode() + { + try + { + if (genesisHead != null) + { + if (serialDriver == null) + serialDriver = BuildConnection(genesisHead); + + RadioService headService = new RadioService(serialDriver); + bool optTestMode = headService.SetOptTestMode(genesisHead); + if (genesisHead.ConfigStruct != null) + genesisHead.ConfigStruct.OpthoStatusMode = optTestMode ? DiagnosticLedState.State4 : DiagnosticLedState.StatusUnknown; + return optTestMode; + } + } + catch (Exception ex) + { + log.Error("SetOptTestMode() - Exception:" + ex.StackTrace); + } + return false; + } + + /// + /// Set Active mode - string response + /// + /// + /// + /// + public string SetActiveMode(ref bool isTestModeSuccessful) + { + if (genesisHead.DebugLevel == DebugMode.Simulate) + { + isTestModeSuccessful = true; + return "-OK Simulated response-"; + } + + try + { + bool activeMode = SetActiveMode(); + isTestModeSuccessful = activeMode; + return activeMode ? "Set Active Mode - OK" : "Set Active Mode - FAILED"; + } + catch (Exception ex) + { + log.Error("SetActiveMode() - Exception:" + ex.StackTrace); + return "Set Active Mode - Exception"; + } + } + /// + /// Set Optical -> Active mode + /// + /// + /// + /// + private bool SetOptActiveMode(GenesisSmartReader iHead) + { + try + { + if (iHead != null) + { + if (serialDriver == null) + serialDriver = BuildConnection(iHead); + + RadioService headService = new RadioService(serialDriver); + return headService.SetOptActiveMode(iHead); + } + } + catch (Exception ex) + { + log.Error("SetOptActiveMode() - Exception:" + ex.StackTrace); + } + + return false; + } + + /// + /// Set activity mode to active + /// + /// + /// + /// + private bool SetActivityMode_Active() + { + try + { + if (genesisHead != null) + { + if (serialDriver == null) + serialDriver = BuildConnection(genesisHead); + + log.Debug("SetActivityMode_Active called for iHead: " + genesisHead.ToString() + " serialDriver: " + serialDriver); + RadioService headService = new RadioService(serialDriver); + return headService.SetActivityMode_Active(genesisHead); + } + } + catch (Exception ex) + { + log.Error("SetActivityMode_Active() - Exception:" + ex.StackTrace); + } + + return false; + } + + /// + /// Set activity mode to idle + /// + /// + /// + /// + private bool SetActivityMode_Idle() + { + try + { + if (genesisHead != null) + { + if (serialDriver == null) + serialDriver = BuildConnection(genesisHead); + + log.Debug("SetActivityMode_Idle called for iHead: " + genesisHead.ToString() + " serialDriver: " + serialDriver); + + RadioService headService = new RadioService(serialDriver); + return headService.SetActivityMode_Idle(genesisHead); + } + } + catch (Exception ex) + { + log.Error("SetActivityMode_Idle() - Exception:" + ex.StackTrace); + } + + return false; + } + + + public void Dispose() + { + CloseConnection(); + } + + /// + /// Read configuration from iHead + /// DiagnosticLedState is not readable, mus only be set! + /// + /// + /// + /// + public bool ReadConfiguration(DiagnosticLedState ledState ) + { + if (genesisHead.DebugLevel == DebugMode.Simulate) + { + return true; + } + + try + { + + if (genesisHead != null) + { + genesisHead.ConfigStruct = new ConfigStruct(); + + if (serialDriver == null) + serialDriver = BuildConnection(genesisHead); + + RadioService headService = new RadioService(serialDriver); + genesisHead.ConfigStruct.PCBNumberString = headService.ReadRequest_PCB(ref genesisHead); + genesisHead.ConfigStruct.StatusMode = headService.GetActivityStatusMode(genesisHead); + genesisHead.ConfigStruct.Unit = headService.GetUnit(genesisHead); + + if (ledState != DiagnosticLedState.StatusUnknown) // do set + { + genesisHead.ConfigStruct.OpthoStatusMode = headService.SetOptoStatusMode(genesisHead, ledState); + } + else + { + genesisHead.ConfigStruct.OpthoStatusMode = DiagnosticLedState.StatusUnknown; + } + + genesisHead.ConfigStruct.Version = headService.GetVersion(genesisHead); + + return true; + } + + else + { + return false; + } + } + catch (Exception ex) + { + return false; + } + } + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/RadioService.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/RadioService.cs new file mode 100644 index 000000000..8d7c647ec --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/RadioService.cs @@ -0,0 +1,319 @@ +using log4net; +using TBF.Rig.RegisterReaders.GenesisRegReader.implementations; +using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed; +using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol; +using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommons; +using TBF.Rig.TestMethods.iPerlCommunication.communication.Utils; + + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication +{ + public class RadioService + { + private static readonly ILog log = LogManager.GetLogger(typeof(RadioService)); + + static string okResponse = "Command complete, no errors"; + static string errorResponse = "Unable to execute"; + + private ISerialDriver serialDriver; + public RadioService(SerialDriver serialDriver) + { + this.serialDriver = serialDriver; + log.Debug("RadioService created with serialDriver= " + serialDriver + ""); + } + + public RadioService(ISerialDriver serialDriver) + { + this.serialDriver = serialDriver; + log.Debug("RadioService created with serialDriver= " + serialDriver + ""); + } + + public string ReadRequest_PCB(ref GenesisSmartReader iHead) + { + if (!serialDriver.IsOpen()) + { + serialDriver.Open(); + } + + var request = new IperlHatFrameBuilder() + .RequestResponse(true) + .AddCommand(ProtocolCommand.ViewFactoryId) + .BuildBytes(); + + + byte[] rawData = serialDriver.SendAndWait(request, 10000); + if (rawData == null) + return null; + + // parse rawData + var parser = new IperlHatFrameParser(); + IperlHatResponse decoded = parser.Parse(rawData); + if (decoded.IsOk) + { + string asciiPayload = decoded.GetAsciiPayload(); + if (iHead.ConfigStruct != null) // store mechanism + { + iHead.ConfigStruct.PCBNumberString = asciiPayload; + } + return asciiPayload; + } + + return null; + } + + public ProtocolStatuses GetActivityStatusMode(GenesisSmartReader iHead) + { + if (!serialDriver.IsOpen()) + serialDriver.Open(); + + var request = new IperlHatFrameBuilder() + .RequestResponse(true) + .AddCommand(ProtocolCommand.ViewState) + .BuildBytes(); + + byte[] rawData = serialDriver.SendAndWait(request, 10000); + if (rawData == null) + return ProtocolStatuses.Unknown; + + var parser = new IperlHatFrameParser(); + IperlHatResponse decoded = parser.Parse(rawData); + + if (!decoded.IsOk) + return ProtocolStatuses.Unknown; + + ProtocolStatuses statusMode = decoded.GetResponse(out bool isOK); + + if (!isOK) + return ProtocolStatuses.Unknown; // wrong payload + + return statusMode; + } + + public DiagnosticLedState SetOptoStatusMode(GenesisSmartReader iHead, DiagnosticLedState opthoStatusMode) + { + if (!serialDriver.IsOpen()) + serialDriver.Open(); + + var request = new IperlHatFrameBuilder() + .RequestResponse(true) + .AddDeviceCommand(ProtocolDeviceSubCommand.SetDiagnosticLEDState) + .AddPayload(opthoStatusMode) + .BuildBytes(); + + byte[] rawData = serialDriver.SendAndWait(request, 10000); + if (rawData == null) + return DiagnosticLedState.StatusUnknown; + + var parser = new IperlHatFrameParser(); + IperlHatResponse decoded = parser.Parse(rawData); + + log.Debug("SetOptoStatusMode isOK: " + decoded.IsOk); + // if is response ok - it set it correctly + if (!decoded.IsOk) + return DiagnosticLedState.StatusUnknown; + + return opthoStatusMode; + } + + + private static ushort SafeIntToUShort(int value) + { + if (value < ushort.MinValue || value > ushort.MaxValue) + return 0xFD; // your error code + + return (ushort)value; + } + + + public string GetVersion(GenesisSmartReader iHead) + { + if (!serialDriver.IsOpen()) + { + serialDriver.Open(); + } + + byte[] request = new IperlHatFrameBuilder() + .RequestResponse(true) + .AddCommand(ProtocolCommand.Question) + .AddPayload(IperlHatProtocolConstants.Version) + .BuildBytes(); + + + byte[] rawData = serialDriver.SendAndWait(request, 10000); + if (rawData == null) + return ""; + + // parse rawData + var parser = new IperlHatFrameParser(); + IperlHatResponse decoded = parser.Parse(rawData); + log.Debug("GetVersion isOK: " + decoded.IsOk); + if (decoded.IsOk) + { + return decoded.GetAsciiPayload(); + } + + return ""; + } + + + public bool SetActivityMode_Active(GenesisSmartReader iHead) + { + if (!serialDriver.IsOpen()) + { + serialDriver.Open(); + } + + //Set LED to state 4 + byte[] request = new IperlHatFrameBuilder() + .RequestResponse(true) + .AddCommand(ProtocolCommand.SetState) + .AddSubCommand(ProtocolStatuses.Active) // Active + .BuildBytes(); + + byte[] rawData = serialDriver.SendAndWait(request, 5000); + if (rawData == null) + return false; + + + // parse rawData + var parser = new IperlHatFrameParser(); + IperlHatResponse decoded = parser.Parse(rawData); + log.Debug("SetActivityMode_Active isOK: " + decoded.IsOk); + if (decoded.IsOk && iHead.ConfigStruct != null) + { + iHead.ConfigStruct.StatusMode = ProtocolStatuses.Active; + } + return decoded.IsOk; + } + + public bool SetActivityMode_Idle(GenesisSmartReader iHead) + { + if (!serialDriver.IsOpen()) + { + serialDriver.Open(); + } + + //Set Activity State Idle + byte[] request = new IperlHatFrameBuilder() + .RequestResponse(true) + .AddCommand(ProtocolCommand.SetState) + .AddSubCommand(ProtocolStatuses.Idle) + .BuildBytes(); + + byte[] rawData = serialDriver.SendAndWait(request, 5000); + if (rawData == null) + return false; + + + // parse rawData + var parser = new IperlHatFrameParser(); + IperlHatResponse decoded = parser.Parse(rawData); + log.Debug("SetActivityMode_Idle isOK: " + decoded.IsOk); + if (decoded.IsOk && iHead.ConfigStruct != null) + { + iHead.ConfigStruct.StatusMode = ProtocolStatuses.Idle; + } + return decoded.IsOk; + } + + public bool SetOptTestMode(GenesisSmartReader iHead) + { + if (!serialDriver.IsOpen()) + { + serialDriver.Open(); + } + + //Set LED to state 4 + var request = new IperlHatFrameBuilder() + .RequestResponse(true) + .AddDeviceCommand(ProtocolDeviceSubCommand.SetDiagnosticLEDState) + .AddPayload(DiagnosticLedState.State4) + .BuildBytes(); + + byte[] rawData = serialDriver.SendAndWait(request, 5000); + if (rawData == null) + return false; + + + // parse rawData + var parser = new IperlHatFrameParser(); + IperlHatResponse decoded = parser.Parse(rawData); + bool isOk = decoded.IsOk; + log.Debug("SetOptTestMode isOK: " + isOk); + if (decoded.IsOk && iHead.ConfigStruct != null) + { + iHead.ConfigStruct.OpthoStatusMode = DiagnosticLedState.State4; + } + return isOk; + + } + + /// + /// stop data streaming by LED + /// + /// + /// + public bool SetOptActiveMode(GenesisSmartReader iHead) + { + if (!serialDriver.IsOpen()) + { + serialDriver.Open(); + } + + //Set LED to state 1 + var request = new IperlHatFrameBuilder() + .RequestResponse(true) + .AddDeviceCommand(ProtocolDeviceSubCommand.SetDiagnosticLEDState) + .AddPayload(DiagnosticLedState.StateOFF) + .BuildBytes(); + + byte[] rawData = serialDriver.SendAndWait(request, 5000); + if (rawData == null) + return false; + + + // parse rawData + var parser = new IperlHatFrameParser(); + IperlHatResponse decoded = parser.Parse(rawData); + log.Debug("SetOptActiveMode isOK: " + decoded.IsOk); + if (decoded.IsOk && iHead.ConfigStruct != null) + { + iHead.ConfigStruct.OpthoStatusMode = DiagnosticLedState.StateOFF; + } + return decoded.IsOk; + } + + public string GetUnit(GenesisSmartReader iperlHead) + { + if (!serialDriver.IsOpen()) + { + serialDriver.Open(); + } + + var request = new IperlHatFrameBuilder() + .RequestResponse(true) + .AddCommand(ProtocolCommand.ViewFactoryId) + .BuildBytes(); + + + byte[] rawData = serialDriver.SendAndWait(request, 10000); + if (rawData == null) + return null; + + // parse rawData + var parser = new IperlHatFrameParser(); + IperlHatResponse decoded = parser.Parse(rawData); + if (decoded.IsOk) + { + string asciiPayload = decoded.GetAsciiPayload(); + if (iperlHead.ConfigStruct != null) // store mechanism + { + iperlHead.ConfigStruct.Unit = asciiPayload; + } + return asciiPayload; + } + + return null; + } + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Utils/ISerialDriver.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Utils/ISerialDriver.cs new file mode 100644 index 000000000..3e9b8362a --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Utils/ISerialDriver.cs @@ -0,0 +1,9 @@ +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils +{ + public interface ISerialDriver + { + bool IsOpen(); + bool Open(); + byte[] SendAndWait(byte[] request, int timeout); + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Utils/SerialDriver.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Utils/SerialDriver.cs new file mode 100644 index 000000000..a13bce574 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Utils/SerialDriver.cs @@ -0,0 +1,308 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO.Ports; +using System.Threading; +using FluentNHibernate.Conventions; +using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.hexLogger; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils +{ + public class SerialDriver : IDisposable, TestMethods.iPerlCommunication.communication.Utils.ISerialDriver + { + readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(SerialDriver)); + public string ErrorMessage { get; private set; } + private List SerialPortReadBuffer = new List(); + + private SerialPort _serialPort; + private readonly List _binMessages = new List(); + private bool _isReading; + + // Stored configuration (used by Builder) + private readonly string _portName; + private readonly int _baudRate; + private readonly int _dataBits; + private readonly Parity _parity; + private readonly StopBits _stopBits; + private readonly int _readTimeout; + private readonly int _writeTimeout; + + private readonly ManualResetEvent _responseReceived = new ManualResetEvent(false); + + #region Constructors + + // Default constructor (legacy support) + public SerialDriver() + { + _serialPort = new SerialPort(); + } + + // Builder constructor + internal SerialDriver( + string portName, + int baudRate, + int dataBits, + Parity parity, + StopBits stopBits, + int readTimeout, + int writeTimeout) + { + _portName = portName; + _baudRate = baudRate; + _dataBits = dataBits; + _parity = parity; + _stopBits = stopBits; + _readTimeout = readTimeout; + _writeTimeout = writeTimeout; + } + + #endregion + + #region Open / Close + + // Builder-based open + public bool Open() + { + return OpenConnection( + _portName, + _baudRate, + _dataBits, + _parity, + _stopBits, + _readTimeout, + _writeTimeout + ); + } + + // Legacy API (unchanged) + public bool OpenConnection( + string comPort, + int baudrate, + int dataBits, + Parity parity, + StopBits stopbits, + int readTimeout = 1000, + int writeTimeout = 1000) + { + lock (this) + { + CloseConnection(); + + try + { + ErrorMessage = string.Empty; + + _serialPort = new SerialPort(comPort, baudrate, parity, dataBits, stopbits) + { + ReadTimeout = readTimeout, + WriteTimeout = writeTimeout + }; + + _serialPort.DataReceived += DataReceivedHandler; + _serialPort.Open(); + } + catch (Exception ex) + { + ErrorMessage = $"COM error: Open failed {comPort}. {ex.Message}"; + return false; + } + + if (!_serialPort.IsOpen) + { + ErrorMessage = $"COM error: Can't open {comPort}."; + return false; + } + + log.Debug("SerialDriver opened successfully for port: " + comPort); + } + return true; + } + + public void CloseConnection() + { + if (_serialPort != null) + { + _serialPort.DataReceived -= DataReceivedHandler; + if (_serialPort.IsOpen) + _serialPort.Close(); + + _serialPort.Dispose(); + _serialPort = null; + } + } + + public bool IsOpen() => _serialPort?.IsOpen == true; + + #endregion + + #region Send / Receive + + public bool SendMessage(byte[] sendDataBytes, int length, int readTimeout = 1000, int writeTimeout = 1000) + { + if (!IsOpen()) return false; + if (sendDataBytes.Length == 0) return true; + + try + { + PrepareReading(); + + _serialPort.WriteTimeout = writeTimeout; + _serialPort.ReadTimeout = readTimeout; + _serialPort.Write(sendDataBytes, 0, length); + + _isReading = true; + + var stopwatch = Stopwatch.StartNew(); + while (_isReading) + { + if (stopwatch.ElapsedMilliseconds > readTimeout) + { + ErrorMessage = "COM error: Receive timeout"; + return false; + } + } + } + catch (Exception ex) + { + ErrorMessage = $"COM error: Transmit failed {_serialPort.PortName}. {ex.Message}"; + return false; + } + + return true; + } + + private void PrepareReading() + { + _serialPort.DiscardInBuffer(); + _binMessages.Clear(); + _responseReceived.Reset(); + SerialPortReadBuffer.Clear(); + _isReading = true; + } + + public byte[] GetRawData() + { + return _binMessages.ToArray(); + } + + private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e) + { + lock (this) + { + if (_serialPort == null || !_serialPort.IsOpen) return; + + try + { + //Thread.Sleep(5); + + if (!SerialPortReadBuffer.IsEmpty()) + { + SerialPortReadBuffer.Clear(); + } + + int iWordCounter = 0; + bool isStart = false; + bool isQuestion = false; + int iLength = 0; + while (true)//_serialPort.BytesToRead > 0 + { + byte readByte = (byte)_serialPort.ReadByte(); + + //I have START + if (readByte == TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.IperlHatProtocolConstants.Start) + { + iWordCounter++; + isStart = true; + } + // I have QUESTION + if (readByte == TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.IperlHatProtocolConstants.Question) + { + iWordCounter++; + isQuestion = true; + } + //I count length from start + if (iWordCounter > 0) + iWordCounter++; + + if (iWordCounter > 0) + { + //Store byte to data + SerialPortReadBuffer.Add(readByte); + } + // we have length + if (iLength == 0 && isStart && SerialPortReadBuffer.Count > 2 ) + { + iLength = (int)SerialPortReadBuffer[2]; + } + + //If we have enough bytes + if (isStart && iLength > 0 + && (SerialPortReadBuffer.Count >= iLength || + readByte == TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.IperlHatProtocolConstants.End + ) + ) + { + break; + } + //if we read END + if (isQuestion && readByte == TestMethods.iPerlCommunication.communication.C4.IperlHatProtocol.IperlHatProtocolConstants.End) + { + break; + } + } + + if (SerialPortReadBuffer.Count > 0) + { + _binMessages.AddRange(SerialPortReadBuffer.ToArray()); + _responseReceived.Set(); + } + } + catch (TimeoutException te) + { + // Ignore shutdown race conditions + } + finally + { + _isReading = false; + } + } + } + + public byte[] SendAndWait(byte[] data, int timeoutMs) + { + if (!IsOpen()) + throw new InvalidOperationException("Serial port not open"); + + log.Debug("SendAndWait() - TX: " + HexFormatter.ToHex(data)); + PrepareReading(); + _serialPort.Write(data, 0, data.Length); + + if (!_responseReceived.WaitOne(timeoutMs)) + { + log.Error("SendAndWait() - Response timeout! Details: " + + " SerialPortReadBuffer: " + HexFormatter.ToHex(SerialPortReadBuffer.ToArray()) + + " _binMessages" + HexFormatter.ToHex(_binMessages.ToArray()) + + "_responseReceived: " + _responseReceived.WaitOne(0) + ); + + ErrorMessage = "COM error: response timeout"; + return null; + } + + return GetRawData(); + } + + + #endregion + + public void Dispose() + { + CloseConnection(); + } + + public override string ToString() + { + return "SerialDriver: " + _serialPort.PortName + " (opened status:" + _serialPort.IsOpen +")"; + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Utils/SerialDriverBuilder.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Utils/SerialDriverBuilder.cs new file mode 100644 index 000000000..36651723b --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/communication/Utils/SerialDriverBuilder.cs @@ -0,0 +1,82 @@ +using System; +using System.IO.Ports; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Utils +{ + public class SerialDriverBuilder + { + private string _portName; + private int _baudRate = 9600; + private int _dataBits = 8; + private Parity _parity = Parity.None; + private StopBits _stopBits = StopBits.One; + private int _readTimeout = 1000; + private int _writeTimeout = 1000; + + public SerialDriverBuilder WithPort(string portName) + { + _portName = portName; + return this; + } + + public SerialDriverBuilder WithBaudRate(int baudRate) + { + _baudRate = baudRate; + return this; + } + + public SerialDriverBuilder WithDataBits(int dataBits) + { + _dataBits = dataBits; + return this; + } + + public SerialDriverBuilder WithParity(Parity parity) + { + _parity = parity; + return this; + } + + public SerialDriverBuilder WithStopBits(StopBits stopBits) + { + _stopBits = stopBits; + return this; + } + + public SerialDriverBuilder WithTimeouts(int readTimeout, int writeTimeout) + { + _readTimeout = readTimeout; + _writeTimeout = writeTimeout; + return this; + } + + /// + /// Build driver WITHOUT opening connection + /// + public TestMethods.iPerlCommunication.communication.Utils.SerialDriver Build() + { + return new TestMethods.iPerlCommunication.communication.Utils.SerialDriver( + _portName, + _baudRate, + _dataBits, + _parity, + _stopBits, + _readTimeout, + _writeTimeout + ); + } + + /// + /// Build driver AND open connection + /// + public TestMethods.iPerlCommunication.communication.Utils.SerialDriver BuildAndConnect() + { + var driver = Build(); + if (!driver.Open()) + { + throw new InvalidOperationException(driver.ErrorMessage); + } + return driver; + } + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/CalibrationStruct.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/CalibrationStruct.cs new file mode 100644 index 000000000..b639f2a98 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/CalibrationStruct.cs @@ -0,0 +1,243 @@ +/// +/// Copyright (c) 2015-2020 Sensus Metering Systems +/// + +using System; +using System.IO; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations +{ + public class CalibrationStruct + { + public const int Length = 35; + + public Byte Version; + public MeterType MeterType; + public UInt16 Calibration; + public VolumeUnits VolumeUnits; + public FlowArrow FlowArrow; + public UInt16 FWVersion; + public UInt16[] TargetField; + public UInt16 RecipMeanCurrent; + public UInt16 ThresholdVolume; + public UInt16 ThresholdTime; + public UInt16 FlowActivationThr; + public UInt16 VolumeArrowThr; + public UInt32 CalibrationTime; + public ulong SerialNumber; + public MeterSealed MeterSealed; + public byte CheckSum; + + + public CalibrationStruct() + { + TargetField = new UInt16[3]; + } + + public byte[] ToByteArray() + { + byte[] result = new byte[Length]; + + result[0] = Version; + result[1] = (byte)MeterType; + + result[2] = (byte)(Calibration & 0x00FF); + result[3] = (byte)((Calibration >> 8) & 0x00FF); + + result[4] = (byte)VolumeUnits; + + result[5] = (byte)FlowArrow; + + result[6] = (byte)(FWVersion & 0x00FF); + result[7] = (byte)((FWVersion >> 8) & 0x00FF); + + result[8] = (byte)( TargetField[0] & 0x00FF); + result[9] = (byte)((TargetField[0] >> 8) & 0x00FF); + result[10] = (byte)( TargetField[1] & 0x00FF); + result[11] = (byte)((TargetField[1] >> 8) & 0x00FF); + result[12] = (byte)( TargetField[2] & 0x00FF); + result[13] = (byte)((TargetField[2] >> 8) & 0x00FF); + + result[14] = (byte)(RecipMeanCurrent & 0x00FF); + result[15] = (byte)((RecipMeanCurrent >> 8) & 0x00FF); + + result[16] = (byte)(ThresholdVolume & 0x00FF); + result[17] = (byte)((ThresholdVolume >> 8) & 0x00FF); + + result[18] = (byte)(ThresholdTime & 0x00FF); + result[19] = (byte)((ThresholdTime >> 8) & 0x00FF); + + result[20] = (byte)(FlowActivationThr & 0x00FF); + result[21] = (byte)((FlowActivationThr >> 8) & 0x00FF); + + result[22] = (byte)(VolumeArrowThr & 0x00FF); + result[23] = (byte)((VolumeArrowThr >> 8) & 0x00FF); + + result[24] = (byte)(CalibrationTime & 0x000000FF); + result[25] = (byte)((CalibrationTime >> 8) & 0x000000FF); + result[26] = (byte)((CalibrationTime >> 16) & 0x000000FF); + result[27] = (byte)((CalibrationTime >> 24) & 0x000000FF); + + result[28] = (byte)(SerialNumber & 0x00000000000000FF); + result[29] = (byte)((SerialNumber >> 8) & 0x00000000000000FF); + result[30] = (byte)((SerialNumber >> 16) & 0x00000000000000FF); + result[31] = (byte)((SerialNumber >> 24) & 0x00000000000000FF); + result[32] = (byte)((SerialNumber >> 32) & 0x00000000000000FF); + + result[33] = (byte)MeterSealed; + result[34] = CheckSum; + + return result; + } + + /// + /// Create a calibration structure from a complete byte array + /// + /// A complete byte array data + /// CalibrationStruct or null when byte array was not complete + public static CalibrationStruct FromByteArray(byte[] data) + { + if (data.Length != Length) return null; + + CalibrationStruct result = new CalibrationStruct(); + + result.Version = data[0]; + result.MeterType = (MeterType)data[1]; + result.Calibration = (UInt16)(data[2] + 256 * data[3]); + result.VolumeUnits = (VolumeUnits)data[4]; + result.FlowArrow = (FlowArrow)data[5]; + result.FWVersion = (UInt16)(data[6] + 256 * data[7]); + result.TargetField[0] = (UInt16)(data[8] + 256 * data[9]); + result.TargetField[1] = (UInt16)(data[10] + 256 * data[11]); + result.TargetField[2] = (UInt16)(data[12] + 256 * data[13]); + result.RecipMeanCurrent = (UInt16)(data[14] + 256 * data[15]); + result.ThresholdVolume = (UInt16)(data[16] + 256 * data[17]); + result.ThresholdTime = (UInt16)(data[18] + 256 * data[19]); + result.FlowActivationThr = (UInt16)(data[20] + 256 * data[21]); + result.VolumeArrowThr = (UInt16)(data[22] + 256 * data[23]); + result.CalibrationTime = (((UInt32)data[27] * 256 + data[26]) * 256 + data[25]) * 256 + data[24]; + result.SerialNumber = ((((UInt64)data[32] * 256 + data[31]) * 256 + data[30]) * 256 + data[29]) * 256 + data[28]; + result.MeterSealed = (MeterSealed)data[33]; + result.CheckSum = data[34]; + + return result; + } + + /// + /// Update the calibration structure from an incomplete byte array + /// + /// Byte array data + /// Offset of byte array data in CalibrationStruct + /// true when successful, false when data are not appropriate + public bool Update(byte[] data, int offset) + { + if ((data.Length == 2) && (offset == 2)) + { + /// Data containing iPerl calibration factor + Calibration = (UInt16)(data[0] + 256 * data[1]); + return true; + } + else if ((data.Length == Length) && (offset == 0)) + { + /// Data containing a complete CalibrationStruct + Version = data[0]; + MeterType = (MeterType)data[1]; + Calibration = (UInt16)(data[2] + 256 * data[3]); + VolumeUnits = (VolumeUnits)data[4]; + FlowArrow = (FlowArrow)data[5]; + FWVersion = (UInt16)(data[6] + 256 * data[7]); + TargetField[0] = (UInt16)(data[8] + 256 * data[9]); + TargetField[1] = (UInt16)(data[10] + 256 * data[11]); + TargetField[2] = (UInt16)(data[12] + 256 * data[13]); + RecipMeanCurrent = (UInt16)(data[14] + 256 * data[15]); + ThresholdVolume = (UInt16)(data[16] + 256 * data[17]); + ThresholdTime = (UInt16)(data[18] + 256 * data[19]); + FlowActivationThr = (UInt16)(data[20] + 256 * data[21]); + VolumeArrowThr = (UInt16)(data[22] + 256 * data[23]); + CalibrationTime = (((UInt32)data[27] * 256 + data[26]) * 256 + data[25]) * 256 + data[24]; + SerialNumber = ((((UInt64)data[32] * 256 + data[31]) * 256 + data[30]) * 256 + data[29]) * 256 + data[28]; + MeterSealed = (MeterSealed)data[33]; + CheckSum = data[34]; + return true; + } + else + return false; + } + + public string FWVersionStr() + { + int d1 = (FWVersion >> 8) & 0x000F; + int d2 = (FWVersion >> 12) & 0x000F; + int d3 = (FWVersion >> 4) & 0x000F; + int d4 = FWVersion & 0x000F; + return string.Format("{0}.{1}{2}{3}", d1, d2, d3, d4); + } + + public override string ToString() + { + return string.Format("Calibration: V{0} Type={1} Cal={2} Units={3} FlowArrow.{4} FW={5} Hi={6} Norm={7} Low={8} RMC={9} ThrVol={10} ThrTime={11} FlActThr={12} VolArrThr={13} CalTm={14} SN={15} MeterSealed={16} Chksum={17}", + Version, + MeterType, + Calibration, + VolumeUnits, + FlowArrow, + FWVersion, + TargetField[0], + TargetField[1], + TargetField[2], + RecipMeanCurrent, + ThresholdVolume, + ThresholdTime, + FlowActivationThr, + VolumeArrowThr, + CalibrationTime, + SerialNumber, + MeterSealed, + CheckSum.ToString("X2")); + } + + public virtual void WriteBinary(BinaryWriter writer) + { + writer.Write(Version); + writer.Write((byte)MeterType); + writer.Write(Calibration); + writer.Write((byte)VolumeUnits); + writer.Write((byte)FlowArrow); + writer.Write(FWVersion); + writer.Write(TargetField[0]); + writer.Write(TargetField[1]); + writer.Write(TargetField[2]); + writer.Write(RecipMeanCurrent); + writer.Write(ThresholdVolume); + writer.Write(ThresholdTime); + writer.Write(FlowActivationThr); + writer.Write(VolumeArrowThr); + writer.Write(CalibrationTime); + writer.Write(SerialNumber); + writer.Write((byte)MeterSealed); + writer.Write(CheckSum); + } + + public virtual void ReadBinary(BinaryReader reader) + { + Version = reader.ReadByte(); + MeterType = (MeterType)reader.ReadByte(); + Calibration = reader.ReadUInt16(); + VolumeUnits = (VolumeUnits)reader.ReadByte(); + FlowArrow = (FlowArrow)reader.ReadByte(); + FWVersion = reader.ReadUInt16(); + TargetField[0] = reader.ReadUInt16(); + TargetField[1] = reader.ReadUInt16(); + TargetField[2] = reader.ReadUInt16(); + RecipMeanCurrent = reader.ReadUInt16(); + ThresholdVolume = reader.ReadUInt16(); + ThresholdTime = reader.ReadUInt16(); + FlowActivationThr = reader.ReadUInt16(); + VolumeArrowThr = reader.ReadUInt16(); + CalibrationTime = reader.ReadUInt32(); + SerialNumber = reader.ReadUInt64(); + MeterSealed = (MeterSealed)reader.ReadByte(); + CheckSum = reader.ReadByte(); + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/CalibrationStructV4.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/CalibrationStructV4.cs new file mode 100644 index 000000000..d7a5b90c6 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/CalibrationStructV4.cs @@ -0,0 +1,259 @@ +/// +/// Copyright (c) 2018-2020 Sensus Slovensko a.s. +/// + +using System; +using System.IO; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations +{ + public class CalibrationStructV4 + { + public const int Length = 37; + + public Byte Version; + public TestMethods.iPerlCommunication.iPerlHead.MeterType MeterType; + public UInt16 Calibration; + public TestMethods.iPerlCommunication.iPerlHead.VolumeUnits VolumeUnits; + public TestMethods.iPerlCommunication.iPerlHead.FlowArrow FlowArrow; + public UInt16 FWVersion; + public UInt16[] TargetField; + public UInt16 RecipMeanCurrent; + public UInt16 ThresholdVolume; + public UInt16 ThresholdTime; + public UInt16 FlowActivationThr; + public UInt16 VolumeArrowThr; + public UInt32 CalibrationTime; + public ulong SerialNumber; + public TestMethods.iPerlCommunication.iPerlHead.MeterSealed MeterSealed; + public UInt16 CalibrationLNA; + public byte CheckSum; + + + public CalibrationStructV4() + { + TargetField = new UInt16[3]; + } + + public byte[] ToByteArray() + { + byte[] result = new byte[Length]; + + result[0] = Version; + result[1] = (byte)MeterType; + + result[2] = (byte)(Calibration & 0x00FF); + result[3] = (byte)((Calibration >> 8) & 0x00FF); + + result[4] = (byte)VolumeUnits; + + result[5] = (byte)FlowArrow; + + result[6] = (byte)(FWVersion & 0x00FF); + result[7] = (byte)((FWVersion >> 8) & 0x00FF); + + result[8] = (byte)( TargetField[0] & 0x00FF); + result[9] = (byte)((TargetField[0] >> 8) & 0x00FF); + result[10] = (byte)( TargetField[1] & 0x00FF); + result[11] = (byte)((TargetField[1] >> 8) & 0x00FF); + result[12] = (byte)( TargetField[2] & 0x00FF); + result[13] = (byte)((TargetField[2] >> 8) & 0x00FF); + + result[14] = (byte)(RecipMeanCurrent & 0x00FF); + result[15] = (byte)((RecipMeanCurrent >> 8) & 0x00FF); + + result[16] = (byte)(ThresholdVolume & 0x00FF); + result[17] = (byte)((ThresholdVolume >> 8) & 0x00FF); + + result[18] = (byte)(ThresholdTime & 0x00FF); + result[19] = (byte)((ThresholdTime >> 8) & 0x00FF); + + result[20] = (byte)(FlowActivationThr & 0x00FF); + result[21] = (byte)((FlowActivationThr >> 8) & 0x00FF); + + result[22] = (byte)(VolumeArrowThr & 0x00FF); + result[23] = (byte)((VolumeArrowThr >> 8) & 0x00FF); + + result[24] = (byte)(CalibrationTime & 0x000000FF); + result[25] = (byte)((CalibrationTime >> 8) & 0x000000FF); + result[26] = (byte)((CalibrationTime >> 16) & 0x000000FF); + result[27] = (byte)((CalibrationTime >> 24) & 0x000000FF); + + result[28] = (byte)(SerialNumber & 0x00000000000000FF); + result[29] = (byte)((SerialNumber >> 8) & 0x00000000000000FF); + result[30] = (byte)((SerialNumber >> 16) & 0x00000000000000FF); + result[31] = (byte)((SerialNumber >> 24) & 0x00000000000000FF); + result[32] = (byte)((SerialNumber >> 32) & 0x00000000000000FF); + + result[33] = (byte)MeterSealed; + + result[34] = (byte)(CalibrationLNA & 0x00FF); + result[35] = (byte)((CalibrationLNA >> 8) & 0x00FF); + + result[36] = CheckSum; + + return result; + } + + /// + /// Create a calibration structure from a complete byte array + /// + /// A complete byte array data + /// CalibrationStructV4 or null when byte array was not complete + public static CalibrationStructV4 FromByteArray(byte[] data) + { + if (data.Length != Length) return null; + + CalibrationStructV4 result = new CalibrationStructV4(); + + result.Version = data[0]; + result.MeterType = (TestMethods.iPerlCommunication.iPerlHead.MeterType)data[1]; + result.Calibration = (UInt16)(data[2] + 256 * data[3]); + result.VolumeUnits = (TestMethods.iPerlCommunication.iPerlHead.VolumeUnits)data[4]; + result.FlowArrow = (TestMethods.iPerlCommunication.iPerlHead.FlowArrow)data[5]; + result.FWVersion = (UInt16)(data[6] + 256 * data[7]); + result.TargetField[0] = (UInt16)(data[8] + 256 * data[9]); + result.TargetField[1] = (UInt16)(data[10] + 256 * data[11]); + result.TargetField[2] = (UInt16)(data[12] + 256 * data[13]); + result.RecipMeanCurrent = (UInt16)(data[14] + 256 * data[15]); + result.ThresholdVolume = (UInt16)(data[16] + 256 * data[17]); + result.ThresholdTime = (UInt16)(data[18] + 256 * data[19]); + result.FlowActivationThr = (UInt16)(data[20] + 256 * data[21]); + result.VolumeArrowThr = (UInt16)(data[22] + 256 * data[23]); + result.CalibrationTime = (((UInt32)data[27] * 256 + data[26]) * 256 + data[25]) * 256 + data[24]; + result.SerialNumber = ((((UInt64)data[32] * 256 + data[31]) * 256 + data[30]) * 256 + data[29]) * 256 + data[28]; + result.MeterSealed = (TestMethods.iPerlCommunication.iPerlHead.MeterSealed)data[33]; + result.CalibrationLNA = (UInt16)(data[34] + 256 * data[35]); + result.CheckSum = data[36]; + + return result; + } + + /// + /// Update the calibration structure from an incomplete byte array + /// + /// Byte array data + /// Offset of byte array data in CalibrationStructV2 + /// true when successful, false when data are not appropriate + public bool Update(byte[] data, int offset) + { + if ((data.Length == 2) && (offset == 2)) + { + /// Data containing iPerl calibration factor + Calibration = (UInt16)(data[0] + 256 * data[1]); + return true; + } + else if ((data.Length == 2) && (offset == 34)) + { + /// Data containing iPerl calibration factor + CalibrationLNA = (UInt16)(data[0] + 256 * data[1]); + return true; + } + else if ((data.Length == Length) && (offset == 0)) + { + /// Data containing a complete CalibrationStruct + Version = data[0]; + MeterType = (TestMethods.iPerlCommunication.iPerlHead.MeterType)data[1]; + Calibration = (UInt16)(data[2] + 256 * data[3]); + VolumeUnits = (TestMethods.iPerlCommunication.iPerlHead.VolumeUnits)data[4]; + FlowArrow = (TestMethods.iPerlCommunication.iPerlHead.FlowArrow)data[5]; + FWVersion = (UInt16)(data[6] + 256 * data[7]); + TargetField[0] = (UInt16)(data[8] + 256 * data[9]); + TargetField[1] = (UInt16)(data[10] + 256 * data[11]); + TargetField[2] = (UInt16)(data[12] + 256 * data[13]); + RecipMeanCurrent = (UInt16)(data[14] + 256 * data[15]); + ThresholdVolume = (UInt16)(data[16] + 256 * data[17]); + ThresholdTime = (UInt16)(data[18] + 256 * data[19]); + FlowActivationThr = (UInt16)(data[20] + 256 * data[21]); + VolumeArrowThr = (UInt16)(data[22] + 256 * data[23]); + CalibrationTime = (((UInt32)data[27] * 256 + data[26]) * 256 + data[25]) * 256 + data[24]; + SerialNumber = ((((UInt64)data[32] * 256 + data[31]) * 256 + data[30]) * 256 + data[29]) * 256 + data[28]; + MeterSealed = (TestMethods.iPerlCommunication.iPerlHead.MeterSealed)data[33]; + CalibrationLNA = (UInt16)(data[34] + 256 * data[35]); + CheckSum = data[36]; + return true; + } + else + return false; + } + + public string FWVersionStr() + { + int d1 = (FWVersion >> 8) & 0x000F; + int d2 = (FWVersion >> 12) & 0x000F; + int d3 = (FWVersion >> 4) & 0x000F; + int d4 = FWVersion & 0x000F; + return string.Format("{0}.{1}{2}{3}", d1, d2, d3, d4); + } + + public override string ToString() + { + return string.Format("Calibration: V{0} Type={1} Cal={2} Units={3} FlowArrow.{4} FW={5} Hi={6} Norm={7} Low={8} RMC={9} ThrVol={10} ThrTime={11} FlActThr={12} VolArrThr={13} CalTm={14} SN={15} MeterSealed={16} CalLNA={17} Chksum={18}", + Version, + MeterType, + Calibration, + VolumeUnits, + FlowArrow, + FWVersion, + TargetField[0], + TargetField[1], + TargetField[2], + RecipMeanCurrent, + ThresholdVolume, + ThresholdTime, + FlowActivationThr, + VolumeArrowThr, + CalibrationTime, + SerialNumber, + MeterSealed, + CalibrationLNA, + CheckSum.ToString("X2")); + } + + public virtual void WriteBinary(BinaryWriter writer) + { + writer.Write(Version); + writer.Write((byte)MeterType); + writer.Write(Calibration); + writer.Write((byte)VolumeUnits); + writer.Write((byte)FlowArrow); + writer.Write(FWVersion); + writer.Write(TargetField[0]); + writer.Write(TargetField[1]); + writer.Write(TargetField[2]); + writer.Write(RecipMeanCurrent); + writer.Write(ThresholdVolume); + writer.Write(ThresholdTime); + writer.Write(FlowActivationThr); + writer.Write(VolumeArrowThr); + writer.Write(CalibrationTime); + writer.Write(SerialNumber); + writer.Write((byte)MeterSealed); + writer.Write(CalibrationLNA); + writer.Write(CheckSum); + } + + public virtual void ReadBinary(BinaryReader reader) + { + Version = reader.ReadByte(); + MeterType = (TestMethods.iPerlCommunication.iPerlHead.MeterType)reader.ReadByte(); + Calibration = reader.ReadUInt16(); + VolumeUnits = (TestMethods.iPerlCommunication.iPerlHead.VolumeUnits)reader.ReadByte(); + FlowArrow = (TestMethods.iPerlCommunication.iPerlHead.FlowArrow)reader.ReadByte(); + FWVersion = reader.ReadUInt16(); + TargetField[0] = reader.ReadUInt16(); + TargetField[1] = reader.ReadUInt16(); + TargetField[2] = reader.ReadUInt16(); + RecipMeanCurrent = reader.ReadUInt16(); + ThresholdVolume = reader.ReadUInt16(); + ThresholdTime = reader.ReadUInt16(); + FlowActivationThr = reader.ReadUInt16(); + VolumeArrowThr = reader.ReadUInt16(); + CalibrationTime = reader.ReadUInt32(); + SerialNumber = reader.ReadUInt64(); + MeterSealed = (TestMethods.iPerlCommunication.iPerlHead.MeterSealed)reader.ReadByte(); + CalibrationLNA = reader.ReadUInt16(); + CheckSum = reader.ReadByte(); + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/ConfigStruct.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/ConfigStruct.cs new file mode 100644 index 000000000..26f33f733 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/ConfigStruct.cs @@ -0,0 +1,206 @@ +/// +/// Copyright (c) 2015-2020 Sensus Metering Systems +/// + +using System; +using System.IO; +using System.Text; +using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed; +using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommons; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations +{ + public class ConfigStruct + { + //public const int Length = 32; // dynamic + + //public Byte Version; // 0: 1 byte + public string PCBNumberString; // dynamic + public ProtocolStatuses StatusMode; // byte + public DiagnosticLedState OpthoStatusMode;// byte + public string Unit; //dynamic + public string Version; // dynamic + + + public ConfigStruct() + { + } + + //Optho test status mode + public DiagnosticLedState TestModeConfig + { + get { return OpthoStatusMode; } + } + //Activity test status mode + public ProtocolStatuses MeterState + { + get + { + return StatusMode; + } + } + + + /// + /// Returns PCB number string (12 characters, 12 decimal digits) + /// + /// PCB number STRING + public string GetPcbNrString(){ + return PCBNumberString; + } + + + public string GetStatusModeString() + { + return string.Format( + "Config: StatusMode={0}", + StatusMode + ); + } + + public string GetActiveModeString() + { + return string.Format( + "Config: StatusMode={0}, OpthoStatusMode={1}", + StatusMode, + OpthoStatusMode + ); + } + + public override string ToString() + { + return string.Format( + "Config: PCB#={0} StatusMode={1} Unit={2} V{3}", + + GetPcbNrString(), + StatusMode, + Unit, + Version + ); + } + + public string ToString(int sel) + { + return string.Format( + "Config: PCB#={0} StatusMode={1} Unit={2} V{3}", + + GetPcbNrString(), + StatusMode, + Unit, + Version + ); + } + + public virtual void WriteBinary(BinaryWriter writer) + { + if (writer == null) + throw new ArgumentNullException(nameof(writer)); + + // ---- Marker ---- + writer.Write((byte)0x11); + + // ---- Version ---- + if (!string.IsNullOrEmpty(Version)) + { + // Convert string to bytes (UTF8 is standard) + byte[] versionBytes = Encoding.UTF8.GetBytes(Version); + // 1) write length + writer.Write(versionBytes.Length); + // 2) write string bytes + writer.Write(versionBytes); + //writer.Write(Version); + } + else + { + writer.Write(0);//Length + } + + // ---- StatusMode ---- + writer.Write((byte)StatusMode); + + // ---- PCB Number ---- + if (!string.IsNullOrEmpty(PCBNumberString)) + { + byte[] PCBNumberStringBytes = Encoding.UTF8.GetBytes(PCBNumberString); + // 1) write length + writer.Write(PCBNumberStringBytes.Length); + // 2) write string bytes + writer.Write(PCBNumberStringBytes); + } + else + { + writer.Write(0); //Length + } + + // ---- Unit ---- + if (!string.IsNullOrEmpty(Unit)) + { + // Convert string to bytes (UTF8 is standard) + byte[] unitBytes = Encoding.UTF8.GetBytes(Unit); + // 1) write length + writer.Write(unitBytes.Length); + // 2) write string bytes + writer.Write(unitBytes); + //writer.Write(Version); + } + else + { + writer.Write(0); //Length + } + + } + + public virtual void ReadBinary(BinaryReader reader) + { + if (reader == null) + throw new ArgumentNullException(nameof(reader)); + + // ---- Marker ---- + byte marker = reader.ReadByte(); + if (marker != 0x11) + throw new InvalidDataException($"Invalid config marker: 0x{marker:X2}"); + + // ---- Version ---- + int versionLength = reader.ReadInt32(); + if (versionLength < 0) + throw new InvalidDataException("Invalid Version length."); + + byte[] versionBytes = reader.ReadBytes(versionLength); + if (versionBytes.Length != versionLength) + throw new EndOfStreamException("Unexpected end of stream while reading Version."); + + Version = versionLength > 0 + ? Encoding.UTF8.GetString(versionBytes) + : string.Empty; + + // ---- StatusMode ---- + StatusMode = (ProtocolStatuses)reader.ReadByte(); + + // ---- PCB Number ---- + int pcbLength = reader.ReadInt32(); + if (pcbLength < 0) + throw new InvalidDataException("Invalid PCB number length."); + + byte[] pcbBytes = reader.ReadBytes(pcbLength); + if (pcbBytes.Length != pcbLength) + throw new EndOfStreamException("Unexpected end of stream while reading PCB number."); + + PCBNumberString = pcbLength > 0 + ? Encoding.UTF8.GetString(pcbBytes) + : string.Empty; + + // ---- Unit ---- + int unitLength = reader.ReadInt32(); + if (unitLength < 0) + throw new InvalidDataException("Invalid Unit length."); + + byte[] unitBytes = reader.ReadBytes(unitLength); + if (unitBytes.Length != unitLength) + throw new EndOfStreamException("Unexpected end of stream while reading Unit."); + + Unit = unitLength > 0 + ? Encoding.UTF8.GetString(unitBytes) + : string.Empty; + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/Enums.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/Enums.cs new file mode 100644 index 000000000..5c651f549 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/Enums.cs @@ -0,0 +1,120 @@ +/// +/// Copyright (c) 2015-2017 Sensus Metering Systems +/// + +using System.ComponentModel; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations +{ + public enum MessageID + { + Calibration = 0x00, /// Access to stCalibration + Configuration = 0x01, /// Access to stConfig + Status = 0x02, /// Access to stStaus, read only + Power = 0x03, /// Access to stPower, containing power info from both processors + LCD = 0x04, /// Access to stLCD + EventData = 0x05, /// NOT USED + IntervalData = 0x06, /// NOT USED + Diagnostics = 0x07, /// Access tostMetroDiagArray, read onl + MetrologyMemory = 0x08, /// Memory block access, read only + Error_LongAck = 0x09, /// Error message + Command = 0x0A, /// Command to execute, with no arguments + Parameterizing = 0x0B, /// Parameterizing message is used in MCI-SPI interface only + Error_ShortAck = 0x0C, /// Short acknowledge message is used in MCI-SPI interface only + ChanelAlive = 0x0D, /// Channel alive message is used in MCI-SPI interface only + RadioPassthrough = 0x0E, /// RFID <-> Metrology <-> Radio passthrough message + ASICRegisterReadTest = 0x0F, /// ASIC Register read test + IMIDebugMessagesAccess = 0x10, /// IMI debug messages read test + ProductionChecksumsRead = 0x11, /// Production checksum read: Calibration (1 byte), Configuration (2 bytes), spare (4 bytes) + HardwareParametersTest = 0x12, /// Hardware Parameters Testing: User configurable fixed field drive time (1 byte) + /// + /// Notes: + /// 1. Short Ack message contains 1-byte error code and all the fields (Offset, Payload length, payload and password) will not be present. + /// 2. Channel Alive message does not contain the fields (Offset, Payload length, payload and password). + /// 3. Except the above two special messages, rest all the messages in the above table will follow the message format mentioned in sections 3.1 and 3.2. + /// + Count /// Number of MessageID-s + } + + public enum MeterType : byte + { + DN15 = 0, + CoaxManifold = 1, + DN20 = 2, + DN25 = 3, + DN26 = 4, /// DN25* + DN32 = 5, + DN40 = 6, + AutoDetect, + Count /// Number of meter types + } + + public enum VolumeUnits : byte + { + m3 = 0, + UK_gallon = 1, + US_gallon = 2, + Count /// Number of volume units + } + + public enum FlowArrow : byte + { + No = 0, + Right = 1, + Left = 2, + Count /// Number of flow arrows + } + + public enum MeterSealed : byte + { + InProduction = 0x00, + OutOfProduction = 0xA5, + Sealed = 0x5A, + } + + public enum MeterState : byte + { + None = 0, + Idle = 1, + Active = 2, + Test = 3, + EndOfLife = 4, + Count /// Number of meter states + } + + public enum FlowState : byte + { + No = 0, + Reverse = 1, + Forward = 2, + EmptyPipe = 3, + Count /// Number of flow states + } + + // public enum OptoTelegramFlags : byte + // { + // OK = 0, + // OK_TestStart, + // OK_TestEnd, + // InvalidTelegram, /// Wrong telegram format of checksum error + // SyncError, + // } + + public enum OptoState + { + Read, + Flush, + } + + public enum DataStreamState + { + Flush = 0, + ProcessAndSave, + } + + public enum CommunicationInterface + { + [Description("RFID")]RFID, + [Description("NFC")]NFC + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/FlowDirectionDetection.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/FlowDirectionDetection.cs new file mode 100644 index 000000000..78b20ed94 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/FlowDirectionDetection.cs @@ -0,0 +1,145 @@ +/// +/// Copyright (c) 2018-2019 Sensus Slovensko a.s. +/// + +using System; +using Common; +using log4net; +using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations +{ + public class FlowDirectionDetection + { + private static readonly ILog log = LogManager.GetLogger(typeof(IperlHead)); + + const int FIFO_SIZE = 64; // 8 sec @ 8Hz + const double MAX_OPTO_DROPOUT = 4.5; // sec + + private readonly double[] volumeRawFifo; + private readonly double[] timestampFifo; // centered timestamps + + private int fifoCount; + private int fifoIx; + private DateTime lastFifoWriteTime; + + // regression sums (double is ideal here) + private double sumXX; + private double sumX; + private double sumXY; + private double sumY; + + private double minSlope; + private double maxSlope; + + // timestamp centering for numerical stability + private double firstTimestamp = double.NaN; + + public FlowDirectionDetection() + { + volumeRawFifo = new double[FIFO_SIZE]; + timestampFifo = new double[FIFO_SIZE]; + ClearFifo(); + } + + public void ClearFifo() + { + fifoCount = 0; + fifoIx = 0; + lastFifoWriteTime = DateTime.MinValue; + + sumXX = 0; + sumX = 0; + sumXY = 0; + sumY = 0; + + minSlope = 0; + maxSlope = 0; + firstTimestamp = double.NaN; + } + + /// + /// Add sample to rolling FIFO and update regression sums + /// + public void WriteToFifo(double volumeRaw, double timestamp) + { + // establish time origin (CRITICAL for double precision) + if (double.IsNaN(firstTimestamp)) + firstTimestamp = timestamp; + + double x = timestamp - firstTimestamp; // centered time + double y = volumeRaw; + + // remove oldest sample if buffer full + if (fifoCount == FIFO_SIZE) + { + double oldX = timestampFifo[fifoIx]; + double oldY = volumeRawFifo[fifoIx]; + + sumXX -= oldX * oldX; + sumX -= oldX; + sumXY -= oldX * oldY; + sumY -= oldY; + } + else + { + fifoCount++; + } + + // add new sample + sumXX += x * x; + sumX += x; + sumXY += x * y; + sumY += y; + + // store sample + timestampFifo[fifoIx] = x; + volumeRawFifo[fifoIx] = y; + + fifoIx = (fifoIx + 1) % FIFO_SIZE; + lastFifoWriteTime = DateTime.Now; + } + + public bool AreFifoDataValid() + { + return (DateTime.Now.Subtract(lastFifoWriteTime).TotalSeconds <= MAX_OPTO_DROPOUT) + && (fifoCount == FIFO_SIZE); + } + + public OptoHeadState CheckFlowDirection(Counting counting, string iPerlHeadName) + { + if (!AreFifoDataValid()) + return OptoHeadState.OptoNok; + + try + { + double N = fifoCount; + + double numer = N * sumXY - sumX * sumY; + double denom = N * sumXX - sumX * sumX; + + if (Math.Abs(denom) < 1e-12) + return OptoHeadState.DirNok; + + double slope = numer / denom; + + if (slope > maxSlope) maxSlope = slope; + if (slope < minSlope) minSlope = slope; + + if (counting == Counting.Arbitrary || + (counting == Counting.Positive && maxSlope > Math.Abs(2 * minSlope)) || + (counting == Counting.Negative && minSlope < -Math.Abs(2 * maxSlope))) + { + return OptoHeadState.OptoAndDirOK; + } + + return OptoHeadState.DirNok; + } + catch (Exception ex) + { + log.ErrorFormat("{0} : CheckFlowDirection() failed: {1}", iPerlHeadName, ex); + return OptoHeadState.DirNok; + } + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisImplHeadTestCtrl.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisImplHeadTestCtrl.cs new file mode 100644 index 000000000..3f4f5e437 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisImplHeadTestCtrl.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Web.UI.WebControls; +using System.Windows.Forms; +using Common; +using TBF.Rig.Generic; +using TBF.Rig.RegisterReaders.iPerlReaderUNI.common; +using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations +{ + public class GenesisImplHeadTestCtrl : IUniHeadTestCtrl + { + Thread optoThread; + GenesisSmartReader _genesiHead; + + public GenesisSmartReader Head { get { return _genesiHead;} } + public ISmartReader ISmartReader { get; set; } + + public bool stopWorkerThread { get; set; } + + public event EventHandler OptoReceivedHandler; + + public IComponentCfg config { get; set; } + + public void Initialize() + { + stopWorkerThread = false; + } + + public void Destroy() + { + stopWorkerThread = true; + if (optoThread != null) + { + optoThread.Abort(); + } + } + + private const string StrReadPcbCmd = "ReadPCB"; + private const string StrSetTestModeCmd = "SetTestMode"; + private const string StrSetActiveModeCmd = "SetActiveMode"; + private const string StrReadOptoDataCmd = "ReadOptoData"; + private const string StrStopReadOptoDataCmd = "StopReadOptoData"; + private const string StrResetNfcHeadCmd = "ResetNfcHead"; + private const string StrSetNfcHeadCmd = "SetNfcHead"; + private const string StrSetRfidHeadCmd = "SetRfidHead"; + private const string StrEmptyCmd = ""; + + public enum Operations + { + [Description(StrReadPcbCmd)]ReadPcbCmd, + [Description(StrSetTestModeCmd)]SetTestModeCmd, + [Description(StrSetActiveModeCmd)]SetActiveModeCmd, + [Description(StrReadOptoDataCmd)]ReadOptoDataCmd, + [Description(StrStopReadOptoDataCmd)]StopReadOptoDataCmd, + [Description(StrResetNfcHeadCmd)]ResetNfcHeadCmd, + [Description(StrSetNfcHeadCmd)]SetNfcHeadCmd, + [Description(StrSetRfidHeadCmd)]SetRfidHeadCmd, + [Description(StrEmptyCmd)]EmptyCmd + } + + + + private static readonly Dictionary ItemsForIperlOperations = new Dictionary + { + {"Read PCB", Operations.ReadPcbCmd}, + {"Set Test Mode", Operations.SetTestModeCmd}, + {"Set Active Mode", Operations.SetActiveModeCmd}, +#if DEBUG + {"Start Read Opto Data", Operations.ReadOptoDataCmd}, + {"Stop Read Opto Data", Operations.StopReadOptoDataCmd}, +#endif + {" ", Operations.EmptyCmd}, + {"Reset NFC Head", Operations.ResetNfcHeadCmd}, + {"Set NFC Head Interface", Operations.SetNfcHeadCmd}, + {"Set RFID Head interface", Operations.SetRfidHeadCmd} + }; + + public (string Name, string Value)[] GetComboOperationsPairs() + { + //return ItemsForIperlOperations.Select(kvp => (kvp.Key, kvp.Value)).ToArray(); + return ItemsForIperlOperations.Select(kvp => (kvp.Key, kvp.Value.ToDescription())).ToArray(); + } + + public void CommandTestButtonClick(object sender, MouseEventArgs e, Arguments a) + { + a.RfidOutputListBox.Items.Clear(); + + using (Tools.LogChecker logChecker = new Tools.LogChecker("RfidData", log4net.Core.Level.Debug)) + { + ListItem rfidListItem = new ListItem(); + rfidListItem.Attributes.Add("style", "font-weight:bold"); + Operations selectedOperation; + if(!ItemsForIperlOperations.TryGetValue((string)a.RfidCommandComboBox.SelectedValue, out selectedOperation)) + selectedOperation = Operations.EmptyCmd; + bool isTestModeSuccessful = false; + switch (selectedOperation) + { + case Operations.ReadPcbCmd: + rfidListItem.Text = $"PCB: {Head.OptoHeadTest.ReadRequest_PCB()}"; + break; + case Operations.SetTestModeCmd: + rfidListItem.Text = Head.OptoHeadTest.SetTestMode(ref isTestModeSuccessful); + a.OptoListBox.Items.Clear(); + stopWorkerThread = false; + optoThread = new Thread(OptoWorker); + if (!optoThread.IsAlive) + { + a.ISmartReader.StartDataStreamProcessing(); // open opto port + optoThread.Start(); + } + + break; + case Operations.SetActiveModeCmd: + rfidListItem.Text = Head.OptoHeadTest.SetActiveMode( ref isTestModeSuccessful); + stopWorkerThread = true; + a.ISmartReader.StopDataStreamProcessing(); // close opto port + break; + case Operations.ResetNfcHeadCmd: + a.ISmartReader.ResetNfcInterface(); + break; + case Operations.SetNfcHeadCmd: + a.ISmartReader.SetNfcInterface(); + break; + case Operations.SetRfidHeadCmd: + a.ISmartReader.SetRfidInterface(); + break; + case Operations.ReadOptoDataCmd: + a.OptoListBox.Items.Clear(); + stopWorkerThread = false; + optoThread = new Thread(OptoWorker); + if (optoThread.IsAlive) + { + stopWorkerThread = true; + a.ISmartReader.StopDataStreamProcessing(); // close opto port + } + + if (!optoThread.IsAlive) + { + a.ISmartReader.StartDataStreamProcessing(); // open opto port + optoThread.Start(); + } + + break; + case Operations.StopReadOptoDataCmd: + stopWorkerThread = true; + a.ISmartReader.StopDataStreamProcessing(); // close opto port + break; + } + + a.RfidOutputListBox.Items.Add(rfidListItem); + a.RfidOutputListBox.Items.AddRange(logChecker.Messages.ToArray()); + } + } + + private void OptoWorker() + { + while (!this.stopWorkerThread) + { + Thread.Sleep(250); + if (this.stopWorkerThread) + break; + + try + { + string buffer = ISmartReader.ReadOptoData(); + if (string.IsNullOrEmpty(buffer)) + { + this.OnOptoReceived((object)this, new OptoReceivedEventArgs(".")); + } + else + OnOptoReceived((object)this, new OptoReceivedEventArgs(buffer)); + } + catch (Exception ex) + { + this.OnOptoReceived((object)this, new OptoReceivedEventArgs(ex.Message)); + } + } + } + + public void OnOptoReceived(object sender, OptoReceivedEventArgs args) + { + if (this.OptoReceivedHandler == null) + return; + try + { + this.OptoReceivedHandler(sender, args); + } + catch (Exception ex) + { + } + } + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader.cs new file mode 100644 index 000000000..05a1bbff1 --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReader.cs @@ -0,0 +1,1842 @@ +using System; +using System.IO; +using System.IO.Ports; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; +using Common; +using Config.Entities; +using log4net; +using NHibernate; +using Sensus.iPerl.NfcHandler; +using TBF.Rig.Generic; +using TBF.Rig.GenericDevices; +using TBF.Rig.RegisterReaders.GenesisRegReader.common; +using TBF.Rig.RegisterReaders.GenesisRegReader.communication; +using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed; +using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed.parserer; +using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead; +using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common; + + + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations +{ + /// + /// based on IPerlReader class + /// + public class GenesisSmartReader : ComponentBase, IDevice, IRegReaderDatastream, ISessionDataMngmnt, IOperation, /*ISmartReader,*/ IRegReaderSmart + + { + private static readonly ILog log = LogManager.GetLogger(typeof(GenesisSmartReader)); + private static readonly ILog logStream = LogManager.GetLogger("StreamData"); + public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); } + +#if TURA_SPECIAL + public const int OptoDataBufferSize = 250000; +#else + public const int OptoDataBufferSize = 40000; /// Opto data count is not limitted by the buffer size +#endif + public const string OptoDataDirectory = "C:\\TBF\\ProcessData"; + public const int StartOptoDataCount = OptoDataBufferSize / 2; + public const int EndOptoDataCount = OptoDataBufferSize - StartOptoDataCount; + public const int StartEndFilterSamplesCount2 = 2; //20 /// StartEndFilterSamplesCount = 2 * StartEndFilterSamplesCount2 + 1 + public const int FeatureVectorSize = 9; + + private OptoHeadTest _optoHeadTest; + + public OptoHeadTest OptoHeadTest + { + get + { + if (_optoHeadTest == null) + _optoHeadTest = new OptoHeadTest(this); + return _optoHeadTest; + } + set { _optoHeadTest = value; } + } + + + readonly GenesisCfg genesisHeadCfg; + + public int RfidComPortNr { get { return genesisHeadCfg.RfidComPortNr; } } + public int OptoComPortNr { get { return genesisHeadCfg.OptoComPortNr; } } + public int MuxBoardNrOrGroup14 { get { return genesisHeadCfg.MuxBoardNr; } } + public int Group { get { return genesisHeadCfg.Group; } } + public MeterType MeterType { get { return genesisHeadCfg.MeterType; } } + public CommunicationInterface CommInterface { get { return genesisHeadCfg.CommunicationInterface; } } + + public int Position + { + get + { + int firstDigitPos = Name.IndexOfAny(new char[] { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' }); + int position; + return (firstDigitPos < 0) ? 0 : (int.TryParse(Name.Substring(firstDigitPos), out position) ? position : 0); + } + } + public RegisterReaderType RegisterReaderType { get { return RegisterReaderType.DataStream; } } + public double PulsesPerLtr { + get { return 1000.0; } + set { } + } + public double LtrsPerPulse { get { return 1 / PulsesPerLtr; } } + public string QuantityUnits { get; set; } + + public double CalibTarget { get { return genesisHeadCfg.ProcParams.CalibTarget; } } + public ushort FactorLimitLo { get { return (ushort)genesisHeadCfg.ProcParams.FactorLimitLo; } } + public ushort FactorLimitHi { get { return (ushort)genesisHeadCfg.ProcParams.FactorLimitHi; } } + public Counting InitFlowDir { get { return (genesisHeadCfg != null && genesisHeadCfg.ProcParams != null) ? genesisHeadCfg.ProcParams.Counting : Counting.Arbitrary; } } + + + /// Properties set by the Begin and the End form + public string SerialNr + { + get + { + if (ConfigStruct != null) + return ConfigStruct.GetPcbNrString(); + else if (simulatedPcbNr != null) + return simulatedPcbNr; + else + return string.Empty; + } + set + { + simulatedPcbNr = value; + } + } + + public bool Disabled; + public bool CommFailed; + public int ResultCode; + + + string extraDataPath; + public string ExtraDataPath { get { return extraDataPath; } } + + float[] x; + public float[] X { get { return x; } } + + + /// + /// Passed to OptoTelegramRaw.UpdateFromString(...) + /// + double volumeRawExtLast; + double timestampExtLast; + + FlowDirectionDetection flowDirectionDetection; + + public bool PositiveCounting; + + + public ConfigStruct ConfigStruct; /// ConfigStruct of WM obtained or updated by iPerlCommunication + public CalibrationStruct CalibrationStruct; /// CalibrationStruct of WM obtained or updated by iPerlCommunication + public CalibrationStructV4 CalibrationStructV4; /// CalibrationStruct of WM obtained or updated by iPerlCommunication + + public Byte OrigTestModeConfig; /// Written to by StartTestingSealedMeter(), read from by EndTestingSealedMeter() + + public ushort OrigCalibFactor; + public ushort CalibFactor + { + get + { + return (CalibrationStruct != null) ? CalibrationStruct.Calibration + : ((CalibrationStructV4 != null) ? CalibrationStructV4.Calibration + : (ushort)0); + } + } + + public ushort OrigCalibFactorLNA; + public ushort CalibFactorLNA { get { return (CalibrationStructV4 != null) ? CalibrationStructV4.CalibrationLNA : (ushort)0; } } + + + public double Q2ErrWOCorrection; + public int Q2CorrRL; + public int Q2CorrLR; + + public double Diff2Hz8Hz; + public bool Hz2CorrectionDone; + public int Hz2Correction; + + public string FWVersion + { + get + { + return (CalibrationStruct != null) ? CalibrationStruct.FWVersionStr() + : ((CalibrationStructV4 != null) ? CalibrationStructV4.FWVersionStr() + : string.Empty); + } + } + + + /// Result of the last test used to calculate Q2 correction factors, etc + public Results.Entities.MeterTestRslt LastTestResult; + public Results.Entities.MeterTestRslt LastTestResult2; + + + /// + /// Required for IRegisterReader interface + /// + public int WMPulses { get { return wmPulses; } } + public int WMRefPulses { get { return wmRefPulses; } } + public double BeginWMState { get { return ResolveNaNDouble(beginWMState); } } + public double EndWMState { get { return ResolveNaNDouble(endWMState); } } + public double WMVolume { get { return ResolveNaNDouble(wmVolume); } } + public double WMTestTime { get { return ResolveNaNDouble(wmTestTime); } } + + string simulatedPcbNr = null; + + double ResolveNaNDouble(double d) + { + if (Double.IsNaN(d)) + { + return 0.0; + } + else + return d; + } + + int wmPulses; + int wmRefPulses; + double beginWMState; + double endWMState; + double wmVolume; + double wmTestTime; + + + /// + /// New calibration factor calculated from the original factor (argument) + /// and results of any test(s). + /// Uses also: this.CalibTarget, this.VolumeLtrStart, this.VolumeLtrEnd + /// Side effects: this.OrigCalibFactor, this.PositiveCounting + /// + /// Test result for calculations + /// Original calibration factor + /// Lower limit for the calibration factor + /// Upper limit for the calibration factor + /// New calibration factor or 0 (= Out of range) + public UInt16 CalculateNewCalibFactor(Results.Entities.MeterTestRslt adjustTestResult, UInt16 originalCalibrationFactor, UInt16 factorLimitLo, UInt16 factorLimitHi) + { + double meterVolume = adjustTestResult.VolumeMeter; + double targetVolume = adjustTestResult.VolumeRef * (1.0f + CalibTarget / 100.0f); + + OrigCalibFactor = originalCalibrationFactor; + + if (meterVolume > 1E-2) + { + PositiveCounting = VolumeLtrEnd > VolumeLtrStart; + + UInt16 newFactor = (UInt16)((double)originalCalibrationFactor * targetVolume / meterVolume + 0.5); + log.InfoFormat("Calibration factor: orig={0} new={1} V_iperl={2} V_ref={3} V_target={4}", + originalCalibrationFactor, + newFactor, + meterVolume.ToString("F3"), + adjustTestResult.VolumeRef.ToString("F3"), + targetVolume.ToString("F3")); + + + if (newFactor < factorLimitLo || newFactor > factorLimitHi) return 0; + + return newFactor; + } + else + { + log.ErrorFormat("Calibration factor: orig={0} new={0} (unchanged!) V_iperl={1}", + originalCalibrationFactor, + meterVolume.ToString("F3")); + return originalCalibrationFactor; /// Too small volume in the denominator -> no correction at all + } + } + + + /// + /// Q2 correction factor calculated from the last test (Q2). + /// This factor should be used only for R800 meters. + /// + /// A test result from which to calculate the factor + /// Nominal flow in m3/h + /// 0 or the current Q2 correction factor when updating the factor + /// Calculated Q2 correction factor + public double CalculateQ2CorrectionFactor(Results.Entities.MeterTestRslt currentQ2Result, int currentFactor, double nominalFlow, double errorTarget = 0) + { + double nominalTestFlowLph = Units.ConvertTo(Unit.lph, nominalFlow); + double volumeRefShiftedToTarget = currentQ2Result.VolumeRef * (1.0 + errorTarget / 100.0); + double q2adjErrorShiftedToTarget = Config.Formulas.ErrorFromVolumes(currentQ2Result.VolumeMeter, volumeRefShiftedToTarget); + + double A = 16.0 / ScalingFactor(); /// Raw units per ml: DN15=16, DN20=8, DN25=4, DN32=2, DN40=1 + const double B = 8.0; /// Raw units per minute, 8 + const double C = B * 60.0; /// Raw units per hour, 480 + double D = C / A; /// ml correction per hour + double F = D / (nominalTestFlowLph * 10.0); /// Error corrected with 8 Raw Units per minute [%] + double G = F / B; /// Error corrected with 1 Raw Units per minute [%] + + /// Do not change the factor for an invalid measurement (q2adjResult.VolumeMeter == 0) + double q2CorrectionFactor = (Math.Abs(currentQ2Result.VolumeMeter) <= float.Epsilon) ? Convert.ToDouble(currentFactor) : + Convert.ToDouble(currentFactor) - (q2adjErrorShiftedToTarget / G) * (volumeRefShiftedToTarget / currentQ2Result.VolumeMeter); + + log.WarnFormat("CalculateQ2CorrectionFactor() : Pos={0}, PCB#={1}, Error={2}%, Target={3}%, Current factor={4} New factor={5}", + Name, + SerialNr, + currentQ2Result.Error.ToString("F2"), + errorTarget.ToString("F3"), + currentFactor.ToString("F1"), + q2CorrectionFactor.ToString("F1")); + + return q2CorrectionFactor; + } + + + /// + /// 2 Hz correction factor calculated from two Q3 tests - done at 2Hz and at 8Hz. + /// This factors should be used only for DN32 and DN40 meters. + /// + /// Test result @2Hz from which to calculate the factor + /// Test result @8Hz from which to calculate the factor + /// The calculated Q2 correction factor + /// true = OK, false = failed + public bool Calculate2HzCorrectionFactor(Results.Entities.MeterTestRslt resultAt2Hz, + Results.Entities.MeterTestRslt resultAt8Hz, + out double diff2Hz8Hz, out int hz2CorrectionFactor) + { + hz2CorrectionFactor = 0; + diff2Hz8Hz = 0; + + if ((resultAt2Hz == null) || (resultAt8Hz == null)) + { + return false; /// Test result @2Hz and/or @8Hz is missing ==> water meter failed + } + + diff2Hz8Hz = resultAt2Hz.Error - resultAt8Hz.Error; + + if (Math.Abs(diff2Hz8Hz) > 2.5) return false; /// Difference of errors > 2.5 % ==> water meter failed + + hz2CorrectionFactor = -1 * (int)Math.Round(10 * diff2Hz8Hz); + + log.WarnFormat("2Hz correction: Pos={0}, PCB#={1}, corrFactor={2}, erro@2Hz={3}%, erro@8Hz={4}%", + Name, + SerialNr, + hz2CorrectionFactor, + resultAt2Hz.Error.ToString("F2"), + resultAt8Hz.Error.ToString("F2")); + + return true; + } + + + /// + /// Store/update values to be used as a part of the opto-data log file name. + /// Stop data stream processing and saving, if it is enabled. + /// + /// Currently executed test + /// Currently executed repetition number + public void TestIsGoingToStartSoon(Test _test, int _repetitionNr) + { + /// Store/update values to be used as a part of the opto-data log file name + this.test = _test; + this.repetitionNr = _repetitionNr; + + if (IsDataStreamProcessing()) + { + StopDataStreamProcessing(); + + /// Dummy '#### start test ####' and '#### end of test ####' marks are added + /// to the raw data file on request of Joern Goege + if (optoDataCount >= 100 && optoDataCount <= optoData.Length && + optoData[40].Flags == OptoTelegramFlags.OK && + optoData[optoDataCount - 40].Flags == OptoTelegramFlags.OK) + { + optoData[40].Flags = OptoTelegramFlags.OK_TestStart; + optoData[optoDataCount - 40].Flags = OptoTelegramFlags.OK_TestEnd; + } + + DataStreamPostProcessing(); + + string pcbNr = (ConfigStruct != null) ? ConfigStruct.GetPcbNrString() : "UnknownPcbNr"; + string wmPosition = Name.Substring(5); /// WMPosition is extracted from a component name in form 'iPerl#' + if (wmPosition.Length == 1) wmPosition = "0" + wmPosition; + string cycleStartTime = StateMachine.CycleStartTimeStamp.ToString("HH_mm_ss"); + /// + string relativeDirectory = Path.Combine(StateMachine.CycleStartTimeStamp.ToString("yy"), + StateMachine.CycleStartTimeStamp.ToString("MM"), + StateMachine.CycleStartTimeStamp.ToString("dd")); + string directory = Path.Combine(OptoDataDirectory, relativeDirectory); + string fileName = string.Format("{0}_{1}_{2}_{3}.txt", pcbNr, wmPosition, "WM", cycleStartTime); + + if (SaveOptoDataToFile(directory, fileName)) + { + extraDataPath = Path.Combine(relativeDirectory, fileName); + } + } + } + /// + Test test; + int repetitionNr; + + + /// + /// Indices to determine centers of start / end samples + /// + public int TestStartTelegramIx; + public int TestEndTelegramIx; + int endTelegramIdx1; + int endTelegramIdx2; + int endTelegramIdx3; + + int currentTelegramIx; + bool startSampleAcquired; + + /// + /// Timestamp from the opto telegram + /// + private double lastTimestamp; + private double timestampSec; + private double timestampSec0; + + /// Test start volume for metrology in seconds + public double TimestampSecStart + { + get { return TimeFromSamples(optoData, optoDataCount, TestStartTelegramIx, StartEndFilterSamplesCount2); } + } + /// Test end time for metrology in seconds + public double TimestampSecEnd + { + get { return TimeFromSamples(optoData, optoDataCount, TestEndTelegramIx, StartEndFilterSamplesCount2); } + } + /// + public bool NoSamples + { + get { return TimestampSecStart == 0 || TimestampSecEnd == 0 || (TimestampSecEnd - TimestampSecStart) < float.Epsilon; } + } + + /// + /// Volume of water from the opto telegram + /// + private double lastVolumeRaw; /// Last read raw volume + private double volumeLtr; + private double volumeLtr0; + + /// Test start volume for metrology in liters + public double VolumeLtrStart + { + get { return NoSamples ? 0 : VolumeFromSamples(optoData, optoDataCount, TestStartTelegramIx, ScalingFactor(), 0); } + } + /// Test end volume for metrology in liters + public double VolumeLtrEnd + { + get { return NoSamples ? 0 : VolumeFromSamples(optoData, optoDataCount, TestEndTelegramIx, ScalingFactor(), StartEndFilterSamplesCount2); } + } + + + OptoTelegramRaw[] optoData; + int optoDataCount; /// Real opto deta count, can be larger then optoData.Length + /// + OptoTelegramRaw toBeFlushed; + + /// + /// Opto serial port and worker thread related private variables + /// + private SerialPort optoSerialPort; + + + public GenesisSmartReader() { } + + public GenesisSmartReader(Generic.IComponentCfg cfg) + : base(cfg) + { + genesisHeadCfg = cfg as GenesisCfg; + } + + public override void Initialize() + { + x = new float[FeatureVectorSize]; + flowDirectionDetection = new FlowDirectionDetection(); + + /// Allocate memory for opto-data from iPerl + optoData = new OptoTelegramRaw[OptoDataBufferSize]; + for (int i = 0; i < OptoDataBufferSize; i++) + { + optoData[i] = new OptoTelegramRaw(); + } + + toBeFlushed = new OptoTelegramRaw(); + + dataStreamState = DataStreamState.Flush; + synchronized = false; + synchronized2 = false; + partOfTelegram = string.Empty; + optoSerialPort = null; + + if (DebugLevel == DebugMode.Normal) + { + /// Open serial port: 9600 Bd, 8 data bits, 1 stop bit, no parity + /// Check whether head is connected, working + try + { + OpenOptoSerialPort($"COM{genesisHeadCfg.OptoComPortNr}", 38400, Parity.None, 8, StopBits.One, Handshake.None); + CloseOptoSerialPort(); + log.FatalFormat($"{Name} initialized: {this}"); + } + catch (Exception ex) + { + throw new Exception(ex.Message); + } + } + else + { + log.FatalFormat($"{Name} simulated: {this}"); + } + } + + /// + /// Clear data related to a specific water meter + /// + public void StartSession() + { + ResultCode = 0; + + Disabled = false; + CommFailed = false; + + ConfigStruct = null; + CalibrationStruct = null; + CalibrationStructV4 = null; + + OrigTestModeConfig = 0; + + LastTestResult = null; + LastTestResult2 = null; + + OrigCalibFactor = 0; + OrigCalibFactorLNA = 0; + Q2ErrWOCorrection = 0; + Q2CorrRL = 0; + Q2CorrLR = 0; + + simulatedPcbNr = null; + + dataStreamState = DataStreamState.Flush; + + currentFlowDir = InitFlowDir; + } + + public void SaveMark(object mark) + { + /// TODO + } + + public void EndSession() + { + StopDataStreamProcessing(); + } + + + Counting currentFlowDir; + /// + public OptoHeadState CheckFlowDirection() + { + return (flowDirectionDetection != null) ? flowDirectionDetection.CheckFlowDirection(currentFlowDir, Name) : OptoHeadState.DirNok; + } + /// + public void ChangeFlowDirection() + { + switch (InitFlowDir) + { + case Counting.Positive: + currentFlowDir = Counting.Negative; + break; + + case Counting.Negative: + currentFlowDir = Counting.Positive; + break; + + case Counting.Arbitrary: + default: + currentFlowDir = Counting.Arbitrary; + break; + } + + if (flowDirectionDetection != null) flowDirectionDetection.ClearFifo(); /// Clear FIFO for flow direction detection + } + + + public void RunDeviceBefore() + { + if (DebugLevel == DebugMode.Normal) + { + try + { + ReadOptoData(dataStreamState); + } + catch (Exception e) + { + DebugLevel = DebugMode.FailureDuringOperation; + + log.FatalFormat("Opto-data serial port failure : {0}", e.Message); + if (e.InnerException != null) + { + log.FatalFormat("InnerMessage : {0}", e.InnerException.Message); + } + } + } + else if (DebugLevel == DebugMode.FailureDuringOperation) + { + } + } + + public void RunDeviceAfter() { } + + public void StopDevice() + { + try + { + if (optoSerialPort != null) + { + CloseOptoSerialPort(); + } + } + catch + { + } + } + + public void StopDevice2() { } + + /// + /// Events: Event.ReadRegisterDone, Event.Error + /// + /// ReadWaterMeter instance reference casted to IOperaton + public IOperation ReadRegisterOp() + { + return this; + } + + /// + /// Clear data/counters related to a specific tests + /// + public void Clear() + { + ResultCode = 0; + + volumeLtr = Double.NaN; + volumeLtr0 = Double.NaN; + timestampSec = Double.NaN; + timestampSec0 = Double.NaN; + + extraDataPath = null; + + /// Clear the feature vector + for (int i = 0; i < FeatureVectorSize; i++) + { + x[i] = 0; + } + } + + public void TestCompleted() + { + /// TODO: Implement + } + + + int timeFromStart; /// [s] Time from test start to determine when the test start sample should be taken + + /// + /// Start this operation + /// + public void Start() + { + lock (this) + { + Clear(); + ReadPulses(); + StartDataStreamProcessing(); + } + } + + /// + /// Run this operation + /// + /// eventDone + public Event Run() + { + lock (this) + { + timeFromStart += StateMachine.Period; + ReadPulses(); + + if (!startSampleAcquired && (timeFromStart >= 8) && (currentTelegramIx >= 0)) + { + /// Take the test start sample + startSampleAcquired = true; + TestStartTelegramIx = currentTelegramIx; + } + else if (startSampleAcquired) + { + /// Shift data in pipelines + TestEndTelegramIx = endTelegramIdx3; + endTelegramIdx3 = endTelegramIdx2; + endTelegramIdx2 = endTelegramIdx1; + endTelegramIdx1 = currentTelegramIx; + } + } + + return Event.ReadRegisterDone; + } + + /// + /// Stop this operation + /// + public void Stop() + { + log.DebugFormat("Flow filtering end, feature vector calculation start: {0:HH:mm:ss.fff}", DateTime.Now); + + int startIx; + int endIx; + + lock (this) + { + StopDataStreamProcessing(); + AddTestStartEndMarksToData(out startIx, out endIx); + } + + DataStreamPostProcessing(); + + // TODO: Enable when calculations completed + // + // float[] offsetV, kOhmsR, kOhmsC, dutFlow, refFlow, flowRatio, magField, emfV; + // x = Common.StatisticalMetrics.Calculate(optoData, optoDataCount, startIx, endIx, true, + // out offsetV, out kOhmsR, out kOhmsC, out dutFlow, + // out refFlow, out flowRatio, out magField, out emfV); + // + // log.DebugFormat("Feature vector calculation end, save opto-file start: {0:HH:mm:ss.fff}", DateTime.Now); + +#if ORACLE_DB + if (test.RawDataId + (test.Repeats - repetitionNr) * test.RawDataIdRepetMulti != 0) + { + string relativeDirectory = Path.Combine(StateMachine.CycleStartTimeStamp.ToString("yy"), + StateMachine.CycleStartTimeStamp.ToString("MM"), + StateMachine.CycleStartTimeStamp.ToString("dd")); + string fileName = DetermineExtraDataFileName(); + if (SaveOptoDataToFile(Path.Combine(OptoDataDirectory, relativeDirectory), fileName)) + { + extraDataPath = Path.Combine(relativeDirectory, fileName); + } + + log.WarnFormat("IperlHead.Stop() startIx={0} endIx={1} len={2} raw data file = {3}", + startIx, endIx, optoData.Length, fileName); + } + else +#endif + { + log.WarnFormat("IperlHead.Stop() startIx={0} endIx={1} len={2} no raw data file", startIx, endIx, optoData.Length); + } + + if (TestStartTelegramIx == 0 || optoDataCount < 100) + { + ResultCode |= (int)Results.Entities.ResultCode.MissingOptoData; + } + else if (VolumeLtrEnd == VolumeLtrStart) + { + ResultCode |= (int)Results.Entities.ResultCode.OptoDataWithZeroFlow; + } + } + + void AddTestStartEndMarksToData(out int startIx, out int endIx) + { + startIx = BufferIdx(TestStartTelegramIx); + if ((startIx > 0) && (startIx < StartOptoDataCount) && (optoData[startIx].Flags == OptoTelegramFlags.OK)) + { + optoData[startIx].Flags = OptoTelegramFlags.OK_TestStart; + OptoTelegramRaw.TestStartTimestampDec = optoData[startIx].TimestampDec(); + } + else + { + for (int ix = 0; ix < StartOptoDataCount; ix++) + { + if (optoData[ix].Flags == OptoTelegramFlags.OK) + { + /// This is the first correct opto-telegram received + OptoTelegramRaw.TestStartTimestampDec = optoData[ix].TimestampDec(); + break; + } + } + } + + endIx = BufferIdx(TestEndTelegramIx); + if ((endIx > 0) && (optoData[endIx].Flags == OptoTelegramFlags.OK)) + { + optoData[endIx].Flags = OptoTelegramFlags.OK_TestEnd; + } + } + + /// + /// Data stream post processing: + /// Flow from a reference flowmeter is FIR filtered + /// + void DataStreamPostProcessing() + { + if (optoDataCount <= OptoDataBufferSize) + { + FIRFilterFlow(optoData, 0, optoDataCount - 1); + } + else + { + FIRFilterFlow(optoData, 0, StartOptoDataCount - 1); + FIRFilterFlow(optoData, (optoDataCount - EndOptoDataCount), optoDataCount - 1); + } + } + + /// + /// Determine opto data file name: PCB_AA_BB_HH_MI_SS..txt + /// + /// Test name + /// Test repeats count (>= 1) + /// Repetition number (1 .. testRepeats) + /// + string DetermineExtraDataFileName() + { + /// + /// Get required pieces of information + /// + string pcbNr = (ConfigStruct != null) ? ConfigStruct.GetPcbNrString() : "UnknownPcbNr"; + string wmPosition = Name.Substring(5); /// WMPosition is extracted from a component name in form 'iPerl#' + if (wmPosition.Length == 1) wmPosition = "0" + wmPosition; + string cycleStartTime = StateMachine.CycleStartTimeStamp.ToString("HH_mm_ss"); +#if ORACLE_DB + string[] designations = string.IsNullOrEmpty(test.RawDataDesignation) ? new string[0] : test.RawDataDesignation.Split(new char[] { '~' }); + int testId = test.RawDataId + (test.Repeats - repetitionNr) * test.RawDataIdRepetMulti; + string designation = string.IsNullOrEmpty(test.RawDataDesignation) + ? testId.ToString(testId > 0 ? "D2" : "D1") /// Name is generated from Id + : (designations.Length > repetitionNr - 1) ? designations[repetitionNr - 1] /// Name is from 'RawDataDesignation' parameter + : string.Format("{0}-{1}", designations[0], repetitionNr); /// Name is form test name and repetition nr. +#else + int testId = 0; + string designation = (test.Repeats == 1) ? test.Name : string.Format("{0}-{1}", test.Name, repetitionNr); +#endif + /// + /// Return the file name + /// + return string.Format("{0}_{1}_{2}_{3}.txt", pcbNr, wmPosition, designation, cycleStartTime); + } + + + /// + /// Save opto data to a file. + /// + bool SaveOptoDataToFile(string directory, string fileName) + { + string fullFileName = Path.Combine(directory, fileName); + log.WarnFormat("Saving {0} raw data to {1}", Name, fullFileName); + + try + { + Directory.CreateDirectory(directory); + + double scalFact = ScalingFactor(); + + using (TextWriter optoLogFile = new StreamWriter(fullFileName)) + { + if (optoDataCount <= OptoDataBufferSize) + { + /// Telegrams are stored continuously, save them. + optoLogFile.WriteLine(optoData[0].ToString(scalFact, null)); + for (int i = 1; i < optoDataCount; i++) + { + optoLogFile.WriteLine(optoData[i].ToString(scalFact, optoData[i - 1])); + } + } + else /// if (optoDataCount > MaxOptoDataCount) + { + /// Buffer overflow + /// First part of the buffer is saved as is + optoLogFile.WriteLine(optoData[0].ToString(scalFact, null)); + for (int i = 1; i < StartOptoDataCount; i++) + { + optoLogFile.WriteLine(optoData[i].ToString(scalFact, optoData[i - 1])); + } + + optoLogFile.WriteLine(" ..."); + + /// Second part of the buffer is an overflowed circular buffer + optoLogFile.WriteLine(optoData[BufferIdx(optoDataCount)].ToString(scalFact, null)); + for (int i = optoDataCount - EndOptoDataCount + 1; i < optoDataCount; i++) + { + optoLogFile.WriteLine(optoData[BufferIdx(i)].ToString(scalFact, optoData[BufferIdx(i - 1)])); + } + } + + log.WarnFormat("{0} opto data successfully saved: {1} lines", Name, optoDataCount); + + optoLogFile.Close(); + } + + return true; + } + catch (Exception exc) + { + File.Delete(fullFileName); + log.ErrorFormat(string.Format("Error writing into file {0}", fullFileName)); + log.ErrorFormat(string.Format("Exception message: {0}", exc.Message)); + return false; + } + } + + void ReadPulses() + { + beginWMState = volumeLtr0; + endWMState = volumeLtr; + wmVolume = Math.Abs(endWMState - beginWMState); + wmPulses = (int)(wmVolume * (double)PulsesPerLtr + 0.5); + log.Debug("wmPulses = " + wmPulses + "wmVolume = " + wmVolume + "PulsesPerLtr = " + PulsesPerLtr + ""); + wmRefPulses = StateMachine.ControlBoardMain.RefPulses; + wmTestTime = timestampSec - timestampSec0; + } + + private void OpenOptoSerialPort( + string comPort, + int baudRate, + Parity parity, + int dataBits, + StopBits stopBit, + Handshake handshake, + int openTimeoutMs = 3000) + { + if (DebugLevel == DebugMode.FailureDuringOperation) DebugLevel = DebugMode.Normal; + if (DebugLevel != DebugMode.Normal) + { + optoSerialPort = null; + log.FatalFormat($"{Name} OproPort simulated: {this}"); + return; + } + + try + { + CloseOptoSerialPort(); + + var port = new SerialPort(comPort, baudRate, parity, dataBits, stopBit) + { + Handshake = handshake, + NewLine = "\r\n", + Encoding = Encoding.ASCII + }; + + port.ReadTimeout = 5000; + port.WriteTimeout = 5000; + port.DtrEnable = true; + port.RtsEnable = true; + + // Run Open() on separate task + var openTask = Task.Run(() => port.Open()); + + if (!openTask.Wait(openTimeoutMs)) + { + port.Dispose(); + throw new TimeoutException( + $"Opening serial port {comPort} timed out after {openTimeoutMs} ms."); + } + + optoSerialPort = port; + + log.FatalFormat($"{Name} OptoPort opened: {this}"); + } + catch (Exception ex) + { + log.FatalFormat($"{Name} OptoPort - error opening port: {this}" + + Environment.NewLine + ex.Message); + throw; // NEVER use "throw ex;" (destroys stack trace) + } + } + + private void CloseOptoSerialPort() + { + if (optoSerialPort != null) + { + optoSerialPort.Close(); + optoSerialPort = null; + log.FatalFormat($"{Name} OptoPort closed: {this}"); + } + } + + DataStreamState dataStreamState; + + /// + /// Reset counters / indices / time and start processing and saving datastream data + /// + public void StartDataStreamProcessing() + { + try + { + OpenOptoSerialPort($"COM{genesisHeadCfg.OptoComPortNr}", 38400, Parity.None, 8, StopBits.One, Handshake.None); + } + catch (Exception) + { + } + /// Reset opto-data, etc. + optoDataCount = 0; + timeFromStart = 0; + currentTelegramIx = -1; + startSampleAcquired = false; + TestStartTelegramIx = 0; + endTelegramIdx1 = 0; + endTelegramIdx2 = 0; + endTelegramIdx3 = 0; + TestEndTelegramIx = 0; + + if (optoSerialPort != null && optoSerialPort.IsOpen) optoSerialPort.DiscardInBuffer(); + + if (flowDirectionDetection != null) flowDirectionDetection.ClearFifo(); /// Clear FIFO for flow direction detection + + /// Enable opto-data parsing and saving + dataStreamState = DataStreamState.ProcessAndSave; + } + + /// + /// Returns true when processing and saving datastream data is in progress + /// + bool IsDataStreamProcessing() + { + return dataStreamState == DataStreamState.ProcessAndSave; + } + + /// + /// Stop processing and saving datastream data + /// + public void StopDataStreamProcessing() + { + dataStreamState = DataStreamState.Flush; + CloseOptoSerialPort(); + } + + /// + /// Variables storing the context of serial port data parsing (ReadOptoSerialPort(...)) + /// + bool synchronized; + bool synchronized2; + string partOfTelegram; + + DiagnosticLedParser parser = new DiagnosticLedParser(DiagnosticLedState.State4); + + /// + /// Reads opto-datastream via serial port. Invoked from RunDeviceBefore() + /// + /// Telegram description: + /// AAAAAA[tab]BBBB[tab]CCCC[tab]DDDDDD[tab]EEEE[tab]FFFFFFFF[tab]GG[cr][lf] (42 bytes) + /// Example: + /// FFFFFE 51EA 0000 65324E 0087 F6319DFF 86 + /// FFDD3A 51F9 0000 65324E 0088 F631A60B 45 + /// ... + /// + /// OptoState.Read or OptoState.Flush + void ReadOptoData(DataStreamState optoState) + { + if (optoSerialPort is null) return; + + lock (this) + { + try + { + int nrBytes = optoSerialPort.BytesToRead; + if (nrBytes > 0) + { + // This will now wait max 3 seconds (ReadTimeout) + string line = optoSerialPort.ReadLine(); + + byte[] bytes = optoSerialPort.Encoding.GetBytes(line); + log.Debug("ComPort: " + OptoComPortNr + " OPTHO RX ← " + HexFormatter.ToSerialHex(bytes)); + + if (optoState == DataStreamState.ProcessAndSave) + { + DiagnosticLedState4Data data = + (DiagnosticLedState4Data)parser.ParseLine(line, false); + + int bufferIx = BufferIdx(optoDataCount); + + if (synchronized) + { + optoData[bufferIx].Counter = optoDataCount; + optoData[bufferIx].SetFlags(OptoTelegramFlags.SyncError); + } + + if (data != null) + { + log.Debug($"OPTHO {OptoComPortNr} Parsed optho data:" + data); + logStream.Debug($"ID: {OptoComPortNr} " + data); + + optoData[bufferIx].UpdateFromSmart( + data, + optoDataCount, + Convert.ToSingle(Sequences.ProcessData.RefFlow.Val), + ref volumeRawExtLast, + ref timestampExtLast); + + flowDirectionDetection.WriteToFifo(volumeRawExtLast, timestampExtLast); + + OptoTelegramReceived( + optoDataCount, + true, + volumeRawExtLast, + timestampExtLast); + } + + optoDataCount++; + } + else + { + // Flush mode + DiagnosticLedState4Data data = + (DiagnosticLedState4Data)parser.ParseLine(line, false); + + flowDirectionDetection.WriteToFifo(volumeRawExtLast, timestampExtLast); + } + } + } + catch (TimeoutException) + { + // ✅ No data received within 3 seconds + log.Debug($"OPTHO {OptoComPortNr} ReadLine timeout (3s) - continuing."); + + // Just continue without parsing + } + catch (Exception ex) + { + log.Error($"OPTHO {OptoComPortNr} Read error: {ex.Message}"); + } + } + } + + public string ReadOptoData() + { + if (optoSerialPort is null) return ""; + string received = "."; + lock (this) + { + try + { + string line = optoSerialPort.ReadLine(); // string + byte[] bytes = optoSerialPort.Encoding.GetBytes(line); + received = HexFormatter.ToSerialHex(bytes); + log.Debug("RX ← " + received); + } + catch (TimeoutException) + { + // ✅ No data received within 3 seconds + log.Debug($"ReadOptoData() OPTHO {OptoComPortNr} ReadLine timeout (3s) - continuing."); + + // Just continue without parsing + } + catch (Exception ex) + { + log.Error($"ReadOptoData() OPTHO {OptoComPortNr} Read error: {ex.Message}"); + } + } + return received; + } + + public async Task ReadOptoDataWithTimeoutAsync(int timeoutMs = 5000) + { + if (optoSerialPort == null) + return string.Empty; + + var readTask = Task.Run(() => + { + lock (this) + { + if (optoSerialPort== null) return string.Empty; + try + { + string line = optoSerialPort.ReadLine(); + byte[] bytes = optoSerialPort.Encoding.GetBytes(line); + string received = HexFormatter.ToSerialHex(bytes); + + log.Debug("RX ← " + received); + return line; + } + catch (TimeoutException) + { + // ✅ No data received within 3 seconds + log.Debug($"ReadOptoData() OPTHO {OptoComPortNr} ReadLine timeout (3s) - continuing."); + + // Just continue without parsing + } + catch (Exception ex) + { + log.Error($"ReadOptoData() OPTHO {OptoComPortNr} Read error: {ex.Message}"); + } + + return string.Empty; + } + }); + + var completedTask = await Task.WhenAny(readTask, Task.Delay(timeoutMs)); + + if (completedTask == readTask) + { + return await readTask; // completed successfully + } + + log.Debug("ReadOptoData timeout after " + timeoutMs + " ms"); + return string.Empty; // timeout case + } + + public string ReadOptoDataWithTimeout(int timeoutMs = 5000) + { + try + { + return ReadOptoDataWithTimeoutAsync(timeoutMs) + .GetAwaiter() + .GetResult(); + } + catch + { + return string.Empty; + } + } + + + void OptoTelegramReceived(int currentIx, bool async, double volumeRawExt, double timestampRawExt) + { + currentTelegramIx = currentIx; + + lastVolumeRaw = volumeRawExt; + lastTimestamp = timestampRawExt; + + if (Double.IsNaN(volumeLtr) && Double.IsNaN(volumeLtr0)) + { + volumeLtr = lastVolumeRaw; + volumeLtr0 = volumeLtr; + } + else + { + volumeLtr = lastVolumeRaw; + } + + if (Double.IsNaN(timestampSec)&& Double.IsNaN(timestampSec0)) + { + timestampSec = lastTimestamp; + timestampSec0 = timestampSec; + } + else + { + timestampSec = lastTimestamp; + } + } + + + /// + /// + /// Called from the state machine when a test is selected and UI needs to be updated. + /// + public void OnOptoReceived(object sender, OptoReceivedEventArgs args) + { + if (OptoReceivedHandler == null) return; + try { OptoReceivedHandler(sender, args); } + catch (Exception) { } + } + + public event EventHandler OptoReceivedHandler; + + + /// + /// Compares CalibrationStruct.MeterType with iPerlCfg.MeterType. + /// iPerlCfg.MeterType == MeterType.AutoDetect disables type checking + /// CalibrationStruct == null disables type checking ... + /// ... so that failed RFID communication does not cause type verification failure) + /// + /// true when type is OK + public bool VerifyIPerlType() + { + if (genesisHeadCfg.MeterType == MeterType.AutoDetect || CalibrationStruct == null) + { + return true; + } + + return genesisHeadCfg.MeterType == CalibrationStruct.MeterType; + } + + public static double UnitVolume(VolumeUnits units) + { + switch (units) + { + default: + case VolumeUnits.m3: return Common.Units.ConvertFrom(Common.Unit.m3, 1.0); /// 1 liter + case VolumeUnits.UK_gallon: return Common.Units.ConvertFrom(Common.Unit.UKgal, 1.0); /// 1 imperial gallon + case VolumeUnits.US_gallon: return Common.Units.ConvertFrom(Common.Unit.USgal, 1.0); /// 1 US gallon + } + } + + public double ScalingFactor() + { + if ((genesisHeadCfg.MeterType == MeterType.AutoDetect) && (CalibrationStruct != null)) + { + return ScalingFactor(CalibrationStruct.MeterType); + } + else if (genesisHeadCfg.MeterType != MeterType.AutoDetect) + { + return ScalingFactor(genesisHeadCfg.MeterType); + } + else + { + return ScalingFactor(MeterType.DN20); + } + } + + /// + /// Scaling factor: + /// 0, 1 (DN15, Coax) . . . . 1 + /// 2 (DN20) . . . . . . . . 2 + /// 3 (DN25) . . . . . . . . 4 + /// 4, 5 (DN26, DN32) . . . . 8 + /// 6 (DN40) . . . . . . . . 16 + /// + /// MeterType (0..6) + /// Scaling factor + public static double ScalingFactor(MeterType meterType) + { + switch (meterType) + { + default: + case MeterType.DN15: + case MeterType.CoaxManifold: + return 1.0; + + case MeterType.DN20: + return 2.0; + + case MeterType.DN25: + return 4.0; + + case MeterType.DN32: + return 8.0; + + case MeterType.DN40: + return 16.0; + } + } + + /// + /// Calculate a filtered volume from data stream samples + /// + /// Samples used in calculation are centered around unwrappedIx + /// Count of samples used in calculation is 2 * smaplesCount2 + 1 + /// Filtered volume + double VolumeFromSamples(OptoTelegramRaw[] optoData, int optoDataCount, int unwrappedIx, double scalingFactor, int samplesCount2 = 0) + { + log.Debug("-- Get VolumeFromSamples() --"); + if (samplesCount2 == 0) + { + if (unwrappedIx >= optoDataCount) + { + log.Debug( + $"-- FAILED VolumeFromSamples() - unwrappedIx {unwrappedIx} >= optoDataCount{optoDataCount}--"); + return 0; + } + + int wrappedIx = BufferIdx(unwrappedIx); + + if (optoData[wrappedIx].Flags != OptoTelegramFlags.OK && + optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestStart && + optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestEnd) + { + log.Debug($"-- Get VolumeFromSamples() - Quit because:{optoData[wrappedIx].Flags}--"); + return 0; + } + + log.Debug($"Valid data VolumeRawExt: {optoData[wrappedIx].VolumeRawExt}"); + return optoData[wrappedIx].VolumeRawExt; + } + + + //TODO BUMI - do result as average from data - usually 5 samples + + if (samplesCount2 < 0) samplesCount2 = 0; + if ((unwrappedIx - samplesCount2) < 0 || (unwrappedIx + samplesCount2) >= optoDataCount) return 0; + + + double sum = 0; + for (int i = unwrappedIx - samplesCount2; i <= unwrappedIx + samplesCount2; i++) + { + int wrappedIx = BufferIdx(i); + + if (optoData[wrappedIx].Flags != OptoTelegramFlags.OK && + optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestStart && + optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestEnd) + { + return 0; + } + + sum += optoData[wrappedIx].VolumeRawExt; + } + + return sum / (double)(2 * samplesCount2 + 1); + //return 0.0000625 * scalingFactor * sum / (double)(2 * samplesCount2 + 1); + + } + + /// + /// Calculate a filtered time from data stream samples + /// + /// Samples used in calculation are centered around unwrappedIx + /// Count of samples used in calculation is 2 * smaplesCount2 + 1 + /// Filtered time + double TimeFromSamples(OptoTelegramRaw[] optoData, int optoDataCount, int unwrappedIx, int samplesCount2 = 0) + { + log.Debug("-- Get TimeFromSamples() --"); + if (unwrappedIx >= optoDataCount) + { + log.Debug( + $"-- FAILED TimeFromSamples() - unwrappedIx {unwrappedIx} >= optoDataCount{optoDataCount}--"); + return 0; + } + + int wrappedIx = BufferIdx(unwrappedIx); + + if (optoData[wrappedIx].Flags != OptoTelegramFlags.OK && + optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestStart && + optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestEnd) + { + log.Debug($"-- Get TimeFromSamples() - Quit because:{optoData[wrappedIx].Flags}--"); + return 0; + } + log.Debug($"Valid data TimestampExt: {optoData[wrappedIx].TimestampExt}"); + return optoData[wrappedIx].TimestampExt; + + // if (samplesCount2 < 0) samplesCount2 = 0; + // if ((unwrappedIx - samplesCount2) < 0 || (unwrappedIx + samplesCount2) >= optoDataCount) return 0; + // + // Int64 sum = 0; + // for (int i = unwrappedIx - samplesCount2; i <= unwrappedIx + samplesCount2; i++) + // { + // int wrappedIx = BufferIdx(i); + // + // if (optoData[wrappedIx].Flags != OptoTelegramFlags.OK && + // optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestStart && + // optoData[wrappedIx].Flags != OptoTelegramFlags.OK_TestEnd) + // { + // return 0; + // } + // + // sum += optoData[wrappedIx].TimestampExt; + // } + // + // return sum / (double)(8192 * (2 * samplesCount2 + 1)); + } + + /// + /// Filter RefFlow data in an array of OptoTelegramRaw objects by a FIR filter: + /// + /// kSize = 5, kSize2 = 2 + /// + /// i k + /// --------------------------------------------------------------------------- + /// 0 -5 filtered[0] = data[0] + /// 1 -4 filtered[1] = data[1] + /// 2 -3 filtered[2] = data[0]*k[0] + ... + data[4]*k[4] + /// 3 -2 filtered[3] = data[1]*k[0] + ... + data[5]*k[4] + /// 4 -1 filtered[4] = data[2]*k[0] + ... + data[6]*k[4] + /// 5 0 data[0] = filtered[0], filtered[0] = data[3]*k[0] + ... + data[7]*k[4] + /// 6 1 data[1] = filtered[1], filtered[1] = data[4]*k[0] + ... + data[8]*k[4] + /// 7 ... + /// + /// array of OptoTelegramRaw objects + /// Index of the first optoData item to process + /// Index of the last optoData item to process + public static void FIRFilterFlow(OptoTelegramRaw[] optoData, int from, int to) + { + float[] kernel = new float[] { 0.1f, 0.2f, 0.4f, 0.2f, 0.1f }; + int kSize = kernel.Length; + int kSize2 = kernel.Length / 2; + + float[] filtered = new float[kSize]; + + for (int i = from; i <= to; i++) + { + if ((i < from + kSize2) || (i > to - kSize2)) + { + /// Beginning or end of optoData buffer => Just copy data (=do not filter) + filtered[i % kSize] = optoData[BufferIdx(i)].RefFlow; + } + else + { + /// Make a convolution of optoData and the kernel + float weightedSum = 0; + for (int j = -kSize2; j <= kSize2; j++) + weightedSum += optoData[BufferIdx(i + j)].RefFlow * kernel[j + kSize2]; + + filtered[i % kSize] = weightedSum; + } + + if (i >= from + kSize) + { + /// filtered[] buffer full => copy filtered data to optoData + optoData[BufferIdx(i - kSize)].RefFlow = filtered[i % kSize]; + } + } + + for (int i = to - kSize + 1; i <= to; i++) + { + if (i >= 0) optoData[BufferIdx(i)].RefFlow = filtered[i % kSize]; + } + } + + /// + /// Get index to optoData buffer + /// + /// Original unwrapped index + /// Index to the buffer + public static int BufferIdx(int index) + { + if (index < IperlHead.OptoDataBufferSize) + { + return index; + } + else + { + return IperlHead.StartOptoDataCount + (index - IperlHead.OptoDataBufferSize) % IperlHead.EndOptoDataCount; + } + } + + public void WriteBinary(BinaryWriter writer) + { + writer.Write(Disabled); + writer.Write(CommFailed); + writer.Write(ResultCode); + writer.Write(PositiveCounting); + + if (ConfigStruct != null) + { + writer.Write(true); + ConfigStruct.WriteBinary(writer); + } + else writer.Write(false); + + if (CalibrationStruct != null) + { + writer.Write(true); + CalibrationStruct.WriteBinary(writer); + } + else writer.Write(false); + + if (CalibrationStructV4 != null) + { + writer.Write(true); + CalibrationStructV4.WriteBinary(writer); + } + else writer.Write(false); + + writer.Write(OrigCalibFactor); + writer.Write(OrigCalibFactorLNA); + writer.Write(Q2ErrWOCorrection); + writer.Write(Q2CorrRL); + writer.Write(Q2CorrLR); + writer.Write(Diff2Hz8Hz); + writer.Write(Hz2CorrectionDone); + writer.Write(Hz2Correction); + + if (LastTestResult != null) + { + writer.Write(true); + LastTestResult.WriteBinary(writer); + } + else writer.Write(false); + + if (LastTestResult2 != null) + { + writer.Write(true); + LastTestResult2.WriteBinary(writer); + } + else writer.Write(false); + } + + public void ReadBinary(BinaryReader reader) + { + Disabled = reader.ReadBoolean(); + CommFailed = reader.ReadBoolean(); + ResultCode = reader.ReadInt32(); + PositiveCounting = reader.ReadBoolean(); + + if (reader.ReadBoolean()) (ConfigStruct = new ConfigStruct()).ReadBinary(reader); + if (reader.ReadBoolean()) (CalibrationStruct = new CalibrationStruct()).ReadBinary(reader); + if (reader.ReadBoolean()) (CalibrationStructV4 = new CalibrationStructV4()).ReadBinary(reader); + + OrigCalibFactor = reader.ReadUInt16(); + OrigCalibFactorLNA = reader.ReadUInt16(); + Q2ErrWOCorrection = reader.ReadDouble(); + Q2CorrRL = reader.ReadInt32(); + Q2CorrLR = reader.ReadInt32(); + Diff2Hz8Hz = reader.ReadDouble(); + Hz2CorrectionDone = reader.ReadBoolean(); + Hz2Correction = reader.ReadInt32(); + + if (reader.ReadBoolean()) (LastTestResult = new Results.Entities.MeterTestRslt()).ReadBinary(reader, null); + if (reader.ReadBoolean()) (LastTestResult2 = new Results.Entities.MeterTestRslt()).ReadBinary(reader, null); + } + + internal void ResetNfcInterface(bool? nfc_on = null) + { + if (genesisHeadCfg.HeadCommunicationComPortNr == 0) return; + + SERIAL_Driver _SERIAL_Driver_Head_Config = new SERIAL_Driver(); + _SERIAL_Driver_Head_Config.OpenConnection($"COM{genesisHeadCfg.HeadCommunicationComPortNr}", 9600, 8, Parity.None, StopBits.One); + + NFCHeadConfig _NFCHead_Config = new NFCHeadConfig(_SERIAL_Driver_Head_Config); + if (nfc_on == null || nfc_on == false) _NFCHead_Config.NFCHeadConfig_SetInterface(false); // set RFID interface + if (nfc_on == null || nfc_on == true ) _NFCHead_Config.NFCHeadConfig_SetInterface(true); // set NFC interface + _SERIAL_Driver_Head_Config.Close(); + _SERIAL_Driver_Head_Config.Dispose(); + } + + internal void SetNfcInterface() + { + ResetNfcInterface(true); + } + + internal void SetRfidInterface() + { + ResetNfcInterface(false); + } + + internal void SetCommunicationInterface(CommunicationInterface commInterface) + { + //using (ISession session = TBF.DB.ConfigDBSessionFactory.OpenSession()) + // Replace the problematic line with the following code to fix the error: + using (ISession session = TBF.DB.SessionFactories[(int)DBKind.Config].OpenSession()) + using (ITransaction tx = session.BeginTransaction()) + { + try + { + var cmpntEntities = session.QueryOver() + .OrderBy(x => x.ItemNr).Asc + .List(); + var cmpnt = cmpntEntities.Where(x => x.Name == Name).First(); + if (cmpnt != null) + { + XDocument doc = XDocument.Parse(cmpnt.Parameters); + if (doc != null) + { + XElement element = doc.Root.Element("CommunicationInterface"); + if (element != null) + { + element.Value = commInterface.ToString(); + cmpnt.Parameters = doc.ToString(); + session.SaveOrUpdate(cmpnt); + tx.Commit(); + log.FatalFormat($"Set CommunicationInterface {Name} to {commInterface.ToString()}"); + } + } + } + } + catch (Exception ex) + { + if (tx != null) tx.Rollback(); + log.FatalFormat($"Set CommunicationInterface {Name} error: {ex.Message}"); + } + } + } + + public IOperation ReadDatastreamOp() + { + return this; + } + + + public async Task DataEntry_ReadSerialNumber() + { + log.Debug("called DataEntry_ReadSerialNumber()"); + if (!string.IsNullOrEmpty(SerialNr)) return SerialNr; + + //need to find serial number + SerialNr = await DataEntry_ReadSerialNumberAsync(); + + return SerialNr; + } + + public Task DataEntry_ReadBeginVolume() + { + log.Debug("called DataEntry_ReadBeginVolumer()"); + + Task readedVolume = DataEntry_BeginVolumeAsync(); + + return readedVolume; + } + + public Task DataEntry_ReadEndVolume() + { + log.Debug("called DataEntry_ReadBeginVolumer()"); + + Task readedVolume = DataEntry_EndVolumeAsync(); + + return readedVolume; + } + + + public async Task DataEntry_EndVolumeAsync() + { + + if (optoSerialPort == null || !optoSerialPort.IsOpen) + { + StartDataStreamProcessing(); + if (optoSerialPort == null || !optoSerialPort.IsOpen) + { + log.Error($"optoSerialPort COM: {this.OptoComPortNr} is not open - DataEntry_EndVolumeAsync()"); + return Double.NaN; + } + } + + return await Task.Run(() => + { + log.Debug($"Try get End Volume! COM: {this.OptoComPortNr}"); + volumeLtr = Double.NaN; + + int counter = 0; + while (Double.IsNaN(volumeLtr) && counter < 2) + { + counter++; + try + { + string readOptoDataWithTimeout = ReadOptoDataWithTimeout(2000); + if (!string.IsNullOrEmpty(readOptoDataWithTimeout)) + { + try + { + DiagnosticLedState4Data data = + (DiagnosticLedState4Data)parser.ParseLine(readOptoDataWithTimeout, false); + volumeLtr = data.RawVolume; + break; + } + catch (Exception ex) + { + log.Warn($"Parsing DiagnosticLedState4Data: {ex.Message}"); + } + } + } + catch (Exception ex) + { + break; + } + } + + + log.Debug($"Try get End Volume! COM: {this.OptoComPortNr}, Volume: {volumeLtr}"); + if (optoSerialPort != null && optoSerialPort.IsOpen) CloseOptoSerialPort(); + + if (!Double.IsNaN(volumeLtr)) + { + endWMState = volumeLtr; + if (!Double.IsNaN(beginWMState) && !Double.IsNaN(endWMState)) + { + //Solve roll over + if (endWMState < beginWMState) + { + log.Debug($"Solve roll over! endWMState: {endWMState} < beginWMState: {beginWMState}"); + const double VOL_RANGE_LITERS = 16777216.0 * 0.00025; // 4,194.304 l + endWMState += VOL_RANGE_LITERS; + volumeLtr = endWMState; + ReadPulses(); + log.Debug($"Solve roll over! Upgraded endWMState: {endWMState}, beginWMState: {beginWMState}"); + } + } + return endWMState; + } + //} + + log.Warn("Default NaN value returned! Data Opto stream reading failed!"); + return Double.NaN; + }).ConfigureAwait(false); + } + + + public async Task DataEntry_BeginVolumeAsync() + { + if (ConfigStruct == null) + { + log.Debug("ConfigStruct is null - created new in ReadSerialNr()"); + ConfigStruct = new ConfigStruct(); + } + + return await Task.Run(() => + { + + log.Debug($"Try get Start Volume! COM: {this.OptoComPortNr}"); + + Start(); + + volumeLtr0 = Double.NaN; + int counter = 0; + while (Double.IsNaN(volumeLtr0) && counter < 10) + { + counter++; + try + { + string readOptoDataWithTimeout = ReadOptoDataWithTimeout(5000); + if (!string.IsNullOrEmpty(readOptoDataWithTimeout)) + { + try + { + DiagnosticLedState4Data data = + (DiagnosticLedState4Data)parser.ParseLine(readOptoDataWithTimeout, false); + volumeLtr0 = data.RawVolume; + break; + } + catch (Exception ex) + { + log.Warn($"Parsing DiagnosticLedState4Data: {ex.Message}"); + } + } + } + catch (Exception ex) + { + break; + } + } + + log.Debug($"Try get Start Volume! COM: {this.OptoComPortNr}, Volume: {volumeLtr0}"); + if (optoSerialPort != null && optoSerialPort.IsOpen) CloseOptoSerialPort(); + + if (!Double.IsNaN(volumeLtr0)) + { + beginWMState = volumeLtr0; + ReadPulses(); + return beginWMState; + } + //} + + log.Warn("Default NaN value returned! Data Opto stream reading failed!"); + return Double.NaN; + }).ConfigureAwait(false); + } + + public async Task DataEntry_ReadSerialNumberAsync() + { + if (!string.IsNullOrEmpty(SerialNr)) + return SerialNr; + + if (ConfigStruct == null) + { + log.Debug("ConfigStruct is null - created new in ReadSerialNr()"); + ConfigStruct = new ConfigStruct(); + } + + if (CommFailed || ConfigStruct == null) + return CommFailed.ToString(); + + return await Task.Run(() => + { + + + + log.Debug($"Try get ReadSerialNr! COM: {this.RfidComPortNr}"); + if (OptoHeadTest.ReadSerialNr()) + { + SerialNr = this.ConfigStruct.PCBNumberString; + log.Debug("ReadSerialNr successful"); + } + + //optoHeadTest.CloseConnection(); + + return SerialNr; + }); + } + + } +} \ No newline at end of file diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/OptoReceivedEventArgs.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/OptoReceivedEventArgs.cs new file mode 100644 index 000000000..be741687f --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/OptoReceivedEventArgs.cs @@ -0,0 +1,18 @@ +/// +/// Copyright (c) 2015-2017 Sensus Metering Systems +/// + +using System; + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations +{ + public class OptoReceivedEventArgs : EventArgs + { + public string Data; + + public OptoReceivedEventArgs(string data) + { + this.Data = data; + } + } +} diff --git a/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/ProcParams.cs b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/ProcParams.cs new file mode 100644 index 000000000..23487dd0a --- /dev/null +++ b/TBF/Rig/RegisterReaders/GenesisRegReader/implementations/ProcParams.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml.Serialization; +using Common; +using Config.Entities; +using log4net; +using TBF.Rig.Generic; +/// +/// Copyright (c) 2015-2023 Sensus Slovensko a.s. +/// + +namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations +{ + public class ProcParams : ProcedureParamsBase, IParamsProvider, IProcedureParams + { + private static readonly ILog log = LogManager.GetLogger(typeof(ProcParams)); + + public static XmlSerializer Serializer = XmlSerializer.FromTypes(new[] { typeof(ProcParams) })[0]; + public override XmlSerializer GetSerializer() { return Serializer; } + + public int WMType_ID; + public MeterType MeterType; + public float CalibTarget; /// Target error after calibration in [%] + public int FactorLimitLo; /// Lower limit for the calibration factor + public int FactorLimitHi; /// Upper limit for the calibration factor + public Counting Counting; /// Initial iPerl counting (Artbitrary, Positive or Negative) + + + public override void InitializeAll() + { + MeterType = MeterType.AutoDetect; + CalibTarget = 0; + FactorLimitLo = 1000; + FactorLimitHi = 8000; + Counting = Counting.Arbitrary; + } + + string[] paramNames = new string[] + { + "iPerl type", + "Calib. target [%]", + "Calib. factor Lo", + "Calib. factor Hi", + "Counting", + }; + public override string ParamName(int i) { return paramNames[i]; } + public override int ParamsCount() { return paramNames.Length; } + + public override ICollection ParamValues(int i) + { + if (i == 0) + { + var retVal = new List(); + for (MeterType mt = 0; mt < MeterType.Count; mt++) retVal.Add(mt.ToString()); + return retVal; + } + else if (i == 5) + { + var retVal = new List(); + for (Counting c = 0; c < Counting.Count; c++) retVal.Add(c.ToString()); + return retVal; + } + return null; + } + + public override string ToString(int i) + { + switch (i) + { + case 0: return MeterType.ToString(); + case 1: return CalibTarget.ToString(); + case 2: return FactorLimitLo.ToString(); + case 3: return FactorLimitHi.ToString(); + case 4: return Counting.ToString(); + default: return string.Empty; + } + } + + public CfgUpdateFlags UpdateParam(int i, string strValue) + { + switch (i) + { + case 0: + for (MeterType mt = 0; mt < MeterType.Count; mt++) + { + if (mt.ToString().Equals(strValue)) { MeterType = mt; return CfgUpdateFlags.None; } + } + break; + case 1: CalibTarget = Utils.ParseSFloat(strValue); return CfgUpdateFlags.None; + case 2: FactorLimitLo = int.Parse(strValue); return CfgUpdateFlags.None; + case 3: FactorLimitHi = int.Parse(strValue); return CfgUpdateFlags.None; + case 4: + for (Counting c = 0; c < Counting.Count; c++) + { + if (c.ToString().Equals(strValue)) { Counting = c; return CfgUpdateFlags.None; } + } + break; + default: return CfgUpdateFlags.None; + } + + return CfgUpdateFlags.None; + } + + public bool ValidateParam(int i, string strValue, out string message) + { + message = string.Empty; + + int iDummy; + float fDummy; + + switch (i) + { + case 0: + for (MeterType mt = 0; mt < MeterType.Count; mt++) if (mt.ToString().Equals(strValue)) return true; + break; + case 1: + if (Utils.TryParseSFloat(strValue, out fDummy) && fDummy >= -10.0f && fDummy <= 10.0f) return true; + break; + case 2: + case 3: + if (int.TryParse(strValue, out iDummy) && iDummy >= 1000 && iDummy <= 8000) return true; + break; + case 4: + for (Counting c = 0; c < Counting.Count; c++) if (c.ToString().Equals(strValue)) return true; + break; + default: + message = "Invalid index"; + return false; + } + + message = ParamName(i) + " is invalid"; + return false; + } + + void CopyContentTo(ProcParams prms) + { + prms.MeterType = this.MeterType; + prms.CalibTarget = this.CalibTarget; + prms.FactorLimitLo = this.FactorLimitLo; + prms.FactorLimitHi = this.FactorLimitHi; + prms.Counting = this.Counting; + } + + public IParamsProvider Clone() + { + ProcParams pars = new ProcParams(); + CopyContentTo(pars); + return pars; + } + + public override bool UpdateFromDbEntity(ComponentProcedure dbEntity) + { + if (dbEntity == null) return false; + try + { + ProcParams tmp = Serializer.Deserialize(new StringReader(dbEntity.Parameters)) as ProcParams; + + procedureParamsEntity = dbEntity; + componentName = dbEntity.CmpntName; + procedure = dbEntity.Procedure; + + if (tmp != null) + { + tmp.CopyContentTo(this); + return true; + } + else return false; + } + catch (Exception ex) + { + log.DebugFormat( + "Error during Procedure parameters deserialization. CmpntName='{0}', Procedure='{1}', Parameters='{2}', Exception: {3}", + dbEntity?.CmpntName, + dbEntity?.Procedure, + dbEntity?.Parameters, + ex + ); + + return false; + } + } + + + public ProcParams() + { + } + + public ProcParams(bool initialize) + { + if (initialize) InitializeAll(); + } + + public ProcParams(ComponentProcedure procParamsEntity, string componentName, Procedure procedure) + { + this.procedureParamsEntity = procParamsEntity; + this.componentName = componentName; + this.procedure = procedure; + } + } +} diff --git a/TBF/Rig/RegisterReaders/IPerlReader/IperlUniHeadTestCtrl.cs b/TBF/Rig/RegisterReaders/IPerlReader/IperlUniHeadTestCtrl.cs index 0b9098546..7888eb6b1 100644 --- a/TBF/Rig/RegisterReaders/IPerlReader/IperlUniHeadTestCtrl.cs +++ b/TBF/Rig/RegisterReaders/IPerlReader/IperlUniHeadTestCtrl.cs @@ -13,8 +13,8 @@ namespace TBF.Rig.RegisterReaders.IPerlReader { public partial class IperlUniHeadTestCtrl : UserControl { - private IUniHeadTestCtrl _ctrl; - private IUniHeadTestCtrl Ctrl { get => _ctrl; } + private IUniHeadTestCtrl _ctrl; + private IUniHeadTestCtrl Ctrl { get => _ctrl; } diff --git a/TBF/Rig/RegisterReaders/IPerlReader/implementations/IPerlImplHeadTestCtrl.cs b/TBF/Rig/RegisterReaders/IPerlReader/implementations/IPerlImplHeadTestCtrl.cs index 496070761..67851a0c1 100644 --- a/TBF/Rig/RegisterReaders/IPerlReader/implementations/IPerlImplHeadTestCtrl.cs +++ b/TBF/Rig/RegisterReaders/IPerlReader/implementations/IPerlImplHeadTestCtrl.cs @@ -13,7 +13,7 @@ using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common; namespace TBF.Rig.RegisterReaders.IPerlReader.implementations { - public class IPerlImplHeadTestCtrl : IUniHeadTestCtrl + public class IPerlImplHeadTestCtrl : IUniHeadTestCtrl { Thread optoThread; public ISmartReader ISmartReader { get; set; } diff --git a/TBF/Rig/RegisterReaders/PoseidonReader/UniHeadTestCtrl.cs b/TBF/Rig/RegisterReaders/PoseidonReader/UniHeadTestCtrl.cs index 3c8d3d5b6..d5e178d47 100644 --- a/TBF/Rig/RegisterReaders/PoseidonReader/UniHeadTestCtrl.cs +++ b/TBF/Rig/RegisterReaders/PoseidonReader/UniHeadTestCtrl.cs @@ -11,8 +11,8 @@ namespace TBF.Rig.RegisterReaders.PoseidonReader { public partial class UniHeadTestCtrl : UserControl { - private IUniHeadTestCtrl _ctrl; - private IUniHeadTestCtrl Ctrl { get => _ctrl; } + private IUniHeadTestCtrl _ctrl; + private IUniHeadTestCtrl Ctrl { get => _ctrl; } public UniHeadTestCtrl(Generic.IComponentCfg config) { diff --git a/TBF/Rig/RegisterReaders/PoseidonReader/implementations/PoseidonImplHeadTestCtrl.cs b/TBF/Rig/RegisterReaders/PoseidonReader/implementations/PoseidonImplHeadTestCtrl.cs index 65761e1b2..8b062daaf 100644 --- a/TBF/Rig/RegisterReaders/PoseidonReader/implementations/PoseidonImplHeadTestCtrl.cs +++ b/TBF/Rig/RegisterReaders/PoseidonReader/implementations/PoseidonImplHeadTestCtrl.cs @@ -13,7 +13,8 @@ using OptoReceivedEventArgs = TBF.Rig.RegisterReaders.CommonRR.IPerl.communicati namespace TBF.Rig.RegisterReaders.PoseidonReader.implementations { - public class PoseidonImplHeadTestCtrl : IUniHeadTestCtrl + + public class PoseidonImplHeadTestCtrl : IUniHeadTestCtrl { // ----------MEMBER VARIABLES-------------- Thread optoThread; diff --git a/TBF/Rig/RegisterReaders/iPerlASICReader/IperlASICUniHeadTestCtrl.cs b/TBF/Rig/RegisterReaders/iPerlASICReader/IperlASICUniHeadTestCtrl.cs index 938fd88a6..f34591185 100644 --- a/TBF/Rig/RegisterReaders/iPerlASICReader/IperlASICUniHeadTestCtrl.cs +++ b/TBF/Rig/RegisterReaders/iPerlASICReader/IperlASICUniHeadTestCtrl.cs @@ -13,8 +13,8 @@ namespace TBF.Rig.RegisterReaders.iPerlASICReader { public partial class IperlASICUniHeadTestCtrl : UserControl { - private IUniHeadTestCtrl _ctrl; - private IUniHeadTestCtrl Ctrl { get => _ctrl; } + private IUniHeadTestCtrl _ctrl; + private IUniHeadTestCtrl Ctrl { get => _ctrl; } diff --git a/TBF/Rig/RegisterReaders/iPerlASICReader/implementations/IPerlASICImplHeadTestCtrl.cs b/TBF/Rig/RegisterReaders/iPerlASICReader/implementations/IPerlASICImplHeadTestCtrl.cs index aec5172ef..e167d8516 100644 --- a/TBF/Rig/RegisterReaders/iPerlASICReader/implementations/IPerlASICImplHeadTestCtrl.cs +++ b/TBF/Rig/RegisterReaders/iPerlASICReader/implementations/IPerlASICImplHeadTestCtrl.cs @@ -13,7 +13,7 @@ using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common; namespace TBF.Rig.RegisterReaders.iPerlASICReader.implementations { - public class IPerlASICImplHeadTestCtrl : IUniHeadTestCtrl + public class IPerlASICImplHeadTestCtrl : IUniHeadTestCtrl { Thread optoThread; public ISmartReader ISmartReader { get; set; } diff --git a/TBF/Rig/RegisterReaders/iPerlReaderUNI/IperlUniHeadTestCtrl.cs b/TBF/Rig/RegisterReaders/iPerlReaderUNI/IperlUniHeadTestCtrl.cs index bae46f5e0..4562975f6 100644 --- a/TBF/Rig/RegisterReaders/iPerlReaderUNI/IperlUniHeadTestCtrl.cs +++ b/TBF/Rig/RegisterReaders/iPerlReaderUNI/IperlUniHeadTestCtrl.cs @@ -15,8 +15,8 @@ namespace TBF.Rig.RegisterReaders.iPerlReaderUNI { public partial class IperlUniHeadTestCtrl : UserControl { - private IUniHeadTestCtrl _ctrl; - private IUniHeadTestCtrl Ctrl { get => _ctrl; } + private IUniHeadTestCtrl _ctrl; + private IUniHeadTestCtrl Ctrl { get => _ctrl; } diff --git a/TBF/Rig/RegisterReaders/iPerlReaderUNI/common/IUniHeadTestCtrl.cs b/TBF/Rig/RegisterReaders/iPerlReaderUNI/common/IUniHeadTestCtrl.cs index 61dd5cffb..cef65b0e4 100644 --- a/TBF/Rig/RegisterReaders/iPerlReaderUNI/common/IUniHeadTestCtrl.cs +++ b/TBF/Rig/RegisterReaders/iPerlReaderUNI/common/IUniHeadTestCtrl.cs @@ -12,14 +12,20 @@ namespace TBF.Rig.RegisterReaders.iPerlReaderUNI.common public ListBox OptoListBox { get; set; } public ISmartReader ISmartReader { get; set; } } - - public interface IUniHeadTestCtrl + public interface IOptoReceiver where TEventArgs : EventArgs { - Generic.IComponentCfg config { get; set; } - ISmartReader ISmartReader { get; set; } - bool stopWorkerThread { get; set; } + event EventHandler OptoReceivedHandler; + } + + public interface IUniHeadTestCtrl + : IOptoReceiver + where TEventArgs : EventArgs + { + public Generic.IComponentCfg config { get; set; } + public ISmartReader ISmartReader { get; set; } + public bool stopWorkerThread { get; set; } - public event EventHandler OptoReceivedHandler; + //public event IOptoReceiver OptoReceivedHandler; // see IOptoReceiver public void Initialize(); public void Destroy(); diff --git a/TBF/Rig/TestMethods/iPerlCommunication/communication/OpthoHeadService.cs b/TBF/Rig/TestMethods/iPerlCommunication/communication/OpthoHeadService.cs deleted file mode 100644 index 08372dd45..000000000 --- a/TBF/Rig/TestMethods/iPerlCommunication/communication/OpthoHeadService.cs +++ /dev/null @@ -1,10 +0,0 @@ -using log4net; - -namespace TBF.Rig.TestMethods.iPerlCommunication.communication -{ - public class OpthoHeadService - { - - - } -} \ No newline at end of file diff --git a/TBF/TBF.csproj b/TBF/TBF.csproj index 3e764dd47..c1d529130 100644 --- a/TBF/TBF.csproj +++ b/TBF/TBF.csproj @@ -1291,6 +1291,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GenesisHeadTestCtrl.cs + + + + + GenesisCfgCtrl.cs + + + + + + + + + + @@ -1654,7 +1766,6 @@ - @@ -3414,6 +3525,14 @@ RRCfgCtrl.cs + + + + GenesisHeadTestCtrl.cs + + + GenesisCfgCtrl.cs + diff --git a/TBFTests/Rig/TestMethods/GenesisCommunication/GenesisHead/GenesisHeadBatchIntegrationTest.cs b/TBFTests/Rig/TestMethods/GenesisCommunication/GenesisHead/GenesisHeadBatchIntegrationTest.cs new file mode 100644 index 000000000..ea6eb02ae --- /dev/null +++ b/TBFTests/Rig/TestMethods/GenesisCommunication/GenesisHead/GenesisHeadBatchIntegrationTest.cs @@ -0,0 +1,49 @@ +using System.IO.Ports; +using JetBrains.Annotations; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TBF.Rig.TestMethods.GenesisCommunication.GenesisHead; + +namespace TBFTests.Rig.TestMethods.GenesisCommunication.GenesisHead +{ + [TestClass] + //[TestSubject(typeof(GenesisHeadBatchIntegration))] + public class GenesisHeadBatchIntegrationTest + { + string ComPort = "COM3"; + int BaudRate = 112500; + + + + [TestMethod] + [TestCategory("Hardware")] + [TestCategory("Serial")] + public void SerialPortConnection_IntegrationTest() + { + using (var port = new SerialPort(ComPort, BaudRate, Parity.None, 8, StopBits.One)) + { + //port.Handshake = Handshake.None; + //port.ReadTimeout = 5000; + port.Open(); + + + // var bufferSize = this.serialPort.ReadBufferSize; + // var buffer = new byte[bufferSize]; + // var length = default(int); + // + // while (this.serialPort.IsOpen) + // { + // buffer = new byte[bufferSize]; + // length = this.serialPort.Read(buffer, 0, bufferSize); + // + // Array.Resize(ref buffer, length); + // + // yield return buffer; + // + // manualResetEventSlim.Reset(); + // manualResetEventSlim.Wait(); + // } + + } + } + } +} \ No newline at end of file diff --git a/TBFTests/TBFTests.csproj b/TBFTests/TBFTests.csproj index 25bd1dc29..19483322f 100644 --- a/TBFTests/TBFTests.csproj +++ b/TBFTests/TBFTests.csproj @@ -116,6 +116,7 @@ +