private static void Main(string[] args) { #region Setup for Reactive ASCOM var connectionString = Settings.Default.ConnectionString; // Edit in App.config, default is "COM1:" var channelFactory = new ChannelFactory(); var channel = channelFactory.FromConnectionString(connectionString); var transactionObserver = new TransactionObserver(channel); var transactionProcessor = new ReactiveTransactionProcessor(); transactionProcessor.SubscribeTransactionObserver(transactionObserver); channel.Open(); #endregion Setup for Reactive ASCOM #region Submit some transactions // Ready to go. We are going to use tasks to submit the transactions, just to demonstrate thread safety. var raTransaction = new TerminatedStringTransaction(":GR#", '#', ':') { Timeout = TimeSpan.FromSeconds(2) }; // The terminator and initiator are optional parameters and default to values that work for Meade style protocols. var decTransaction = new TerminatedStringTransaction(":GD#") { Timeout = TimeSpan.FromSeconds(2) }; Task.Run(() => transactionProcessor.CommitTransaction(raTransaction)); Task.Run(() => transactionProcessor.CommitTransaction(decTransaction)); #endregion Submit some transactions #region Wait for the results // NOTE we are using the transactions in the reverse order that we committed them, just to prove a point. Console.WriteLine("Waiting for declination"); decTransaction.WaitForCompletionOrTimeout(); Console.WriteLine("Declination: {0}", decTransaction.Response); Console.WriteLine("Waiting for Right Ascensions"); raTransaction.WaitForCompletionOrTimeout(); Console.WriteLine("Right Ascension: {0}", raTransaction.Response); #endregion Wait for the results #region Cleanup /* * To clean up, we just need to dispose the transaction processor. This terminates the * sequence of transactions, which causes the TransactionObserver's OnCompleted method * to be called, which unsubscribes the receive sequence, which closes the * communications channel */ transactionProcessor.Dispose(); #endregion Cleanup Console.WriteLine("Press ENTER:"); Console.ReadLine(); }
/// <summary> /// Destroys the transaction processor and its dependencies. Ensures that the /// <see cref="Channel" /> is closed. Once this method has been called, the /// <see cref="Channel" /> and <see cref="Endpoint" /> properties will be null. A new /// connection to the same endpoint can be created by calling /// <see cref="CreateTransactionProcessor" /> again. /// </summary> public void DestroyTransactionProcessor() { processor?.Dispose(); processor = null; // [Sentinel] observer = null; if (Channel?.IsOpen ?? false) { Channel.Close(); } Channel?.Dispose(); Channel = null; // [Sentinel] GC.Collect(3, GCCollectionMode.Forced, blocking: true); }
private static void Main(string[] args) { #region Setup for Reactive ASCOM /* * First create our channel factory. * This is also the place where we can inject a logging service. * If you don't provide a logging service here, there will be no logging! * Note that logging is configured in NLog.config. * See also: TA.Utils.Core.Diagnostics * * The channel factory knows about serial ports by default, but we are going to * register a custom channel implementation that contains a hardware simulator. * (the simulator is abstracted from the Digital Domeworks dome driver). * If you make your own custom channels, you can register them the same way. */ var log = new LoggingService(); // TA.Utils.Logging.NLog log.Info() .Message("Program start. Version {gitversion}", GitVersion.GitInformationalVersion) .Property("CommitSha", GitVersion.GitCommitSha) .Property("CommitDate", GitVersion.GitCommitDate) .Write(); var channelFactory = new ChannelFactory(log); channelFactory.RegisterChannelType( SimulatorEndpoint.IsConnectionStringValid, SimulatorEndpoint.FromConnectionString, endpoint => new SimulatorCommunicationsChannel((SimulatorEndpoint)endpoint, log)); /* * Now get our connection string from the AppSettings.json file. */ var connectionString = Configuration["ConnectionString"]; /* * Create the communications channel and connect it to the transaction observer. * The TransactionObserver is the beating heart of Rx Comms and is where everything is synchronized. */ var channel = channelFactory.FromConnectionString(connectionString); var transactionObserver = new TransactionObserver(channel); /* * TransactionObsever is an IObservable<DeviceTransaction>, so we need a sequence for it to observe. * The transaction sequence can be created by any convenient means, and Rx Comms provides * a handy helper class for doing this, called ReactiveTransactionProcessor. * * However the sequence is created, it must be fed into the transaction observer * by creating a subscription. ReactiveTransactionProcessor has a helper method for that. * The helper method also gives us a way to rate-limit commands to the hardware, if needed. */ var transactionProcessor = new ReactiveTransactionProcessor(); transactionProcessor.SubscribeTransactionObserver(transactionObserver, TimeSpan.FromSeconds(1)); channel.Open(); #endregion Setup for Reactive ASCOM // All set up and ready to go. try { // First we'll create a StatusTransaction and get the device's status. var statusTransaction = new StatusTransaction(); transactionProcessor.CommitTransaction(statusTransaction); statusTransaction.WaitForCompletionOrTimeout(); // There is also an async version // If the transaction failed, log and throw TransactionException. TransactionExtensions.ThrowIfFailed(statusTransaction, log); // Now we can retrieve the results and use them. // If the transaction succeeded, we should be assured of a valid result. // When you write your own transactions, make sure this is so! var status = statusTransaction.HardwareStatus; log.Debug().Message("Got status: {deviceStatus}", status).Write(); // Get the user GPIO pins and flip the value of pin 0. var gpioPins = status.UserPins; var gpioPinsToWrite = gpioPins.WithBitSetTo(0, !gpioPins[0]); // Now we'll use another transaction to set the new GPIO pin state. var gpioTransaction = new SetUserPinTransaction(gpioPinsToWrite); transactionProcessor.CommitTransaction(gpioTransaction); gpioTransaction.WaitForCompletionOrTimeout(); TransactionExtensions.ThrowIfFailed(gpioTransaction, log); var newPinState = gpioTransaction.UserPins; if (newPinState != gpioPinsToWrite) { throw new ApplicationException("Failed to write GPIO pins"); } log.Info() .Message("Successfully changed GPIO pins {oldState} => {newState}", gpioPins, newPinState) .Write(); } catch (TransactionException exception) { log.Error().Exception(exception) .Message("Transaction failed {transaction}", exception.Transaction) .Write(); Console.WriteLine(exception); } catch (ApplicationException exception) { log.Error().Message("Failed to set teh GPIO pins.").Exception(exception).Write(); } finally { /* * We just need to dispose the transaction processor. This terminates the * sequence of transactions, which causes the TransactionObserver's OnCompleted method * to be called, which unsubscribes the receive sequence, which closes the * communications channel. */ transactionProcessor.Dispose(); log.Info().Message("Cleanup complete").Write(); /* * It is best practice to explicitly shut down the logging service, * otherwise any buffered log entries might not be written to the log. * This may take a few seconds if you are using a slow log target. */ log.Shutdown(); } }