add XSigUtility from Troy Garner

https://github.com/bitm0de/XSigUtilityLibrary
This commit is contained in:
Andrew Welker
2020-09-16 15:12:43 -06:00
parent 1ad5461f7e
commit f3c814f6ec
11 changed files with 786 additions and 0 deletions

View File

@@ -122,6 +122,16 @@
<Compile Include="WebApi\Presets\Preset.cs" />
<Compile Include="WebApi\Presets\User.cs" />
<Compile Include="WebApi\Presets\WebApiPasscodeClient.cs" />
<Compile Include="XSigUtility\Serialization\IXSigSerialization.cs" />
<Compile Include="XSigUtility\Serialization\XSigSerializationException.cs" />
<Compile Include="XSigUtility\Tokens\XSigAnalogToken.cs" />
<Compile Include="XSigUtility\Tokens\XSigDigitalToken.cs" />
<Compile Include="XSigUtility\Tokens\XSigSerialToken.cs" />
<Compile Include="XSigUtility\Tokens\XSigToken.cs" />
<Compile Include="XSigUtility\Tokens\XSigTokenType.cs" />
<Compile Include="XSigUtility\XSigHelpers.cs" />
<Compile Include="XSigUtility\XSigTokenStreamReader.cs" />
<Compile Include="XSigUtility\XSigTokenStreamWriter.cs" />
<None Include="Properties\ControlSystem.cfg" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CompactFramework.CSharp.targets" />

View File

@@ -0,0 +1,14 @@
using System.Collections.Generic;
using PepperDash.Core.Intersystem.Tokens;
namespace PepperDash.Core.Intersystem.Serialization
{
/// <summary>
/// Interface to determine XSig serialization for an object.
/// </summary>
public interface IXSigSerialization
{
IEnumerable<XSigToken> Serialize();
T Deserialize<T>(IEnumerable<XSigToken> tokens) where T : class, IXSigSerialization;
}
}

View File

@@ -0,0 +1,11 @@
using System;
namespace PepperDash.Core.Intersystem.Serialization
{
public class XSigSerializationException : Exception
{
public XSigSerializationException() { }
public XSigSerializationException(string message) : base(message) { }
public XSigSerializationException(string message, Exception inner) : base(message, inner) { }
}
}

View File

@@ -0,0 +1,55 @@
using System;
namespace PepperDash.Core.Intersystem.Tokens
{
public sealed class XSigAnalogToken : XSigToken, IFormattable
{
private readonly ushort _value;
public XSigAnalogToken(int index, ushort value)
: base(index)
{
// 10-bits available for analog encoded data
if (index >= 1024 || index < 0)
throw new ArgumentOutOfRangeException("index");
_value = value;
}
public ushort Value
{
get { return _value; }
}
public override XSigTokenType TokenType
{
get { return XSigTokenType.Analog; }
}
public override byte[] GetBytes()
{
return new[] {
(byte)(0xC0 | ((Value & 0xC000) >> 10) | (Index >> 7)),
(byte)((Index - 1) & 0x7F),
(byte)((Value & 0x3F80) >> 7),
(byte)(Value & 0x7F)
};
}
public override XSigToken GetTokenWithOffset(int offset)
{
if (offset == 0) return this;
return new XSigAnalogToken(Index + offset, Value);
}
public override string ToString()
{
return Index + " = 0x" + Value.ToString("X4");
}
public string ToString(string format, IFormatProvider formatProvider)
{
return Value.ToString(format, formatProvider);
}
}
}

View File

@@ -0,0 +1,53 @@
using System;
namespace PepperDash.Core.Intersystem.Tokens
{
public sealed class XSigDigitalToken : XSigToken
{
private readonly bool _value;
public XSigDigitalToken(int index, bool value)
: base(index)
{
// 12-bits available for digital encoded data
if (index >= 4096 || index < 0)
throw new ArgumentOutOfRangeException("index");
_value = value;
}
public bool Value
{
get { return _value; }
}
public override XSigTokenType TokenType
{
get { return XSigTokenType.Digital; }
}
public override byte[] GetBytes()
{
return new[] {
(byte)(0x80 | (Value ? 0 : 0x20) | (Index >> 7)),
(byte)((Index - 1) & 0x7F)
};
}
public override XSigToken GetTokenWithOffset(int offset)
{
if (offset == 0) return this;
return new XSigDigitalToken(Index + offset, Value);
}
public override string ToString()
{
return Index + " = " + (Value ? "High" : "Low");
}
public string ToString(IFormatProvider formatProvider)
{
return Value.ToString(formatProvider);
}
}
}

