GPSEngine.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Concurrent;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using System.Net;
  7. using System.Net.Sockets;
  8. using System.Threading;
  9. using System.Threading.Tasks;
  10. using Comal.Classes;
  11. using InABox.Client.IPC;
  12. using InABox.Clients;
  13. using InABox.Core;
  14. using InABox.DigitalMatter;
  15. using PRSServer.Engines;
  16. namespace PRSServer
  17. {
  18. internal class Device
  19. {
  20. public Guid ID { get; set; }
  21. public DateTime TimeStamp { get; set; }
  22. public CoreExpression<GPSBatteryFormulaModel, double>? BatteryFormula { get; set; }
  23. public Device(Guid id, DateTime timeStamp, CoreExpression<GPSBatteryFormulaModel, double>? batteryFormula)
  24. {
  25. ID = id;
  26. TimeStamp = timeStamp;
  27. BatteryFormula = batteryFormula;
  28. }
  29. /// <summary>
  30. /// Should return a percentage
  31. /// </summary>
  32. /// <returns></returns>
  33. public double CalculateBatteryLevel(double batteryValue)
  34. {
  35. if (BatteryFormula != null)
  36. return BatteryFormula.Evaluate(new Dictionary<string, object?>
  37. {
  38. { nameof(GPSBatteryFormulaModel.BatteryLevel), batteryValue }
  39. });
  40. return batteryValue;
  41. }
  42. }
  43. /// <summary>
  44. /// TCPServer is the Server class. When "StartServer" method is called
  45. /// this Server object tries to connect to a IP Address specified on a port
  46. /// configured. Then the server start listening for client socket requests.
  47. /// As soon as a requestcomes in from any client then a Client Socket
  48. /// Listening thread will be started. That thread is responsible for client
  49. /// communication.
  50. /// </summary>
  51. internal class GPSEngine : Engine<GPSServerProperties>
  52. {
  53. /// <summary>
  54. /// Default Constants.
  55. /// </summary>
  56. public static IPAddress DEFAULT_SERVER = IPAddress.Any;
  57. public static int DEFAULT_PORT = 7999;
  58. public static IPEndPoint DEFAULT_IP_END_POINT = new(DEFAULT_SERVER, DEFAULT_PORT);
  59. private readonly CancellationTokenSource cts = new();
  60. private readonly ConcurrentDictionary<string, Device> m_devices = new();
  61. private Task m_purgingThread;
  62. private DateTime m_RefreshDate = DateTime.MinValue;
  63. /// <summary>
  64. /// Local Variables Declaration.
  65. /// </summary>
  66. private TcpListener m_server;
  67. private Task m_serverThread;
  68. private ArrayList m_socketListenersList;
  69. private bool m_stopPurging;
  70. private bool m_stopServer;
  71. private readonly ConcurrentQueue<Tuple<GPSTrackerLocation, string>> m_updates = new();
  72. private Task m_updatetask;
  73. public GPSEngine()
  74. {
  75. Init(DEFAULT_IP_END_POINT);
  76. }
  77. public GPSEngine(IPAddress serverIP)
  78. {
  79. Init(new IPEndPoint(serverIP, DEFAULT_PORT));
  80. }
  81. public GPSEngine(int port)
  82. {
  83. Init(new IPEndPoint(DEFAULT_SERVER, port));
  84. }
  85. public GPSEngine(IPAddress serverIP, int port)
  86. {
  87. Init(new IPEndPoint(serverIP, port));
  88. }
  89. public GPSEngine(IPEndPoint ipNport)
  90. {
  91. Init(ipNport);
  92. }
  93. ~GPSEngine()
  94. {
  95. Stop();
  96. }
  97. public override void Configure(Server server)
  98. {
  99. base.Configure(server);
  100. }
  101. /// <summary>
  102. /// Init method that create a server (TCP Listener) Object based on the
  103. /// IP Address and Port information that is passed in.
  104. /// </summary>
  105. /// <param name="ipNport"></param>
  106. private void Init(IPEndPoint ipNport)
  107. {
  108. try
  109. {
  110. m_server = new TcpListener(ipNport);
  111. }
  112. catch (Exception e)
  113. {
  114. m_server = null;
  115. }
  116. m_updatetask = Task.Run(() =>
  117. {
  118. while (m_server != null && !cts.IsCancellationRequested)
  119. if (m_updates.Any())
  120. if (m_updates.TryDequeue(out var tuple))
  121. {
  122. Logger.Send(LogType.Information, "",
  123. string.Format("Updating Server ({0}): {1} - {2}", m_updates.Count, tuple.Item1.DeviceID, tuple.Item2));
  124. new Client<GPSTrackerLocation>().Save(tuple.Item1, tuple.Item2, (_, __) => { });
  125. Thread.Sleep(Properties.UpdateTimer);
  126. }
  127. }, cts.Token);
  128. }
  129. private static byte[] StringToByteArray(string hex)
  130. {
  131. var NumberChars = hex.Length;
  132. var bytes = new byte[NumberChars / 2];
  133. for (var i = 0; i < NumberChars; i += 2)
  134. bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
  135. return bytes;
  136. }
  137. private bool CheckConnection()
  138. {
  139. if (ClientFactory.UserGuid == Guid.Empty)
  140. {
  141. // Wait for server connection
  142. while (!Client.Ping())
  143. {
  144. Logger.Send(LogType.Error, "", "Database server unavailable. Trying again in 30 seconds...");
  145. Task.Delay(30_000).Wait();
  146. Logger.Send(LogType.Information, "", "Retrying connection...");
  147. }
  148. ClientFactory.SetBypass();
  149. }
  150. if (DateTime.Now - m_RefreshDate > new TimeSpan(0, 5, 0))
  151. {
  152. Logger.Send(LogType.Information, "", "Refreshing Tracker Cache");
  153. var table = new Client<GPSTracker>().Query(
  154. null,
  155. new Columns<GPSTracker>(x => x.ID, x => x.DeviceID, x => x.Type.BatteryFormula)
  156. );
  157. m_RefreshDate = DateTime.Now;
  158. Logger.Send(LogType.Information, "", string.Format("- Tracker Cache: {0} devices", table.Rows.Count));
  159. m_devices.Clear();
  160. foreach (var row in table.Rows)
  161. {
  162. var formula = row.Get<GPSTracker, string?>(x => x.Type.BatteryFormula);
  163. var expression = string.IsNullOrWhiteSpace(formula) ? null : new CoreExpression<GPSBatteryFormulaModel, double>(formula);
  164. m_devices[row.Get<GPSTracker, string>(x => x.DeviceID)] =
  165. new Device(row.Get<GPSTracker, Guid>(x => x.ID), DateTime.MinValue, expression);
  166. }
  167. }
  168. return true;
  169. }
  170. public void StartSigfoxListener()
  171. {
  172. if(Properties.SigfoxListenPort == 0)
  173. {
  174. Logger.Send(LogType.Information, "", "No Sigfox listen port specified\n");
  175. return;
  176. }
  177. var listener = new Listener<SigfoxHandler, SigfoxHandlerProperties>(new SigfoxHandlerProperties(m_devices));
  178. listener.InitPort((ushort)Properties.SigfoxListenPort);
  179. Logger.Send(LogType.Information, "", "Starting Sigfox Listener on port " + Properties.SigfoxListenPort);
  180. listener.Start();
  181. }
  182. /// <summary>
  183. /// Method that starts TCP/IP Server.
  184. /// </summary>
  185. public override void Run()
  186. {
  187. if (string.IsNullOrWhiteSpace(Properties.Server))
  188. {
  189. Logger.Send(LogType.Error, "", "Server is blank!");
  190. return;
  191. }
  192. Logger.Send(LogType.Information, "", "Registering Classes");
  193. CoreUtils.RegisterClasses();
  194. ComalUtils.RegisterClasses();
  195. ClientFactory.SetClientType(typeof(PipeIPCClient<>), Platform.GPSEngine, Version, DatabaseServerProperties.GetPipeName(Properties.Server));
  196. CheckConnection();
  197. DMFactory.Initialise(Properties.DumpFormat, Properties.DumpFile);
  198. if (m_server != null)
  199. {
  200. // Create a ArrayList for storing SocketListeners before
  201. // starting the server.
  202. m_socketListenersList = new ArrayList();
  203. // Start the Server and start the thread to listen client
  204. // requests.
  205. m_server.Start();
  206. m_serverThread = Task.Run(ServerThreadStart, cts.Token);
  207. // Create a low priority thread that checks and deletes client
  208. // SocktConnection objcts that are marked for deletion.
  209. m_purgingThread = Task.Run(PurgingThreadStart, cts.Token);
  210. }
  211. StartSigfoxListener();
  212. }
  213. /// <summary>
  214. /// Method that stops the TCP/IP Server.
  215. /// </summary>
  216. public override void Stop()
  217. {
  218. if (m_server != null)
  219. {
  220. // It is important to Stop the server first before doing
  221. // any cleanup. If not so, clients might being added as
  222. // server is running, but supporting data structures
  223. // (such as m_socketListenersList) are cleared. This might
  224. // cause exceptions.
  225. cts.Cancel();
  226. // Stop the TCP/IP Server.
  227. m_stopServer = true;
  228. m_server.Stop();
  229. // Wait for one second for the the thread to stop.
  230. //m_serverThread.Join(1000);
  231. // If still alive; Get rid of the thread.
  232. //if (m_serverThread.IsAlive)
  233. //{
  234. // m_serverThread.Abort();
  235. //}
  236. m_serverThread = null;
  237. m_stopPurging = true;
  238. //m_purgingThread.Join(1000);
  239. //if (m_purgingThread.IsAlive)
  240. //{
  241. // m_purgingThread.Abort();
  242. //}
  243. //m_purgingThread = null;
  244. // Free Server Object.
  245. m_server = null;
  246. while (m_updates.Any())
  247. if (m_updates.TryDequeue(out var tuple))
  248. {
  249. Logger.Send(LogType.Information, "",
  250. string.Format("Updating Server ({0}): {1} - {2}", m_updates.Count, tuple.Item1.DeviceID, tuple.Item2));
  251. new Client<GPSTrackerLocation>().Save(tuple.Item1, tuple.Item2, (_, __) => { });
  252. Thread.Sleep(2000);
  253. }
  254. // Stop All clients.
  255. StopAllSocketListers();
  256. }
  257. }
  258. /// <summary>
  259. /// Method that stops all clients and clears the list.
  260. /// </summary>
  261. private void StopAllSocketListers()
  262. {
  263. foreach (GPSListener socketListener
  264. in m_socketListenersList)
  265. socketListener.StopSocketListener();
  266. // Remove all elements from the list.
  267. m_socketListenersList.Clear();
  268. m_socketListenersList = null;
  269. }
  270. /// <summary>
  271. /// TCP/IP Server Thread that is listening for clients.
  272. /// </summary>
  273. private void ServerThreadStart()
  274. {
  275. // Client Socket variable;
  276. Socket clientSocket = null;
  277. GPSListener socketListener = null;
  278. while (!m_stopServer)
  279. try
  280. {
  281. if (!CheckConnection())
  282. return;
  283. // Wait for any client requests and if there is any
  284. // request from any client accept it (Wait indefinitely).
  285. clientSocket = m_server.AcceptSocket();
  286. // Create a SocketListener object for the client.
  287. socketListener = new GPSListener(clientSocket, m_devices, m_updates);
  288. // Add the socket listener to an array list in a thread
  289. // safe fashon.
  290. //Monitor.Enter(m_socketListenersList);
  291. lock (m_socketListenersList)
  292. {
  293. m_socketListenersList.Add(socketListener);
  294. }
  295. //Monitor.Exit(m_socketListenersList);
  296. // Start a communicating with the client in a different
  297. // thread.
  298. socketListener.StartSocketListener();
  299. }
  300. catch (SocketException se)
  301. {
  302. m_stopServer = true;
  303. }
  304. }
  305. /// <summary>
  306. /// Thread method for purging Client Listeneres that are marked for
  307. /// deletion (i.e. clients with socket connection closed). This thead
  308. /// is a low priority thread and sleeps for 10 seconds and then check
  309. /// for any client SocketConnection obects which are obselete and
  310. /// marked for deletion.
  311. /// </summary>
  312. private void PurgingThreadStart()
  313. {
  314. while (!m_stopPurging)
  315. {
  316. var deleteList = new ArrayList();
  317. // Check for any clients SocketListeners that are to be
  318. // deleted and put them in a separate list in a thread sage
  319. // fashon.
  320. //Monitor.Enter(m_socketListenersList);
  321. lock (m_socketListenersList)
  322. {
  323. foreach (GPSListener socketListener
  324. in m_socketListenersList)
  325. if (socketListener.IsMarkedForDeletion())
  326. {
  327. deleteList.Add(socketListener);
  328. socketListener.StopSocketListener();
  329. }
  330. // Delete all the client SocketConnection ojects which are
  331. // in marked for deletion and are in the delete list.
  332. for (var i = 0; i < deleteList.Count; ++i) m_socketListenersList.Remove(deleteList[i]);
  333. }
  334. //Monitor.Exit(m_socketListenersList);
  335. deleteList = null;
  336. Thread.Sleep(10000);
  337. }
  338. }
  339. }
  340. }