123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479 |
- using System;
- using System.Collections.Concurrent;
- using System.Collections.Generic;
- using System.Linq;
- using System.Net.Sockets;
- using System.Threading;
- using System.Windows.Media.Media3D;
- using Comal.Classes;
- using InABox.Core;
- using InABox.DigitalMatter;
- namespace PRSServer
- {
- /// <summary>
- /// Summary description for TCPSocketListener.
- /// </summary>
- internal class GPSListener
- {
- private Thread m_clientListenerThread;
- /// <summary>
- /// Variables that are accessed by other classes indirectly.
- /// </summary>
- private Socket m_clientSocket;
- private DateTime m_currentReceiveDateTime;
- private readonly ConcurrentDictionary<string, Device> m_devices = new();
- /// <summary>
- /// Working Variables.
- /// </summary>
- private DateTime m_lastReceiveDateTime;
- private bool m_markedForDeletion;
- private bool m_stopClient;
- private readonly ConcurrentQueue<Tuple<GPSTrackerLocation, string>> m_updates;
- /// <summary>
- /// Client Socket Listener Constructor.
- /// </summary>
- /// <param name="clientSocket"></param>
- public GPSListener(Socket clientSocket,
- ConcurrentDictionary<string, Device> devices,
- ConcurrentQueue<Tuple<GPSTrackerLocation, string>> updates)
- {
- m_clientSocket = clientSocket;
- if (devices != null)
- m_devices = devices;
- m_updates = updates;
- }
- /// <summary>
- /// Client SocketListener Destructor.
- /// </summary>
- ~GPSListener()
- {
- StopSocketListener();
- }
- /// <summary>
- /// Method that starts SocketListener Thread.
- /// </summary>
- public void StartSocketListener()
- {
- if (m_clientSocket != null)
- {
- m_clientListenerThread =
- new Thread(SocketListenerThreadStart);
- m_clientListenerThread.Start();
- }
- }
- /// <summary>
- /// Thread method that does the communication to the client. This
- /// thread tries to receive from client and if client sends any data
- /// then parses it and again wait for the client data to come in a
- /// loop. The recieve is an indefinite time receive.
- /// </summary>
- private void SocketListenerThreadStart()
- {
- var _threshold = TimeSpan.FromMinutes(2);
- var size = 0;
- var buf = new byte[2048];
- var consolidated = new List<byte>();
- var m_product = "";
- var m_serial = "";
- var datarequests = new List<DMDataRequest>();
- //var filename = Path.Combine(Logger.LogFolder,DateTime.Now.Ticks.ToString()+".txt");
- //var writer = File.AppendText(filename);
- m_lastReceiveDateTime = DateTime.Now;
- m_currentReceiveDateTime = DateTime.Now;
- var t = new Timer(CheckClientCommInterval,
- null, 15000, 15000);
- while (!m_stopClient)
- {
- var bClose = false;
- try
- {
- size = m_clientSocket.Receive(buf);
- m_currentReceiveDateTime = DateTime.Now;
- if (size > 0)
- {
- var buf2 = buf.Take(size).ToArray();
- //writer.WriteLine(BitConverter.ToString(buf2).Replace("-", string.Empty));
- consolidated.AddRange(buf2);
- try
- {
- while (consolidated.Count > 0)
- {
- DMMessage message = null;
- try
- {
- message = DMFactory.ParseMessage(consolidated.ToArray());
- }
- catch (Exception e)
- {
- Logger.Send(
- LogType.Error,
- Thread.CurrentThread.ManagedThreadId.ToString(),
- string.Format("Unable to Parse Record: {0} ({1})", e.Message, BitConverter.ToString(consolidated.ToArray()))
- );
- }
- if (message is DMHelloRequest hello)
- {
- m_product = DMFactory.GetDeviceName(hello.ProductID);
- m_serial = hello.SerialNumber.ToString();
- Logger.Send(LogType.Information, m_serial, string.Format("Hello {0} ({1})", m_product, m_serial));
- var ack = new DMHelloResponse();
- m_clientSocket.Send(ack.Encode());
- }
- else if (message is DMDataRequest)
- {
- //Logger.Send(LogType.Information, m_serial, String.Format("Data: {0}", String.Join(":",consolidated.Select(x => x.ToString("X2")))));
- var data = message as DMDataRequest;
- Logger.Send(LogType.Information, m_serial, string.Format("{0} DataRecords Received", data.Records.Length));
- var iRecord = 1;
- foreach (var record in data.Records)
- {
- Logger.Send(LogType.Information, m_serial,
- string.Format("- Data Record #{0}: {1:dd MMM yy hh-mm-ss} ({2} Fields)", iRecord,
- record.TimeStampToDateTime(record.TimeStamp), record.Fields.Length));
- iRecord++;
- foreach (var field in record.Fields)
- Logger.Send(LogType.Information, m_serial,
- string.Format(" [{0}] {1}: {2}", field.IsValid() ? "X" : " ", DMFactory.GetFieldName(field.Type),
- field));
- }
- datarequests.Add(data);
- // Update the Server Here
- }
- else if (message is DMConfirmRequest)
- {
- Logger.Send(LogType.Information, m_serial, string.Format("Goodbye {0} ({1})", m_product, m_serial));
- var updates = new List<GPSTrackerLocation>();
- foreach (var data in datarequests)
- foreach (var record in data.Records)
- {
- var gps = record.Fields.FirstOrDefault(x => x is DMGPSField && x.IsValid()) as DMGPSField;
- if (m_devices.ContainsKey(m_serial))
- {
- if (gps != null)
- {
- if (record.TimeStamp != 0 && gps.Latitude != 0 && gps.Longitude != 0)
- {
- if (!gps.StatusFlags().Any(x => x == GPSStatus.NoSignal))
- {
- var timestamp = record.TimeStampToDateTime(record.TimeStamp);
- var age = timestamp - m_devices[m_serial].TimeStamp;
- if (age > _threshold)
- {
- var device = m_devices[m_serial];
- var location = new GPSTrackerLocation();
- location.DeviceID = m_serial;
- location.Tracker.ID = device.ID;
- location.Location.Timestamp = timestamp;
- location.Location.Latitude = (double)gps.Latitude / 10000000.0F;
- location.Location.Longitude = (double)gps.Longitude / 10000000.0F;
- updates.Add(location);
- var analoguedata =
- record.Fields.FirstOrDefault(x =>
- x is DMAnalogueDataField16) as DMAnalogueDataField16;
- if (analoguedata != null)
- location.BatteryLevel = analoguedata.BatteryStrength
- ?? device.CalculateBatteryLevel(analoguedata.InternalVoltage);
- }
- else
- {
- Logger.Send(LogType.Information, m_serial,
- string.Format("- Skipping: Recent Update ({0}) {1:mm\\:ss}", m_serial, age));
- }
- }
- else
- {
- Logger.Send(LogType.Information, m_serial,
- string.Format("- Skipping: Invalid Signal ({0})", m_serial));
- }
- var taglists = record.Fields.Where(x => x is DMBluetoothTagList);
- foreach (DMBluetoothTagList taglist in taglists)
- foreach (var item in taglist.Items.Where(x => x.LogReason != 2))
- {
- var tagid = item.Tag.ID();
- if (!m_devices.ContainsKey(tagid) && tagid.Length == 17 && tagid.Split(':').Length == 6)
- {
- var truncated = tagid.Substring(0, 15);
- var newtag = m_devices.Keys.FirstOrDefault(x => x.StartsWith(truncated));
- Logger.Send(LogType.Information, m_serial,
- string.Format("- Truncating BT Tag: {0} -> {1} -> {2}", tagid, truncated, newtag));
- if (!string.IsNullOrWhiteSpace(newtag))
- tagid = newtag;
- }
- if (m_devices.ContainsKey(tagid))
- {
- var timestamp = record.TimeStampToDateTime(record.TimeStamp);
- var age = timestamp - m_devices[tagid].TimeStamp;
- if (age > _threshold)
- {
- var device = m_devices[tagid];
- var btloc = new GPSTrackerLocation();
- btloc.DeviceID = tagid;
- btloc.Tracker.ID = device.ID;
- btloc.Location.Timestamp = timestamp;
- btloc.Location.Latitude = (double)gps.Latitude / 10000000.0F;
- btloc.Location.Longitude = (double)gps.Longitude / 10000000.0F;
- if (item.Tag is DMGuppyBluetoothTag guppy)
- {
- btloc.BatteryLevel = device.CalculateBatteryLevel(guppy.BatteryVoltage);
- //guppy.BatteryVoltage * 5F / 3F;
- }
- else if (item.Tag is DMSensorNodeBluetoothTag sensornode)
- {
- // Need to check with Kenrick about the calcs here..
- // Guppies have 1 battery (ie 1.5V) while Sensornodes have 3 (4.5V)
- btloc.BatteryLevel = device.CalculateBatteryLevel(sensornode.BatteryVoltage);
- //btloc.BatteryLevel = sensornode.BatteryVoltage * 5F / 3F;
- }
- updates.Add(btloc);
- }
- else
- {
- Logger.Send(LogType.Information, m_serial,
- string.Format("- Skipping: Recent Update ({0}) {1:mm\\:ss}", tagid, age));
- }
- }
- else
- {
- Logger.Send(LogType.Information, m_serial,
- string.Format("- Skipping: Unknown Tag ({0})", tagid));
- }
- }
- var tags = record.Fields.Where(x => x is DMBluetoothTagData);
- foreach (DMBluetoothTagData tag in tags)
- if (tag.LogReason != 2 && tag.TimeStamp != 0 && tag.Latitude != 0 && tag.Longitude != 0)
- {
- var tagid = tag.Tag.ID();
- if (!m_devices.ContainsKey(tagid) && tagid.Length == 17 && tagid.Split(':').Length == 6)
- {
- var truncated = tagid.Substring(0, 15);
- var newtag = m_devices.Keys.FirstOrDefault(x => x.StartsWith(truncated));
- Logger.Send(LogType.Information, m_serial,
- string.Format("- Truncating BT Tag: {0} -> {1} -> {2}", tagid, truncated,
- newtag));
- if (!string.IsNullOrWhiteSpace(newtag))
- tagid = newtag;
- }
- if (m_devices.ContainsKey(tagid))
- {
- var timestamp = record.TimeStampToDateTime(record.TimeStamp);
- var age = timestamp - m_devices[tagid].TimeStamp;
- if (age > _threshold)
- {
- var device = m_devices[tagid];
- var btloc = new GPSTrackerLocation();
- btloc.DeviceID = tagid;
- btloc.Tracker.ID = device.ID;
- btloc.Location.Timestamp = timestamp;
- btloc.Location.Latitude = (double)gps.Latitude / 10000000.0F;
- btloc.Location.Longitude = (double)gps.Longitude / 10000000.0F;
- updates.Add(btloc);
-
- if (tag.Tag is DMGuppyBluetoothTag guppy)
- {
- btloc.BatteryLevel = device.CalculateBatteryLevel(guppy.BatteryVoltage);
- //guppy.BatteryVoltage * 5F / 3F;
- }
- else if (tag.Tag is DMSensorNodeBluetoothTag sensornode)
- {
- // Need to check with Kenrick about the calcs here..
- // Guppies have 1 battery (ie 1.5V) while Sensornodes have 3 (4.5V)
- btloc.BatteryLevel = device.CalculateBatteryLevel(sensornode.BatteryVoltage);
- //btloc.BatteryLevel = sensornode.BatteryVoltage * 5F / 3F;
- }
- }
- else
- {
- Logger.Send(LogType.Information, m_serial,
- string.Format("- Skipping: Recent Update ({0}) {1:mm\\:ss}", tagid, age));
- }
- }
- else
- {
- Logger.Send(LogType.Information, m_serial,
- string.Format("- Skipping: Unknown Tag ({0})", tagid));
- }
- }
- }
- else
- {
- Logger.Send(LogType.Information, m_serial,
- string.Format("- Skipping: Invalid GPS Data ({0}) {1}{2}{3}", m_serial,
- gps.TimeStamp == 0 ? "Bad TimeStamp " : "", gps.Latitude == 0 ? "Bad Latitude " : "",
- gps.Longitude == 0 ? "Bad Longitude " : "").Trim());
- }
- }
- else
- {
- Logger.Send(LogType.Information, m_serial,
- string.Format("- Skipping: Missing GPS Data ({0})", m_serial));
- }
- }
- else
- {
- Logger.Send(LogType.Information, m_serial, string.Format("- Skipping: Unknown Device ({0})", m_serial));
- }
- }
- if (updates.Any())
- {
- Logger.Send(LogType.Information, m_serial,
- string.Format("Sending updates ({0}): {1}", updates.Count,
- string.Join(", ", updates.Select(x => x.DeviceID).Distinct())));
- foreach (var update in updates)
- {
- Logger.Send(LogType.Information, m_serial,
- string.Format("- Updating Device Cache: ({0}): {1:yyyy-MM-dd hh:mm:ss}", update.DeviceID,
- update.Location.Timestamp));
- //if (m_devices.ContainsKey(update.DeviceID))
- var oldDevice = m_devices[update.DeviceID];
- m_devices[update.DeviceID] =
- new Device(oldDevice.ID, update.Location.Timestamp, oldDevice.BatteryFormula);
- }
- foreach (var update in updates)
- m_updates.Enqueue(new Tuple<GPSTrackerLocation, string>(update,
- string.Format("Updated by {0} ({1})", m_product, m_serial)));
- //new Client<GPSTrackerLocation>().Save(
- // updates,
- // String.Format("Updated by {0} ({1})", m_product, m_serial),
- // (locations, errors) =>
- // {
- // //if (locations != null)
- // //{
- // // foreach (var location in locations)
- // // {
- // // if (m_devices.ContainsKey(location.DeviceID))
- // // m_devices[location.DeviceID] = location.Tracker.ID;
- // // }
- // //}
- // }
- //);
- }
- var ack = new DMConfirmResponse();
- ack.Status = 1;
- m_clientSocket.Send(ack.Encode());
- //bClose = true;
- }
- consolidated.RemoveRange(0, message.CheckSum + 5);
- }
- }
- catch (Exception e)
- {
- Logger.Send(LogType.Error, Thread.CurrentThread.ManagedThreadId.ToString(), e.Message + "\n" + e.StackTrace);
- }
- }
- else
- {
- bClose = true;
- }
- }
- catch (SocketException se)
- {
- bClose = true;
- }
- if (bClose)
- {
- //writer.Flush();
- //writer.Close();
- //writer.Dispose();
- //writer = null;
- m_stopClient = true;
- m_markedForDeletion = true;
- }
- }
- t.Change(Timeout.Infinite, Timeout.Infinite);
- t = null;
- }
- /// <summary>
- /// Method that stops Client SocketListening Thread.
- /// </summary>
- public void StopSocketListener()
- {
- if (m_clientSocket != null)
- {
- m_stopClient = true;
- m_clientSocket.Close();
- // Wait for one second for the the thread to stop.
- m_clientListenerThread.Join(1000);
- // If still alive; Get rid of the thread.
- if (m_clientListenerThread.IsAlive)
- {
- Logger.Send(LogType.Error, "", "Thread didn't die in time.");
- }
- //m_clientListenerThread.Abort();
- m_clientListenerThread = null;
- m_clientSocket = null;
- m_markedForDeletion = true;
- }
- }
- /// <summary>
- /// Method that returns the state of this object i.e. whether this
- /// object is marked for deletion or not.
- /// </summary>
- /// <returns></returns>
- public bool IsMarkedForDeletion()
- {
- return m_markedForDeletion;
- }
- /// <summary>
- /// Method that checks whether there are any client calls for the
- /// last 15 seconds or not. If not this client SocketListener will
- /// be closed.
- /// </summary>
- /// <param name="o"></param>
- private void CheckClientCommInterval(object o)
- {
- if (m_lastReceiveDateTime.Equals(m_currentReceiveDateTime))
- StopSocketListener();
- else
- m_lastReceiveDateTime = m_currentReceiveDateTime;
- }
- }
- }
|