/// <summary>
        /// Entry point of the program
        /// </summary>
        /// <param name="args">Command-line arguments</param>
        static void Main(string[] args)
        {
            Console.WriteLine($"Duet Control Server v{Assembly.GetExecutingAssembly().GetName().Version}");
            Console.WriteLine("Written by Christian Hammacher for Duet3D");
            Console.WriteLine("Licensed under the terms of the GNU Public License Version 3");
            Console.WriteLine();

            // Deal with program termination requests (SIGTERM)
            AppDomain.CurrentDomain.ProcessExit += (sender, eventArgs) => CancelSource.Cancel();

            // Initialise settings
            Console.Write("Loading settings... ");
            try
            {
                Settings.Load(args);
                Console.WriteLine("Done!");
            }
            catch (Exception e)
            {
                Console.WriteLine($"Error: {e.Message}");
                return;
            }

            // Check if another instance is already running
            using (DuetAPIClient.CommandConnection testConnection = new DuetAPIClient.CommandConnection())
            {
                try
                {
                    testConnection.Connect(Settings.SocketPath, CancelSource.Token).Wait();
                    Console.WriteLine("Error: Another instance is already running. Stopping.");
                    return;
                }
                catch
                {
                    // expected
                }
            }

            // Initialise object model
            Console.Write("Initialising object model... ");
            try
            {
                Model.Provider.Init();
                Model.Updater.Init();
                Console.WriteLine("Done!");
            }
            catch (Exception e)
            {
                Console.WriteLine($"Error: {e.Message}");
                return;
            }

            // Connect to the controller
            Console.Write("Connecting to RepRapFirmware... ");
            try
            {
                SPI.Interface.Init();
                if (!SPI.Interface.Connect())
                {
                    Console.WriteLine("Error: Duet is not available");
                    return;
                }
                Console.WriteLine("Done!");
            }
            catch (AggregateException ae)
            {
                Console.WriteLine($"Error: {ae.InnerException.Message}");
                return;
            }

            // Start up the IPC server
            Console.Write("Creating IPC socket... ");
            try
            {
                Server.CreateSocket();
                Console.WriteLine("Done!");
            }
            catch (Exception e)
            {
                Console.WriteLine($"Error: {e.Message}");
                return;
            }

            Console.WriteLine();

            // Run the main tasks in the background
            Task spiTask         = Task.Run(SPI.Interface.Run);
            Task ipcTask         = Server.AcceptConnections();
            Task modelUpdateTask = Model.UpdateTask.UpdatePeriodically();

            Task[] taskList = { spiTask, ipcTask, modelUpdateTask };

            // Wait for program termination
            Task.WaitAny(taskList);

            // Tell other tasks to stop in case this is an abnormal program termination
            if (!CancelSource.IsCancellationRequested)
            {
                Console.WriteLine("[crit] Abnormal program termination:");
                if (spiTask.IsCompleted)
                {
                    Console.WriteLine("SPI task terminated");
                }
                if (ipcTask.IsCompleted)
                {
                    Console.WriteLine("IPC task terminated");
                }
                if (modelUpdateTask.IsCompleted)
                {
                    Console.WriteLine("Model task terminated");
                }
                CancelSource.Cancel();
            }

            // Stop the IPC subsystem. This has to happen here because Socket.AcceptAsync() does not have a CancellationToken parameter
            Server.Shutdown();

            // Wait for all tasks to finish
            try
            {
                Task.WaitAll(taskList);
            }
            catch (AggregateException ae)
            {
                foreach (Exception e in ae.InnerExceptions)
                {
                    if (!(e is OperationCanceledException) && !(e is SocketException))
                    {
                        Console.Write("[crit] ");
                        Console.WriteLine(e);
                    }
                }
            }
        }