/// <summary> /// Encode an OpenThings <see cref="Message"/> /// </summary> /// <param name="message">The <see cref="Message"/> message to encode</param> /// <returns>A <see cref="IList{T}"/> of the encoded OpentThings message bytes</returns> public IList <byte> Encode(Message message) { Crc16Ccitt crc16Ccitt = new Crc16Ccitt(0); List <byte> encoded = new List <byte> { 0, // Length message.Header.ManufacturerId, message.Header.ProductId, 0, // Pip Msb 0, // Pip Lsb (byte)(message.Header.SensorId >> 16), (byte)((message.Header.SensorId & 0xFF00) >> 8), (byte)(message.Header.SensorId & 0xFF) }; foreach (var record in message.Records) { encoded.Add((byte)record.Parameter.Identifier); encoded.AddRange(record.Data.Encode()); } encoded.Add(0); var crcBytes = crc16Ccitt.ComputeChecksumBytes(encoded.Skip(5).ToArray()); encoded.AddRange(crcBytes.Reverse()); encoded[0] = (byte)(encoded.Count - 1); return(encoded); }
/// <summary> /// Decode a <see cref="IList{T}"/> of bytes representing the OpenThings message payload /// </summary> /// <param name="payload">The OpenThings payload message bytes</param> /// <param name="pidMaps">A mapping of PID to manufacture Ids to enable linear shift encryption decoding</param> /// <returns>A decode <see cref="Message"/></returns> public Message Decode(IList <byte> payload, IList <PidMap> pidMaps) { Crc16Ccitt crc16Ccitt = new Crc16Ccitt(0); if (payload == null || payload.Count == 0) { throw new ArgumentOutOfRangeException(nameof(payload)); } if (payload.Count < 11) { throw new OpenThingsException($"Invalid buffer length [{payload.Count}] too short"); } if (payload[0] < 11) { throw new OpenThingsException($"Invalid OpenThings Header length [{payload[0]}]"); } if (payload[0] > payload.Count) { throw new OpenThingsException($"Invalid OpenThings Header length [{payload[0]}] Buffer Length: [{payload.Count}]"); } var pip = BitConverter.ToUInt16(payload.Skip(3).Take(2).Reverse().ToArray(), 0); var header = new MessageHeader(payload[1], payload[2], pip, 0x0); var body = payload.Take(payload[0] + 1).Skip(5).ToList(); if (pip != 0) { var pidMap = pidMaps.FirstOrDefault(_ => _.ManufacturerId == header.ManufacturerId); if (pidMap == null) { throw new OpenThingsException($"No [{nameof(PidMap)}] found for manufacture id [0x{header.ManufacturerId:X}]"); } body = Decrypt(body, pidMap.Pid, header.Pip); header.SetSensorId(body.Take(3).ToList()); } else { header.SetSensorId(body.Take(3).ToList()); } ushort crcActual = (ushort)((body.Skip(body.Count - 2).First() << 8) + body.Skip(body.Count - 1).First()); ushort crcExpected = crc16Ccitt.ComputeChecksum(body.Take(body.Count - 2).ToArray()); if (crcActual != crcExpected) { throw new OpenThingsException($"Invalid Crc Expected: [0x{crcExpected:X4}] Actual: [0x{crcActual:X4}]"); } var message = new Message(header); ParseMessageRecords(message, body.Skip(3).Take(body.Count - 6).ToList()); return(message); }