// *** TemperatureControl *** // This example expands the previous ArduinoController example. The PC will now send a start command to the Arduino, // and wait for a response from the Arduino. The Arduino will start sending temperature data and the heater steering // value data which the PC will plot in a chart. With a slider we can set the goal temperature, which will make the // PID software on the controller adjust the setting of the heater. // // This example shows how to design a responsive performance UI that sends and receives commands // - Send queued commands // - Manipulate the send and receive queue // - Add queue strategies // - Use bluetooth connection // - Use auto scanning and connecting // - Use watchdog using System; using CommandMessenger; using CommandMessenger.Serialport; using CommandMessenger.TransportLayer; using System.Threading; using CommandMessenger.Bluetooth; namespace DataLogging { enum Command { RequestId, // Command to request application ID SendId, // Command to send application ID Acknowledge, // Command to acknowledge a received command Error, // Command to message that an error has occurred StartLogging, // Command to turn on data logging StopLogging, // Command to turn off data logging PlotDataPoint, // Command to request plotting a data point SetGoalTemperature, // Command to set the goal temperature SetStartTime, // Command to set the new start time for the logger }; enum TransportMode { Serial, // Serial port connection (over USB) Bluetooth, // Bluetooth connection }; public class TemperatureControl { // This class (kind of) contains presentation logic, and domain model. // ChartForm.cs contains the view components private ITransport _transport; private CmdMessenger _cmdMessenger; private ConnectionManager _connectionManager; private ChartForm _chartForm; //private float _startTime; private double _goalTemperature; // ------------------ MAIN ---------------------- public bool AcquisitionStarted { get; set; } public bool AcceptData { get; set; } /// <summary> Gets or sets the goal temperature. </summary> /// <value> The goal temperature. </value> public double GoalTemperature { get { return _goalTemperature; } set { if (Math.Abs(_goalTemperature - value) > float.Epsilon) { _goalTemperature = value; SetGoalTemperature(_goalTemperature); if (GoalTemperatureChanged!=null) GoalTemperatureChanged(); } } } public Action GoalTemperatureChanged; // Action that is called when the goal temperature has changed private long _startTime; // Setup function public void Setup(ChartForm chartForm) { // Choose which transport mode you want to use: // 1. Serial port. This can be a real serial port but is usually a virtual serial port over USB. // It can also be a virtual serial port over Bluetooth, but the direct bluetooth works better // 2. Bluetooth This bypasses the Bluetooth virtual serial port, but communicates over the RFCOMM layer var transportMode = TransportMode.Serial; // getting the chart control on top of the chart form. _chartForm = chartForm; // Set up chart _chartForm.SetupChart(); // Connect slider to GoalTemperatureChanged GoalTemperatureChanged += () => _chartForm.GoalTemperatureTrackBarScroll(null, null); // Set up transport if (transportMode == TransportMode.Bluetooth) _transport = new BluetoothTransport(); // We do not need to set the device: it will be found by the connection manager else _transport = new SerialTransport { CurrentSerialSettings = { DtrEnable = false } }; // some boards (e.g. Sparkfun Pro Micro) DtrEnable may need to be true. // We do not need to set serial port and baud rate: it will be found by the connection manager // Initialize the command messenger with the Serial Port transport layer _cmdMessenger = new CmdMessenger(_transport) { BoardType = BoardType.Bit16, // Set if it is communicating with a 16- or 32-bit Arduino board PrintLfCr = false // Do not print newLine at end of command, to reduce data being sent }; // Tell CmdMessenger to "Invoke" commands on the thread running the WinForms UI _cmdMessenger.SetControlToInvokeOn(chartForm); // Set command strategy to continuously to remove all commands on the receive queue that // are older than 1 sec. This makes sure that if data logging comes in faster that it can // be plotted, the graph will not start lagging _cmdMessenger.AddReceiveCommandStrategy(new StaleGeneralStrategy(1000)); // Attach the callbacks to the Command Messenger AttachCommandCallBacks(); // Attach to NewLinesReceived for logging purposes _cmdMessenger.NewLineReceived += NewLineReceived; // Attach to NewLineSent for logging purposes _cmdMessenger.NewLineSent += NewLineSent; // Set up connection manager if (transportMode == TransportMode.Bluetooth) _connectionManager = new BluetoothConnectionManager((_transport as BluetoothTransport), _cmdMessenger, (int)Command.RequestId, (int)Command.SendId); else _connectionManager = new SerialConnectionManager ((_transport as SerialTransport), _cmdMessenger, (int)Command.RequestId, (int)Command.SendId); // Tell the Connection manager to "Invoke" commands on the thread running the WinForms UI _connectionManager.SetControlToInvokeOn(chartForm); // Event when the connection manager finds a connection _connectionManager.ConnectionFound += ConnectionFound; // Event when the connection manager watchdog notices that the connection is gone _connectionManager.ConnectionTimeout += ConnectionTimeout; // Event notifying on scanning process _connectionManager.Progress += LogProgress; // Initialize the application InitializeTemperatureControl(); // Start scanning for ports/devices _connectionManager.StartScan(); } private void InitializeTemperatureControl() { _startTime = TimeUtils.Millis; // Set initial goal temperature GoalTemperature = 25; // _startTime = 0.0f; AcquisitionStarted = false; AcceptData = false; _chartForm.SetDisConnected(); } // Exit function public void Exit() { // Disconnect ConnectionManager _connectionManager.Progress -= LogProgress; _connectionManager.ConnectionTimeout -= ConnectionTimeout; _connectionManager.ConnectionFound -= ConnectionFound; // Dispose ConnectionManager _connectionManager.Dispose(); // Disconnect Command Messenger _cmdMessenger.Disconnect(); // Dispose Command Messenger _cmdMessenger.Dispose(); // Dispose transport layer _transport.Dispose(); } /// Attach command call backs. private void AttachCommandCallBacks() { _cmdMessenger.Attach(OnUnknownCommand); _cmdMessenger.Attach((int)Command.Acknowledge, OnAcknowledge); _cmdMessenger.Attach((int)Command.Error, OnError); _cmdMessenger.Attach((int)Command.PlotDataPoint, OnPlotDataPoint); } // ------------------ CALLBACKS --------------------- // Called when a received command has no attached function. // In a WinForm application, console output gets routed to the output panel of your IDE void OnUnknownCommand(ReceivedCommand arguments) { _chartForm.LogMessage(@"Command without attached callback received"); //Console.WriteLine(@"Command without attached callback received"); } // Callback function that prints that the Arduino has acknowledged void OnAcknowledge(ReceivedCommand arguments) { _chartForm.LogMessage(@"Arduino acknowledged"); //Console.WriteLine(@" Arduino is ready"); } // Callback function that prints that the Arduino has experienced an error void OnError(ReceivedCommand arguments) { _chartForm.LogMessage(@"Arduino has experienced an error"); //Console.WriteLine(@"Arduino has experienced an error"); } // Callback function that plots a data point for the current temperature, the goal temperature, // the heater steer value and the Pulse Width Modulated (PWM) value. private void OnPlotDataPoint(ReceivedCommand arguments) { // Plot data if we are accepting data if (!AcceptData) return; // Get all arguments from plot data point command var time = arguments.ReadBinFloatArg(); time = (TimeUtils.Millis-_startTime)/1000.0f; var currTemp = arguments.ReadBinFloatArg(); var goalTemp = arguments.ReadBinFloatArg(); var heaterValue = arguments.ReadBinFloatArg(); var heaterPwm = arguments.ReadBinBoolArg(); // do not log data if times are out of sync //if (time<_startTime) return; // Update chart with new data point; _chartForm.UpdateGraph(time, currTemp, goalTemp, heaterValue, heaterPwm); // Update _startTime in case it needs to be resend after disconnection //_startTime = time; } // Log received line to console private void NewLineReceived(object sender, NewLineEvent.NewLineArgs e) { _chartForm.LogMessage(@"Received > " + e.Command.CommandString()); // Console.WriteLine(@"Received > " + e.Command.CommandString()); } // Log sent line to console private void NewLineSent(object sender, NewLineEvent.NewLineArgs e) { _chartForm.LogMessage(@"Sent > " + e.Command.CommandString()); // Console.WriteLine(@"Sent > " + e.Command.CommandString()); } // Log connection manager progress to status bar void LogProgress(object sender, ConnectionManagerProgressEventArgs e) { if (e.Level <= 2) { _chartForm.SetStatus(e.Description); } _chartForm.LogMessage(e.Description); // Console.WriteLine(e.Level + @" :" + e.Description); } private void ConnectionTimeout(object sender, EventArgs e) { // Connection time-out! // Disable UI .. _chartForm.SetStatus(@"Connection timeout, attempting to reconnect"); _chartForm.SetDisConnected(); // and start scanning _connectionManager.StartScan(); } private void ConnectionFound(object sender, EventArgs e) { //We have been connected! // Make sure we do not receive data until we are ready AcceptData = false; // Enable UI _chartForm.SetConnected(); // Send command to set goal Temperature SetGoalTemperature(_goalTemperature); // Restart acquisition if needed if (AcquisitionStarted) StartAcquisition(); else StopAcquisition(); AcceptData = true; // Start Watchdog _connectionManager.StartWatchDog(); // Yield time slice in order to get UI updated Thread.Yield(); } // Set the goal temperature on the embedded controller public void SetGoalTemperature(double goalTemperature) { _goalTemperature = goalTemperature; // Create command to start sending data var command = new SendCommand((int)Command.SetGoalTemperature); command.AddBinArgument(_goalTemperature); // Collapse this command if needed using CollapseCommandStrategy // This strategy will avoid duplicates of this command on the queue: if a SetGoalTemperature command is // already on the queue when a new one is added, it will be replaced at its current queue-position. // Otherwise the command will be added to the back of the queue. // // This will make sure that when the slider raises a lot of events that each set a new goal temperature, the // controller will not start lagging. _chartForm.LogMessage(@"Queue command - SetGoalTemperature"); _cmdMessenger.QueueCommand(new CollapseCommandStrategy(command)); } // Set the start time on the embedded controller public void SetStartTime(float startTime) { var command = new SendCommand((int)Command.SetStartTime, (int)Command.Acknowledge,500); command.AddBinArgument((float)startTime); // We place this command at the front of the queue in order to receive correctly timestamped data as soon as possible // Meanwhile, the data in the receivedQueue is cleared as these will contain the wrong timestamp _cmdMessenger.SendCommand(command,SendQueue.ClearQueue, ReceiveQueue.ClearQueue, UseQueue.BypassQueue); } // Signal the embedded controller to start sending temperature data. public bool StartAcquisition() { // Send command to start sending data var command = new SendCommand((int)Command.StartLogging,(int)Command.Acknowledge,500); // Wait for an acknowledgment that data is being sent. Clear both the receive queue until the acknowledgment is received _chartForm.LogMessage(@"Send command - Start acquisition"); var receivedCommand = _cmdMessenger.SendCommand(command, SendQueue.ClearQueue, ReceiveQueue.ClearQueue); if (receivedCommand.Ok) { AcquisitionStarted = true; } else _chartForm.LogMessage(@" Failure > no OK received from controller"); return receivedCommand.Ok; } // Signal the embedded controller to stop sending temperature data. public bool StopAcquisition() { // Send command to stop sending data var command = new SendCommand((int)Command.StopLogging, (int)Command.Acknowledge, 2500); // Wait for an acknowledgment that data is being sent. Clear both the send and receive queue until the acknowledgment is received _chartForm.LogMessage(@"Send command - Stop acquisition"); var receivedCommand = _cmdMessenger.SendCommand(command, SendQueue.ClearQueue, ReceiveQueue.ClearQueue); if (receivedCommand.Ok) { AcquisitionStarted = false; } else _chartForm.LogMessage(@" Failure > no OK received from controller"); return receivedCommand.Ok; } } }