SIRT component added, ver. 2.26.1774

This commit is contained in:
Milan Hanajik 2021-10-13 08:10:39 +02:00
parent e00e7556d6
commit 171aee4f1d
7 changed files with 626 additions and 4 deletions

View File

@ -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;
}
/// <summary>
/// Checks whether there is a valid telegram frame in 'buffer' at 'offset'.
/// </summary>
/// <param name="buffer">Buffer with received bytes</param>
/// <param name="rcvdBytesCount">Received bytes count (stored always at the beginning of the buffer)</param>
/// <param name="offset">Checked telegram frame offset</param>
/// <returns>true when telegram frame fits received data</returns>
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";
}
}
}
}

View File

@ -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<IComponent> 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);
}
}
}

View File

@ -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
{
/// <summary>
/// Root component for HART communication via a modem connected to a serial port (RS232)
/// </summary>
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<UInt32, byte[]> fromAirDictionary;
Dictionary<UInt32, IList<byte[]>> alertMsgDictionary;
Dictionary<UInt32, byte[]> ackOrAnswerDictionary;
/// <summary>
/// An array of Queue-s of received telegrams fetched by HART components (as Nivotrack).
/// There is one queue for each device (each slave address).
/// </summary>
public Queue<byte[]> UnprocessedTelegrams
{
get { return unprocessedTelegrams; }
}
Queue<byte[]> unprocessedTelegrams;
/// <summary>
/// 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( ).
/// </summary>
Queue<byte[]> telegramsToSend;
public Sirt() { }
/// <summary>
/// HART modem connected via serial interface (RS232).
/// Connection settings: 1200 Bd 8-bits odd-parity 1-stop-bit Flow control: none or hardware.
/// </summary>
public Sirt(Generic.IComponentCfg cfg)
: base(cfg)
{
sirtCfg = cfg as SirtCfg;
}
public override void Initialize()
{
receiveBuffer = new byte[ReceiveBufferSize];
receivedBytesCount = 0;
telegramsToSend = new Queue<byte[]>();
unprocessedTelegrams = new Queue<byte[]>();
fromAirDictionary = new Dictionary<UInt32, byte[]>();
alertMsgDictionary = new Dictionary<UInt32, IList<byte[]>>();
ackOrAnswerDictionary = new Dictionary<UInt32, byte[]>();
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);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="message">Message incl. a reserved checksum byte, checksum does not have to be set</param>
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<byte[]> { 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);
}
}
}

View File

@ -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<Config.Entities.Component> 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<string> 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
}
}
}

View File

@ -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());

View File

@ -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")]

View File

@ -1280,6 +1280,10 @@
<Compile Include="BenchControl\Sequences\Plotter.cs" />
<Compile Include="BenchControl\Sequences\Statistics.cs" />
<Compile Include="BenchControl\Sequences\ProcessData.cs" />
<Compile Include="BenchControl\Sirt\SirtUtils.cs" />
<Compile Include="BenchControl\Sirt\V2012_433Mhz\Factory.cs" />
<Compile Include="BenchControl\Sirt\V2012_433Mhz\Sirt.cs" />
<Compile Include="BenchControl\Sirt\V2012_433Mhz\SirtCfg.cs" />
<Compile Include="BenchControl\Telegram.cs" />
<Compile Include="BenchControl\TestMethods\Adjustment\WMErrorsForm12.cs">
<SubType>Form</SubType>
@ -3837,7 +3841,9 @@
<Name>Users</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Folder Include="BenchControl\Sirt\V2012_868MHz\" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.