| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 | 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<byte>();            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<int>();            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<GPSTrackerLicense>, IOneToMany<GPSTracker>    {        [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);        }    }}
 |