GPSListener.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Net.Sockets;
  6. using System.Threading;
  7. using System.Windows.Media.Media3D;
  8. using Comal.Classes;
  9. using InABox.Core;
  10. using InABox.DigitalMatter;
  11. namespace PRSServer
  12. {
  13. /// <summary>
  14. /// Summary description for TCPSocketListener.
  15. /// </summary>
  16. internal class GPSListener
  17. {
  18. private Thread m_clientListenerThread;
  19. /// <summary>
  20. /// Variables that are accessed by other classes indirectly.
  21. /// </summary>
  22. private Socket m_clientSocket;
  23. private DateTime m_currentReceiveDateTime;
  24. private readonly ConcurrentDictionary<string, Device> m_devices = new();
  25. /// <summary>
  26. /// Working Variables.
  27. /// </summary>
  28. private DateTime m_lastReceiveDateTime;
  29. private bool m_markedForDeletion;
  30. private bool m_stopClient;
  31. private readonly ConcurrentQueue<Tuple<GPSTrackerLocation, string>> m_updates;
  32. /// <summary>
  33. /// Client Socket Listener Constructor.
  34. /// </summary>
  35. /// <param name="clientSocket"></param>
  36. public GPSListener(Socket clientSocket,
  37. ConcurrentDictionary<string, Device> devices,
  38. ConcurrentQueue<Tuple<GPSTrackerLocation, string>> updates)
  39. {
  40. m_clientSocket = clientSocket;
  41. if (devices != null)
  42. m_devices = devices;
  43. m_updates = updates;
  44. }
  45. /// <summary>
  46. /// Client SocketListener Destructor.
  47. /// </summary>
  48. ~GPSListener()
  49. {
  50. StopSocketListener();
  51. }
  52. /// <summary>
  53. /// Method that starts SocketListener Thread.
  54. /// </summary>
  55. public void StartSocketListener()
  56. {
  57. if (m_clientSocket != null)
  58. {
  59. m_clientListenerThread =
  60. new Thread(SocketListenerThreadStart);
  61. m_clientListenerThread.Start();
  62. }
  63. }
  64. /// <summary>
  65. /// Thread method that does the communication to the client. This
  66. /// thread tries to receive from client and if client sends any data
  67. /// then parses it and again wait for the client data to come in a
  68. /// loop. The recieve is an indefinite time receive.
  69. /// </summary>
  70. private void SocketListenerThreadStart()
  71. {
  72. var _threshold = TimeSpan.FromMinutes(2);
  73. var size = 0;
  74. var buf = new byte[2048];
  75. var consolidated = new List<byte>();
  76. var m_product = "";
  77. var m_serial = "";
  78. var datarequests = new List<DMDataRequest>();
  79. //var filename = Path.Combine(Logger.LogFolder,DateTime.Now.Ticks.ToString()+".txt");
  80. //var writer = File.AppendText(filename);
  81. m_lastReceiveDateTime = DateTime.Now;
  82. m_currentReceiveDateTime = DateTime.Now;
  83. var t = new Timer(CheckClientCommInterval,
  84. null, 15000, 15000);
  85. while (!m_stopClient)
  86. {
  87. var bClose = false;
  88. try
  89. {
  90. size = m_clientSocket.Receive(buf);
  91. m_currentReceiveDateTime = DateTime.Now;
  92. if (size > 0)
  93. {
  94. var buf2 = buf.Take(size).ToArray();
  95. //writer.WriteLine(BitConverter.ToString(buf2).Replace("-", string.Empty));
  96. consolidated.AddRange(buf2);
  97. try
  98. {
  99. while (consolidated.Count > 0)
  100. {
  101. DMMessage message = null;
  102. try
  103. {
  104. message = DMFactory.ParseMessage(consolidated.ToArray());
  105. }
  106. catch (Exception e)
  107. {
  108. Logger.Send(
  109. LogType.Error,
  110. Thread.CurrentThread.ManagedThreadId.ToString(),
  111. string.Format("Unable to Parse Record: {0} ({1})", e.Message, BitConverter.ToString(consolidated.ToArray()))
  112. );
  113. }
  114. if (message is DMHelloRequest hello)
  115. {
  116. m_product = DMFactory.GetDeviceName(hello.ProductID);
  117. m_serial = hello.SerialNumber.ToString();
  118. Logger.Send(LogType.Information, m_serial, string.Format("Hello {0} ({1})", m_product, m_serial));
  119. var ack = new DMHelloResponse();
  120. m_clientSocket.Send(ack.Encode());
  121. }
  122. else if (message is DMDataRequest)
  123. {
  124. //Logger.Send(LogType.Information, m_serial, String.Format("Data: {0}", String.Join(":",consolidated.Select(x => x.ToString("X2")))));
  125. var data = message as DMDataRequest;
  126. Logger.Send(LogType.Information, m_serial, string.Format("{0} DataRecords Received", data.Records.Length));
  127. var iRecord = 1;
  128. foreach (var record in data.Records)
  129. {
  130. Logger.Send(LogType.Information, m_serial,
  131. string.Format("- Data Record #{0}: {1:dd MMM yy hh-mm-ss} ({2} Fields)", iRecord,
  132. record.TimeStampToDateTime(record.TimeStamp), record.Fields.Length));
  133. iRecord++;
  134. foreach (var field in record.Fields)
  135. Logger.Send(LogType.Information, m_serial,
  136. string.Format(" [{0}] {1}: {2}", field.IsValid() ? "X" : " ", DMFactory.GetFieldName(field.Type),
  137. field));
  138. }
  139. datarequests.Add(data);
  140. // Update the Server Here
  141. }
  142. else if (message is DMConfirmRequest)
  143. {
  144. Logger.Send(LogType.Information, m_serial, string.Format("Goodbye {0} ({1})", m_product, m_serial));
  145. var updates = new List<GPSTrackerLocation>();
  146. foreach (var data in datarequests)
  147. foreach (var record in data.Records)
  148. {
  149. var gps = record.Fields.FirstOrDefault(x => x is DMGPSField && x.IsValid()) as DMGPSField;
  150. if (m_devices.ContainsKey(m_serial))
  151. {
  152. if (gps != null)
  153. {
  154. if (record.TimeStamp != 0 && gps.Latitude != 0 && gps.Longitude != 0)
  155. {
  156. if (!gps.StatusFlags().Any(x => x == GPSStatus.NoSignal))
  157. {
  158. var timestamp = record.TimeStampToDateTime(record.TimeStamp);
  159. var age = timestamp - m_devices[m_serial].TimeStamp;
  160. if (age > _threshold)
  161. {
  162. var device = m_devices[m_serial];
  163. var location = new GPSTrackerLocation();
  164. location.DeviceID = m_serial;
  165. location.Tracker.ID = device.ID;
  166. location.Location.Timestamp = timestamp;
  167. location.Location.Latitude = (double)gps.Latitude / 10000000.0F;
  168. location.Location.Longitude = (double)gps.Longitude / 10000000.0F;
  169. updates.Add(location);
  170. var analoguedata =
  171. record.Fields.FirstOrDefault(x =>
  172. x is DMAnalogueDataField16) as DMAnalogueDataField16;
  173. if (analoguedata != null)
  174. location.BatteryLevel = analoguedata.BatteryStrength
  175. ?? device.CalculateBatteryLevel(analoguedata.InternalVoltage);
  176. }
  177. else
  178. {
  179. Logger.Send(LogType.Information, m_serial,
  180. string.Format("- Skipping: Recent Update ({0}) {1:mm\\:ss}", m_serial, age));
  181. }
  182. }
  183. else
  184. {
  185. Logger.Send(LogType.Information, m_serial,
  186. string.Format("- Skipping: Invalid Signal ({0})", m_serial));
  187. }
  188. var taglists = record.Fields.Where(x => x is DMBluetoothTagList);
  189. foreach (DMBluetoothTagList taglist in taglists)
  190. foreach (var item in taglist.Items.Where(x => x.LogReason != 2))
  191. {
  192. var tagid = item.Tag.ID();
  193. if (!m_devices.ContainsKey(tagid) && tagid.Length == 17 && tagid.Split(':').Length == 6)
  194. {
  195. var truncated = tagid.Substring(0, 15);
  196. var newtag = m_devices.Keys.FirstOrDefault(x => x.StartsWith(truncated));
  197. Logger.Send(LogType.Information, m_serial,
  198. string.Format("- Truncating BT Tag: {0} -> {1} -> {2}", tagid, truncated, newtag));
  199. if (!string.IsNullOrWhiteSpace(newtag))
  200. tagid = newtag;
  201. }
  202. if (m_devices.ContainsKey(tagid))
  203. {
  204. var timestamp = record.TimeStampToDateTime(record.TimeStamp);
  205. var age = timestamp - m_devices[tagid].TimeStamp;
  206. if (age > _threshold)
  207. {
  208. var device = m_devices[tagid];
  209. var btloc = new GPSTrackerLocation();
  210. btloc.DeviceID = tagid;
  211. btloc.Tracker.ID = device.ID;
  212. btloc.Location.Timestamp = timestamp;
  213. btloc.Location.Latitude = (double)gps.Latitude / 10000000.0F;
  214. btloc.Location.Longitude = (double)gps.Longitude / 10000000.0F;
  215. if (item.Tag is DMGuppyBluetoothTag guppy)
  216. {
  217. btloc.BatteryLevel = device.CalculateBatteryLevel(guppy.BatteryVoltage);
  218. //guppy.BatteryVoltage * 5F / 3F;
  219. }
  220. else if (item.Tag is DMSensorNodeBluetoothTag sensornode)
  221. {
  222. // Need to check with Kenrick about the calcs here..
  223. // Guppies have 1 battery (ie 1.5V) while Sensornodes have 3 (4.5V)
  224. btloc.BatteryLevel = device.CalculateBatteryLevel(sensornode.BatteryVoltage);
  225. //btloc.BatteryLevel = sensornode.BatteryVoltage * 5F / 3F;
  226. }
  227. updates.Add(btloc);
  228. }
  229. else
  230. {
  231. Logger.Send(LogType.Information, m_serial,
  232. string.Format("- Skipping: Recent Update ({0}) {1:mm\\:ss}", tagid, age));
  233. }
  234. }
  235. else
  236. {
  237. Logger.Send(LogType.Information, m_serial,
  238. string.Format("- Skipping: Unknown Tag ({0})", tagid));
  239. }
  240. }
  241. var tags = record.Fields.Where(x => x is DMBluetoothTagData);
  242. foreach (DMBluetoothTagData tag in tags)
  243. if (tag.LogReason != 2 && tag.TimeStamp != 0 && tag.Latitude != 0 && tag.Longitude != 0)
  244. {
  245. var tagid = tag.Tag.ID();
  246. if (!m_devices.ContainsKey(tagid) && tagid.Length == 17 && tagid.Split(':').Length == 6)
  247. {
  248. var truncated = tagid.Substring(0, 15);
  249. var newtag = m_devices.Keys.FirstOrDefault(x => x.StartsWith(truncated));
  250. Logger.Send(LogType.Information, m_serial,
  251. string.Format("- Truncating BT Tag: {0} -> {1} -> {2}", tagid, truncated,
  252. newtag));
  253. if (!string.IsNullOrWhiteSpace(newtag))
  254. tagid = newtag;
  255. }
  256. if (m_devices.ContainsKey(tagid))
  257. {
  258. var timestamp = record.TimeStampToDateTime(record.TimeStamp);
  259. var age = timestamp - m_devices[tagid].TimeStamp;
  260. if (age > _threshold)
  261. {
  262. var device = m_devices[tagid];
  263. var btloc = new GPSTrackerLocation();
  264. btloc.DeviceID = tagid;
  265. btloc.Tracker.ID = device.ID;
  266. btloc.Location.Timestamp = timestamp;
  267. btloc.Location.Latitude = (double)gps.Latitude / 10000000.0F;
  268. btloc.Location.Longitude = (double)gps.Longitude / 10000000.0F;
  269. updates.Add(btloc);
  270. if (tag.Tag is DMGuppyBluetoothTag guppy)
  271. {
  272. btloc.BatteryLevel = device.CalculateBatteryLevel(guppy.BatteryVoltage);
  273. //guppy.BatteryVoltage * 5F / 3F;
  274. }
  275. else if (tag.Tag is DMSensorNodeBluetoothTag sensornode)
  276. {
  277. // Need to check with Kenrick about the calcs here..
  278. // Guppies have 1 battery (ie 1.5V) while Sensornodes have 3 (4.5V)
  279. btloc.BatteryLevel = device.CalculateBatteryLevel(sensornode.BatteryVoltage);
  280. //btloc.BatteryLevel = sensornode.BatteryVoltage * 5F / 3F;
  281. }
  282. }
  283. else
  284. {
  285. Logger.Send(LogType.Information, m_serial,
  286. string.Format("- Skipping: Recent Update ({0}) {1:mm\\:ss}", tagid, age));
  287. }
  288. }
  289. else
  290. {
  291. Logger.Send(LogType.Information, m_serial,
  292. string.Format("- Skipping: Unknown Tag ({0})", tagid));
  293. }
  294. }
  295. }
  296. else
  297. {
  298. Logger.Send(LogType.Information, m_serial,
  299. string.Format("- Skipping: Invalid GPS Data ({0}) {1}{2}{3}", m_serial,
  300. gps.TimeStamp == 0 ? "Bad TimeStamp " : "", gps.Latitude == 0 ? "Bad Latitude " : "",
  301. gps.Longitude == 0 ? "Bad Longitude " : "").Trim());
  302. }
  303. }
  304. else
  305. {
  306. Logger.Send(LogType.Information, m_serial,
  307. string.Format("- Skipping: Missing GPS Data ({0})", m_serial));
  308. }
  309. }
  310. else
  311. {
  312. Logger.Send(LogType.Information, m_serial, string.Format("- Skipping: Unknown Device ({0})", m_serial));
  313. }
  314. }
  315. if (updates.Any())
  316. {
  317. Logger.Send(LogType.Information, m_serial,
  318. string.Format("Sending updates ({0}): {1}", updates.Count,
  319. string.Join(", ", updates.Select(x => x.DeviceID).Distinct())));
  320. foreach (var update in updates)
  321. {
  322. Logger.Send(LogType.Information, m_serial,
  323. string.Format("- Updating Device Cache: ({0}): {1:yyyy-MM-dd hh:mm:ss}", update.DeviceID,
  324. update.Location.Timestamp));
  325. //if (m_devices.ContainsKey(update.DeviceID))
  326. var oldDevice = m_devices[update.DeviceID];
  327. m_devices[update.DeviceID] =
  328. new Device(oldDevice.ID, update.Location.Timestamp, oldDevice.BatteryFormula);
  329. }
  330. foreach (var update in updates)
  331. m_updates.Enqueue(new Tuple<GPSTrackerLocation, string>(update,
  332. string.Format("Updated by {0} ({1})", m_product, m_serial)));
  333. //new Client<GPSTrackerLocation>().Save(
  334. // updates,
  335. // String.Format("Updated by {0} ({1})", m_product, m_serial),
  336. // (locations, errors) =>
  337. // {
  338. // //if (locations != null)
  339. // //{
  340. // // foreach (var location in locations)
  341. // // {
  342. // // if (m_devices.ContainsKey(location.DeviceID))
  343. // // m_devices[location.DeviceID] = location.Tracker.ID;
  344. // // }
  345. // //}
  346. // }
  347. //);
  348. }
  349. var ack = new DMConfirmResponse();
  350. ack.Status = 1;
  351. m_clientSocket.Send(ack.Encode());
  352. //bClose = true;
  353. }
  354. consolidated.RemoveRange(0, message.CheckSum + 5);
  355. }
  356. }
  357. catch (Exception e)
  358. {
  359. Logger.Send(LogType.Error, Thread.CurrentThread.ManagedThreadId.ToString(), e.Message + "\n" + e.StackTrace);
  360. }
  361. }
  362. else
  363. {
  364. bClose = true;
  365. }
  366. }
  367. catch (SocketException se)
  368. {
  369. bClose = true;
  370. }
  371. if (bClose)
  372. {
  373. //writer.Flush();
  374. //writer.Close();
  375. //writer.Dispose();
  376. //writer = null;
  377. m_stopClient = true;
  378. m_markedForDeletion = true;
  379. }
  380. }
  381. t.Change(Timeout.Infinite, Timeout.Infinite);
  382. t = null;
  383. }
  384. /// <summary>
  385. /// Method that stops Client SocketListening Thread.
  386. /// </summary>
  387. public void StopSocketListener()
  388. {
  389. if (m_clientSocket != null)
  390. {
  391. m_stopClient = true;
  392. m_clientSocket.Close();
  393. // Wait for one second for the the thread to stop.
  394. m_clientListenerThread.Join(1000);
  395. // If still alive; Get rid of the thread.
  396. if (m_clientListenerThread.IsAlive)
  397. {
  398. Logger.Send(LogType.Error, "", "Thread didn't die in time.");
  399. }
  400. //m_clientListenerThread.Abort();
  401. m_clientListenerThread = null;
  402. m_clientSocket = null;
  403. m_markedForDeletion = true;
  404. }
  405. }
  406. /// <summary>
  407. /// Method that returns the state of this object i.e. whether this
  408. /// object is marked for deletion or not.
  409. /// </summary>
  410. /// <returns></returns>
  411. public bool IsMarkedForDeletion()
  412. {
  413. return m_markedForDeletion;
  414. }
  415. /// <summary>
  416. /// Method that checks whether there are any client calls for the
  417. /// last 15 seconds or not. If not this client SocketListener will
  418. /// be closed.
  419. /// </summary>
  420. /// <param name="o"></param>
  421. private void CheckClientCommInterval(object o)
  422. {
  423. if (m_lastReceiveDateTime.Equals(m_currentReceiveDateTime))
  424. StopSocketListener();
  425. else
  426. m_lastReceiveDateTime = m_currentReceiveDateTime;
  427. }
  428. }
  429. }