View File

@@ -0,0 +1,53 @@
using System;
using System.Text;
namespace PepperDash.Core.Intersystem.Tokens
{
public sealed class XSigSerialToken : XSigToken
{
private readonly string _value;
public XSigSerialToken(int index, string value)
: base(index)
{
// 10-bits available for serial encoded data
if (index >= 1024 || index < 0)
throw new ArgumentOutOfRangeException("index");
_value = value;
}
public string Value
{
get { return _value; }
}
public override XSigTokenType TokenType
{
get { return XSigTokenType.Serial; }
}
public override byte[] GetBytes()
{
var serialBytes = Encoding.GetEncoding(28591).GetBytes(Value);
var xsig = new byte[serialBytes.Length + 3];
xsig[0] = (byte)(0xC8 | (Index >> 7));
xsig[1] = (byte)((Index - 1) & 0x7F);
xsig[xsig.Length - 1] = 0xFF;
Buffer.BlockCopy(serialBytes, 0, xsig, 2, serialBytes.Length);
return xsig;
}
public override XSigToken GetTokenWithOffset(int offset)
{
if (offset == 0) return this;
return new XSigSerialToken(Index + offset, Value);
}
public override string ToString()
{
return Index + " = \"" + Value + "\"";
}
}
}

View File

@@ -0,0 +1,45 @@
namespace PepperDash.Core.Intersystem.Tokens
{
/// <summary>
/// Represents the base class for all XSig datatypes.
/// </summary>
public abstract class XSigToken
{
private readonly int _index;
/// <summary>
/// Constructs an XSigToken with the specified index.
/// </summary>
/// <param name="index">Index for the data.</param>
protected XSigToken(int index)
{
_index = index;
}
/// <summary>
/// XSig 1-based index.
/// </summary>
public int Index
{
get { return _index; }
}
/// <summary>
/// XSigToken type.
/// </summary>
public abstract XSigTokenType TokenType { get; }
/// <summary>
/// Generates the XSig bytes for the corresponding token.
/// </summary>
/// <returns>XSig byte array.</returns>
public abstract byte[] GetBytes();
/// <summary>
/// Returns a new token if necessary with an updated index based on the specified offset.
/// </summary>
/// <param name="offset">Offset to adjust the index with.</param>
/// <returns>XSigToken</returns>
public abstract XSigToken GetTokenWithOffset(int offset);
}
}

View File

@@ -0,0 +1,23 @@
namespace PepperDash.Core.Intersystem.Tokens
{
/// <summary>
/// XSig token types.
/// </summary>
public enum XSigTokenType
{
/// <summary>
/// Digital signal datatype.
/// </summary>
Digital,
/// <summary>
/// Analog signal datatype.
/// </summary>
Analog,
/// <summary>
/// Serial signal datatype.
/// </summary>
Serial
}
}

View File

