using System; using System.Collections.Generic; using System.Globalization; using InABox.Core; namespace Comal.Classes { public enum SigfoxType { Location = 0, DeviceStats = 1, DownloadAcknowledgement = 2 } public abstract class SigfoxBase { public SigfoxBase(byte[] buffer) { if (buffer == null) throw new Exception("Invalid Payload"); var recordType = buffer[0] & 0x0f; if (recordType != (int)ValidType()) throw new Exception(string.Format("Invalid Record Type: {0}", recordType)); Parse(buffer); } private SigfoxBase() { } public static byte[] hex2Bytes(string val) { if (string.IsNullOrWhiteSpace(val)) return null; //return new byte[] { }; val = val.Trim(); if (val.StartsWith("0x")) val = val.Substring(2); //get rid of starting '0x' var numBytes = val.Length / 2; //byte[] bytes = new byte[] { }; var byteList = new List(); for (var i = 0; i < numBytes; i++) { var sSub = val.Substring(i * 2, 2); var bSub = byte.Parse(sSub, NumberStyles.HexNumber, CultureInfo.InvariantCulture); byteList.Add(bSub); //, 16)); } return byteList.ToArray(); } protected int ParseLittleEndianInt32(byte[] buffer, int offset) { return (buffer[offset + 3] << 24) + (buffer[offset + 2] << 16) + (buffer[offset + 1] << 8) + buffer[offset]; } protected int ParseLittleEndianInt16(byte[] buffer, int offset) { var arg1 = buffer[offset + 1] << 8; var arg2 = buffer[offset]; return arg1 + arg2; } protected int ParseLittleEndianInt16Bits(byte[] buffer, int offset, int bitOffset, int bitLength) { var temp = ParseLittleEndianInt16(buffer, offset); temp = temp >> bitOffset; var mask = 0xffff >> (16 - bitLength); return temp & mask; } protected abstract SigfoxType ValidType(); protected abstract void Parse(byte[] buffer); } public class SigfoxLocation : SigfoxBase { public SigfoxLocation(byte[] buffer) : base(buffer) { } public double Latitude { get; private set; } public double Longitude { get; private set; } public bool InTrip { get; private set; } public bool LastFixFailed { get; private set; } public int Heading { get; private set; } public int Speed { get; private set; } public double BatteryLevel { get; private set; } protected override SigfoxType ValidType() { return SigfoxType.Location; } protected override void Parse(byte[] buffer) { var flags = buffer[0] & 0xF0; InTrip = (flags & 0x10) > 0; LastFixFailed = (flags & 0x20) > 0; Latitude = ParseLittleEndianInt32(buffer, 1) * 1e-7; Longitude = ParseLittleEndianInt32(buffer, 5) * 1e-7; Heading = buffer[9] * 2; Speed = buffer[10]; BatteryLevel = buffer[11] * 25 / 1000.0f; } } public class SigfoxDeviceStatistics : SigfoxBase { public SigfoxDeviceStatistics(byte[] buffer) : base(buffer) { } public int UptimeWeeks { get; private set; } public int TxCountRaw { get; private set; } public byte RxCountRaw { get; private set; } public int TripCountRaw { get; private set; } public int GpsSuccessRaw { get; private set; } public int GpsFailuresRaw { get; private set; } public int AverageFixTime { get; private set; } public int AverageFailTime { get; private set; } public int AverageFreshenTime { get; private set; } public int WakeupsPerTrip { get; private set; } protected override void Parse(byte[] buffer) { if (buffer == null) return; var recordType = buffer[0] & 0x0f; if (recordType == 2) { UptimeWeeks = ParseLittleEndianInt16Bits(buffer, 0, 4, 9 /*bits*/); TxCountRaw = ParseLittleEndianInt16Bits(buffer, 1, 5, 11 /*bits*/); RxCountRaw = buffer[3]; TripCountRaw = ParseLittleEndianInt16Bits(buffer, 4, 0, 13 /*bits*/); GpsSuccessRaw = ParseLittleEndianInt16Bits(buffer, 5, 5, 10 /*bits*/); GpsFailuresRaw = ParseLittleEndianInt16Bits(buffer, 6, 7, 8 /*bits*/); AverageFixTime = ParseLittleEndianInt16Bits(buffer, 7, 7, 9 /*bits*/); AverageFailTime = ParseLittleEndianInt16Bits(buffer, 9, 0, 9 /*bits*/); AverageFreshenTime = ParseLittleEndianInt16Bits(buffer, 10, 1, 8 /*bits*/); WakeupsPerTrip = buffer[11] >> 1; } } protected override SigfoxType ValidType() { return SigfoxType.DeviceStats; } } public class SigfoxDownlinkAcknowledgement : SigfoxBase { public SigfoxDownlinkAcknowledgement(byte[] buffer) : base(buffer) { } public bool DownlinkAccepted { get; private set; } public byte FirmwareMajor { get; private set; } public byte FirmwareMinor { get; private set; } public int[] DownlinkData { get; private set; } protected override SigfoxType ValidType() { return SigfoxType.DownloadAcknowledgement; } protected override void Parse(byte[] buffer) { var flags = buffer[0] & 0xF0; DownlinkAccepted = (flags & 0x10) > 0; FirmwareMajor = buffer[2]; FirmwareMinor = buffer[3]; var data = new List(); for (var i = 0; i < 8; i++) data.Add(i + 4); DownlinkData = data.ToArray(); } } public static class SigfoxFactory { public static SigfoxBase Parse(string data) { var bytes = SigfoxBase.hex2Bytes(data); var type = bytes[0] & 0x0f; if (type == (int)SigfoxType.Location) return new SigfoxLocation(bytes); if (type == (int)SigfoxType.DeviceStats) return new SigfoxDeviceStatistics(bytes); if (type == (int)SigfoxType.DownloadAcknowledgement) return new SigfoxDownlinkAcknowledgement(bytes); throw new Exception(string.Format("Invalid Record Type: {0}", type)); } } [Caption("Locations")] [UserTracking(typeof(GPSTracker))] public class GPSTrackerLocation : Entity, IPersistent, IRemotable, ILicense, IOneToMany { [EditorSequence(1)] [DateTimeEditor(Visible = Visible.Default, Editable = Editable.Disabled)] public override DateTime Created { get => base.Created; set => base.Created = value; } [EditorSequence(2)] public GPSTrackerLink Tracker { get; set; } [EditorSequence(3)] public Location Location { get; set; } [Obsolete("Replaced with Location.Address")] [TextBoxEditor] [EditorSequence(4)] public string Address { get; set; } [DoubleEditor] [EditorSequence(5)] public double BatteryLevel { get; set; } [DoubleEditor] [EditorSequence(6)] public double Hours { get; set; } [DoubleEditor] [EditorSequence(7)] public double Distance { get; set; } [DoubleEditor] [EditorSequence(8)] public double Counter1 { get; set; } [DoubleEditor] [EditorSequence(9)] public double Counter2 { get; set; } [DoubleEditor] [EditorSequence(10)] public double Counter3 { get; set; } [DoubleEditor] [EditorSequence(11)] public double Counter4 { get; set; } [DoubleEditor] [EditorSequence(12)] public double Counter5 { get; set; } [NullEditor] public string DeviceID { get; set; } [NullEditor] public string MessageID { get; set; } [NullEditor] public string Payload { get; set; } [NullEditor] public long TimeStamp { get; set; } [NullEditor] public bool InTrip { get; set; } [NullEditor] public bool LastFixFailed { get; set; } [NullEditor] public int Heading { get; set; } [NullEditor] public int Speed { get; set; } public override string ToString() { if (!string.IsNullOrWhiteSpace(Payload)) return string.Format("ID {0} TimeStamp {1} Payload {2}", DeviceID, TimeStamp, Payload); return string.Format( "ID {0} Lat: {1:F4} Lng: {2:F4} Batt: {3:F2} Hrs: {4:F2} Dist: {5:F2} C1: {6:F2} C2: {7:F2} C3: {8:F2} C4: {9:F2} C5: {10:F2}", DeviceID, Location.Latitude, Location.Longitude, BatteryLevel, Hours, Distance, Counter1, Counter2, Counter3, Counter4, Counter5); } } }