omni progress

This commit is contained in:
Stoyan Zlatev
2023-11-09 08:59:09 +01:00
parent 9a22cb9d0e
commit 8796272107
109 changed files with 3831 additions and 2684 deletions
@@ -0,0 +1,98 @@
using OmniPlus.Enums;
using OmniPlus.Hanlers;
using System;
using System.IO.Ports;
using System.Threading;
namespace OmniPlus.Connections
{
internal class IrdaConnection : SerialPortConnection
{
protected event DataReceivedHandler MessageReceived;
public IrdaConnection(string portName)
{
this.Options.PortName = portName;
this.Options.BaudRate = BaudRate.K115200;
this.Options.DataBits = DataBits.X8;
this.Options.Parity = Parity.None;
this.Options.StopBits = StopBits.One;
this.Options.LogFile = $".\\IRDA\\[{portName}].txt";
}
public byte[] SendMessage(byte[] command)
{
var response = new byte[0];
var received = false;
if (command is null)
{
return response;
}
void MessageReceivedHandler(byte[] bytes)
{
response = bytes
.FromIrdaMessage()
.FromUI1236Message();
received = true;
}
this.MessageReceived += MessageReceivedHandler;
this.Write(1, 1, 1, 1);
var request = command
.ToUI1236Message()
.ToIrdaMessage();
var sleep = 5000;
var delay = 500;
this.Write(request);
while (!received && sleep >= 0)
{
sleep -= delay;
Thread.Sleep(delay);
}
this.MessageReceived -= MessageReceivedHandler;
return response;
}
protected override void SerialPortConnection_DataReceived(byte[] bytes)
{
var message = default(byte[]);
var length = bytes.Length;
var start = 0;
while (start < bytes.Length)
{
if (bytes[start] == OmniPlusExtensions.IRDA_START)
{
try
{
var messageLength = bytes[start + 2] * 2;
messageLength += OmniPlusExtensions.IRDA_MIN_LENGTH;
message = new byte[messageLength];
Array.Copy(bytes, start, message, 0, messageLength);
}
catch (Exception e)
{
this.SerialPortConnection_LogMessage(e.ToString());
}
break;
}
start++;
}
this.MessageReceived?.Invoke(message);
}
}
}
@@ -0,0 +1,209 @@
using OmniPlus.Enums;
using OmniPlus.Hanlers;
using OmniPlus.Options;
using System;
using System.IO;
using System.IO.Ports;
namespace OmniPlus.Connections
{
internal abstract class SerialPortConnection
{
protected event StateChangedEventHandler StateChanged;
protected event DataReceivedHandler DataReceived;
protected event LogMessageEventHandler LogMessage;
private SerialPort serialPort;
private SerialPortState state;
protected SerialPortConnection()
=> this.Options = new SerialPortOptions();
protected SerialPortOptions Options { get; }
public bool Connected => this.state == SerialPortState.Open;
public virtual void Close()
{
try
{
if (this.serialPort != null)
{
this.serialPort.DataReceived -= this.SerialPort_DataReceived;
this.serialPort.Disposed -= this.SerialPort_Disposed;
this.serialPort.ErrorReceived -= this.SerialPort_ErrorReceived;
this.serialPort.PinChanged -= this.SerialPort_PinChanged;
}
this.serialPort?.Close();
this.serialPort?.Dispose();
}
catch (Exception e)
{
this.StateChanged?.Invoke(SerialPortState.ErrorReceived, e.ToString());
}
this.serialPort = null;
this.state = SerialPortState.Closed;
this.StateChanged?.Invoke(this.state, $"{this.Options.PortName} closed.");
if (this.LogMessage != null)
{
foreach (var @delegate in this.LogMessage.GetInvocationList())
{
if (@delegate is LogMessageEventHandler eventHandler)
{
this.LogMessage -= eventHandler;
}
}
}
if (this.StateChanged != null)
{
foreach (var @delegate in this.StateChanged.GetInvocationList())
{
if (@delegate is LogMessageEventHandler eventHandler)
{
this.LogMessage -= eventHandler;
}
}
}
if (this.DataReceived != null)
{
foreach (var @delegate in this.DataReceived.GetInvocationList())
{
if (@delegate is LogMessageEventHandler eventHandler)
{
this.LogMessage -= eventHandler;
}
}
}
}
public virtual void Open()
{
this.Close();
try
{
var fileInfo = new FileInfo(this.Options.LogFile);
fileInfo.Directory.Create();
File.AppendAllText(this.Options.LogFile, string.Empty);
this.LogMessage += this.SerialPortConnection_LogMessage;
}
catch (Exception e)
{
this.state = SerialPortState.ErrorReceived;
this.StateChanged?.Invoke(this.state, $"{this.Options.PortName}: {e}");
}
try
{
this.serialPort = new SerialPort
{
PortName = this.Options.PortName,
BaudRate = (int)this.Options.BaudRate,
DataBits = (int)this.Options.DataBits,
Parity = this.Options.Parity,
StopBits = this.Options.StopBits,
RtsEnable = this.Options.RtsEnabled,
DtrEnable = this.Options.DtrEnabled
};
this.serialPort.DataReceived += this.SerialPort_DataReceived;
this.serialPort.Disposed += this.SerialPort_Disposed;
this.serialPort.ErrorReceived += this.SerialPort_ErrorReceived;
this.serialPort.PinChanged += this.SerialPort_PinChanged;
this.DataReceived += this.SerialPortConnection_DataReceived;
this.StateChanged += this.SerialPortConnection_StateChanged;
this.serialPort.Open();
this.state = SerialPortState.Open;
this.StateChanged?.Invoke(this.state
, $"{this.Options.PortName} opened"
+ $", {this.Options.BaudRate}"
+ $", {this.Options.DataBits}"
+ $", {this.Options.Parity}"
+ $", {this.Options.StopBits}");
}
catch (Exception e)
{
this.state = SerialPortState.ErrorReceived;
this.StateChanged?.Invoke(this.state, $"{this.Options.PortName}: {e}");
this.Close();
}
}
protected void Write(params byte[] message)
{
if (this.Connected && message.Length > 0)
{
this.serialPort.Write(message, 0, message.Length);
}
this.LogMessage?.Invoke($"TX: {BitConverter.ToString(message)}");
}
protected abstract void SerialPortConnection_DataReceived(byte[] bytes);
protected void SerialPortConnection_LogMessage(string message)
=> File.AppendAllText(this.Options.LogFile, $"{message}{Environment.NewLine}");
protected void SerialPortConnection_StateChanged(SerialPortState state, string message)
=> File.AppendAllText(this.Options.LogFile, $"[{state}] {message}{Environment.NewLine}");
private void SerialPort_PinChanged(object sender, SerialPinChangedEventArgs args)
{
this.state = SerialPortState.PinChanged;
this.StateChanged?.Invoke(this.state, $"{this.Options.PortName} {args.EventType}.");
this.Close();
}
private void SerialPort_ErrorReceived(object sender, SerialErrorReceivedEventArgs args)
{
this.state = SerialPortState.ErrorReceived;
this.StateChanged?.Invoke(this.state, $"{this.Options.PortName} {args.EventType}.");
this.Close();
}
private void SerialPort_Disposed(object sender, EventArgs args)
{
this.state = SerialPortState.Disposed;
this.StateChanged?.Invoke(this.state, $"{this.Options.PortName} disposed.");
this.Close();
}
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs args)
{
if (this.Connected && sender is SerialPort port && port.BytesToRead > 0)
{
var bytes = new byte[port.BytesToRead];
var lock_object = new object();
lock (lock_object)
{
port.Read(bytes, 0, port.BytesToRead);
}
this.DataReceived?.Invoke(bytes);
this.LogMessage?.Invoke($"RX: {BitConverter.ToString(bytes)}");
}
}
}
}
@@ -0,0 +1,92 @@
using OmniPlus.Enums;
using OmniPlus.Hanlers;
using System;
using System.IO.Ports;
using System.Threading;
namespace OmniPlus.Connections
{
internal class UniProConnection : SerialPortConnection
{
protected event DataReceivedHandler MessageReceived;
public UniProConnection(string portName)
{
this.Options.PortName = portName;
this.Options.BaudRate = BaudRate.K2400;
this.Options.DataBits = DataBits.X8;
this.Options.Parity = Parity.None;
this.Options.StopBits = StopBits.One;
this.Options.LogFile = $".\\UniPro\\[{portName}].txt";
}
public byte[] SendMessage(byte[] command)
{
var response = new byte[0];
var received = false;
if (command is null)
{
return response;
}
void MessageReceivedHandler(byte[] bytes)
{
response = bytes.FromUniProMessage();
received = true;
}
this.MessageReceived += MessageReceivedHandler;
var request = command.ToUniProMessage();
var sleep = 5000;
var delay = 500;
this.Write(request);
while (!received && sleep >= 0)
{
sleep -= delay;
Thread.Sleep(delay);
}
this.MessageReceived -= MessageReceivedHandler;
return response;
}
protected override void SerialPortConnection_DataReceived(byte[] bytes)
{
var message = default(byte[]);
var length = bytes.Length;
var start = 0;
while (start < bytes.Length)
{
if (bytes[start] == OmniPlusExtensions.UNIPRO_START)
{
try
{
var messageLength = bytes[start + 2];
messageLength += OmniPlusExtensions.UNIPRO_MIN_LENGTH;
message = new byte[messageLength];
Array.Copy(bytes, start, message, 0, messageLength);
}
catch (Exception e)
{
this.SerialPortConnection_LogMessage(e.ToString());
}
break;
}
start++;
}
this.MessageReceived?.Invoke(message);
}
}
}
+19
View File
@@ -0,0 +1,19 @@
namespace OmniPlus.Enums
{
public enum BaudRate
{
K300 = 300,
K600 = 600,
K1200 = 1200,
K2400 = 2400,
K4800 = 4800,
K9600 = 9600,
K19200 = 19200,
K38400 = 38400,
K57600 = 57600,
K115200 = 115200,
K230400 = 230400,
K460800 = 460800,
K921600 = 921600
}
}
+10
View File
@@ -0,0 +1,10 @@
namespace OmniPlus.Enums
{
public enum DataBits
{
X5 = 5,
X6 = 6,
X7 = 7,
X8 = 8
}
}
+17
View File
@@ -0,0 +1,17 @@
namespace OmniPlus.Enums
{
public enum MessurementUnits : byte
{
CubicMeters = 0,
CubicFeet = 1,
CubicInches = 2,
CubicYards = 3,
USGallons = 4,
ImperialGallons = 5,
AcreFeet = 6,
Kiloliters = 7,
Liters = 8,
WattHours = 9,
KilowattHours = 10,
}
}
+16
View File
@@ -0,0 +1,16 @@
namespace OmniPlus.Enums
{
public enum ReadingSettings : byte
{
/// <summary>
/// When forward and reverse volumes are supported,
/// this will be equal to the value of the expression (total forward volume) – (total reverse volume).
/// Use this value for sensors with no concept of forward or reverse.
/// </summary>
Totalizer = 0x00,
TotalForward = 0x01,
TotalReverse = 0x02,
LifetimeTotalizer = 0x03,
TestTotalizer = 0x04,
}
}
+11
View File
@@ -0,0 +1,11 @@
namespace OmniPlus.Enums
{
public enum SerialPortState
{
Closed,
Disposed,
PinChanged,
ErrorReceived,
Open
}
}
+52
View File
@@ -0,0 +1,52 @@
namespace OmniPlus.Enums
{
public enum Status : byte
{
/// <summary>
/// Used by communication devices to indicate communication failure/timeout.
/// Should not be used by a slave device implementing UI-1236.
/// </summary>
FailureOrTimeout = 0x00,
/// <summary>
/// Command complete, no errors
/// </summary>
Completed = 0x01,
/// <summary>
/// Error, unable to execute command
/// </summary>
UnableToExecute = 0x02,
/// <summary>
/// Error, unknown command
/// </summary>
UnknownCommand = 0x03,
/// <summary>
/// Error, unsupported control bit pattern
/// </summary>
UnsupportedControl = 0x04,
/// <summary>
/// Error, command blocked by factory seal.
/// Added in version B1.23
/// </summary>
BlockedBySeal = 0x05,
/// <summary>
/// Custom field.
/// </summary>
Request = 0x80,
/// <summary>
/// Frame received, no error
/// </summary>
FrameReceived = 0xFC,
/// <summary>
/// Message corrupt
/// </summary>
MessageCorrupt = 0,
/// <summary>
/// Wait status
/// </summary>
Waiting = 0xFE,
/// <summary>
/// Used by communication devices to indicate unrecognized communication device command.
/// </summary>
UnrecognizedDevice = 0xFF
}
}
+12
View File
@@ -0,0 +1,12 @@
namespace OmniPlus.Enums
{
public enum TimeUnits : byte
{
Milliseconds = 0,
Seconds = 1,
Minutes = 2,
Hours = 3,
Days = 4,
Years = 5,
}
}
+41
View File
@@ -0,0 +1,41 @@
namespace OmniPlus.Features
{
using System.Text;
public class ASCIIString : FeatureRequest
{
public ASCIIString(int length) : base(length)
{
}
public ASCIIString(byte[] response, int length) : base(response, length)
{
}
public string Value
{
get => this.GetValue();
set => this.SetValue(value);
}
private string GetValue()
=> Encoding.ASCII
.GetString(this.bytes)
.Trim();
private void SetValue(string value)
{
this.bytes.Clear();
var factoryID = value
?.Substring(0, this.length)
?? string.Empty;
Encoding.ASCII
.GetBytes(factoryID)
.CopyTo(this.bytes, 0);
}
}
}
@@ -0,0 +1,29 @@
namespace OmniPlus.Features
{
using System;
public class BatteryVoltage : FeatureRequest
{
const int LENGTH = 6;
public BatteryVoltage() : base(LENGTH)
{
}
public BatteryVoltage(byte[] response) : base(response, LENGTH)
{
}
public ushort Volts
{
get => BitConverter.ToUInt16(this.bytes, 0);
set => BitConverter.GetBytes(value).CopyTo(this.bytes, 0);
}
public uint ADCCount
{
get => BitConverter.ToUInt32(this.bytes, 2);
set => BitConverter.GetBytes(value).CopyTo(this.bytes, 2);
}
}
}
+19
View File
@@ -0,0 +1,19 @@
namespace OmniPlus.Features
{
/// <summary>
/// Build Information string must be at most 14 characters long. There are no restrictions on character type.
/// Command is not available after factory seal.
/// </summary>
public class BuildInfo : ASCIIString
{
const int LENGTH = 14;
public BuildInfo() : base(LENGTH)
{
}
public BuildInfo(byte[] response) : base(response, LENGTH)
{
}
}
}
@@ -0,0 +1,68 @@
using System;
namespace OmniPlus.Features
{
public class ChamberCalibration : FeatureRequest
{
public const byte ACTIVATE_TEST = 0x00;
public const byte REQUEST_RESULTS = 0x01;
public const byte TEST_ACTIVATED = 0x00;
public const byte TEST_FAILED = 0x01;
public const byte TEST_COMPLETED = 0x02;
const int LENGTH = 5;
public ChamberCalibration() : base(LENGTH)
{
}
public ChamberCalibration(byte[] response) : base(response, LENGTH)
{
}
/// <summary>
/// Request Response
/// 0 – Activate Factory Calibration Test Test Activation Successful
/// 1 – Request Factory Calibration Test Results Test Failed (Timeout)
/// 2 - ........................................ Test Successfully Completed
/// </summary>
public byte Control
{
get => this.bytes[0];
set => this.bytes[0] = value;
}
/// <summary>
/// <para>
/// Request: Number of Rotations to collect. (not required if Requesting Calibration results) (Limited to 1 to 5000 rotations)
/// </para>
/// <para>
/// Response: Number of rotations collected
/// On Test Activation, this field echoes the number of rotations requested.
/// On Test Failed and Test Successfully Completed, returns the number of rotations collected.
/// </para>
/// </summary>
public ushort Rotations
{
get => BitConverter.ToUInt16(this.bytes, 1);
set => BitConverter.GetBytes(value).CopyTo(this.bytes, 1);
}
/// <summary>
/// <para>
/// Request: Maximum Time command is supposed to take to execute - 1 to 3600 Seconds.
/// </para>
/// <para>
/// Response: Total Time it took to collect the indicated rotations. Each count is equal to 30.5176us.
/// On Test Activation Successful, returns number of seconds test is allowed to take.
/// On Test Failed, this field will show 0 indicating that the tested failed.
/// </para>
/// </summary>
public ushort Duration
{
get => BitConverter.ToUInt16(this.bytes, 3);
set => BitConverter.GetBytes(value).CopyTo(this.bytes, 3);
}
}
}
@@ -0,0 +1,60 @@
namespace OmniPlus.Features
{
using System;
public class ConfigurationParameters : FeatureRequest
{
const int LENGTH = 62;
public ConfigurationParameters() : base(LENGTH)
{
}
public ConfigurationParameters(byte[] response) : base(response, LENGTH)
{
this.ProgrammableID = new ProgrammableID();
this.ProgrammableID.Status = this.Status;
this.ProgrammableID.Value = this.bytes.ToASCIIString(0, 20);
this.FactoryID = new FactoryID();
this.FactoryID.Status = this.Status;
this.FactoryID.Value = this.bytes.ToASCIIString(21, 33);
this.ReadingDigits = new ReadingDigits();
this.ReadingDigits.Status = this.Status;
this.ReadingDigits.Count = this.bytes[34];
this.ReadingDigits.Offset = this.bytes[35];
this.Readings = new Readings();
this.Readings.Status = this.Status;
this.Readings.Value = this.bytes.ToASCIIString(36, 45);
this.OptionalUniDirFields = new OptionalUniDirFields();
this.OptionalUniDirFields.Status = this.Status;
this.OptionalUniDirFields.Past(this.bytes, 46);
this.AlartPersistencePeriod = this.bytes[50];
this.DataLogIntervalMinutes = BitConverter.ToUInt16(this.bytes, 51);
this.DeviceID = new DeviceID();
this.Readings.Status = this.Status;
this.Readings.Value = this.bytes.ToASCIIString(53, 61);
}
public ProgrammableID ProgrammableID { get; set; }
public FactoryID FactoryID { get; set; }
public ReadingDigits ReadingDigits { get; set; }
public Readings Readings { get; set; }
public OptionalUniDirFields OptionalUniDirFields { get; set; }
public byte AlartPersistencePeriod { get; set; }
public ushort DataLogIntervalMinutes { get; set; }
public DeviceID DeviceID { get; set; }
}
}
+19
View File
@@ -0,0 +1,19 @@
namespace OmniPlus.Features
{
/// <summary>
/// All printable characters except ';' (semicolon) and ',' (comma) are allowed.
/// </summary>
public class CustomerText : ASCIIString
{
const int LENGTH = 20;
public CustomerText() : base(LENGTH)
{
}
public CustomerText(byte[] response) : base(response, LENGTH)
{
}
}
}
+15
View File
@@ -0,0 +1,15 @@
namespace OmniPlus.Features
{
public class DeviceID : ASCIIString
{
const int LENGTH = 9;
public DeviceID() : base(LENGTH)
{
}
public DeviceID(byte[] response) : base(response, LENGTH)
{
}
}
}
+31
View File
@@ -0,0 +1,31 @@
namespace OmniPlus.Features
{
using System;
public class Epoch20000101 : FeatureRequest
{
static readonly DateTime epoch = new DateTime(2000, 01, 01, 00, 00, 00, DateTimeKind.Utc);
const int LENGTH = 4;
public Epoch20000101() : base(LENGTH)
{
}
public Epoch20000101(byte[] response) : base(response, LENGTH)
{
}
public uint SecondsSince
{
get => BitConverter.ToUInt32(this.bytes, 0);
set => BitConverter.GetBytes(value).CopyTo(this.bytes, 0);
}
public DateTime DateTime
{
get => epoch.AddSeconds(this.SecondsSince);
set => this.SecondsSince = (uint)(value - epoch).TotalSeconds;
}
}
}
+16
View File
@@ -0,0 +1,16 @@
namespace OmniPlus.Features
{
public class FactoryID : ASCIIString
{
const int LENGTH = 12;
public FactoryID() : base(LENGTH)
{
}
public FactoryID(byte[] response) : base(response, LENGTH)
{
}
}
}
@@ -0,0 +1,29 @@
namespace OmniPlus.Features
{
public class FeatureRequest : FeatureResponse
{
protected readonly int length;
public FeatureRequest(int length)
: base(length)
=> this.length = length;
public FeatureRequest(byte[] response, int length = 0)
: base(response, length)
=> this.length = length;
public byte[] this[params byte[] cmd]
{
get
{
var cmdLength = cmd.Length;
var request = new byte[this.length + cmdLength];
cmd.CopyTo(request, 0);
this.bytes.CopyTo(request, cmdLength);
return request;
}
}
}
}
@@ -0,0 +1,34 @@
using OmniPlus.Enums;
using System;
namespace OmniPlus.Features
{
public class FeatureResponse
{
protected readonly byte[] bytes;
public FeatureResponse(int length = 0)
=> this.bytes = new byte[length];
public FeatureResponse(byte[] response, int length = 0) : this(length)
{
if (response != null )
{
this.Status = (Status)response[0];
if (response.Length > 1 && response.Length >= length)
{
Array.Copy(response, 1, this.bytes, 0, length);
}
}
}
public Status Status { get; set; }
public bool Succeeded => this.Status == Status.Completed;
public static implicit operator byte[](FeatureResponse response)
=> response?.bytes ?? new byte[0];
}
}
+324
View File
@@ -0,0 +1,324 @@
namespace OmniPlus.Features
{
using static System.Collections.Specialized.BitVector32;
using System.Collections.Generic;
using System.Security.Claims;
using System.Security.Policy;
using System;
internal class Features
{
/// <summary>
/// The View Factory ID command shall cause the slave device to transmit the factory ID.
/// The CDU shall consist of the command byte.
/// The slave device shall respond by sending a message containing a status byte followed by the ASCII ID string
/// terminated by a NULL character.
/// <para>Note: This value is reported in the ;K field of UI-1203’s AMR string.</para>
/// </summary>
public const byte VIEW_FACTORY_ID = 0x01;
/// <summary>
/// The Set Factory ID command shall set the device ID.
/// This command shall only be executed at the factory.
/// The CDU shall consist of the one-byte command field followed by one to twelve ASCII alphanumeric characters.
/// The ID shall be NULL terminated.
/// The length of the ID string is device dependent.
/// <para>It is recommended that all devices implement this command, though it may be protected by a factory seal.</para>
/// </summary>
public const byte SET_FACTORY_ID = 0x02;
/// <summary>
/// The View Programmable ID command shall cause the slave device to return the customer ID.
/// The CDU shall consist of the command byte.
/// The slave device shall transmit a message block containing a NULL terminated ASCII string
/// representing the customer ID.
/// If the ID is NULL, a single NULL character (hex 00) shall be returned in the response string.
/// <para>Note: This is the ID that is always reported in the UI-1203 AMR string.</para>
/// </summary>
public const byte VIEW_PROGRAMMABLE_ID = 0x03;
/// <summary>
/// The Set Programmable ID command shall set the customer ID in the slave device.
/// The CDU shall consist of the one byte command code followed by a NULL terminated ASCII string
/// of 1-20 alphanumeric characters.
/// The ID shall be cleared and set to NULL if this command is sent with a string consisting
/// of a single NULL (hex 00) character.
/// <para>
/// Note: This sets the ID that is required to be reported in the UI-1203 AMR string.
/// In Fixed Length reading mode, the length is restricted to 8 characters.
/// In Variable Length reading mode, the ;I field is limited to 12 characters.
/// Some devices my reject long IDs based on the UI-1203 restrictions.
/// </para>
/// <para>It is recommended that all devices implement this command.</para>
/// </summary>
public const byte SET_PROGRAMMABLE_ID = 0x04;
/// <summary>
/// The View Version and Type command is used to determine the type of device being programmed.
/// The CDU shall consist of the command byte.
/// The slave device shall respond by sending data containing the protocol version, device type and software version.
/// The format of the data is three comma-delimited strings with a single NULL terminator.
/// [Protocol version],[Device type],[Software version]
/// Each string consists of a maximum of eight characters.
/// </summary>
public const byte VIEW_VERSION_AND_TYPE = 0x05;
/// <summary>
/// The View Customer Text command shall cause the slave device to return a string containing the customer text field.
/// The CDU shall consist of the command byte.
/// The slave device shall transmit a message block containing a NULL terminated ASCII string
/// representing the customer text field.
/// If the ID is NULL, a single NULL character (hex 00) shall be returned in the response string.
/// <para>Note: This is the value reported in the ;J field of the UI-1203 AMR string.</para>
/// </summary>
public const byte VIEW_CUSTOMER_TEXT = 0x07;
/// <summary>
/// The Set Customer Text command shall set the Customer Text field in the slave device.
/// The CDU shall consist of the one byte command code followed by a NULL terminated ASCII string
/// of 1-20 alphanumeric characters.
/// The Customer Text field shall be cleared and set to NULL if this command is sent with a string consisting
/// of a single NULL (hex 00) character.
/// <para>Note: This is the value reported in the ;J field of the UI-1203 AMR string.</para>
/// </summary>
public const byte SET_CUSTOMER_TEXT = 0x08;
/// <summary>
/// The View Reading Digits command shall cause the slave device to return the number of reading digits.
/// The slave device shall transmit a message block containing two 8-bit binary numbers:
/// <para>
/// 0: The first number represents the number of reading digits to report in the AMR string, from 4 to 9.
/// If 9, then the; NB field will be enabled in the AMR string.
/// </para>
/// <para>
/// 1: The second number represents the AMR Reading Offset,
/// which indicates the offset into the totalizer value where the AMR reading begins,
/// counting from the right.The value is either 0 or negative.A value of 0 means to discard no digits.
/// A value of -1 means to discard the least significant digit, etc.
/// </para>
/// <para>
/// For example, if the totalizer on the meter shows 876543210,
/// and the number of reading digits is 5 with an AMR Reading Offset of -2 (0xFE),
/// the AMR reading reported will be 65432.
/// </para>
/// <para>
/// Note that previously, the AMR Reading Offset was defined to be the number of digits to the right of the decimal point
/// being reported, but no known devices implemented this command that way,
/// since the position of the decimal point was unknown (electronically) on mechanical registers.
/// </para>
/// </summary>
public const byte VIEW_READING_DIGITS = 0x09;
/// <summary>
/// The Set Number of Reading Digits command shall set the number of reading digits reported in the AMR string.
/// The CDU shall consist of the one-byte command code followed by two 8-bit binary numbers.
/// The first octet represents the total number of digits in the reading string.
/// The second octet represents the AMR Reading Offset (see cmd 0x09).
/// </summary>
public const byte SET_READING_DIGITS = 0x0A;
/// <summary>
/// The View Reading Units command shall cause the slave device to return the reading units.
/// The CDU shall consist of the command byte.
/// The slave device shall transmit an eight bit binary number representing the reading units.
/// The table listing reading units and the associated number is contained in UI-1203.
/// On devices that do not know their reading units, the value 255 (0xFF) will be returned.
/// </summary>
public const byte VIEW_READING_UNITS = 0x0B;
/// <summary>
/// The Set Reading Units command shall set the reading units for the slave device.
/// The CDU shall consist of the one-byte command code followed by an eight bit binary number representing the reading units.
/// The table listing the reading units and the associated number is contained in UI-1203.
/// On some devices, sending the value 255 (0xFF) will disable the units subfield of the UI-1203 ; R field,
/// on others the resolution and units subfields will be disabled.On some devices,
/// sending the value 255 (0xFF) will result in an Unable to Execute response.
/// On many devices, there is no error checking on the value, and it is possible to set the value to something invalid.
/// </summary>
public const byte SET_READING_UNITS = 0x0C;
/// <summary>
/// The View Rate Units command shall cause the slave device to return the rate units.
/// The CDU shall consist of the command byte.
/// The slave device shall transmit two bytes representing the rate units.
/// The first byte represents the totalization units and the second byte represents the time units.
/// The table listing the rate units and the associated numbers is contained in UI-1203.
/// </summary>
public const byte VIEW_RATE_UNITS = 0x0D;
/// <summary>
/// The Set Rate Units command shall set the rate units for the slave device.
/// The CDU shall consist of the one byte command code followed by two eight bit binary numbers
/// representing the rate units.
/// The first byte represents the volume units and the second byte represents the time units.
/// The table listing the rate units and the associated numbers is contained in UI-1203.
/// </summary>
public const byte SET_RATE_UNITS = 0x0E;
/// <summary>
/// The View Reading Resolution command shall cause the slave device to return the resolution setting for the reading.
/// The CDU shall consist of the command byte.
/// The slave device shall respond by sending a signed eight bit binary number representing
/// the reading resolution in powers of ten.
/// The value returned is the resolution reported in the UI-1203 read response’s ;R field,
/// if the resolution subfield is enabled.
/// On some devices, if the resolution subfield is disabled in the UI-1203 read response,
/// this command will return -128 (0x80).
/// On other devices, the resolution of the currently selected AMR digits is returned.
/// </summary>
public const byte VIEW_READING_RESOLUTION = 0x0F;
/// <summary>
/// The Set Reading Resolution command shall set the slave device resolution for the reading.
/// The CDU consists of the command byte followed by a signed eight bit binary number representing
/// the reading resolution in powers of ten.
/// The maximum range of reading resolution is specified in UI-1203.
/// Use this command to enable the resolution subfield of the UI-1203 ; R field.
/// The value -128 (0x80) disables that subfield on most devices.On many devices,
/// there is no error checking on this value, and what is set is what will be reported in the ; R field,
/// even if it is incorrect.
/// If your goal is to change the resolution of the reported reading,
/// cmd 0x0A – Set Number of Reading Digits is most likely the command you need.
/// </summary>
public const byte SET_READING_RESOLUTION = 0x10;
/// <summary>
/// The View Rate Resolution command shall cause the slave device to return the resolution setting for the rate.
/// The CDU shall consist of the command byte.
/// The slave device will send a response message with a signed eight bit binary number representing
/// the rate resolution in powers of ten.
/// </summary>
public const byte VIEW_RATE_RESOLUTION = 0x11;
/// <summary>
/// The Set Rate Resolution command shall set the slave device resolution for rate.
/// The CDU consists of the command code followed by a signed eight bit binary number representing
/// the rate resolution in powers of ten.
/// The maximum range of rate resolution is specified in UI-1203.
/// </summary>
public const byte SET_RATE_RESOLUTION = 0x12;
/// <summary>
/// As of B1.23, there are two variants of this command:
/// If the CDU consists of only the command byte.
/// The slave device shall return a message block containing a NULL terminated ASCII string of one to eight characters.
/// This version of the command existed in B1.22.
/// In B1.23, the command was updated to allow for more than 8 digits and to support multiple sensors.
/// The format of the B1.23 version of the command is shown in the table below.The response will be a
/// null-terminated ASCII string.
/// </summary>
public const byte VIEW_READINGS = 0x13;
/// <summary>
/// As of B1.23, there are two variants of this command:
/// If the CDU consists of only the command byte.
/// The slave device shall return a message block containing a NULL terminated ASCII string of one to eight characters.
/// This version of the command existed in B1.22.
/// In B1.23, the command was updated to allow for more than 8 digits and to support multiple sensors.
/// The format of the B1.23 version of the command is shown in the table below.The response will be a
/// null-terminated ASCII string.
/// </summary>
public const byte SET_READING_PRESET = 0x14;
/// <summary>
/// The View Reading Mode command shall cause the slave device to return the current value of the reading mode.
/// The CDU shall consist of the command byte. The slave device shall return a message block
/// containing one byte binary number representing the reading mode.
/// If the reading mode is not one of the formats specified in the description of cmd 0x16,
/// the response for this command will be 0xFF to indicate Undefined.
/// Command 0x27 which was introduced with B1.23 is preferable to this command as it is more flexible.
/// </summary>
public const byte VIEW_READING_MODE = 0x15;
/// <summary>
/// The Set Reading Mode command shall set the unidirectional reading mode in a register.
/// The CDU shall consist of the command byte followed by a one byte binary number.
/// The number represents the unidirectional reading mode setting as shown below in table three.
/// Unidirectional reading modes and their respective fields are described
/// in the communication protocol document(s) for each device.
/// Some devices use different definitions than those found in the table above.
/// Most device report the; R field before the; I field.
/// Command 0x28 which was introduced with B1.23 is preferable to this command as it is more flexible.
/// </summary>
public const byte SET_READING_MODE = 0x16;
/// <summary>
/// The View Build Information command shall cause the slave device to return a string containing the build information.
/// The CDU shall consist of the command byte.
/// The slave device shall transmit a message block containing a NULL terminated ASCII string
/// representing the build information.
/// If the ID is NULL, a single NULL character (hex 00) shall be returned in the response string.
/// </summary>
public const byte VIEW_BUILD_INFORMATION = 0x17;
/// <summary>
/// The Set Build Information command shall set the build Information field in the slave device.
/// The CDU shall consist of the one byte command code followed by a NULL terminated ASCII string
/// of 1-14 alphanumeric characters.
/// The Build Info field shall be cleared and set to NULL if this command is sent with a string consisting of
/// a single NULL (hex 00) character.
/// Build information may vary from product to product.
/// Build information may include, but not be limited to, date, time, manufacturing facility code,
/// production line code, etc.
/// Each project should detail build information data in its respective communication protocol specification document.
/// </summary>
public const byte SET_BUILD_INFORMATION = 0x18;
public const byte DEVICE_SPECIFIC = 0xFD;
/// <summary>
/// Command is provided to allow the Factory to view multiple System Configurations with one
/// UI1236 command to save time during Factory Configuration.
/// </summary>
public const byte VIEW_SYSTEM_PARAMETERS = 0xC6;
/// <summary>
/// Command is provided to allow the Factory to view multiple Configurations with
/// one UI1236 command to save time during Factory Configuration.
/// Command is configured to take 0 to 1 parameters.
/// To keep the command backward compatible with previous versions of the command,
/// if no parameters are provided, the command will generate a response
/// with all of the response elements up to View Device ID String but no including the View ID String.
/// </summary>
public const byte VIEW_CONFIGURATION_PARAMETERS = 0xC7;
/// <summary>
/// View volume per pulse.
/// Volume per pulse is the amount of volume (in units of milliliters) that is added to the volume counter
/// when one pulse of a meter shaft magnet is detected.
/// (Commonly meter shaft magnets have two or four magnetic poles and produce four or eight pulses per full rotation.)
/// </summary>
public const byte VIEW_INPUT_VOLUME = 0x02;
/// <summary>
/// Set volume per pulse.
/// </summary>
public const byte SET_INPUT_VOLUME = 0x03;
/// <summary>
/// Command is used to test the accuracy of meter and to calibrate the Meter Register assembly.
/// This is a factory command and is only available when the Register is not Factory Sealed.
/// The time to execute the command is determined by the present flow on the meter when
/// the command was sent and how many rotations are requested to be collected.
/// Since the time is not known by the Register, it is up to the user to provide a maximum time the command will execute.
/// If the test does not complete by the maximum time, a failure response will be generated when the Test Results are requested.
/// Once the command is sent to execute the calibration procedure,
/// the user will receive a response indicating that the test has started and then the Register will perform the test.
/// While the test is being performed, the Register will be unresponsive until the test has completed.
/// The user must wait the appropriate time to before requesting the results.
/// Based on the flow rate and the number of samples it takes to complete the test,
/// the user should be able to calculate how long to wait before requesting results.
/// </summary>
public const byte CHAMBER_CALIBRATION = 0x67;
/// <summary>
/// System time (a.k.a. epoch time) is the number of seconds from midnight January 1, 2000 UTC.
/// </summary>
public const byte VIEW_SYSTEM_TIME = 0x10;
/// <summary>
/// After powering up system time starts at midnight January 2, 2000 UTC/GMT; i.e. 86400 seconds from January 1, 2000 epoch.
/// System time can be advanced forward, but cannot be changed into the past.
/// Setting system time to 0 while the register is not sealed causes the following to happen:
/// <para>• Volume data log is cleared</para>
/// <para>• Epoch time and volume reading are cleared</para>
/// <para>• Periodic alarm log is cleared</para>
/// <para>• Alarm counters are cleared</para>
/// <para>• Processor is reset(command times out).</para>
/// <para>• Upon reset system time is 86400 seconds(24 hours, January 2, 2000)</para>
/// When system time is successfully changed by this command (except when it is set to 0),
/// a new boot recovery record is written into flash.This way time,
/// current volume reading and alarm counts can be recovered if Set System Time is followed by a reboot.
/// </summary>
public const byte SET_SYSTEM_TIME = 0x11;
}
}
+43
View File
@@ -0,0 +1,43 @@
using System;
namespace OmniPlus.Features
{
public class InputVolume : FeatureRequest
{
const int LENGTH = 6;
public InputVolume() : base(LENGTH)
{
}
public InputVolume(byte[] response) : base(response, LENGTH)
{
}
public ushort IntPart
{
get => BitConverter.ToUInt16(this.bytes, 0);
set => BitConverter.GetBytes(value).CopyTo(this.bytes, 0);
}
public ushort Fraction
{
get => BitConverter.ToUInt16(this.bytes, 2);
set => BitConverter.GetBytes(value).CopyTo(this.bytes, 2);
}
public byte Size
{
get => this.bytes[4];
set => this.bytes[4] = value;
}
public byte Range
{
get => this.bytes[5];
set => this.bytes[5] = value;
}
public int Factor { get; set; } = 100;
}
}
@@ -0,0 +1,6 @@
namespace OmniPlus.Features
{
public class ManifacturingTime : Epoch20000101
{
}
}
@@ -0,0 +1,27 @@
namespace OmniPlus.Features
{
using System;
/// <summary>
/// TODO: Need more investigation of the purpose
/// </summary>
public class OptionalUniDirFields : FeatureRequest
{
const int LENGTH = 4;
public OptionalUniDirFields() : base(LENGTH)
{
}
public OptionalUniDirFields(byte[] response) : base(response, LENGTH)
{
}
public void Past(byte[] bytes, int start)
{
this.bytes.Clear();
Array.Copy(bytes, start, this.bytes, 0, 4);
}
}
}
@@ -0,0 +1,18 @@
namespace OmniPlus.Features
{
using System.Text;
public class ProgrammableID : ASCIIString
{
const int LENGTH = 20;
public ProgrammableID() : base(LENGTH)
{
}
public ProgrammableID(byte[] response) : base(response, LENGTH)
{
}
}
}
@@ -0,0 +1,47 @@
using System;
namespace OmniPlus.Features
{
/// <summary>
/// Rate resolution depends on the reading units and the size of the meter.
/// The customer is allowed to change the units of measure but they are not allowed to change Meter Range
/// which indicates the actual size of the meter.
/// </summary>
public class RateResolution : FeatureRequest
{
const int LENGTH = 2;
public RateResolution() : base(LENGTH)
{
}
public RateResolution(byte[] response) : base(response, LENGTH)
{
}
public sbyte Value
{
get => (sbyte)BitConverter.ToUInt16(this.bytes, 0);
set => BitConverter
.GetBytes(value)
.CopyTo(this.bytes, 0);
}
}
}
/*
* TODO: Implement the rate resolution table below:
* ------------------------------------------------------------------
* Meter Range Cubic Meters Cubic Feet Gallons Acre Feet
* ------------------------------------------------------------------
* m³/h cf/min gal/min
* igal/min
* ------------------------------------------------------------------
* 1 ½” or 2” -3 -3 -3 Not Supported
* 3” or 4” Extended -3 -3 -3 Not Supported
* 3” or 4” Normal -3 -3 -3 Not Supported
* 6” -3 -2 -2 Not Supported
* 8” -2 -2 -2 Not Supported
* 10” -2 -2 -2 Not Supported
*/
+52
View File
@@ -0,0 +1,52 @@
namespace OmniPlus.Features
{
using OmniPlus.Enums;
/// <summary>
/// Omni+ flow rate units are independent of the reading units.
/// <para>Note: Acre Feet per Hour is not supported.</para>
/// </summary>
public class RateUnits : FeatureRequest
{
const int LENGTH = 2;
public RateUnits() : base(LENGTH)
{
}
public RateUnits(byte[] response) : base(response, LENGTH)
{
}
public MessurementUnits Flow
{
get => (MessurementUnits)this.bytes[0];
set
{
var flow = (byte)MessurementUnits.CubicMeters;
var time = (byte)TimeUnits.Hours;
switch (value)
{
case MessurementUnits.CubicFeet:
flow = (byte)value;
time = (byte)TimeUnits.Minutes;
break;
case MessurementUnits.USGallons:
flow = (byte)value;
time = (byte)TimeUnits.Minutes;
break;
case MessurementUnits.ImperialGallons:
flow = (byte)value;
time = (byte)TimeUnits.Minutes;
break;
}
this.bytes[0] = flow;
this.bytes[1] = time;
}
}
public TimeUnits Time => (TimeUnits)this.bytes[1];
}
}
+27
View File
@@ -0,0 +1,27 @@
namespace OmniPlus.Features
{
public class ReadingDigits : FeatureRequest
{
const int LENGTH = 2;
public ReadingDigits() : base(LENGTH)
{
}
public ReadingDigits(byte[] response) : base(response, LENGTH)
{
}
public byte Count
{
get => this.bytes[0];
set => this.bytes[0] = value;
}
public byte Offset
{
get => this.bytes[1];
set => this.bytes[1] = value;
}
}
}
+28
View File
@@ -0,0 +1,28 @@
namespace OmniPlus.Features
{
/// <summary>
/// Response will either indicate Fixed or Undefined (like ally). Use cmd 0x27 instead.
/// Fixed mode requires a Programmable ID of exactly 8 characters and exactly 4 AMR digits.
/// If Programmable ID or number of AMR digits is invalid, command will return Unable to Execute.
/// The following actions will cause the register to exit Fixed mode:
/// Setting the reading string fields with cmd 0x28 will exit fixed mode.
/// Changing the Programmable ID to a length other than exactly 8 characters will exit fixed mode.
/// Changing the number AMR digits to a value other than 4 will exit fixed mode.
/// If programmable ID or number of AMR digits is set to something invalid for fixed mode,
/// then to re - enter fixed mode requires correcting the changed parameter and sending command 0x16 Set Reading Mode again.
/// </summary>
public class ReadingMode : FeatureRequest
{
const int LENGTH = 13;
public ReadingMode() : base(LENGTH)
{
}
public ReadingMode(byte[] response) : base(response, LENGTH)
{
}
// TODO: To be implemented.
}
}
+39
View File
@@ -0,0 +1,39 @@
using OmniPlus.Enums;
namespace OmniPlus.Features
{
/// <summary>
/// Versions B1.24 of this command are supported. Total Forward and Total Reverse are not supported.
/// Absence of command payload(i.e.of the reading preset) clears the Totalizer reading unless
/// Display View is set to Totalizer Test View which sets the Test Totalizer Counter to 0.
/// Command will support setting Totalizer Value, Test Totalizer Value and the Lifetime Totalizer value
/// while the device is not factory sealed.
/// If the device is factory sealed, the Lifetime Totalizer is prevented from being changed
/// and Test Totalizer is limited to setting it to 0.
/// The Set Reading Preset command shall send the value to be used as the initial reading.
/// The CDU consists of a one-byte command code followed by a NULL terminated ASCII string of one to eight characters
/// representing the preset reading value.This value is assumed to be in the current units used for totalization.
/// In protocol version B1.23, 9 digits are allowed on devices that support it.
/// Some devices that implement B1.22 may also support a 9-digit null-terminated string.
/// </summary>
public class ReadingPreset : FeatureRequest
{
const int LENGTH = 2;
public ReadingPreset() : base(LENGTH)
{
}
public byte SensorID
{
get => this.bytes[0];
set => this.bytes[0] = value;
}
public ReadingSettings Setting
{
get => (ReadingSettings)this.bytes[1];
set => this.bytes[1] = (byte)value;
}
}
}
@@ -0,0 +1,42 @@
using System;
namespace OmniPlus.Features
{
/// <summary>
/// Reading Resolution of the device depends on reading units and the size of the Meter.
/// The customer is allowed to change the Units of measure but they are not able to change
/// the Reading Resolution for the Meter Range.
/// </summary>
public class ReadingResolution : FeatureRequest
{
const int LENGTH = 2;
public ReadingResolution() : base(LENGTH)
{
}
public ReadingResolution(byte[] response) : base(response, LENGTH)
{
}
public sbyte Value
{
get => (sbyte)BitConverter.ToUInt16(this.bytes, 0);
set => BitConverter
.GetBytes(value)
.CopyTo(this.bytes, 0);
}
}
}
/*
* TODO: Implement the reading resolution table below:
* -----------------------------------------------------------------------------------------------
* Meter Range Cubic Meters Cubic Feet Imperial Gallons Gallons Acre Feet
* -----------------------------------------------------------------------------------------------
* 1 ½” or 2” -3 -2 0 0 -5
* 3” or 4” Extended -2 -2 0 0 -5
* 3” or 4” Normal -2 0 0 0 -5
* 6” -2 0 0 0 -5
* 8” -2 0 0 0 -4
*/
+31
View File
@@ -0,0 +1,31 @@
using OmniPlus.Enums;
namespace OmniPlus.Features
{
/// <summary>
/// 0xFF in the reading units byte does not change the reading units.
/// 0xFF disables reporting of multiplier and units in the reading and flowrate uni-directional fields.
/// Setting reading units to one of the actual supported values,
/// enables reporting of multiplier and units in the reading and flowrate uni-directional fields.
/// Reading Resolution is based on Meter Range and Units of Measure.
/// Omni+ does not support a separate command to set reading resolution.
/// </summary>
public class ReadingUnits : FeatureRequest
{
const int LENGTH = 1;
public ReadingUnits() : base(LENGTH)
{
}
public ReadingUnits(byte[] response) : base(response, LENGTH)
{
}
public MessurementUnits Value
{
get => (MessurementUnits)this.bytes[0];
set => this.bytes[0] = (byte)value;
}
}
}
+52
View File
@@ -0,0 +1,52 @@
using OmniPlus.Enums;
using System.Text;
namespace OmniPlus.Features
{
/// <summary>
/// Versions B1.24 of this command are supported. Total Forward and Total Reverse are not supported.
/// <para>
/// If sensor filed is set to 0, command supports viewing Lifetime Totalizer Value(Setting of 3)
/// and Test Totalizer Value(Setting of 4). If reading the Life Time totalizer value,
/// the number of digits reported will be increased to 13 digits to support a larger Reading.
/// </para>
/// <para>
/// Requesting a reading from an invalid sensor ID or reading type will result in an “Unable to execute” response.
/// </para>
/// <para>
/// Omni supports the B1.24 version of the command which allows additional functionality
/// if more data is supplied in the payload. If 2 or more bytes of the payload are provided,
/// the first byte is used to indicate the sensor with 0 being a volume sensor.
/// The second byte of the payload is which one of the specified type of sensor is being read.
/// If a third byte of the payload is provided, the command will also provide the multiplier resolution of the sensor reading.
/// Omni only supports the Volume Sensor.
/// </para>
/// <para>
/// Note: Because of the size of the Meter,
/// the Life Time Totalizer may exceed the reportable range using the Resolution set for the Totalizer value.
/// When the Life Time Totalizer value exceeds 12 reportable digits and the Resolution is not 0,
/// the Resolution will be reduced until either the whole part of the Life Time Totalizer is reported
/// or until there are no more Resolution steps to reduce the reading at which time the number
/// will be truncated to 12 digits.
/// </para>
/// </summary>
public class Readings : ASCIIString
{
const int LENGTH = 8;
public Readings() : base(LENGTH)
{
}
public Readings(byte[] response) : base(response, LENGTH)
{
}
public byte SensorID => this.bytes[0];
public ReadingSettings Setting => (ReadingSettings)this.bytes[1];
public bool Include => this.bytes[2] == 1;
}
}
@@ -0,0 +1,41 @@
namespace OmniPlus.Features
{
using System;
public class RegisterSettings : FeatureRequest
{
const int LENGTH = 13;
public RegisterSettings() : base(LENGTH)
{
}
public RegisterSettings(byte[] response) : base(response, LENGTH)
{
}
public ushort IntPart
{
get => BitConverter.ToUInt16(this.bytes, 0);
set => BitConverter.GetBytes(value).CopyTo(this.bytes, 0);
}
public ushort Fraction
{
get => BitConverter.ToUInt16(this.bytes, 2);
set => BitConverter.GetBytes(value).CopyTo(this.bytes, 2);
}
public byte Size
{
get => this.bytes[4];
set => this.bytes[4] = value;
}
public byte Range
{
get => this.bytes[5];
set => this.bytes[5] = value;
}
}
}
@@ -0,0 +1,60 @@
namespace OmniPlus.Features
{
using System;
public class SystemParameters : FeatureRequest
{
const int LENGTH = 59;
public SystemParameters() : base(LENGTH)
{
this.BuildInfo = new BuildInfo();
this.ManifacturingTime = new ManifacturingTime();
this.SystemTime = new SystemTime();
this.VersionAndType = new VersionAndType();
this.BatteryVoltage = new BatteryVoltage();
}
public SystemParameters(byte[] response) : base(response, LENGTH)
{
this.BuildInfo = new BuildInfo();
this.BuildInfo.Status = this.Status;
this.BuildInfo.Value = this.bytes.ToASCIIString(0, 15);
this.ManifacturingTime = new ManifacturingTime();
this.ManifacturingTime.Status = this.Status;
this.ManifacturingTime.SecondsSince = BitConverter.ToUInt32(this.bytes, 16);
this.SystemTime = new SystemTime();
this.SystemTime.Status = this.Status;
this.SystemTime.SecondsSince = BitConverter.ToUInt32(this.bytes, 20);
this.RebootsCount = this.bytes[24];
this.VersionAndType = new VersionAndType();
this.VersionAndType.Status = this.Status;
this.VersionAndType.Value = this.bytes.ToASCIIString(25, 51);
this.BatteryVoltage = new BatteryVoltage();
this.BatteryVoltage.Status = this.Status;
this.BatteryVoltage.Volts = BitConverter.ToUInt16(this.bytes, 52);
this.BatteryVoltage.ADCCount = BitConverter.ToUInt32(this.bytes, 54);
this.FactorySealed = this.bytes[58] == 1;
}
public BuildInfo BuildInfo { get; set; }
public ManifacturingTime ManifacturingTime { get; set; }
public SystemTime SystemTime { get; set; }
public byte RebootsCount { get; set; }
public VersionAndType VersionAndType { get; set; }
public BatteryVoltage BatteryVoltage { get; set; }
public bool FactorySealed { get; set; }
}
}
+13
View File
@@ -0,0 +1,13 @@
namespace OmniPlus.Features
{
public class SystemTime : Epoch20000101
{
public SystemTime()
{
}
public SystemTime(byte[] response) : base(response)
{
}
}
}
@@ -0,0 +1,25 @@
namespace OmniPlus.Features
{
/// <summary>
/// Protocol version is at least "R1.23".
/// Device Type ID String is "OMNI-01".
/// Software version follows the NA2W FlexNet format; e.g. "R1.0.0A".
/// <para>
/// Note: In R0.3.14 firmware the Device type string is no longer hardcoded to OMNI-01 in the firmware.
/// Now the Factory can program a different string based on if it is a T2 or R2 variant.
/// </para>
/// </summary>
public class VersionAndType : ASCIIString
{
const int LENGTH = 12;
public VersionAndType() : base(LENGTH)
{
}
public VersionAndType(byte[] response) : base(response, LENGTH)
{
}
}
}
@@ -0,0 +1,4 @@
namespace OmniPlus.Hanlers
{
internal delegate void DataReceivedHandler(byte[] bytes);
}
@@ -0,0 +1,4 @@
namespace OmniPlus.Hanlers
{
internal delegate void LogMessageEventHandler(string message);
}
@@ -0,0 +1,4 @@
namespace OmniPlus.Hanlers
{
internal delegate byte[] MessageHandler(params byte[] bytes);
}
@@ -0,0 +1,6 @@
using OmniPlus.Enums;
namespace OmniPlus.Hanlers
{
internal delegate void StateChangedEventHandler(SerialPortState state, string reason);
}
+105
View File
@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{F8D84B9A-0FCF-4083-B067-8C39B7314152}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>OmniPlus</RootNamespace>
<AssemblyName>OmniPlus</AssemblyName>
<TargetFrameworkVersion>v4.8.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Connections\IrdaConnection.cs" />
<Compile Include="Enums\MessurementUnits.cs" />
<Compile Include="Enums\ReadingSettings.cs" />
<Compile Include="Enums\Status.cs" />
<Compile Include="Enums\TimeUnits.cs" />
<Compile Include="Features\ASCIIString.cs" />
<Compile Include="Features\BatteryVoltage.cs" />
<Compile Include="Features\BuildInfo.cs" />
<Compile Include="Features\ChamberCalibration.cs" />
<Compile Include="Features\ConfigurationParameters.cs" />
<Compile Include="Features\CustomerText.cs" />
<Compile Include="Features\DeviceID.cs" />
<Compile Include="Features\Epoch20000101.cs" />
<Compile Include="Features\FactoryID.cs" />
<Compile Include="Features\Features.cs" />
<Compile Include="Features\FeatureRequest.cs" />
<Compile Include="Features\FeatureResponse.cs" />
<Compile Include="Features\InputVolume.cs" />
<Compile Include="Features\ManifacturingTime.cs" />
<Compile Include="Features\OptionalUniDirFields.cs" />
<Compile Include="Features\ProgrammableID.cs" />
<Compile Include="Features\RateResolution.cs" />
<Compile Include="Features\RateUnits.cs" />
<Compile Include="Features\ReadingDigits.cs" />
<Compile Include="Features\ReadingMode.cs" />
<Compile Include="Features\ReadingPreset.cs" />
<Compile Include="Features\Readings.cs" />
<Compile Include="Features\ReadingResolution.cs" />
<Compile Include="Features\ReadingUnits.cs" />
<Compile Include="Features\RegisterSettings.cs" />
<Compile Include="Features\SystemParameters.cs" />
<Compile Include="Features\SystemTime.cs" />
<Compile Include="Features\VersionAndType.cs" />
<Compile Include="OmniPlusConnection.cs" />
<Compile Include="Connections\SerialPortConnection.cs" />
<Compile Include="Connections\UniProConnection.cs" />
<Compile Include="Enums\BaudRate.cs" />
<Compile Include="Enums\DataBits.cs" />
<Compile Include="Enums\SerialPortState.cs" />
<Compile Include="Hanlers\DataReceivedHandler.cs" />
<Compile Include="Hanlers\LogMessageHandler.cs" />
<Compile Include="Hanlers\MessageHandler.cs" />
<Compile Include="Hanlers\StateChangedHandler.cs" />
<Compile Include="OmniPlusConnectionsPool.cs" />
<Compile Include="OmniPlusDatabase.cs" />
<Compile Include="OmniPlusExtensions.cs" />
<Compile Include="OmniPlusInspectionHeaders.cs" />
<Compile Include="OmniPlusInspectionPoint.cs" />
<Compile Include="OmniPlusMeter.cs" />
<Compile Include="OmniPlusStateChangedEventHandler.cs" />
<Compile Include="Options\OmniControlOptions.cs" />
<Compile Include="Options\SerialPortOptions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NamedPipes\NamedPipes.csproj">
<Project>{0CC5A97F-E1CD-412B-AFFE-B256FDB7DC19}</Project>
<Name>NamedPipes</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
+276
View File
@@ -0,0 +1,276 @@
using OmniPlus.Features;
using OmniPlus.Hanlers;
using System;
using static OmniPlus.Features.Features;
namespace OmniPlus.Connections
{
public class OmniPlusConnection
{
private readonly IrdaConnection irdaConnection;
private readonly UniProConnection uniProConnection;
public OmniPlusConnection(string portName)
{
this.irdaConnection = new IrdaConnection(portName);
this.uniProConnection = new UniProConnection(portName);
}
internal MessageHandler MessageHandler;
public bool ConnectIrda()
{
this.uniProConnection.Close();
this.irdaConnection.Open();
this.MessageHandler = this.irdaConnection.SendMessage;
return this.irdaConnection.Connected;
}
public bool ConnectUniPro()
{
this.irdaConnection.Close();
this.uniProConnection.Open();
this.MessageHandler = this.uniProConnection.SendMessage;
return this.irdaConnection.Connected;
}
public void Disconnect()
{
this.uniProConnection.Close();
this.irdaConnection.Close();
}
public FactoryID ViewFactoryID()
{
var response = this.MessageHandler.Invoke(VIEW_FACTORY_ID);
return new FactoryID(response);
}
public FeatureResponse SetFactoryID(FactoryID factoryID)
{
var response = this.MessageHandler.Invoke(factoryID[SET_FACTORY_ID]);
return new FeatureResponse(response);
}
public ProgrammableID ViewProgrammableID()
{
var response = this.MessageHandler.Invoke(VIEW_PROGRAMMABLE_ID);
return new ProgrammableID(response);
}
public FeatureResponse SetProgrammableID(ProgrammableID programmableID)
{
var response = this.MessageHandler.Invoke(programmableID[SET_PROGRAMMABLE_ID]);
return new FeatureResponse(response);
}
public VersionAndType ViewVersionAndType()
{
var response = this.MessageHandler.Invoke(VIEW_VERSION_AND_TYPE);
return new VersionAndType(response);
}
public CustomerText ViewCustomerText()
{
var response = this.MessageHandler.Invoke(VIEW_CUSTOMER_TEXT);
return new CustomerText(response);
}
public FeatureResponse SetCustomerText(CustomerText customerText)
{
var response = this.MessageHandler.Invoke(customerText[SET_CUSTOMER_TEXT]);
return new FeatureResponse(response);
}
public ReadingDigits ViewReadingDigits()
{
var response = this.MessageHandler.Invoke(VIEW_READING_DIGITS);
return new ReadingDigits(response);
}
public FeatureResponse SetReadingDigits(ReadingDigits readingDigits)
{
var response = this.MessageHandler.Invoke(readingDigits[SET_READING_DIGITS]);
return new FeatureResponse(response);
}
public ReadingUnits ViewReadingUnits()
{
var response = this.MessageHandler.Invoke(VIEW_READING_UNITS);
return new ReadingUnits(response);
}
public FeatureResponse SetReadingUnits(ReadingUnits readingUnits)
{
var response = this.MessageHandler.Invoke(readingUnits[SET_READING_UNITS]);
return new FeatureResponse(response);
}
public RateUnits ViewRateUnits()
{
var response = this.MessageHandler.Invoke(VIEW_RATE_UNITS);
return new RateUnits(response);
}
public FeatureResponse SetRateUnits(RateUnits rateUnits)
{
var response = this.MessageHandler.Invoke(rateUnits[SET_RATE_UNITS]);
return new FeatureResponse(response);
}
public ReadingResolution ViewReadingResolution()
{
var response = this.MessageHandler.Invoke(VIEW_READING_RESOLUTION);
return new ReadingResolution(response);
}
public FeatureResponse SetReadingResolution(ReadingResolution readingResolution)
{
var response = this.MessageHandler.Invoke(readingResolution[SET_READING_RESOLUTION]);
return new FeatureResponse(response);
}
public RateResolution ViewRateResolution()
{
var response = this.MessageHandler.Invoke(VIEW_RATE_RESOLUTION);
return new RateResolution(response);
}
public FeatureResponse SetRateResolution(RateResolution rateResolution)
{
var response = this.MessageHandler.Invoke(rateResolution[SET_READING_RESOLUTION]);
return new FeatureResponse(response);
}
public Readings ViewReadings(Readings reading)
{
var response = this.MessageHandler.Invoke(VIEW_READINGS);
return new Readings(response);
}
public FeatureResponse SetReadingPreset(ReadingPreset readingPreset)
{
var response = this.MessageHandler.Invoke(readingPreset[SET_READING_PRESET]);
return new FeatureResponse(response);
}
public ReadingMode ViewReadingMode()
{
var response = this.MessageHandler.Invoke(VIEW_READING_MODE);
return new ReadingMode(response);
}
public FeatureResponse SetReadingMode(ReadingMode readingMode)
{
var response = this.MessageHandler.Invoke(readingMode[SET_READING_MODE]);
return new FeatureResponse(response);
}
public BuildInfo ViewBuildInformation()
{
var response = this.MessageHandler.Invoke(VIEW_BUILD_INFORMATION);
return new BuildInfo(response);
}
public FeatureResponse SetBuildInformation(BuildInfo buildInformation)
{
var response = this.MessageHandler.Invoke(buildInformation[SET_BUILD_INFORMATION]);
return new FeatureResponse(response);
}
public SystemParameters ViewSystemParameters()
{
var response = this.MessageHandler.Invoke(DEVICE_SPECIFIC, VIEW_SYSTEM_PARAMETERS);
return new SystemParameters(response);
}
public ConfigurationParameters ViewConfigurationParameters()
{
var response = this.MessageHandler.Invoke(DEVICE_SPECIFIC, VIEW_CONFIGURATION_PARAMETERS, 0x01);
return new ConfigurationParameters(response);
}
public InputVolume SetInputVolume()
{
var response = this.MessageHandler.Invoke(DEVICE_SPECIFIC, VIEW_INPUT_VOLUME);
return new InputVolume(response);
}
public FeatureResponse SetInputVolume(InputVolume inputVolume)
{
var response = this.MessageHandler.Invoke(inputVolume[DEVICE_SPECIFIC, SET_INPUT_VOLUME]);
return new FeatureResponse(response);
}
public ChamberCalibration StartChamberCalibration(ushort rotations, ushort duration)
{
var response = this.MessageHandler.Invoke(new ChamberCalibration
{
Rotations = rotations,
Duration = duration,
Control = ChamberCalibration.ACTIVATE_TEST
});
return new ChamberCalibration(response);
}
public ChamberCalibration ViewChamberCalibration()
{
var response = this.MessageHandler.Invoke(DEVICE_SPECIFIC, CHAMBER_CALIBRATION, ChamberCalibration.REQUEST_RESULTS);
return new ChamberCalibration(response);
}
internal SystemTime ViewSystemTime()
{
var response = this.MessageHandler.Invoke(DEVICE_SPECIFIC, VIEW_SYSTEM_TIME);
return new SystemTime(response);
}
internal FeatureResponse SetSystemTime(SystemTime systemTime = null)
{
if (systemTime is null)
{
systemTime = new SystemTime();
}
var response = this.MessageHandler.Invoke(systemTime[DEVICE_SPECIFIC, SET_SYSTEM_TIME]);
return new FeatureResponse(response);
}
}
}
+351
View File
@@ -0,0 +1,351 @@
using NamedPipes;
using OmniPlus.Connections;
using OmniPlus.Features;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Threading;
using System.Threading.Tasks;
using static OmniPlus.UI.Inspection.OmniPlusInspectionHeaders;
namespace OmniPlus.UI.Inspection
{
public class OmniPlusConnectionsPool : IDisposable
{
public event OmniPlusStateChangedEventHandler StateChanged;
private readonly ConcurrentDictionary<int, OmniPlusConnection> connections;
private readonly ConcurrentDictionary<int, OmniPlusMeter> meters;
private readonly ConcurrentDictionary<int, Task> tasks;
private readonly NamedPipeClient namedPipeClient;
private readonly OmniPlusDatabase database;
private bool available;
private int size;
public OmniPlusConnectionsPool(string dbConnectionString)
{
this.connections = new ConcurrentDictionary<int, OmniPlusConnection>();
this.meters = new ConcurrentDictionary<int, OmniPlusMeter>();
this.namedPipeClient = new NamedPipeClient();
this.database = new OmniPlusDatabase(dbConnectionString);
}
public void Dispose()
{
if (this.StateChanged != null)
{
foreach (var callback in this.StateChanged.GetInvocationList())
{
if (callback is OmniPlusStateChangedEventHandler stateChangedCallback)
{
this.StateChanged -= stateChangedCallback;
}
}
}
foreach (var connection in this.connections.Values)
{
connection?.Disconnect();
}
this.namedPipeClient.Connected -= this.NamedPipeClient_Connected;
this.namedPipeClient.Disconnected -= this.NamedPipeClient_Disconnected;
this.namedPipeClient.Disconnect();
this.connections.Clear();
this.meters.Clear();
}
public void InitializeInspection(params string[] portNames)
{
this.namedPipeClient.Connected += this.NamedPipeClient_Connected;
this.namedPipeClient.Disconnected += this.NamedPipeClient_Disconnected;
this.namedPipeClient.Connect();
OmniPlusConnection connection;
OmniPlusMeter meter;
var connections = 0;
var position = 1;
foreach (var portName in portNames)
{
connection = new OmniPlusConnection(portName);
meter = new OmniPlusMeter();
if (connection.ConnectIrda())
{
meter.Connected = true;
connections++;
}
else
{
connection.Disconnect();
connection = default(OmniPlusConnection);
meter = default(OmniPlusMeter);
}
this.meters.TryAdd(position, meter);
this.connections.TryAdd(position, connection);
this.StateChanged?.Invoke(position, true, SLOT_NR, position.ToString());
position++;
}
if (this.available)
{
this.available = connections > 0;
}
}
public void StartInspection()
{
this.StartOmniInitialization();
this.StartOmniCalibration();
this.FinishOmniInspection();
}
private void StartOmniInitialization()
{
var factory = Task.Factory;
foreach (var position in this.connections.Keys)
{
this.tasks[position] = factory.StartNew(() => this.StartOmniInitialization(position));
}
factory.ContinueWhenAll(this.tasks.Values.ToArray(), _ => { });
}
private void StartOmniInitialization(int position)
{
if (this.TryGetOmni(position, out var connection, out var meter))
{
this.StateChanged?.Invoke(position, meter.Connected, CONNECTED, meter.Connected ? OK : X);
var systemParameters = connection.ViewSystemParameters();
meter.Initialized = systemParameters.Succeeded;
meter.AddSystemParameters(systemParameters);
this.database.AddOrUpdate(systemParameters);
this.StateChanged?.Invoke(position, meter.FirmwareInRange, FIRMWARE, meter.Firmware);
this.StateChanged?.Invoke(position, true, BUILD_INFO, meter.BuildInfo);
this.StateChanged?.Invoke(position, meter.ManifacturingTimeInRange, CREATED_ON, $"{meter.ManifacturingTime:yyyy-MM-dd}");
this.StateChanged?.Invoke(position, meter.SystemTimeInRange, LAST_UPDATE, $"{meter.SystemTimeDrift} Sek.");
this.StateChanged?.Invoke(position, meter.RebootsCount != 0xFF, REBOOTS_CNT, meter.RebootsCount.ToString());
this.StateChanged?.Invoke(position, !meter.FactorySealed, SEALED, !meter.FactorySealed ? X : OK);
if (meter.Initialized)
{
var inputVolume = this.database.GetInputVolume(meter.Size);
var inputVolumResponse = connection.SetInputVolume(inputVolume);
meter.Initialized = inputVolumResponse.Succeeded;
if (meter.Initialized)
{
meter.AddInputVolume(inputVolume);
}
}
this.StateChanged?.Invoke(position, meter.Initialized, ML_PER_PULSE, $"{meter.MlPerPulse:0.00000}");
}
else // meter or connection not in use
{
this.StateChanged?.Invoke(position, null, CONNECTED, null);
this.StateChanged?.Invoke(position, null, FIRMWARE, null);
this.StateChanged?.Invoke(position, null, BUILD_INFO, null);
this.StateChanged?.Invoke(position, null, CREATED_ON, null);
this.StateChanged?.Invoke(position, null, LAST_UPDATE, null);
this.StateChanged?.Invoke(position, null, REBOOTS_CNT, null);
this.StateChanged?.Invoke(position, null, SEALED, null);
this.StateChanged?.Invoke(position, null, ML_PER_PULSE, null);
}
}
private void StartOmniCalibration()
{
var factory = Task.Factory;
var inspectionPoints = this.database.GetInspectionPoints(this.size);
foreach (var point in inspectionPoints)
{
this.namedPipeClient.RequestFlowRate(point.FlowRate);
foreach (var position in this.connections.Keys)
{
this.tasks[position] = factory.StartNew(() => this.StartOmniCalibration(position, point));
}
factory.ContinueWhenAll(this.tasks.Values.ToArray(), _ => { });
var volume = this.namedPipeClient.RequestReference();
foreach (var meter in this.meters.Values)
{
meter.AddInspectionVolume(point, volume);
}
}
foreach(var position in this.connections.Keys)
{
this.tasks[position] = factory.StartNew(() => this.CalibrateMeter(position));
}
factory.ContinueWhenAll(this.tasks.Values.ToArray(), _ => { });
}
private void StartOmniCalibration(int position, OmniPlusInspectionPoint point)
{
var header = $"{point.Rotations}/{point.Seconds}R/Sek.";
var progress = 0;
this.StateChanged?.Invoke(position, null, header, $"{progress}%");
if (this.TryGetOmni(position, out var connection, out var meter))
{
if (!meter.CanCalibrate)
{
this.StateChanged?.Invoke(position, false, header, X);
return;
}
var activationResponse = connection.StartChamberCalibration(point.Rotations, point.Seconds);
var calibrationResult = default(ChamberCalibration);
if (activationResponse.Succeeded)
{
progress += 10;
this.StateChanged?.Invoke(position, true, header, $"{progress}%");
//0x00 indicates that the parameters are valid and test will start 3 seconds after the response.
Thread.Sleep(3000);
progress += 10;
this.StateChanged?.Invoke(position, true, header, $"{progress}%");
// waiting all the secconds to execute test
Thread.Sleep(point.Seconds * 1000);
progress += 70;
this.StateChanged?.Invoke(position, true, header, $"{progress}%");
var delay = (int)(point.Seconds * 0.1) * 1000;
for (int i = 0; i < 3; i++)
{
calibrationResult = connection.ViewChamberCalibration();
if (calibrationResult.Succeeded)
{
break;
}
Thread.Sleep(delay);
progress += 3;
this.StateChanged?.Invoke(position, true, header, $"{progress}%");
}
if (calibrationResult?.Succeeded == true)
{
if (calibrationResult.Control == ChamberCalibration.TEST_COMPLETED)
{
this.StateChanged?.Invoke(position, true, header, $"100%");
}
else // calibration timeout or not ready but exceeds the 3s + seconds to run + 0.3%
{
this.StateChanged?.Invoke(position, false, header, X);
}
}
else // calibration failed
{
this.StateChanged?.Invoke(position, false, header, "n/a");
}
}
else // activation failed
{
this.StateChanged?.Invoke(position, false, header, "n/a");
}
meter.AddCalibrationResult(point, calibrationResult);
}
else // meter or connection not in use
{
this.StateChanged?.Invoke(position, null, header, null);
}
}
private void CalibrateMeter(int position)
{
if (this.TryGetOmni(position, out var connection, out var meter))
{
var correctionFactor = meter.CalculateCorrectionFactor();
this.StateChanged?.Invoke(position, meter.Calibrated, CORRECTION, $"{correctionFactor:0.00}%");
}
else // meter or connection not in use
{
this.StateChanged?.Invoke(position, null, CORRECTION, null);
}
}
private void FinishOmniInspection()
{
var factory = Task.Factory;
foreach (var position in this.connections.Keys)
{
this.tasks[position] = factory.StartNew(() => this.FinishOmniInspection(position));
}
}
private void FinishOmniInspection(int position)
{
if (this.TryGetOmni(position, out var connection, out var meter))
{
var configurationParameters = connection.ViewConfigurationParameters();
var canContinue = configurationParameters.Succeeded;
if (canContinue)
{
var systemTimeReset = connection.SetSystemTime();
canContinue = systemTimeReset.Succeeded;
}
if (canContinue)
{
var registerSettings = connection.ViewRegisterSettings();
}
}
else // meter or connection not in use
{
}
}
private bool TryGetOmni(int position, out OmniPlusConnection connection, out OmniPlusMeter meter)
{
var connectionFound = this.connections.TryGetValue(position, out connection);
var meterFound = this.meters.TryGetValue(position, out meter);
return connectionFound
&& connection != null
&& meterFound
&& meter != null
&& meter.Connected;
}
private void NamedPipeClient_Connected()
=> this.available = true;
private void NamedPipeClient_Disconnected()
=> this.available = false;
}
}
+30
View File
@@ -0,0 +1,30 @@
using OmniPlus.Features;
using System;
using System.Collections.Generic;
namespace OmniPlus.UI.Inspection
{
internal class OmniPlusDatabase
{
private readonly string connectionString;
public OmniPlusDatabase(string omniDatabase)
=> this.connectionString = omniDatabase;
internal void AddOrUpdate(SystemParameters systemParameters)
{
}
internal InputVolume GetInputVolume(object size)
{
return new InputVolume();
}
internal IEnumerable<OmniPlusInspectionPoint> GetInspectionPoints(int size)
{
return new OmniPlusInspectionPoint[] { };
}
}
}
+275
View File
@@ -0,0 +1,275 @@
using OmniPlus.Options;
using System;
namespace OmniPlus
{
internal static class OmniPlusExtensions
{
public const byte OxOO = 0x00;
public const byte OxO1 = 0x01;
public const byte OxO2 = 0x02;
public const byte OxO3 = 0x03;
public const byte UNIPRO_START = 0x53;
public const byte UNIPRO_PC2REG = 0x57;
public const byte UNIPRO_REG2PC = 0x52;
public const byte UNIPRO_STOP = 0x0D;
public const byte UNIPRO_MIN_LENGTH = 4;
public const byte UI1236_START = 0x0D;
public const byte UI1236_MIN_LENGTH = 5;
public const byte UI1236_HED_LENGTH = 3;
public const byte IRDA_START = 0x9B;
public const byte IRDA_PC2REG = 0x33;
public const byte IRDA_REG2PC = 0x43;
public const byte IRDA_MIN_LENGTH = 5;
public const byte IRDA_CRC_LENGTH = 2;
/// <summary>
/// <para>UI1236 structure:</para>
/// <para> 0. 0x0D - Start byte</para>
/// <para> 1. .... - Length: max. 254</para>
/// <para> 2. .... - Control byte</para>
/// <para>3+n. .... - Payload</para>
/// <para>n-2. .... - Checksum 0 </para>
/// <para>n-1. .... - Checksum 1 </para>
/// </summary>
public static byte[] ToUI1236Message(this byte[] bytes)
{
var bytesLength = bytes.Length;
var payloadLength = bytes.Length + UI1236_MIN_LENGTH;
payloadLength += payloadLength % OxO2;
var index = OxOO;
var message = new byte[payloadLength];
message[index++] = UI1236_START;
message[index++] = (byte)(bytesLength + UI1236_HED_LENGTH);
OmniControlOptions control = OxOO;
control.ReturnResponse = true;
message[index++] = control;
bytes.CopyTo(message, index--);
var checksum = message.CalculateCheckSUM(message.Length - OxO2);
var checksumIndex = bytesLength + UI1236_HED_LENGTH;
message[checksumIndex++] = checksum[OxO1];
message[checksumIndex++] = checksum[OxOO];
return message;
}
/// <summary>
/// <para>UI1236 structure:</para>
/// <para> 0. 0x0D - Start byte</para>
/// <para> 1. .... - Length: max. 254</para>
/// <para> 2. .... - Control byte</para>
/// <para>3+n. .... - Payload</para>
/// <para>n-2. .... - Checksum 0 </para>
/// <para>n-1. .... - Checksum 1 </para>
/// </summary>
public static byte[] FromUI1236Message(this byte[] bytes)
{
var payload = new byte[OxOO];
if (bytes is null)
{
return payload;
}
var payloadLength = bytes.Length - UI1236_MIN_LENGTH;
if (payloadLength <= OxOO)
{
return payload;
}
payload = new byte[payloadLength];
Array.Copy(bytes, OxO3, payload, OxOO, payloadLength);
return payload;
}
/// <summary>
/// <para>UniPro structure:</para>
/// <para> 0. .... - Start byte</para>
/// <para> 1. .... - Direction</para>
/// <para> 2. .... - Length</para>
/// <para> .. .... - Payload</para>
/// <para>n-1. .... - Stop byte</para>
/// </summary>
public static byte[] ToUniProMessage(this byte[] bytes)
{
var messageLength = (byte)(bytes.Length + UNIPRO_MIN_LENGTH);
var message = new byte[messageLength];
var index = OxOO;
message[index++] = UNIPRO_START;
message[index++] = UNIPRO_PC2REG;
message[index++] = messageLength;
message[messageLength - OxO1] = UNIPRO_STOP;
bytes.CopyTo(message, index);
return message;
}
/// <summary>
/// <para>UniPro structure:</para>
/// <para> 0. .... - Start byte</para>
/// <para> 1. .... - Direction</para>
/// <para> 2. .... - Length</para>
/// <para> .. .... - Payload</para>
/// <para>n-1. .... - Stop byte</para>
/// </summary>
public static byte[] FromUniProMessage(this byte[] bytes)
{
var payload = new byte[OxOO];
if (bytes is null)
{
return payload;
}
var payloadLength = bytes.Length - UNIPRO_MIN_LENGTH;
if (payloadLength <= OxOO)
{
return payload;
}
payload = new byte[payloadLength];
Array.Copy(bytes, OxO3, payload, OxOO, payloadLength);
return payload;
}
/// <summary>
/// <para>Irda structure:</para>
/// <para> 0. .... - Start byte</para>
/// <para> 1. .... - Direction</para>
/// <para> 2. .... - Length / 2</para>
/// <para>3+n. .... - Payload</para>
/// <para>n-3. .... - aligned byte</para>
/// <para>n-2. .... - CRC16 0 </para>
/// <para>n-1. .... - CRC16 1 </para>
/// </summary>
public static byte[] ToIrdaMessage(this byte[] bytes)
{
var payloadLength = bytes.Length;
payloadLength += payloadLength % OxO2;
var index = OxOO;
var message = new byte[payloadLength + IRDA_MIN_LENGTH];
message[index++] = IRDA_START;
message[index++] = IRDA_PC2REG;
message[index++] = (byte)(payloadLength / OxO2);
bytes.CopyTo(message, index);
var messageLength = message.Length;
var crc16 = message.CalculateCRC8408(1, messageLength - IRDA_CRC_LENGTH);
message[messageLength - OxO2] = crc16[OxOO];
message[messageLength - OxO1] = crc16[OxO1];
return message;
}
/// <summary>
/// <para>Irda structure:</para>
/// <para> 0. .... - Start byte</para>
/// <para> 1. .... - Direction</para>
/// <para> 2. .... - Length / 2</para>
/// <para>3+n. .... - Payload</para>
/// <para>n-3. .... - aligned byte</para>
/// <para>n-2. .... - CRC16 0 </para>
/// <para>n-1. .... - CRC16 1 </para>
/// </summary>
public static byte[] FromIrdaMessage(this byte[] bytes)
{
var payload = new byte[OxOO];
if (bytes is null)
{
return payload;
}
var payloadLength = bytes.Length - IRDA_MIN_LENGTH;
if (payloadLength <= OxOO)
{
return payload;
}
payload = new byte[payloadLength];
Array.Copy(bytes, OxO3, payload, OxOO, payloadLength);
return payload;
}
public static byte[] CalculateCRC8408(this byte[] message, int start, int length)
{
var crc = 0xFFFF;
for (int x = start; x < length; x++)
{
crc ^= 0x00FF & message[x];
for (int i = 8; i > OxOO; i--)
{
if ((crc & 0x0001) != OxOO)
{
crc = (crc >> OxO1) ^ 0x8408;
}
else
{
crc >>= OxO1;
}
}
}
crc = ~crc;
return BitConverter.GetBytes(crc);
}
public static byte[] CalculateCheckSUM(this byte[] bytes, int length)
{
ushort checksum = OxOO;
for (int i = OxOO; i < length; i++)
{
checksum += bytes[i];
}
return BitConverter.GetBytes(checksum);
}
public static void Clear(this byte[] bytes)
{
var length = bytes.Length;
for (int i = OxOO; i < length; i++)
{
bytes[i] = OxOO;
}
}
public static string ToASCIIString(this byte[] bytes, int start, int end)
{
var asciiString = string.Empty;
while (start <= end)
{
asciiString += (char)bytes[start];
start++;
}
return asciiString.Trim();
}
}
}
@@ -0,0 +1,23 @@
namespace OmniPlus.UI.Inspection
{
internal class OmniPlusInspectionHeaders
{
public const string COMS_CONFIG = "COMS";
public const string SLOT_NR = "Einbauplatz";
public const string CONNECTED = "Verbunden";
public const string FIRMWARE = "Firmware";
public const string CREATED_ON = "Herstellungsdatum";
public const string LAST_UPDATE = "Letzte Aktualisierung";
public const string REBOOTS_CNT = "Neustarts";
public const string SEALED = "Versiegelt";
public const string BUILD_INFO = "Build-Info";
public const string ML_PER_PULSE = "ml/Puls";
public const string CORRECTION = "Korrekturfaktor";
//public const string REBOOTS_CNT = "Neustarts";
//public const string REBOOTS_CNT = "Neustarts";
public const string OK = "✓";
public const string X = "✘";
}
}
@@ -0,0 +1,16 @@
namespace OmniPlus.UI.Inspection
{
using System;
internal struct OmniPlusInspectionPoint : IComparable<OmniPlusInspectionPoint>
{
public double FlowRate { get; set; }
public ushort Rotations { get; set; }
public ushort Seconds { get; set; }
public int CompareTo(OmniPlusInspectionPoint other)
=> this.Rotations.CompareTo(other.Rotations);
}
}
+174
View File
@@ -0,0 +1,174 @@
using OmniPlus.Features;
using System;
using System.Collections.Generic;
namespace OmniPlus.UI.Inspection
{
internal class OmniPlusMeter
{
private readonly IDictionary<OmniPlusInspectionPoint, double> inspectionPoints;
private readonly IDictionary<OmniPlusInspectionPoint, ChamberCalibration> calibrationResults;
private InputVolume inputVolume;
private SystemParameters systemParameters;
public OmniPlusMeter(bool connected = false)
{
this.Connected = connected;
this.inputVolume = new InputVolume();
this.systemParameters = new SystemParameters();
this.inspectionPoints = new Dictionary<OmniPlusInspectionPoint, double>();
this.calibrationResults = new Dictionary<OmniPlusInspectionPoint, ChamberCalibration>();
}
public bool Connected { get; set; }
public bool Initialized { get; set; }
public bool CanCalibrate { get; set; } = true;
public bool Calibrated { get; private set; }
public byte Size
{
get => this.inputVolume.Size;
set => this.inputVolume.Size = value;
}
public byte Range
{
get => this.inputVolume.Range;
set => this.inputVolume.Range = value;
}
public ushort IntPart
{
get => this.inputVolume.IntPart;
set => this.inputVolume.IntPart = value;
}
public ushort Fraction
{
get => this.inputVolume.Fraction;
set => this.inputVolume.Fraction = value;
}
public string BuildInfo
{
get => this.systemParameters.BuildInfo.Value.Trim('\0');
set => this.systemParameters.BuildInfo.Value = value;
}
public DateTime ManifacturingTime
{
get => this.systemParameters.ManifacturingTime.DateTime;
set => this.systemParameters.ManifacturingTime.DateTime = value;
}
public bool ManifacturingTimeInRange { get; private set; }
public DateTime SystemTime
{
get => this.systemParameters.SystemTime.DateTime;
set => this.systemParameters.SystemTime.DateTime = value;
}
public int SystemTimeDrift { get; private set; }
public bool SystemTimeInRange { get; private set; }
public byte RebootsCount
{
get => this.systemParameters.RebootsCount;
set => this.systemParameters.RebootsCount = value;
}
public string Firmware
{
get => this.systemParameters.VersionAndType.Value.Trim('\0');
set => this.systemParameters.VersionAndType.Value = value;
}
public bool FirmwareInRange { get; private set; }
public double Volts
{
get => Math.Max(this.systemParameters.BatteryVoltage.Volts, (ushort)1) / 1000.0;
set => this.systemParameters.BatteryVoltage.Volts = (ushort)(value * 1000);
}
public bool VoltsInRange => 3.19 <= this.Volts && this.Volts <= 3.75;
public uint ADCCounts
{
get => this.systemParameters.BatteryVoltage.ADCCount;
set => this.systemParameters.BatteryVoltage.ADCCount = value;
}
public bool FactorySealed
{
get => this.systemParameters.FactorySealed;
set => this.systemParameters.FactorySealed = value;
}
public double MlPerPulse { get; private set; }
internal void AddInputVolume(InputVolume inputVolume)
{
if (inputVolume != null)
{
this.inputVolume = inputVolume;
if (this.inputVolume.Fraction > 0)
{
this.MlPerPulse = this.inputVolume.Fraction / ushort.MaxValue * this.inputVolume.Factor;
}
}
}
internal void AddSystemParameters(SystemParameters systemParameters)
{
if (systemParameters != null)
{
this.systemParameters = systemParameters;
this.SystemTimeDrift = (DateTime.UtcNow - this.SystemTime).Seconds;
this.SystemTimeInRange = Math.Abs(this.SystemTimeDrift) <= 700;
this.ManifacturingTimeInRange = Math.Abs((DateTime.UtcNow - this.ManifacturingTime).Days) <= 365;
var firmwareTokens = this.Firmware
?.Split(new char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
?? new string[2];
var versionString = firmwareTokens[0]?.Substring(1) ?? string.Empty;
var versionParsed = double.TryParse(versionString, out var version);
var typeString = firmwareTokens[1]?.ToUpper() ?? string.Empty;
var isOmni = typeString == "OMNI-1" || typeString == "OMNI-2";
this.FirmwareInRange = versionParsed && version >= 1.23 && isOmni;
}
}
internal void AddCalibrationResult(OmniPlusInspectionPoint point, ChamberCalibration result)
{
this.calibrationResults[point] = result;
if (this.CanCalibrate)
{
this.CanCalibrate
= result != null
&& result.Succeeded
&& result.Control == ChamberCalibration.TEST_COMPLETED;
}
}
internal void AddInspectionVolume(OmniPlusInspectionPoint point, double volume)
=> this.inspectionPoints[point] = volume;
internal double CalculateCorrectionFactor()
{
// TODO: calculate correction factor.
return double.MinValue;
}
}
}
@@ -0,0 +1,4 @@
namespace OmniPlus.UI.Inspection
{
public delegate void OmniPlusStateChangedEventHandler(int column, bool? succeeded, string header, string value);
}
@@ -0,0 +1,66 @@
namespace OmniPlus.Options
{
internal class OmniControlOptions
{
private readonly byte[] bits;
private OmniControlOptions(byte[] bits)
=> this.bits = bits;
public bool E0Encryption => this.bits[0] == 1;
public bool E1Encryption => this.bits[1] == 1;
public bool E2Encryption => this.bits[2] == 1;
public bool ReturnResponse
{
get => this.bits[3] == 1;
set => this.bits[3] = (byte)(value ? 1 : 0);
}
public bool NFNetwork => this.bits[4] == 1;
public bool MultipleCommands
{
get => this.bits[5] == 1;
set => this.bits[5] = (byte)(value ? 1 : 0);
}
public bool LongControl
{
get => this.bits[6] == 1;
set => this.bits[6] = (byte)(value ? 1 : 0);
}
public bool MultipleFrames
{
get => this.bits[7] == 1;
set => this.bits[7] = (byte)(value ? 1 : 0);
}
public static implicit operator OmniControlOptions(byte controlByte)
{
var bits = new byte[8];
for (int i = 0; i < 8; i++)
{
bits[i] = (byte)((controlByte >> i) & 1);
}
return new OmniControlOptions(bits);
}
public static implicit operator byte (OmniControlOptions control)
{
var @byte = default(byte);
for (int i = 0; i < 8; i++)
{
@byte ^= (byte)(control.bits[i] << i);
}
return @byte;
}
}
}
@@ -0,0 +1,36 @@
using OmniPlus.Enums;
using System.IO.Ports;
namespace OmniPlus.Options
{
public class SerialPortOptions
{
public SerialPortOptions()
{
this.BaudRate = BaudRate.K4800;
this.DataBits = DataBits.X8;
this.Parity = Parity.None;
this.StopBits = StopBits.One;
this.Handshake = Handshake.None;
}
public string PortName { get; set; }
public BaudRate BaudRate { get; set; }
public Parity Parity { get; set; }
public DataBits DataBits { get; set; }
public StopBits StopBits { get; set; }
public Handshake Handshake { get; set; }
public bool RtsEnabled { get; set; }
public bool DtrEnabled { get; set; }
public string LogFile { get; set; }
}
}
@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("OmniPlus")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("OmniPlus")]
[assembly: AssemblyCopyright("Copyright © 2023")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("f8d84b9a-0fcf-4083-b067-8c39b7314152")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]