123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400 |
- using System;
- using System.Collections;
- using System.Collections.Concurrent;
- using System.Collections.Generic;
- using System.Linq;
- using System.Net;
- using System.Net.Sockets;
- using System.Threading;
- using System.Threading.Tasks;
- using Comal.Classes;
- using InABox.Client.IPC;
- using InABox.Clients;
- using InABox.Core;
- using InABox.DigitalMatter;
- using PRSServer.Engines;
- namespace PRSServer
- {
- internal class Device
- {
- public Guid ID { get; set; }
- public DateTime TimeStamp { get; set; }
- public CoreExpression<GPSBatteryFormulaModel, double>? BatteryFormula { get; set; }
- public Device(Guid id, DateTime timeStamp, CoreExpression<GPSBatteryFormulaModel, double>? batteryFormula)
- {
- ID = id;
- TimeStamp = timeStamp;
- BatteryFormula = batteryFormula;
- }
- /// <summary>
- /// Should return a percentage
- /// </summary>
- /// <returns></returns>
- public double CalculateBatteryLevel(double batteryValue)
- {
- if (BatteryFormula != null)
- return BatteryFormula.Evaluate(new Dictionary<string, object?>
- {
- { nameof(GPSBatteryFormulaModel.BatteryLevel), batteryValue }
- });
- return batteryValue;
- }
- }
- /// <summary>
- /// TCPServer is the Server class. When "StartServer" method is called
- /// this Server object tries to connect to a IP Address specified on a port
- /// configured. Then the server start listening for client socket requests.
- /// As soon as a requestcomes in from any client then a Client Socket
- /// Listening thread will be started. That thread is responsible for client
- /// communication.
- /// </summary>
- internal class GPSEngine : Engine<GPSServerProperties>
- {
- /// <summary>
- /// Default Constants.
- /// </summary>
- public static IPAddress DEFAULT_SERVER = IPAddress.Any;
- public static int DEFAULT_PORT = 7999;
- public static IPEndPoint DEFAULT_IP_END_POINT = new(DEFAULT_SERVER, DEFAULT_PORT);
- private readonly CancellationTokenSource cts = new();
- private readonly ConcurrentDictionary<string, Device> m_devices = new();
- private Task m_purgingThread;
- private DateTime m_RefreshDate = DateTime.MinValue;
- /// <summary>
- /// Local Variables Declaration.
- /// </summary>
- private TcpListener m_server;
- private Task m_serverThread;
- private ArrayList m_socketListenersList;
- private bool m_stopPurging;
- private bool m_stopServer;
- private readonly ConcurrentQueue<Tuple<GPSTrackerLocation, string>> m_updates = new();
- private Task m_updatetask;
-
- public GPSEngine()
- {
- Init(DEFAULT_IP_END_POINT);
- }
- public GPSEngine(IPAddress serverIP)
- {
- Init(new IPEndPoint(serverIP, DEFAULT_PORT));
- }
- public GPSEngine(int port)
- {
- Init(new IPEndPoint(DEFAULT_SERVER, port));
- }
- public GPSEngine(IPAddress serverIP, int port)
- {
- Init(new IPEndPoint(serverIP, port));
- }
- public GPSEngine(IPEndPoint ipNport)
- {
- Init(ipNport);
- }
- ~GPSEngine()
- {
- Stop();
- }
- public override void Configure(Server server)
- {
- base.Configure(server);
- }
- /// <summary>
- /// Init method that create a server (TCP Listener) Object based on the
- /// IP Address and Port information that is passed in.
- /// </summary>
- /// <param name="ipNport"></param>
- private void Init(IPEndPoint ipNport)
- {
- try
- {
- m_server = new TcpListener(ipNport);
- }
- catch (Exception e)
- {
- m_server = null;
- }
- m_updatetask = Task.Run(() =>
- {
- while (m_server != null && !cts.IsCancellationRequested)
- if (m_updates.Any())
- if (m_updates.TryDequeue(out var tuple))
- {
- Logger.Send(LogType.Information, "",
- string.Format("Updating Server ({0}): {1} - {2}", m_updates.Count, tuple.Item1.DeviceID, tuple.Item2));
- new Client<GPSTrackerLocation>().Save(tuple.Item1, tuple.Item2, (_, __) => { });
- Thread.Sleep(Properties.UpdateTimer);
- }
- }, cts.Token);
- }
- private static byte[] StringToByteArray(string hex)
- {
- var NumberChars = hex.Length;
- var bytes = new byte[NumberChars / 2];
- for (var i = 0; i < NumberChars; i += 2)
- bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
- return bytes;
- }
- private bool CheckConnection()
- {
- if (ClientFactory.UserGuid == Guid.Empty)
- {
- // Wait for server connection
- while (!Client.Ping())
- {
- Logger.Send(LogType.Error, "", "Database server unavailable. Trying again in 30 seconds...");
- Task.Delay(30_000).Wait();
- Logger.Send(LogType.Information, "", "Retrying connection...");
- URLCache.Clear();
- }
- ClientFactory.SetBypass();
- }
- if (DateTime.Now - m_RefreshDate > new TimeSpan(0, 5, 0))
- {
- Logger.Send(LogType.Information, "", "Refreshing Tracker Cache");
- var table = new Client<GPSTracker>().Query(
- null,
- new Columns<GPSTracker>(x => x.ID, x => x.DeviceID, x => x.Type.BatteryFormula)
- );
- m_RefreshDate = DateTime.Now;
- Logger.Send(LogType.Information, "", string.Format("- Tracker Cache: {0} devices", table.Rows.Count));
- m_devices.Clear();
- foreach (var row in table.Rows)
- {
- var formula = row.Get<GPSTracker, string?>(x => x.Type.BatteryFormula);
- var expression = string.IsNullOrWhiteSpace(formula) ? null : new CoreExpression<GPSBatteryFormulaModel, double>(formula);
- m_devices[row.Get<GPSTracker, string>(x => x.DeviceID)] =
- new Device(row.Get<GPSTracker, Guid>(x => x.ID), DateTime.MinValue, expression);
- }
- }
- return true;
- }
- public void StartSigfoxListener()
- {
- if(Properties.SigfoxListenPort == 0)
- {
- Logger.Send(LogType.Information, "", "No Sigfox listen port specified\n");
- return;
- }
- var listener = new Listener<SigfoxHandler, SigfoxHandlerProperties>(new SigfoxHandlerProperties(m_devices));
- listener.InitPort((ushort)Properties.SigfoxListenPort);
- Logger.Send(LogType.Information, "", "Starting Sigfox Listener on port " + Properties.SigfoxListenPort);
- listener.Start();
- }
- /// <summary>
- /// Method that starts TCP/IP Server.
- /// </summary>
- public override void Run()
- {
- if (string.IsNullOrWhiteSpace(Properties.Server))
- {
- Logger.Send(LogType.Error, "", "Server is blank!");
- return;
- }
- Logger.Send(LogType.Information, "", "Registering Classes");
- CoreUtils.RegisterClasses();
- ComalUtils.RegisterClasses();
- ClientFactory.SetClientType(typeof(PipeIPCClient<>), "GPSServer", Version, DatabaseServerProperties.GetPipeName(Properties.Server));
- CheckConnection();
- DMFactory.Initialise(Properties.DumpFormat, Properties.DumpFile);
- if (m_server != null)
- {
- // Create a ArrayList for storing SocketListeners before
- // starting the server.
- m_socketListenersList = new ArrayList();
- // Start the Server and start the thread to listen client
- // requests.
- m_server.Start();
- m_serverThread = Task.Run(ServerThreadStart, cts.Token);
- // Create a low priority thread that checks and deletes client
- // SocktConnection objcts that are marked for deletion.
- m_purgingThread = Task.Run(PurgingThreadStart, cts.Token);
- }
- StartSigfoxListener();
- }
- /// <summary>
- /// Method that stops the TCP/IP Server.
- /// </summary>
- public override void Stop()
- {
- if (m_server != null)
- {
- // It is important to Stop the server first before doing
- // any cleanup. If not so, clients might being added as
- // server is running, but supporting data structures
- // (such as m_socketListenersList) are cleared. This might
- // cause exceptions.
- cts.Cancel();
- // Stop the TCP/IP Server.
- m_stopServer = true;
- m_server.Stop();
- // Wait for one second for the the thread to stop.
- //m_serverThread.Join(1000);
- // If still alive; Get rid of the thread.
- //if (m_serverThread.IsAlive)
- //{
- // m_serverThread.Abort();
- //}
- m_serverThread = null;
- m_stopPurging = true;
- //m_purgingThread.Join(1000);
- //if (m_purgingThread.IsAlive)
- //{
- // m_purgingThread.Abort();
- //}
- //m_purgingThread = null;
- // Free Server Object.
- m_server = null;
- while (m_updates.Any())
- if (m_updates.TryDequeue(out var tuple))
- {
- Logger.Send(LogType.Information, "",
- string.Format("Updating Server ({0}): {1} - {2}", m_updates.Count, tuple.Item1.DeviceID, tuple.Item2));
- new Client<GPSTrackerLocation>().Save(tuple.Item1, tuple.Item2, (_, __) => { });
- Thread.Sleep(2000);
- }
- // Stop All clients.
- StopAllSocketListers();
- }
- }
- /// <summary>
- /// Method that stops all clients and clears the list.
- /// </summary>
- private void StopAllSocketListers()
- {
- foreach (GPSListener socketListener
- in m_socketListenersList)
- socketListener.StopSocketListener();
- // Remove all elements from the list.
- m_socketListenersList.Clear();
- m_socketListenersList = null;
- }
- /// <summary>
- /// TCP/IP Server Thread that is listening for clients.
- /// </summary>
- private void ServerThreadStart()
- {
- // Client Socket variable;
- Socket clientSocket = null;
- GPSListener socketListener = null;
- while (!m_stopServer)
- try
- {
- if (!CheckConnection())
- return;
- // Wait for any client requests and if there is any
- // request from any client accept it (Wait indefinitely).
- clientSocket = m_server.AcceptSocket();
- // Create a SocketListener object for the client.
- socketListener = new GPSListener(clientSocket, m_devices, m_updates);
- // Add the socket listener to an array list in a thread
- // safe fashon.
- //Monitor.Enter(m_socketListenersList);
- lock (m_socketListenersList)
- {
- m_socketListenersList.Add(socketListener);
- }
- //Monitor.Exit(m_socketListenersList);
- // Start a communicating with the client in a different
- // thread.
- socketListener.StartSocketListener();
- }
- catch (SocketException se)
- {
- m_stopServer = true;
- }
- }
- /// <summary>
- /// Thread method for purging Client Listeneres that are marked for
- /// deletion (i.e. clients with socket connection closed). This thead
- /// is a low priority thread and sleeps for 10 seconds and then check
- /// for any client SocketConnection obects which are obselete and
- /// marked for deletion.
- /// </summary>
- private void PurgingThreadStart()
- {
- while (!m_stopPurging)
- {
- var deleteList = new ArrayList();
- // Check for any clients SocketListeners that are to be
- // deleted and put them in a separate list in a thread sage
- // fashon.
- //Monitor.Enter(m_socketListenersList);
- lock (m_socketListenersList)
- {
- foreach (GPSListener socketListener
- in m_socketListenersList)
- if (socketListener.IsMarkedForDeletion())
- {
- deleteList.Add(socketListener);
- socketListener.StopSocketListener();
- }
- // Delete all the client SocketConnection ojects which are
- // in marked for deletion and are in the delete list.
- for (var i = 0; i < deleteList.Count; ++i) m_socketListenersList.Remove(deleteList[i]);
- }
- //Monitor.Exit(m_socketListenersList);
- deleteList = null;
- Thread.Sleep(10000);
- }
- }
- }
- }
|