Chronos Plugins 5.9.0
This documentation covers the plugin interfaces definitions and an example implementation.
Loading...
Searching...
No Matches
MockDevice.cs
Go to the documentation of this file.
3using System;
4using System.Collections.Generic;
5using System.ComponentModel;
6using System.Diagnostics.CodeAnalysis;
7using System.Drawing.Design;
8using System.IO;
9using System.Threading;
10using System.Threading.Tasks;
11using System.Windows.Forms;
13
14// ReSharper disable LocalizableElement
15#pragma warning disable 169
16
21namespace MockPlugin.Device
22{
23
24
36 [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
37 [SuppressMessage("ReSharper", "UnusedMember.Global")]
38 public class MockDevice :
39 // ReSharper disable once RedundantExtendsListEntry
40 IDevice,
46 INotifyPropertyChanged,
47 IDisposable,
48 IHaveMachineParameters<CoffeMakerParams>,
53 {
54 public const string DeviceTypeName = "ACME Coffee Maker";
55 #region private variables
56
57 private bool mIsConnected;
58
59 #endregion private variables
60
61 #region IDevice
62
63 public MockDevice(IGuiHelper guiHelper)
64 {
65 mGuiHelper = guiHelper;
66 Instances.Add(this);
67 }
68
69 internal static readonly List<MockDevice> Instances = new List<MockDevice>();
70
71 public void Dispose()
72 {
73 Instances.Remove(this);
74 DebugOutput?.Invoke($"MockDevice {Name} disposed");
75 }
76
81
85 public string DeviceTypeDescription => "coffee machine";
86
87 public string Name
88 {
89 get;
90 set;
91 }
92
98 {
101 $"Making a {composition.Volume} mL frappuccino with cream type \"{composition.Cream}\"{(composition.DeCaffeinated ? ", decaffeinated." : "")}.");
102 }
103
107 public void SomeDummyMethod()
108 {
109 DebugOutput?.Invoke($"Dummy method of {Name} was called.");
110 }
111
115 public void Connect()
116 {
118 MessageBox.Show(mGuiHelper.MainWindow, $"Device {Name} was connected to {Connection}.");
120 mIsConnected = true;
122 }
123
127 public void Disconnect()
128 {
130 MessageBox.Show(mGuiHelper.MainWindow, $"Device {Name} was disconnected from {Connection}.");
132 mIsConnected = false;
134 }
135
136 public event Action<ConnectionState> ConnectionStateChanged;
137
138 #endregion IDevice
139
140 #region INeedAConnection
141
148 [Editor(typeof(ConnectionEditor), typeof(UITypeEditor))]
149 public string Connection { get; set; } = "COM17";
150
151 #endregion INeedAConnection
152
153 #region Custom methods and properties
154
159 public void ShowTheMessage(string messageText)
160 {
161 WaitIfPaused();
162 SetStatusMessage?.Invoke("The device just did something wonderful.");
163 MessageBox.Show(mGuiHelper.MainWindow,
164 $"The following message was shown addressing the mock device {Name}:\r\n{messageText}",
165 "Mock Device",
166 MessageBoxButtons.OK);
167 DebugOutput?.Invoke($"Finished showing the message {messageText}");
168 }
169
173 public bool IsConnected
174 {
175 get => mIsConnected;
176 set
177 {
178 if (!mIsConnected && value)
179 {
180 Connect();
181 }
182 else if (mIsConnected && !value)
183 {
184 Disconnect();
185 }
186 }
187 }
188
189 #endregion Custom methods and properties
190
191 #region IProvideStatusMessages
192
193 public event Action<string> SetStatusMessage;
194
195 #endregion IProvideStatusMessages
196
197 #region IHaveDebugOutput
198
199 public event Action<string> DebugOutput;
200
201 #endregion IHaveDebugOutput
202
203 #region INotifyPropertyChanged
204
208 private void OnIsConnectedChanged()
209 {
210 var myHandler = PropertyChanged;
211 if (myHandler != null)
212 {
213 mGuiHelper.GuiTaskFactory.StartNew(() => myHandler(this, new PropertyChangedEventArgs(nameof(IsConnected))));
214 }
215 }
216
217 public event PropertyChangedEventHandler PropertyChanged;
218
219 #endregion INotifyPropertyChanged
220
221 #region IAbortSchedules
222
232 public void TriggerAbort(string reason, bool softStop)
233 {
234 Task.Run(() =>
235 {
236 Thread.Sleep(5000);
237 if (softStop)
238 {
239 StopRun?.Invoke(new StopRunArgs()
240 {
241 How = StopRunArgs.StopMode.NoNewJobs, StopQueue = true, Reason = reason,
242 RestartRemainingJobs = false
243 });
244 }
245 else
246 {
247 AbortSchedule?.Invoke(reason);
248 }
249 });
250}
251
252 public event Action<string> AbortSchedule;
253
254 public void CheckForAbort()
255 {
256 if (mAborted.IsSet)
257 {
258 throw new OperationCanceledException("Aborted");
259 }
260 }
261
262 #endregion IAbortSchedules
263
264 #region Implementation of IHaveMachineParameters<CoffeMakerParams>
265
266 public CoffeMakerParams Parameters { get; set; } = new CoffeMakerParams();
267
268 public Task ApplyParametersAsync(bool waitUntilSetpointIsReached, CancellationToken canToken)
269 {
270
271 SetStatusMessage?.Invoke($"Applying parameters: '{Parameters}' and {(!waitUntilSetpointIsReached ? "not " : "")} waiting for setpoint.");
272 if (waitUntilSetpointIsReached)
273 {
274 return Task.Delay(TimeSpan.FromSeconds(10),canToken);
275 }
276 return Task.CompletedTask;
277 }
278
279 #endregion Implementation of IHaveMachineParameters<CoffeMakerParams>
280
281 public void WaitIfPaused()
282 {
283 WaitHandle.WaitAny(new[] { mPauseEnded.WaitHandle, mAborted.WaitHandle });
285 }
286
287 #region Implementation of ICanInterrupt
288
292 private readonly ManualResetEventSlim mPauseEnded = new ManualResetEventSlim(true);
293
294 private readonly ManualResetEventSlim mAborted = new ManualResetEventSlim(false);
295
296 public bool Aborted
297 {
298 get => mAborted.IsSet;
299 set
300 {
301 if (value) { mAborted.Set(); } else { mAborted.Reset(); }
302 }
303 }
304
305 public bool Paused
306 {
307 get => !mPauseEnded.IsSet;
308 set
309 {
310 if (value)
311 {
312 mPauseEnded.Reset();
313 }
314 else
315 {
316 mPauseEnded.Set();
317 }
318 }
319 }
320
321 #endregion Implementation of ICanInterrupt
322
323 public IEnumerable<string> LogPaths =>
324 MockSampleListWorker.GetFakeLogs($"{GetType().FullName} instance {Name}");
325
326 public Func<StopRunArgs, Task> StopRun { get; set; }
327
328 #region Schedule state events
329
331 private readonly IGuiHelper mGuiHelper;
332
334 {
335 set
336 {
337 if (mScheduleEvents != null)
338 {
339 mScheduleEvents.ScheduleStateChanged -= ScheduleStateChangedHandler;
340 }
341
342 mScheduleEvents = value;
343 if (value != null)
344 {
345 mScheduleEvents.ScheduleStateChanged += ScheduleStateChangedHandler;
346 }
347 }
348 }
349
351 {
352 DebugOutput?.Invoke($"Schedule {e.PlanerName} ({e.PlanerID}) state {e.State}, abort reason {(string.IsNullOrEmpty(e.AbortReason) ? "N/A" : e.AbortReason)}");
353 }
354 #endregion
355
356 public void BeginInteraction()
357 {
358 DebugOutput?.Invoke("Starting error handling interaction");
359 }
360
361 public void EndInteraction()
362 {
363 DebugOutput?.Invoke("Ending error handling interaction");
364 }
365
372 public void RaiseError(string errorDescription, ErrorType errType, bool resolved)
373 {
374 var errArgs = new InteractiveErrorHandlingEventArgs() { Error = errorDescription, ErrorType = errType };
375 do
376 {
377 HandleError?.Invoke(this, errArgs);
378 } while (errArgs.RetryLastAction);
379
380 if (!resolved)
381 {
382 throw new IOException($"Coffee machine reported a problem: {errorDescription}");
383 }
384 }
385 public event EventHandler<InteractiveErrorHandlingEventArgs> HandleError;
386 }
387}
Classes and interfaces that are meant for plugins. The classes and interfaces below this namespace ar...
ConnectionState
If your connectivity state changes, you should tell the user about it.
ErrorType
Lets a device implementing IHaveInteractiveErrorHandling specify which kind of error occurred.
A fake device. This namespace contains the fake device driver and auxiliary classes for settings,...
Example task implementations. Since there are lots of things that can be done from a task,...
The classes in this namespace demonstrate how to interact with the Chronos sample list.
To be implemented by the "device driver" part of a Chronos plugin.
For devices that need some kind of user configured connection string to address the hardware.
Implement this interface if you want to keep the user up-to-date about what your device is doing.
Implement this interface if you wish to provide debug log output.
Implement this interface if you need to abort schedules on some error condition.
Everything needed for showing the error dialog / reacting on input.
For devices that allow interactive error handling (like retrying the last action)
For devices that support pausing/aborting independent of a task.
Parameters that are constant for the duration of a schedule.
Helper functions for GUI related tasks.
Definition Helpers.cs:18
System.Threading.Tasks.TaskFactory GuiTaskFactory
For your convenience, a default task factory for tasks running on the GUI thread.
Definition Helpers.cs:26
IWin32Window MainWindow
If you need to set the owner window yourself or want to show message boxes.
Definition Helpers.cs:37
Information about the current state change.
Currently starting schedule stage.
Implement this interface if you need to track the state of schedules.
This can be called for a sample list worker or device that writes its own set of log files which shou...
Implement this interface with your device or sample list worker to get fine-grained control about sto...
Options for stopping the schedule/queue.
StopMode
Details how to stop the run.
We have a fancy coffee machine that can regulate the warmer temperature for the pot and has lamps for...
Just a primitive UI Type Editor to demonstrate how you can add an editor of your own for connection s...
A chronos plugin implementation for a fake device. We pretend we are controlling a mixture of coffee ...
Definition MockDevice.cs:53
PropertyChangedEventHandler PropertyChanged
Action< string > DebugOutput
Action< string > SetStatusMessage
bool Aborted
If set, abort execution as soon as possible. You can throw an OperationCanceledException....
void BrewFrappuccino(BrewFrappuccino.CompositionData composition)
Pretend we are doing some operation on a complex parameter set.
Definition MockDevice.cs:97
void Connect()
Inform the user of our connect attempt / success. Instead of establishing a real connection,...
readonly ManualResetEventSlim mPauseEnded
Using an event here instead of a simple bool helps us avoid polling while checking for the events.
Action< string > AbortSchedule
void SomeDummyMethod()
Just for testing if methods of this device can be called from some other point in our code.
void OnIsConnectedChanged()
For thread-safe update of the toolbox's GUI elements representing the connection state.
EventHandler< InteractiveErrorHandlingEventArgs > HandleError
void Disconnect()
Pretend we are closing the connection. Actual operation substituted by a message box.
void ScheduleStateChangedHandler(object sender, ScheduleStateEventArgs e)
bool IsConnected
Helper for our toolbox.
Func< StopRunArgs, Task > StopRun
Callback function returning a task that completes once the schedule queue was stopped.
IScheduleEvents mScheduleEvents
void ShowTheMessage(string messageText)
Let our device set a status message and display some message box instead of doing real work.
string Name
User-selected name for the device instance.
Definition MockDevice.cs:88
Task ApplyParametersAsync(bool waitUntilSetpointIsReached, CancellationToken canToken)
string DisplayedTypeName
Visible to the user on the instruments page of the settings editor.
Definition MockDevice.cs:80
string DeviceTypeDescription
Device class specification referred to by some messages.
Definition MockDevice.cs:85
void TriggerAbort(string reason, bool softStop)
This will trigger the AbortSchedule-Event 5 seconds after it was called from a task.
IEnumerable< string > LogPaths
Provide full paths to each of your log files here.
bool Paused
If paused, wait until paused is reset before executing the next command.
readonly IGuiHelper mGuiHelper
void EndInteraction()
Will be called if you can lock the terminal again.
CoffeMakerParams Parameters
MockDevice(IGuiHelper guiHelper)
Definition MockDevice.cs:63
string Connection
Connection as set in the Chronos instrument settings.
void BeginInteraction()
Will be called if interaction is started, gives you a chance to unlock a terminal,...
readonly ManualResetEventSlim mAborted
IScheduleEvents ScheduleEvents
Hook up immediately or save this for later use.
Action< ConnectionState > ConnectionStateChanged
void RaiseError(string errorDescription, ErrorType errType, bool resolved)
"Retry" here means that we don't retry some action, but that we raise the error again.
Provides an endless supply of nonsense sample lists.
static IEnumerable< string > GetFakeLogs(string creator)
Creates a few fake log files.
A task working on a complex parameter set.
Let's pretend the composition is really complex and better done with a custom editor.