Newer
Older
TestStandRepository / Software / Arduino / libraries / Arduino-Libraries / CmdMessenger / CSharp / TemperatureControl / TemperatureControl.cs
  1. // *** TemperatureControl ***
  2.  
  3. // This example expands the previous ArduinoController example. The PC will now send a start command to the Arduino,
  4. // and wait for a response from the Arduino. The Arduino will start sending temperature data and the heater steering
  5. // value data which the PC will plot in a chart. With a slider we can set the goal temperature, which will make the
  6. // PID software on the controller adjust the setting of the heater.
  7. //
  8. // This example shows how to design a responsive performance UI that sends and receives commands
  9. // - Send queued commands
  10. // - Manipulate the send and receive queue
  11. // - Add queue strategies
  12. // - Use bluetooth connection
  13. // - Use auto scanning and connecting
  14. // - Use watchdog
  15.  
  16. using System;
  17. using CommandMessenger;
  18. using CommandMessenger.Serialport;
  19. using CommandMessenger.TransportLayer;
  20. using System.Threading;
  21. using CommandMessenger.Bluetooth;
  22. namespace DataLogging
  23. {
  24. enum Command
  25. {
  26. RequestId, // Command to request application ID
  27. SendId, // Command to send application ID
  28. Acknowledge, // Command to acknowledge a received command
  29. Error, // Command to message that an error has occurred
  30. StartLogging, // Command to turn on data logging
  31. StopLogging, // Command to turn off data logging
  32. PlotDataPoint, // Command to request plotting a data point
  33. SetGoalTemperature, // Command to set the goal temperature
  34. SetStartTime, // Command to set the new start time for the logger
  35. };
  36.  
  37. enum TransportMode
  38. {
  39. Serial, // Serial port connection (over USB)
  40. Bluetooth, // Bluetooth connection
  41. };
  42.  
  43. public class TemperatureControl
  44. {
  45. // This class (kind of) contains presentation logic, and domain model.
  46. // ChartForm.cs contains the view components
  47. private ITransport _transport;
  48. private CmdMessenger _cmdMessenger;
  49. private ConnectionManager _connectionManager;
  50. private ChartForm _chartForm;
  51. //private float _startTime;
  52. private double _goalTemperature;
  53. // ------------------ MAIN ----------------------
  54.  
  55.  
  56. public bool AcquisitionStarted { get; set; }
  57. public bool AcceptData { get; set; }
  58.  
  59. /// <summary> Gets or sets the goal temperature. </summary>
  60. /// <value> The goal temperature. </value>
  61. public double GoalTemperature
  62. {
  63. get { return _goalTemperature; }
  64. set
  65. {
  66. if (Math.Abs(_goalTemperature - value) > float.Epsilon)
  67. {
  68. _goalTemperature = value;
  69. SetGoalTemperature(_goalTemperature);
  70. if (GoalTemperatureChanged!=null) GoalTemperatureChanged();
  71. }
  72. }
  73. }
  74.  
  75. public Action GoalTemperatureChanged; // Action that is called when the goal temperature has changed
  76. private long _startTime;
  77.  
  78. // Setup function
  79. public void Setup(ChartForm chartForm)
  80. {
  81. // Choose which transport mode you want to use:
  82. // 1. Serial port. This can be a real serial port but is usually a virtual serial port over USB.
  83. // It can also be a virtual serial port over Bluetooth, but the direct bluetooth works better
  84. // 2. Bluetooth This bypasses the Bluetooth virtual serial port, but communicates over the RFCOMM layer
  85. var transportMode = TransportMode.Serial;
  86. // getting the chart control on top of the chart form.
  87. _chartForm = chartForm;
  88. // Set up chart
  89. _chartForm.SetupChart();
  90.  
  91. // Connect slider to GoalTemperatureChanged
  92. GoalTemperatureChanged += () => _chartForm.GoalTemperatureTrackBarScroll(null, null);
  93.  
  94. // Set up transport
  95. if (transportMode == TransportMode.Bluetooth)
  96. _transport = new BluetoothTransport();
  97. // We do not need to set the device: it will be found by the connection manager
  98. else
  99. _transport = new SerialTransport { CurrentSerialSettings = { DtrEnable = false } }; // some boards (e.g. Sparkfun Pro Micro) DtrEnable may need to be true.
  100. // We do not need to set serial port and baud rate: it will be found by the connection manager
  101.  
  102. // Initialize the command messenger with the Serial Port transport layer
  103. _cmdMessenger = new CmdMessenger(_transport)
  104. {
  105. BoardType = BoardType.Bit16, // Set if it is communicating with a 16- or 32-bit Arduino board
  106. PrintLfCr = false // Do not print newLine at end of command, to reduce data being sent
  107. };
  108.  
  109. // Tell CmdMessenger to "Invoke" commands on the thread running the WinForms UI
  110. _cmdMessenger.SetControlToInvokeOn(chartForm);
  111.  
  112. // Set command strategy to continuously to remove all commands on the receive queue that
  113. // are older than 1 sec. This makes sure that if data logging comes in faster that it can
  114. // be plotted, the graph will not start lagging
  115. _cmdMessenger.AddReceiveCommandStrategy(new StaleGeneralStrategy(1000));
  116.  
  117. // Attach the callbacks to the Command Messenger
  118. AttachCommandCallBacks();
  119.  
  120. // Attach to NewLinesReceived for logging purposes
  121. _cmdMessenger.NewLineReceived += NewLineReceived;
  122.  
  123. // Attach to NewLineSent for logging purposes
  124. _cmdMessenger.NewLineSent += NewLineSent;
  125.  
  126. // Set up connection manager
  127. if (transportMode == TransportMode.Bluetooth)
  128. _connectionManager = new BluetoothConnectionManager((_transport as BluetoothTransport), _cmdMessenger, (int)Command.RequestId, (int)Command.SendId);
  129. else
  130. _connectionManager = new SerialConnectionManager ((_transport as SerialTransport), _cmdMessenger, (int)Command.RequestId, (int)Command.SendId);
  131. // Tell the Connection manager to "Invoke" commands on the thread running the WinForms UI
  132. _connectionManager.SetControlToInvokeOn(chartForm);
  133.  
  134. // Event when the connection manager finds a connection
  135. _connectionManager.ConnectionFound += ConnectionFound;
  136.  
  137. // Event when the connection manager watchdog notices that the connection is gone
  138. _connectionManager.ConnectionTimeout += ConnectionTimeout;
  139. // Event notifying on scanning process
  140. _connectionManager.Progress += LogProgress;
  141.  
  142. // Initialize the application
  143. InitializeTemperatureControl();
  144.  
  145. // Start scanning for ports/devices
  146. _connectionManager.StartScan();
  147. }
  148.  
  149. private void InitializeTemperatureControl()
  150. {
  151. _startTime = TimeUtils.Millis;
  152.  
  153. // Set initial goal temperature
  154. GoalTemperature = 25;
  155. // _startTime = 0.0f;
  156. AcquisitionStarted = false;
  157. AcceptData = false;
  158. _chartForm.SetDisConnected();
  159. }
  160.  
  161. // Exit function
  162. public void Exit()
  163. {
  164. // Disconnect ConnectionManager
  165. _connectionManager.Progress -= LogProgress;
  166. _connectionManager.ConnectionTimeout -= ConnectionTimeout;
  167. _connectionManager.ConnectionFound -= ConnectionFound;
  168.  
  169. // Dispose ConnectionManager
  170. _connectionManager.Dispose();
  171.  
  172. // Disconnect Command Messenger
  173. _cmdMessenger.Disconnect();
  174.  
  175. // Dispose Command Messenger
  176. _cmdMessenger.Dispose();
  177.  
  178. // Dispose transport layer
  179. _transport.Dispose();
  180. }
  181.  
  182. /// Attach command call backs.
  183. private void AttachCommandCallBacks()
  184. {
  185. _cmdMessenger.Attach(OnUnknownCommand);
  186. _cmdMessenger.Attach((int)Command.Acknowledge, OnAcknowledge);
  187. _cmdMessenger.Attach((int)Command.Error, OnError);
  188. _cmdMessenger.Attach((int)Command.PlotDataPoint, OnPlotDataPoint);
  189. }
  190.  
  191. // ------------------ CALLBACKS ---------------------
  192.  
  193. // Called when a received command has no attached function.
  194. // In a WinForm application, console output gets routed to the output panel of your IDE
  195. void OnUnknownCommand(ReceivedCommand arguments)
  196. {
  197. _chartForm.LogMessage(@"Command without attached callback received");
  198. //Console.WriteLine(@"Command without attached callback received");
  199. }
  200.  
  201. // Callback function that prints that the Arduino has acknowledged
  202. void OnAcknowledge(ReceivedCommand arguments)
  203. {
  204. _chartForm.LogMessage(@"Arduino acknowledged");
  205. //Console.WriteLine(@" Arduino is ready");
  206. }
  207.  
  208. // Callback function that prints that the Arduino has experienced an error
  209. void OnError(ReceivedCommand arguments)
  210. {
  211. _chartForm.LogMessage(@"Arduino has experienced an error");
  212. //Console.WriteLine(@"Arduino has experienced an error");
  213. }
  214.  
  215. // Callback function that plots a data point for the current temperature, the goal temperature,
  216. // the heater steer value and the Pulse Width Modulated (PWM) value.
  217. private void OnPlotDataPoint(ReceivedCommand arguments)
  218. {
  219. // Plot data if we are accepting data
  220. if (!AcceptData) return;
  221.  
  222. // Get all arguments from plot data point command
  223. var time = arguments.ReadBinFloatArg();
  224. time = (TimeUtils.Millis-_startTime)/1000.0f;
  225. var currTemp = arguments.ReadBinFloatArg();
  226. var goalTemp = arguments.ReadBinFloatArg();
  227. var heaterValue = arguments.ReadBinFloatArg();
  228. var heaterPwm = arguments.ReadBinBoolArg();
  229.  
  230. // do not log data if times are out of sync
  231. //if (time<_startTime) return;
  232.  
  233. // Update chart with new data point;
  234. _chartForm.UpdateGraph(time, currTemp, goalTemp, heaterValue, heaterPwm);
  235.  
  236. // Update _startTime in case it needs to be resend after disconnection
  237. //_startTime = time;
  238. }
  239.  
  240. // Log received line to console
  241. private void NewLineReceived(object sender, NewLineEvent.NewLineArgs e)
  242. {
  243. _chartForm.LogMessage(@"Received > " + e.Command.CommandString());
  244. // Console.WriteLine(@"Received > " + e.Command.CommandString());
  245. }
  246.  
  247. // Log sent line to console
  248. private void NewLineSent(object sender, NewLineEvent.NewLineArgs e)
  249. {
  250. _chartForm.LogMessage(@"Sent > " + e.Command.CommandString());
  251. // Console.WriteLine(@"Sent > " + e.Command.CommandString());
  252. }
  253.  
  254. // Log connection manager progress to status bar
  255. void LogProgress(object sender, ConnectionManagerProgressEventArgs e)
  256. {
  257. if (e.Level <= 2) { _chartForm.SetStatus(e.Description); }
  258. _chartForm.LogMessage(e.Description);
  259. // Console.WriteLine(e.Level + @" :" + e.Description);
  260. }
  261.  
  262. private void ConnectionTimeout(object sender, EventArgs e)
  263. {
  264. // Connection time-out!
  265. // Disable UI ..
  266. _chartForm.SetStatus(@"Connection timeout, attempting to reconnect");
  267. _chartForm.SetDisConnected();
  268. // and start scanning
  269. _connectionManager.StartScan();
  270. }
  271.  
  272. private void ConnectionFound(object sender, EventArgs e)
  273. {
  274. //We have been connected!
  275.  
  276. // Make sure we do not receive data until we are ready
  277. AcceptData = false;
  278. // Enable UI
  279. _chartForm.SetConnected();
  280. // Send command to set goal Temperature
  281. SetGoalTemperature(_goalTemperature);
  282.  
  283. // Restart acquisition if needed
  284. if (AcquisitionStarted) StartAcquisition(); else StopAcquisition();
  285. AcceptData = true;
  286. // Start Watchdog
  287. _connectionManager.StartWatchDog();
  288.  
  289. // Yield time slice in order to get UI updated
  290. Thread.Yield();
  291. }
  292.  
  293. // Set the goal temperature on the embedded controller
  294. public void SetGoalTemperature(double goalTemperature)
  295. {
  296. _goalTemperature = goalTemperature;
  297.  
  298. // Create command to start sending data
  299. var command = new SendCommand((int)Command.SetGoalTemperature);
  300. command.AddBinArgument(_goalTemperature);
  301.  
  302. // Collapse this command if needed using CollapseCommandStrategy
  303. // This strategy will avoid duplicates of this command on the queue: if a SetGoalTemperature command is
  304. // already on the queue when a new one is added, it will be replaced at its current queue-position.
  305. // Otherwise the command will be added to the back of the queue.
  306. //
  307. // This will make sure that when the slider raises a lot of events that each set a new goal temperature, the
  308. // controller will not start lagging.
  309. _chartForm.LogMessage(@"Queue command - SetGoalTemperature");
  310. _cmdMessenger.QueueCommand(new CollapseCommandStrategy(command));
  311. }
  312.  
  313. // Set the start time on the embedded controller
  314. public void SetStartTime(float startTime)
  315. {
  316. var command = new SendCommand((int)Command.SetStartTime, (int)Command.Acknowledge,500);
  317. command.AddBinArgument((float)startTime);
  318.  
  319. // We place this command at the front of the queue in order to receive correctly timestamped data as soon as possible
  320. // Meanwhile, the data in the receivedQueue is cleared as these will contain the wrong timestamp
  321. _cmdMessenger.SendCommand(command,SendQueue.ClearQueue, ReceiveQueue.ClearQueue, UseQueue.BypassQueue);
  322. }
  323.  
  324. // Signal the embedded controller to start sending temperature data.
  325. public bool StartAcquisition()
  326. {
  327. // Send command to start sending data
  328. var command = new SendCommand((int)Command.StartLogging,(int)Command.Acknowledge,500);
  329.  
  330. // Wait for an acknowledgment that data is being sent. Clear both the receive queue until the acknowledgment is received
  331. _chartForm.LogMessage(@"Send command - Start acquisition");
  332. var receivedCommand = _cmdMessenger.SendCommand(command, SendQueue.ClearQueue, ReceiveQueue.ClearQueue);
  333. if (receivedCommand.Ok)
  334. {
  335. AcquisitionStarted = true;
  336. }
  337. else
  338. _chartForm.LogMessage(@" Failure > no OK received from controller");
  339. return receivedCommand.Ok;
  340. }
  341.  
  342. // Signal the embedded controller to stop sending temperature data.
  343. public bool StopAcquisition()
  344. {
  345. // Send command to stop sending data
  346. var command = new SendCommand((int)Command.StopLogging, (int)Command.Acknowledge, 2500);
  347.  
  348. // Wait for an acknowledgment that data is being sent. Clear both the send and receive queue until the acknowledgment is received
  349. _chartForm.LogMessage(@"Send command - Stop acquisition");
  350. var receivedCommand = _cmdMessenger.SendCommand(command, SendQueue.ClearQueue, ReceiveQueue.ClearQueue);
  351. if (receivedCommand.Ok)
  352. {
  353. AcquisitionStarted = false;
  354. }
  355. else
  356. _chartForm.LogMessage(@" Failure > no OK received from controller");
  357. return receivedCommand.Ok;
  358. }
  359. }
  360. }