From 171aee4f1d4b1d78595195ee55f47218d436c457 Mon Sep 17 00:00:00 2001 From: Milan Hanajik Date: Wed, 13 Oct 2021 08:10:39 +0200 Subject: [PATCH] SIRT component added, ver. 2.26.1774 --- TBF/BenchControl/Sirt/SirtUtils.cs | 197 +++++++++++++ TBF/BenchControl/Sirt/V2012_433Mhz/Factory.cs | 24 ++ TBF/BenchControl/Sirt/V2012_433Mhz/Sirt.cs | 263 ++++++++++++++++++ TBF/BenchControl/Sirt/V2012_433Mhz/SirtCfg.cs | 131 +++++++++ TBF/BenchControl/TbfComponents.cs | 3 +- TBF/Properties/AssemblyInfo.cs | 4 +- TBF/TBF.csproj | 8 +- 7 files changed, 626 insertions(+), 4 deletions(-) create mode 100644 TBF/BenchControl/Sirt/SirtUtils.cs create mode 100644 TBF/BenchControl/Sirt/V2012_433Mhz/Factory.cs create mode 100644 TBF/BenchControl/Sirt/V2012_433Mhz/Sirt.cs create mode 100644 TBF/BenchControl/Sirt/V2012_433Mhz/SirtCfg.cs diff --git a/TBF/BenchControl/Sirt/SirtUtils.cs b/TBF/BenchControl/Sirt/SirtUtils.cs new file mode 100644 index 000000000..ba5f24da8 --- /dev/null +++ b/TBF/BenchControl/Sirt/SirtUtils.cs @@ -0,0 +1,197 @@ +/// +/// Copyright (c) 2021 Sensus Slovensko a.s. +/// +using System; +using System.Text; + +namespace TBF.BenchControl.Sirt +{ + public enum Mark : byte + { + Send = 0x55, + Receive = 0xFF, + Stop = 0x16, + } + + public enum Tel : byte + { + /// Sent + PamWrite = 0x81, + PamRead = 0x82, + RacWrite = 0x88, + RacRead = 0x8b, + RegisterWrite = 0x84, + RegisterRead = 0x87, + + /// Received + TelFromAir = 0x11, /// sent without a request + AlertMsg = 0x14, /// sent without a request + AckOrAnswer = 0x12, + } + + public static class SirtUtils + { + public const int MinTelegramLen = 12; + + + public static byte[] WriteToPamPool(UInt32 radioAddress, byte param1, byte param2, + byte[] optionalData = null) + { + return GenericReadWrite(radioAddress, Tel.PamWrite, param1, param2, optionalData); + } + + public static byte[] ReadFromPamPool(UInt32 radioAddress, byte param1, byte param2, + byte[] optionalData = null) + { + return GenericReadWrite(radioAddress, Tel.PamRead, param1, param2, optionalData); + } + + public static byte[] WriteToRacPool(UInt32 radioAddress, byte param1, byte param2, + byte[] optionalData = null) + { + return GenericReadWrite(radioAddress, Tel.RacWrite, param1, param2, optionalData); + } + + public static byte[] ReadFromRacPool(UInt32 radioAddress, byte param1, byte param2, + byte[] optionalData = null) + { + return GenericReadWrite(radioAddress, Tel.RacRead, param1, param2, optionalData); + } + + public static byte[] WriteToRegisterAddress(UInt32 radioAddress, byte param, byte wrLenght, + byte[] optionalData = null) + { + return GenericReadWrite(radioAddress, Tel.RegisterWrite, param, wrLenght, optionalData); + } + + public static byte[] ReadFromRegisterAddress(UInt32 radioAddress, byte param, byte wrLenght, + byte[] optionalData = null) + { + return GenericReadWrite(radioAddress, Tel.RegisterRead, param, wrLenght, optionalData); + } + + + static byte[] GenericReadWrite(UInt32 radioAddress, Tel tel, byte param1, byte param2, + byte[] optionalData) + { + byte[] data = (optionalData != null) ? optionalData : new byte[0]; + int dataLen = data.Length; + + if (dataLen > 128) return null; + + byte[] telegram = new byte[12 + dataLen]; + telegram[0] = (byte)Mark.Send; + telegram[1] = (byte)tel; + telegram[2] = param1; + telegram[3] = param2; + telegram[4] = (byte)((radioAddress >> 24) & 0x000000FF); + telegram[5] = (byte)((radioAddress >> 16) & 0x000000FF); + telegram[6] = (byte)((radioAddress >> 8) & 0x000000FF); + telegram[7] = (byte)(radioAddress & 0x000000FF); + telegram[8] = (byte)dataLen; + for (int i = 0; i < dataLen; i++) + { + telegram[9 + i] = data[i]; + } + UpdateCrcCcitt(telegram, 1, dataLen + 8); /// Updates telegram[dataLen + 9] and telegram[dataLen + 10] + telegram[dataLen + 11] = 0x16; + + return telegram; + } + + public static void UpdateCrcCcitt(byte[] data, int start, int length) + { + if (data.Length < start + length + 2) return; + + UInt16 crc_result = CalculateCrcCcitt(data, start, length); + + data[start + length] = (byte)((crc_result >> 8) & 0xFF); + data[start + length + 1] = (byte)(crc_result & 0xFF); + } + + + public static UInt16 CalculateCrcCcitt(byte[] data, int start, int length) + { + if (data.Length < start + length) return 0; + + UInt16 crc_result = 0xFFFF; + + for (int ui_index = start; ui_index < length + start; ui_index++) + { + byte znak = data[ui_index]; + + /// Repeat 8 times + for (int h = 0; h < 8; h++) + { + int test = ((crc_result & 0x8000) != 0) ? 0x80 : 0; + + crc_result <<= 1; + + if (((znak & 0x80) ^ test) != 0) + { + crc_result ^= 0x1021; + } + znak <<= 1; + } + } + + return crc_result; + } + + /// + /// Checks whether there is a valid telegram frame in 'buffer' at 'offset'. + /// + /// Buffer with received bytes + /// Received bytes count (stored always at the beginning of the buffer) + /// Checked telegram frame offset + /// true when telegram frame fits received data + public static bool IsTelegramAt(byte[] buffer, int rcvdBytesCount, int offset) + { + /// It is guaranteed that (offset + MinTelegramLen < rcvdBytesCount) + int optDataLen = buffer[offset + 8]; + + return (buffer[offset] == (byte)Mark.Receive) && + (buffer[offset + 1] == (byte)Tel.TelFromAir || + buffer[offset + 1] == (byte)Tel.AlertMsg || + buffer[offset + 1] == (byte)Tel.AckOrAnswer) && + (offset + SirtUtils.MinTelegramLen + optDataLen < rcvdBytesCount) && + (buffer[offset + 11 + optDataLen] == (byte)Mark.Stop) && + (SirtUtils.CalculateCrcCcitt(buffer, offset + 1, optDataLen + 8) + == (UInt16)(256 * buffer[offset + 9 + optDataLen] + buffer[offset + 10 + optDataLen])); + } + + public static int GetTelegramLength(byte[] data, int offset = 0) + { + return (offset + 8 >= data.Length) ? 0 : MinTelegramLen + data[offset + 8]; + } + + public static UInt32 GetRadioAddress(byte[] data) + { + return (data.Length < MinTelegramLen) ? 0 : (UInt32)(((((data[4] << 8) + data[5]) << 8) + data[6]) << 8) + data[7]; + } + + public static string TelegramToString(byte[] data) + { + var sb = new StringBuilder(); + for (int i = 0; i < data.Length; i++) + { + sb.Append(string.Format("{0:X2} ", data[i])); + } + + return sb.ToString(); + } + + public static string GetPortSettingsStr(System.IO.Ports.SerialPort sp) + { + if (sp != null) + { + return string.Format("BaudRate={0} DataBits={1} Parity={2}, StopBits={3}", + sp.BaudRate, sp.DataBits, sp.Parity, sp.StopBits); + } + else + { + return "null"; + } + } + } +} diff --git a/TBF/BenchControl/Sirt/V2012_433Mhz/Factory.cs b/TBF/BenchControl/Sirt/V2012_433Mhz/Factory.cs new file mode 100644 index 000000000..2c13a5d25 --- /dev/null +++ b/TBF/BenchControl/Sirt/V2012_433Mhz/Factory.cs @@ -0,0 +1,24 @@ +/// +/// Copyright (c) 2021 Sensus Slovensko a.s. +/// +using System.Collections.Generic; +using TBF.BenchControl.Generic; + +namespace TBF.BenchControl.Sirt.V2012_433MHz +{ + public class Factory : IComponentFactory + { + public string ClassName { get { return GetType().Namespace.Substring(17); } } + + public IComponent DummyComponent() { return new Sirt(); } + + public IComponent GetComponent(IComponentCfg cfg, IList components) { return new Sirt(cfg); } + + public IComponentCfg DefaultConfig() { return new SirtCfg("SIRT", this); } + + public IComponentCfg CmpntCfgFromCmpntEntity(Config.Entities.Component component) + { + return ComponentCfgBase.CreateFromDbEntity(SirtCfg.Serializer, component, this); + } + } +} diff --git a/TBF/BenchControl/Sirt/V2012_433Mhz/Sirt.cs b/TBF/BenchControl/Sirt/V2012_433Mhz/Sirt.cs new file mode 100644 index 000000000..d1c5851fb --- /dev/null +++ b/TBF/BenchControl/Sirt/V2012_433Mhz/Sirt.cs @@ -0,0 +1,263 @@ +/// +/// Copyright (c) 2021 Sensus Slovensko a.s. +/// +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO.Ports; +using System.Text; +using System.Threading; +using Common; +using log4net; +using TBF.BenchControl.Generic; +using TBF.Boxes; + +namespace TBF.BenchControl.Sirt.V2012_433MHz +{ + /// + /// Root component for HART communication via a modem connected to a serial port (RS232) + /// + public class Sirt : ComponentBase, IDevice + { + private static readonly ILog log = LogManager.GetLogger(typeof(Sirt)); + public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); } + + const int ReceiveBufferSize = 8000; /// Should be larger then the internal buffer of the RS232 chip + + private readonly SirtCfg sirtCfg; + + /// Private fields + SerialPort serialPort; + byte[] receiveBuffer; + int receivedBytesCount; + DateTime lastSerialPortWrite; + + Dictionary fromAirDictionary; + Dictionary> alertMsgDictionary; + Dictionary ackOrAnswerDictionary; + + /// + /// An array of Queue-s of received telegrams fetched by HART components (as Nivotrack). + /// There is one queue for each device (each slave address). + /// + public Queue UnprocessedTelegrams + { + get { return unprocessedTelegrams; } + } + Queue unprocessedTelegrams; + + /// + /// A Queue of telegrams to send. System prevents sending more then one telegram per 500ms. + /// Function SendMessage( ) either send the telegram immediately using SendMessageNow( ) or + /// inserts the telegram into telegramsToSend queue. The queue is emptied in RunDeviceAfter( ). + /// + Queue telegramsToSend; + + + public Sirt() { } + + /// + /// HART modem connected via serial interface (RS232). + /// Connection settings: 1200 Bd 8-bits odd-parity 1-stop-bit Flow control: none or hardware. + /// + public Sirt(Generic.IComponentCfg cfg) + : base(cfg) + { + sirtCfg = cfg as SirtCfg; + } + + public override void Initialize() + { + receiveBuffer = new byte[ReceiveBufferSize]; + receivedBytesCount = 0; + + telegramsToSend = new Queue(); + unprocessedTelegrams = new Queue(); + + fromAirDictionary = new Dictionary(); + alertMsgDictionary = new Dictionary>(); + ackOrAnswerDictionary = new Dictionary(); + + if (sirtCfg.DebugLevel == DebugMode.Normal) + { + serialPort = new SerialPort + { + PortName = string.Format("COM{0}", sirtCfg.ComPortNr), + BaudRate = 115200, + Parity = Parity.None, + DataBits = 8, + StopBits = StopBits.One, + Handshake = Handshake.None, + }; + serialPort.Open(); + log.FatalFormat("{0} initialized: {1}", Name, this); + } + else + { + serialPort = null; + log.FatalFormat("{0} simulated: {1}", Name, this); + } + } + + public void RunDeviceBefore() + { + if (serialPort != null && serialPort.IsOpen) + { + ParseReceivedData(); + } + } + + public void RunDeviceAfter() + { + if (serialPort != null && serialPort.IsOpen) + { + /// send data from the queue + } + } + + public void StopDevice() + { + if (serialPort != null && serialPort.IsOpen) + { + serialPort.Close(); + } + } + + public void StopDevice2() { } + + + + public void SendMessageNow(byte[] message) + { + serialPort.Write(message, 0, message.Length); + lastSerialPortWrite = DateTime.Now; + + string s = Telegram.LogTelegram(string.Format("{0} - Sending message ", Name), message); + Debug.WriteLine(s); + log.Debug(s); + } + + /// + /// Send an arbitrary modbus message. + /// When the message length is N, however only bytes 1..N-2 have to be set. + /// The last two message bytes (CRC) may be uninitialized or zero. + /// They are calculated inside this function as required by Modbus specification. + /// + /// Message incl. a reserved checksum byte, checksum does not have to be set + public void SendMessage(int preambLen, byte[] message) + { + if (serialPort == null || !serialPort.IsOpen) return; + + } + + void ParseReceivedData() + { + int nrBytes = serialPort.BytesToRead; + if (nrBytes > 0) + { + /// Read available data + int rcvdCount = serialPort.Read(receiveBuffer, receivedBytesCount, nrBytes); + receivedBytesCount += rcvdCount; + + /// Determine count of leading FF-s + int offset = 0; /// Candidate start of a telegram + int lastValidTelegramEnd1 = 0; + + while (offset + SirtUtils.MinTelegramLen < receivedBytesCount) + { + if (SirtUtils.IsTelegramAt(receiveBuffer, receivedBytesCount, offset)) + { + /// Extract a telegram and delete it from the buffer + int telegramLen = SirtUtils.GetTelegramLength(receiveBuffer, offset); + byte[] telegram = new byte[telegramLen]; + Array.Copy(receiveBuffer, offset, telegram, 0, telegramLen); + offset += telegramLen; + lastValidTelegramEnd1 = offset; + + /// Process the telegram + UInt32 radioAddress = SirtUtils.GetRadioAddress(telegram); + if (telegram[1] == (byte)Tel.TelFromAir && telegram[2] == 0xA1 && telegramLen == 25) + { + if (fromAirDictionary.ContainsKey(radioAddress)) + fromAirDictionary[radioAddress] = telegram; + else + { + fromAirDictionary.Add(radioAddress, telegram); + + string s = string.Format("{0} - {1} is on air", Name, radioAddress); + Debug.WriteLine(s); + log.Debug(s); + } + } + else if (telegram[1] == (byte)Tel.AlertMsg) + { + if (alertMsgDictionary.ContainsKey(radioAddress)) + alertMsgDictionary[radioAddress].Add(telegram); + else + { + alertMsgDictionary.Add(radioAddress, new List { telegram }); + + string s = string.Format("{0} - Alarm(s) from {1}", Name, radioAddress); + Debug.WriteLine(s); + log.Debug(s); + } + } + else if (telegram[1] == (byte)Tel.AckOrAnswer) + { + byte[] requestTelegram; + if (ackOrAnswerDictionary.TryGetValue(radioAddress, out requestTelegram)) + { + ProcessRequestAndAnswer(requestTelegram, telegram); + ackOrAnswerDictionary.Remove(radioAddress); + } + } + else + { + unprocessedTelegrams.Enqueue(telegram); + + string s = Telegram.LogTelegram(string.Format("{0} - Unhandled telegram from {1}, len={2}, queueSz={3} : ", + Name, + SirtUtils.GetRadioAddress(telegram), + SirtUtils.GetTelegramLength(telegram), + unprocessedTelegrams.Count), telegram); + Debug.WriteLine(s); + log.Debug(s); + } + } + else + { + offset++; + } + } + + if (lastValidTelegramEnd1 > 0) + { + int dest = 0; + for (int src = lastValidTelegramEnd1; src < receivedBytesCount; src++) + { + receiveBuffer[dest++] = receiveBuffer[src]; + } + receivedBytesCount = dest; + } + } + } + + void ProcessRequestAndAnswer(byte[] request, byte[] answer) + { + string s = Telegram.LogTelegram(string.Format("{0} - Request sent to {1}, len={2} : ", + Name, + SirtUtils.GetRadioAddress(request), + SirtUtils.GetTelegramLength(request)), request); + Debug.WriteLine(s); + log.Debug(s); + + s = Telegram.LogTelegram(string.Format("{0} - Answer received from {1}, len={2} : ", + Name, + SirtUtils.GetRadioAddress(answer), + SirtUtils.GetTelegramLength(answer)), answer); + Debug.WriteLine(s); + log.Debug(s); + } + } +} diff --git a/TBF/BenchControl/Sirt/V2012_433Mhz/SirtCfg.cs b/TBF/BenchControl/Sirt/V2012_433Mhz/SirtCfg.cs new file mode 100644 index 000000000..32016619c --- /dev/null +++ b/TBF/BenchControl/Sirt/V2012_433Mhz/SirtCfg.cs @@ -0,0 +1,131 @@ +/// +/// Copyright (c) 2021 Sensus Slovensko a.s. +/// +using System; +using System.Collections.Generic; +using System.Xml.Serialization; +using Common; +using Config.Entities; +using TBF.BenchControl.Generic; + +namespace TBF.BenchControl.Sirt.V2012_433MHz +{ + public class SirtCfg : ComponentCfgBase, Generic.IComponentCfg, IParamsProvider + { + public static XmlSerializer Serializer = XmlSerializer.FromTypes(new[] { typeof(SirtCfg) })[0]; + public override XmlSerializer GetSerializer() { return Serializer; } + + + public IComponentCfgCtrl GetControl(IList cmpntEntities) + { + return new Configs.ParamsProvider.ComponentCfgCtrl(this, null); + } + + /// + /// Serialized parameters + /// + public int ComPortNr; + + /// Private parameterless constructor invoked by all other (public) constructors + SirtCfg() { } + + public SirtCfg(string name, IComponentFactory factory) + : this() + { + Name = name; + Factory = factory; + ParentName = string.Empty; + InitializeAll(); + } + + public string ComponentName { get { return Name; } } + + public void InitializeAll() + { + ComPortNr = 3; + } + string[] paramNames = new string[] + { + "Com port number", + }; + public string ParamName(int i) { return paramNames[i]; } + public int ParamsCount() { return paramNames.Length; } + + public ICollection ParamValues(int i) + { + return null; + } + + public string ToString(int i) + { + switch (i) + { + case 0: + return ComPortNr.ToString(); + default: + return string.Format("Com{0}", ComPortNr); + } + } + + + public CfgUpdateFlags UpdateParam(int i, string strValue) + { + switch (i) + { + case 0: + if (ComPortNr != int.Parse(strValue)) + { + ComPortNr = int.Parse(strValue); + return CfgUpdateFlags.RestartRqrd; + } + else + { + return CfgUpdateFlags.None; + } + + default: + return CfgUpdateFlags.None; + } + } + + + public bool ValidateParam(int i, string strValue, out string message) + { + message = string.Empty; + + switch (i) + { + case 0: + int comPortNr; + if (int.TryParse(strValue, out comPortNr) && comPortNr > 0 && comPortNr <= 999) return true; + break; + + default: + message = "Invalid index"; + return false; + } + + message = ParamName(i) + " is invalid"; + return false; + } + + void CopyContentTo(SirtCfg prms) + { + prms.ParentName = this.ParentName; + prms.ComPortNr = this.ComPortNr; + } + + + public IParamsProvider Clone() + { + SirtCfg pars = new SirtCfg(); + CopyContentTo(pars); + return pars; + } + + public bool UpdateEmbeddedDbEntity() + { + return true; /// =OK, do nothing + } + } +} diff --git a/TBF/BenchControl/TbfComponents.cs b/TBF/BenchControl/TbfComponents.cs index 5224927f9..ea66e05f8 100644 --- a/TBF/BenchControl/TbfComponents.cs +++ b/TBF/BenchControl/TbfComponents.cs @@ -190,7 +190,8 @@ namespace TBF.BenchControl Factories.Add(new Elde.RegulValveCoax.Factory()); Factories.Add(new Elde.RegulValveMilwaukee.Factory()); Factories.Add(new Elde.RegulValveTandem.RegulValveTandemFactory()); - Factories.Add(new Elde.TempMeter.TempMeterFactory()); + Factories.Add(new Sirt.V2012_433MHz.Factory()); + Factories.Add(new Elde.TempMeter.TempMeterFactory()); Factories.Add(new Elde.TempMeterInternal.TempMeterFactory()); Factories.Add(new Elde.TempMeterMeret.Factory()); /// Meret temp. meter connected to control board Modbus Factories.Add(new Elde.Valve.ValveFactory()); diff --git a/TBF/Properties/AssemblyInfo.cs b/TBF/Properties/AssemblyInfo.cs index 153f9cbaa..c025063a2 100644 --- a/TBF/Properties/AssemblyInfo.cs +++ b/TBF/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ using System.Runtime.InteropServices; // Build Number // Revision // -[assembly: AssemblyVersion("2.26.1771.0")] -[assembly: AssemblyFileVersion("2.26.1771.0")] +[assembly: AssemblyVersion("2.26.1774.0")] +[assembly: AssemblyFileVersion("2.26.1774.0")] diff --git a/TBF/TBF.csproj b/TBF/TBF.csproj index d9e1669a3..6b26d5b73 100644 --- a/TBF/TBF.csproj +++ b/TBF/TBF.csproj @@ -1280,6 +1280,10 @@ + + + + Form @@ -3837,7 +3841,9 @@ Users - + + +