GPSEngine.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  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. URLCache.Clear();
  148. }
  149. ClientFactory.SetBypass();
  150. }
  151. if (DateTime.Now - m_RefreshDate > new TimeSpan(0, 5, 0))
  152. {
  153. Logger.Send(LogType.Information, "", "Refreshing Tracker Cache");
  154. var table = new Client<GPSTracker>().Query(
  155. null,
  156. new Columns<GPSTracker>(x => x.ID, x => x.DeviceID, x => x.Type.BatteryFormula)
  157. );
  158. m_RefreshDate = DateTime.Now;
  159. Logger.Send(LogType.Information, "", string.Format("- Tracker Cache: {0} devices", table.Rows.Count));
  160. m_devices.Clear();
  161. foreach (var row in table.Rows)
  162. {
  163. var formula = row.Get<GPSTracker, string?>(x => x.Type.BatteryFormula);
  164. var expression = string.IsNullOrWhiteSpace(formula) ? null : new CoreExpression<GPSBatteryFormulaModel, double>(formula);
  165. m_devices[row.Get<GPSTracker, string>(x => x.DeviceID)] =
  166. new Device(row.Get<GPSTracker, Guid>(x => x.ID), DateTime.MinValue, expression);
  167. }
  168. }
  169. return true;
  170. }
  171. public void StartSigfoxListener()
  172. {
  173. if(Properties.SigfoxListenPort == 0)
  174. {
  175. Logger.Send(LogType.Information, "", "No Sigfox listen port specified\n");
  176. return;
  177. }
  178. var listener = new Listener<SigfoxHandler, SigfoxHandlerProperties>(new SigfoxHandlerProperties(m_devices));
  179. listener.InitPort((ushort)Properties.SigfoxListenPort);
  180. Logger.Send(LogType.Information, "", "Starting Sigfox Listener on port " + Properties.SigfoxListenPort);
  181. listener.Start();
  182. }
  183. /// <summary>
  184. /// Method that starts TCP/IP Server.
  185. /// </summary>
  186. public override void Run()
  187. {
  188. if (string.IsNullOrWhiteSpace(Properties.Server))
  189. {
  190. Logger.Send(LogType.Error, "", "Server is blank!");
  191. return;
  192. }
  193. Logger.Send(LogType.Information, "", "Registering Classes");
  194. CoreUtils.RegisterClasses();
  195. ComalUtils.RegisterClasses();
  196. ClientFactory.SetClientType(typeof(PipeIPCClient<>), "GPSServer", Version, DatabaseServerProperties.GetPipeName(Properties.Server));
  197. CheckConnection();
  198. DMFactory.Initialise(Properties.DumpFormat, Properties.DumpFile);
  199. if (m_server != null)
  200. {
  201. // Create a ArrayList for storing SocketListeners before
  202. // starting the server.
  203. m_socketListenersList = new ArrayList();
  204. // Start the Server and start the thread to listen client
  205. // requests.
  206. m_server.Start();
  207. m_serverThread = Task.Run(ServerThreadStart, cts.Token);
  208. // Create a low priority thread that checks and deletes client
  209. // SocktConnection objcts that are marked for deletion.
  210. m_purgingThread = Task.Run(PurgingThreadStart, cts.Token);
  211. }
  212. StartSigfoxListener();
  213. }
  214. /// <summary>
  215. /// Method that stops the TCP/IP Server.
  216. /// </summary>
  217. public override void Stop()
  218. {
  219. if (m_server != null)
  220. {
  221. // It is important to Stop the server first before doing
  222. // any cleanup. If not so, clients might being added as
  223. // server is running, but supporting data structures
  224. // (such as m_socketListenersList) are cleared. This might
  225. // cause exceptions.
  226. cts.Cancel();
  227. // Stop the TCP/IP Server.
  228. m_stopServer = true;
  229. m_server.Stop();
  230. // Wait for one second for the the thread to stop.
  231. //m_serverThread.Join(1000);
  232. // If still alive; Get rid of the thread.
  233. //if (m_serverThread.IsAlive)
  234. //{
  235. // m_serverThread.Abort();
  236. //}
  237. m_serverThread = null;
  238. m_stopPurging = true;
  239. //m_purgingThread.Join(1000);
  240. //if (m_purgingThread.IsAlive)
  241. //{
  242. // m_purgingThread.Abort();
  243. //}
  244. //m_purgingThread = null;
  245. // Free Server Object.
  246. m_server = null;
  247. while (m_updates.Any())
  248. if (m_updates.TryDequeue(out var tuple))
  249. {
  250. Logger.Send(LogType.Information, "",
  251. string.Format("Updating Server ({0}): {1} - {2}", m_updates.Count, tuple.Item1.DeviceID, tuple.Item2));
  252. new Client<GPSTrackerLocation>().Save(tuple.Item1, tuple.Item2, (_, __) => { });
  253. Thread.Sleep(2000);
  254. }
  255. // Stop All clients.
  256. StopAllSocketListers();
  257. }
  258. }
  259. /// <summary>
  260. /// Method that stops all clients and clears the list.
  261. /// </summary>
  262. private void StopAllSocketListers()
  263. {
  264. foreach (GPSListener socketListener
  265. in m_socketListenersList)
  266. socketListener.StopSocketListener();
  267. // Remove all elements from the list.
  268. m_socketListenersList.Clear();
  269. m_socketListenersList = null;
  270. }
  271. /// <summary>
  272. /// TCP/IP Server Thread that is listening for clients.
  273. /// </summary>
  274. private void ServerThreadStart()
  275. {
  276. // Client Socket variable;
  277. Socket clientSocket = null;
  278. GPSListener socketListener = null;
  279. while (!m_stopServer)
  280. try
  281. {
  282. if (!CheckConnection())
  283. return;
  284. // Wait for any client requests and if there is any
  285. // request from any client accept it (Wait indefinitely).
  286. clientSocket = m_server.AcceptSocket();
  287. // Create a SocketListener object for the client.
  288. socketListener = new GPSListener(clientSocket, m_devices, m_updates);
  289. // Add the socket listener to an array list in a thread
  290. // safe fashon.
  291. //Monitor.Enter(m_socketListenersList);
  292. lock (m_socketListenersList)
  293. {
  294. m_socketListenersList.Add(socketListener);
  295. }
  296. //Monitor.Exit(m_socketListenersList);
  297. // Start a communicating with the client in a different
  298. // thread.
  299. socketListener.StartSocketListener();
  300. }
  301. catch (SocketException se)
  302. {
  303. m_stopServer = true;
  304. }
  305. }
  306. /// <summary>
  307. /// Thread method for purging Client Listeneres that are marked for
  308. /// deletion (i.e. clients with socket connection closed). This thead
  309. /// is a low priority thread and sleeps for 10 seconds and then check
  310. /// for any client SocketConnection obects which are obselete and
  311. /// marked for deletion.
  312. /// </summary>
  313. private void PurgingThreadStart()
  314. {
  315. while (!m_stopPurging)
  316. {
  317. var deleteList = new ArrayList();
  318. // Check for any clients SocketListeners that are to be
  319. // deleted and put them in a separate list in a thread sage
  320. // fashon.
  321. //Monitor.Enter(m_socketListenersList);
  322. lock (m_socketListenersList)
  323. {
  324. foreach (GPSListener socketListener
  325. in m_socketListenersList)
  326. if (socketListener.IsMarkedForDeletion())
  327. {
  328. deleteList.Add(socketListener);
  329. socketListener.StopSocketListener();
  330. }
  331. // Delete all the client SocketConnection ojects which are
  332. // in marked for deletion and are in the delete list.
  333. for (var i = 0; i < deleteList.Count; ++i) m_socketListenersList.Remove(deleteList[i]);
  334. }
  335. //Monitor.Exit(m_socketListenersList);
  336. deleteList = null;
  337. Thread.Sleep(10000);
  338. }
  339. }
  340. }
  341. }