@@ -0,0 +1,239 @@
using System;
using System.Linq;
using Crestron.SimplSharp.CrestronIO;
using PepperDash.Core.Intersystem.Serialization;
using PepperDash.Core.Intersystem.Tokens;
/*
Digital (2 bytes)
10C##### 0####### (mask = 11000000_10000000b -> 0xC080)
Analog (4 bytes)
11aa0### 0####### (mask = 11001000_10000000b -> 0xC880)
0aaaaaaa 0aaaaaaa
Serial (Variable length)
11001### 0####### (mask = 11111000_10000000b -> 0xF880)
dddddddd ........ <- up to 252 bytes of serial data (255 - 3)
11111111 <- denotes end of data
*/
namespace PepperDash.Core.Intersystem
{
/// <summary>
/// Helper methods for creating XSig byte sequences compatible with the Intersystem Communications (ISC) symbol.
/// </summary>
/// <remarks>
/// Indexing is not from the start of each signal type but rather from the beginning of the first defined signal
/// the Intersystem Communications (ISC) symbol.
/// </remarks>
public static class XSigHelpers
{
/// <summary>
/// Forces all outputs to 0.
/// </summary>
/// <returns>Bytes in XSig format for clear outputs trigger.</returns>
public static byte[] ClearOutputs()
{
return new byte[] { 0xFC };
}
/// <summary>
/// Evaluate all inputs and re-transmit any digital, analog, and permanent serail signals not set to 0.
/// </summary>
/// <returns>Bytes in XSig format for send status trigger.</returns>
public static byte[] SendStatus()
{
return new byte[] { 0xFD };
}
/// <summary>
/// Get bytes for an IXSigStateResolver object.
/// </summary>
/// <param name="xSigSerialization">XSig state resolver.</param>
/// <returns>Bytes in XSig format for each token within the state representation.</returns>
public static byte[] GetBytes(IXSigSerialization xSigSerialization)
{
return GetBytes(xSigSerialization, 0);
}
/// <summary>
/// Get bytes for an IXSigStateResolver object, with a specified offset.
/// </summary>
/// <param name="xSigSerialization">XSig state resolver.</param>
/// <param name="offset">Offset to which the data will be aligned.</param>
/// <returns>Bytes in XSig format for each token within the state representation.</returns>
public static byte[] GetBytes(IXSigSerialization xSigSerialization, int offset)
{
var tokens = xSigSerialization.Serialize();
if (tokens == null) return new byte[0];
using (var memoryStream = new MemoryStream())
{
using (var tokenWriter = new XSigTokenStreamWriter(memoryStream))
tokenWriter.WriteXSigData(xSigSerialization, offset);
return memoryStream.ToArray();
}
}
/// <summary>
/// Get bytes for a single digital signal.
/// </summary>
/// <param name="index">1-based digital index</param>
/// <param name="value">Digital data to be encoded</param>
/// <returns>Bytes in XSig format for digtial information.</returns>
public static byte[] GetBytes(int index, bool value)
{
return GetBytes(index, 0, value);
}
/// <summary>
/// Get bytes for a single digital signal.
/// </summary>
/// <param name="index">1-based digital index</param>
/// <param name="offset">Index offset.</param>
/// <param name="value">Digital data to be encoded</param>
/// <returns>Bytes in XSig format for digtial information.</returns>
public static byte[] GetBytes(int index, int offset, bool value)
{
return new XSigDigitalToken(index + offset, value).GetBytes();
}
/// <summary>
/// Get byte sequence for multiple digital signals.
/// </summary>
/// <param name="startIndex">Starting index of the sequence.</param>
/// <param name="values">Digital signal value array.</param>
/// <returns>Byte sequence in XSig format for digital signal information.</returns>
public static byte[] GetBytes(int startIndex, bool[] values)
{
return GetBytes(startIndex, 0, values);
}
/// <summary>
/// Get byte sequence for multiple digital signals.
/// </summary>
/// <param name="startIndex">Starting index of the sequence.</param>
/// <param name="offset">Index offset.</param>
/// <param name="values">Digital signal value array.</param>
/// <returns>Byte sequence in XSig format for digital signal information.</returns>
public static byte[] GetBytes(int startIndex, int offset, bool[] values)
{
// Digital XSig data is 2 bytes per value
const int fixedLength = 2;
var bytes = new byte[values.Length * fixedLength];
for (var i = 0; i < values.Length; i++)
Buffer.BlockCopy(GetBytes(startIndex++, offset, values[i]), 0, bytes, i * fixedLength, fixedLength);
return bytes;
}
/// <summary>
/// Get bytes for a single analog signal.
/// </summary>
/// <param name="index">1-based analog index</param>
/// <param name="value">Analog data to be encoded</param>
/// <returns>Bytes in XSig format for analog signal information.</returns>
public static byte[] GetBytes(int index, ushort value)
{
return GetBytes(index, 0, value);
}
/// <summary>
/// Get bytes for a single analog signal.
/// </summary>
/// <param name="index">1-based analog index</param>
/// <param name="offset">Index offset.</param>
/// <param name="value">Analog data to be encoded</param>
/// <returns>Bytes in XSig format for analog signal information.</returns>
public static byte[] GetBytes(int index, int offset, ushort value)
{
return new XSigAnalogToken(index + offset, value).GetBytes();
}
/// <summary>
/// Get byte sequence for multiple analog signals.
/// </summary>
/// <param name="startIndex">Starting index of the sequence.</param>
/// <param name="values">Analog signal value array.</param>
/// <returns>Byte sequence in XSig format for analog signal information.</returns>
public static byte[] GetBytes(int startIndex, ushort[] values)
{
return GetBytes(startIndex, 0, values);
}
/// <summary>
/// Get byte sequence for multiple analog signals.
/// </summary>
/// <param name="startIndex">Starting index of the sequence.</param>
/// <param name="offset">Index offset.</param>
/// <param name="values">Analog signal value array.</param>
/// <returns>Byte sequence in XSig format for analog signal information.</returns>
public static byte[] GetBytes(int startIndex, int offset, ushort[] values)
{
// Analog XSig data is 4 bytes per value
const int fixedLength = 4;
var bytes = new byte[values.Length * fixedLength];
for (var i = 0; i < values.Length; i++)
Buffer.BlockCopy(GetBytes(startIndex++, offset, values[i]), 0, bytes, i * fixedLength, fixedLength);
return bytes;
}
/// <summary>
/// Get bytes for a single serial signal.
/// </summary>
/// <param name="index">1-based serial index</param>
/// <param name="value">Serial data to be encoded</param>
/// <returns>Bytes in XSig format for serial signal information.</returns>
public static byte[] GetBytes(int index, string value)
{
return GetBytes(index, 0, value);
}
/// <summary>
/// Get bytes for a single serial signal.
/// </summary>
/// <param name="index">1-based serial index</param>
/// <param name="offset">Index offset.</param>
/// <param name="value">Serial data to be encoded</param>
/// <returns>Bytes in XSig format for serial signal information.</returns>
public static byte[] GetBytes(int index, int offset, string value)
{
return new XSigSerialToken(index + offset, value).GetBytes();
}
/// <summary>
/// Get byte sequence for multiple serial signals.
/// </summary>
/// <param name="startIndex">Starting index of the sequence.</param>
/// <param name="values">Serial signal value array.</param>
/// <returns>Byte sequence in XSig format for serial signal information.</returns>
public static byte[] GetBytes(int startIndex, string[] values)
{
return GetBytes(startIndex, 0, values);
}
/// <summary>
/// Get byte sequence for multiple serial signals.
/// </summary>
/// <param name="startIndex">Starting index of the sequence.</param>
/// <param name="offset">Index offset.</param>
/// <param name="values">Serial signal value array.</param>
/// <returns>Byte sequence in XSig format for serial signal information.</returns>
public static byte[] GetBytes(int startIndex, int offset, string[] values)
{
// Serial XSig data is not fixed-length like the other formats
var dstOffset = 0;
var bytes = new byte[values.Sum(v => v.Length + 3)];
for (var i = 0; i < values.Length; i++)
{
var data = GetBytes(startIndex++, offset, values[i]);
Buffer.BlockCopy(data, 0, bytes, dstOffset, data.Length);
dstOffset += data.Length;
}
return bytes;
}
}
}

