// *** TemperatureControl ***
// This example example is where all previously described features come together in one full application
// 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 serial or bluetooth connection
// - Use auto scanning and connecting
// - Use the autoconnect and watchdog
using System;
using CommandMessenger;
using CommandMessenger.Queue;
using CommandMessenger.Transport;
using CommandMessenger.Transport.Bluetooth;
using CommandMessenger.Transport.Serial;
using System.Threading;
namespace DataLogging
{
enum Command
{
Identify, // Command to identify device
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
{
private const string UniqueDeviceId = "77FAEDD5-FAC8-46BD-875E-5E9B6D44F85C";
// 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 double _goalTemperature;
// ------------------ MAIN ----------------------
public bool AcquisitionStarted { get; set; }
public bool AcceptData { get; set; }
/// Gets or sets the goal temperature.
/// The goal temperature.
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, and instead communicates over the RFCOMM layer
var transportMode = TransportMode.Serial;
//var transportMode = TransportMode.Bluetooth;
// 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 one of the two transport layers
// Set if it is communicating with a 16- or 32-bit Arduino board
_cmdMessenger = new CmdMessenger(_transport, BoardType.Bit32)
{
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.ControlToInvokeOn = 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, corresponding to the transportMode
if (transportMode == TransportMode.Bluetooth)
_connectionManager = new BluetoothConnectionManager((_transport as BluetoothTransport), _cmdMessenger, (int)Command.Identify, UniqueDeviceId);
else
_connectionManager = new SerialConnectionManager ((_transport as SerialTransport), _cmdMessenger, (int)Command.Identify, UniqueDeviceId);
// Enable watchdog functionality.
_connectionManager.WatchdogEnabled = true;
// 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.StartConnectionManager();
}
private void InitializeTemperatureControl()
{
_startTime = TimeUtils.Millis;
// Set initial goal temperature
GoalTemperature = 25;
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");
}
// Callback function that prints that the Arduino has acknowledged
void OnAcknowledge(ReceivedCommand arguments)
{
_chartForm.LogMessage(@"Arduino acknowledged");
}
// Callback function that prints that the Arduino has experienced an error
void OnError(ReceivedCommand arguments)
{
_chartForm.LogMessage(@"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();
// Update chart with new data point;
_chartForm.UpdateGraph(time, currTemp, goalTemp, heaterValue, heaterPwm);
}
// Log received line to console
private void NewLineReceived(object sender, CommandEventArgs e)
{
_chartForm.LogMessage(@"Received > " + e.Command.CommandString());
// Console.WriteLine(@"Received > " + e.Command.CommandString());
}
// Log sent line to console
private void NewLineSent(object sender, CommandEventArgs 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();
}
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;
// 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);
// Make sure to be explicit if sending float or double
command.AddBinArgument((float)_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;
}
}
}