View File

@@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using Crestron.SimplSharp.CrestronIO;
using PepperDash.Core.Intersystem.Serialization;
using PepperDash.Core.Intersystem.Tokens;
namespace PepperDash.Core.Intersystem
{
/// <summary>
/// XSigToken stream reader.
/// </summary>
public sealed class XSigTokenStreamReader : IDisposable
{
private readonly Stream _stream;
private readonly bool _leaveOpen;
/// <inheritdoc />
/// <summary>
/// XSigToken stream reader constructor.
/// </summary>
/// <param name="stream">Input stream to read from.</param>
/// <exception cref="T:System.ArgumentNullException">Stream is null.</exception>
/// <exception cref="T:System.ArgumentException">Stream cannot be read from.</exception>
public XSigTokenStreamReader(Stream stream)
: this(stream, false) { }
/// <summary>
/// XSigToken stream reader constructor.
/// </summary>
/// <param name="stream">Input stream to read from.</param>
/// <param name="leaveOpen">Determines whether to leave the stream open or not.</param>
/// <exception cref="ArgumentNullException">Stream is null.</exception>
/// <exception cref="ArgumentException">Stream cannot be read from.</exception>
public XSigTokenStreamReader(Stream stream, bool leaveOpen)
{
if (stream == null)
throw new ArgumentNullException("stream");
if (!stream.CanRead)
throw new ArgumentException("The specified stream cannot be read from.");
_stream = stream;
_leaveOpen = leaveOpen;
}
/// <summary>
/// Reads a 16-bit unsigned integer from the specified stream using Big Endian byte order.
/// </summary>
/// <param name="stream">Input stream</param>
/// <param name="value">Result</param>
/// <returns>True if successful, otherwise false.</returns>
public static bool TryReadUInt16BE(Stream stream, out ushort value)
{
value = 0;
if (stream.Length < 2)
return false;
var buffer = new byte[2];
stream.Read(buffer, 0, 2);
value = (ushort)((buffer[0] << 8) | buffer[1]);
return true;
}
/// <summary>
/// Read XSig token from the stream.
/// </summary>
/// <returns>XSigToken</returns>
/// <exception cref="ArgumentOutOfRangeException">Offset is less than 0.</exception>
public XSigToken ReadXSigToken()
{
ushort prefix;
if (!TryReadUInt16BE(_stream, out prefix))
return null;
if ((prefix & 0xF880) == 0xC800) // Serial data
{
var index = ((prefix & 0x0700) >> 1) | (prefix & 0x7F);
var n = 0;
const int maxSerialDataLength = 252;
var chars = new char[maxSerialDataLength];
int ch;
while ((ch = _stream.ReadByte()) != 0xFF)
{
if (ch == -1) // Reached end of stream without end of data marker
return null;
chars[n++] = (char)ch;
}
return new XSigSerialToken((ushort)(index + 1), new string(chars, 0, n));
}
if ((prefix & 0xC880) == 0xC000) // Analog data
{
ushort data;
if (!TryReadUInt16BE(_stream, out data))
return null;
var index = ((prefix & 0x0700) >> 1) | (prefix & 0x7F);
var value = ((prefix & 0x3000) << 2) | ((data & 0x7F00) >> 1) | (data & 0x7F);
return new XSigAnalogToken((ushort)(index + 1), (ushort)value);
}
if ((prefix & 0xC080) == 0x8000) // Digital data
{
var index = ((prefix & 0x1F00) >> 1) | (prefix & 0x7F);
var value = (prefix & 0x2000) == 0;
return new XSigDigitalToken((ushort)(index + 1), value);
}
return null;
}
/// <summary>
/// Reads all available XSig tokens from the stream.
/// </summary>
/// <returns>XSigToken collection.</returns>
public IEnumerable<XSigToken> ReadAllXSigTokens()
{
var tokens = new List<XSigToken>();
XSigToken token;
while ((token = ReadXSigToken()) != null)
tokens.Add(token);
return tokens;
}
/// <summary>
/// Attempts to deserialize all XSig data within the stream from the current position.
/// </summary>
/// <typeparam name="T">Type to deserialize the information to.</typeparam>
/// <returns>Deserialized object.</returns>
public T DeserializeStream<T>()
where T : class, IXSigSerialization, new()
{
return new T().Deserialize<T>(ReadAllXSigTokens());
}
/// <summary>
/// Disposes of the internal stream if specified to not leave open.
/// </summary>
public void Dispose()
{
if (!_leaveOpen)
_stream.Dispose();
}
}
}

View File

@@ -0,0 +1,136 @@
using System;
using System.Linq;
using System.Collections.Generic;
using Crestron.SimplSharp.CrestronIO;
using PepperDash.Core.Intersystem.Serialization;
using PepperDash.Core.Intersystem.Tokens;
namespace PepperDash.Core.Intersystem
{
/// <summary>
/// XSigToken stream writer.
/// </summary>
public sealed class XSigTokenStreamWriter : IDisposable
{
private readonly Stream _stream;
private readonly bool _leaveOpen;
/// <inheritdoc />
/// <summary>
/// XSigToken stream writer constructor.
/// </summary>
/// <param name="stream">Input stream to write to.</param>
/// <exception cref="T:System.ArgumentNullException">Stream is null.</exception>
/// <exception cref="T:System.ArgumentException">Stream cannot be written to.</exception>
public XSigTokenStreamWriter(Stream stream)
: this(stream, false) { }
/// <summary>
/// XSigToken stream writer constructor.
/// </summary>
/// <param name="stream">Input stream to write to.</param>
/// <param name="leaveOpen">Determines whether to leave the stream open or not.</param>
/// <exception cref="ArgumentNullException">Stream is null.</exception>
/// <exception cref="ArgumentException">Stream cannot be written to.</exception>
public XSigTokenStreamWriter(Stream stream, bool leaveOpen)
{
if (stream == null)
throw new ArgumentNullException("stream");
if (!stream.CanWrite)
throw new ArgumentException("The specified stream cannot be written to.");
_stream = stream;
_leaveOpen = leaveOpen;
}
/// <summary>
/// Write XSig data gathered from an IXSigStateResolver to the stream.
/// </summary>
/// <param name="xSigSerialization">IXSigStateResolver object.</param>
public void WriteXSigData(IXSigSerialization xSigSerialization)
{
WriteXSigData(xSigSerialization, 0);
}
/// <summary>
/// Write XSig data gathered from an IXSigStateResolver to the stream.
/// </summary>
/// <param name="xSigSerialization">IXSigStateResolver object.</param>
/// <param name="offset">Index offset for each XSigToken.</param>
public void WriteXSigData(IXSigSerialization xSigSerialization, int offset)
{
if (xSigSerialization == null)
throw new ArgumentNullException("xSigSerialization");
var tokens = xSigSerialization.Serialize();
WriteXSigData(tokens, offset);
}
/// <summary>
/// Write XSigToken to the stream.
/// </summary>
/// <param name="token">XSigToken object.</param>
public void WriteXSigData(XSigToken token)
{
WriteXSigData(token, 0);
}
/// <summary>
/// Write XSigToken to the stream.
/// </summary>
/// <param name="token">XSigToken object.</param>
/// <param name="offset">Index offset for each XSigToken.</param>
public void WriteXSigData(XSigToken token, int offset)
{
WriteXSigData(new[] { token }, offset);
}
/// <summary>
/// Writes an array of XSigTokens to the stream.
/// </summary>
/// <param name="tokens">XSigToken objects.</param>
public void WriteXSigData(XSigToken[] tokens)
{
WriteXSigData(tokens.AsEnumerable());
}
/// <summary>
/// Write an enumerable collection of XSigTokens to the stream.
/// </summary>
/// <param name="tokens">XSigToken objects.</param>
public void WriteXSigData(IEnumerable<XSigToken> tokens)
{
WriteXSigData(tokens, 0);
}
/// <summary>
/// Write an enumerable collection of XSigTokens to the stream.
/// </summary>
/// <param name="tokens">XSigToken objects.</param>
/// <param name="offset">Index offset for each XSigToken.</param>
public void WriteXSigData(IEnumerable<XSigToken> tokens, int offset)
{
if (offset < 0)
throw new ArgumentOutOfRangeException("offset", "Offset must be greater than or equal to 0.");
if (tokens != null)
{
foreach (var token in tokens)
{
if (token == null) continue;
var bytes = token.GetTokenWithOffset(offset).GetBytes();
_stream.Write(bytes, 0, bytes.Length);
}
}
}
/// <summary>
/// Disposes of the internal stream if specified to not leave open.
/// </summary>
public void Dispose()
{
if (!_leaveOpen)
_stream.Dispose();
}
}
}