WIP
DirectOutput framework for virtual pinball cabinets WIP
Go to:
Overview 
TeensyStripController.cs
Go to the documentation of this file.
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.IO.Ports;
6 using System.Threading;
7 
8 namespace DirectOutput.Cab.Out.AdressableLedStrip
9 {
23  {
24  private int[] NumberOfLedsPerStrip = new int[8];
25 
32  public int NumberOfLedsStrip1
33  {
34  get
35  {
36  return NumberOfLedsPerStrip[0];
37  }
38  set
39  {
40  NumberOfLedsPerStrip[0] = value;
41  base.SetupOutputs();
42  }
43  }
50  public int NumberOfLedsStrip2
51  {
52  get
53  {
54  return NumberOfLedsPerStrip[1];
55  }
56  set
57  {
58  NumberOfLedsPerStrip[1] = value;
59  base.SetupOutputs();
60  }
61  }
62 
69  public int NumberOfLedsStrip3
70  {
71  get
72  {
73  return NumberOfLedsPerStrip[2];
74  }
75  set
76  {
77  NumberOfLedsPerStrip[2] = value;
78  base.SetupOutputs();
79  }
80  }
87  public int NumberOfLedsStrip4
88  {
89  get
90  {
91  return NumberOfLedsPerStrip[3];
92  }
93  set
94  {
95  NumberOfLedsPerStrip[3] = value;
96  base.SetupOutputs();
97  }
98  }
105  public int NumberOfLedsStrip5
106  {
107  get
108  {
109  return NumberOfLedsPerStrip[4];
110  }
111  set
112  {
113  NumberOfLedsPerStrip[4] = value;
114  base.SetupOutputs();
115  }
116  }
117 
124  public int NumberOfLedsStrip6
125  {
126  get
127  {
128  return NumberOfLedsPerStrip[5];
129  }
130  set
131  {
132  NumberOfLedsPerStrip[5] = value;
133  base.SetupOutputs();
134  }
135  }
142  public int NumberOfLedsStrip7
143  {
144  get
145  {
146  return NumberOfLedsPerStrip[6];
147  }
148  set
149  {
150  NumberOfLedsPerStrip[6] = value;
151  base.SetupOutputs();
152  }
153  }
160  public int NumberOfLedsStrip8
161  {
162  get
163  {
164  return NumberOfLedsPerStrip[7];
165  }
166  set
167  {
168  NumberOfLedsPerStrip[7] = value;
169  base.SetupOutputs();
170  }
171  }
172 
173  private string _ComPortName;
174 
181  public string ComPortName
182  {
183  get { return _ComPortName; }
184  set { _ComPortName = value; }
185  }
186 
187 
188  private int _ComPortTimeOutMs = 200;
189 
197  public int ComPortTimeOutMs
198  {
199  get { return _ComPortTimeOutMs; }
200  set
201  {
202  if (value.IsBetween(1, 5000))
203  {
204  _ComPortTimeOutMs = value;
205  }
206  else
207  {
208  _ComPortTimeOutMs = 200;
209  Log.Warning("The specified value {0} for the ComPortTimeOutMs is outside the valid range of 1 to 5000. Will use the default value of 200ms.".Build(value));
210  }
211  }
212  }
213 
214 
215 
222  protected override int GetNumberOfConfiguredOutputs()
223  {
224  return NumberOfLedsPerStrip.Sum() * 3;
225  }
226 
233  protected override bool VerifySettings()
234  {
235  if (ComPortName.IsNullOrWhiteSpace())
236  {
237  Log.Warning("The ComPortName has not been specified");
238  return false;
239  }
240 
241  if (!SerialPort.GetPortNames().Any(PN => PN == ComPortName))
242  {
243  Log.Warning("The specified Com-Port {0} was not found. Available com-ports: {1}".Build(ComPortName, string.Join(", ", SerialPort.GetPortNames())));
244  return false;
245  }
246 
247  if (NumberOfLedsPerStrip.Any(Nr => Nr < 0))
248  {
249  Log.Warning("At least one ledstrip has a invalid number of leds specified (<0).");
250  return false;
251  }
252  return true;
253  }
254 
255 
256  SerialPort ComPort = null;
257  int NumberOfLedsPerChannel = -1;
258 
259  protected override void UpdateOutputs(byte[] OutputValues)
260  {
261  if (ComPort == null)
262  {
263  throw new Exception("Comport is not initialized");
264  }
265  byte[] CommandData;
266  byte[] AnswerData;
267  int BytesRead;
268  int SourcePosition = 0;
269  for (int i = 0; i < 8; i++)
270  {
271  int NrOfLedsOnStrip = NumberOfLedsPerStrip[i];
272  if (NrOfLedsOnStrip > 0)
273  {
274  int TargetPosition = i * NumberOfLedsPerChannel;
275  CommandData = new byte[5] { (byte)'R', (byte)(TargetPosition >> 8), (byte)(TargetPosition & 255), (byte)(NrOfLedsOnStrip >> 8), (byte)(NrOfLedsOnStrip & 255) };
276 
277  ComPort.Write(CommandData, 0, 5);
278  ComPort.Write(OutputValues, SourcePosition * 3, NrOfLedsOnStrip * 3);
279 
280  BytesRead = -1;
281 
282  AnswerData = new byte[1];
283 
284  try
285  {
286  BytesRead = ComPort.Read(AnswerData, 0, 1);
287  }
288  catch (Exception E)
289  {
290  throw new Exception("A exception occured while waiting for the ACK after sending the data for channel {0} of the TeensyStripController.".Build(i + 1), E);
291  }
292  if (BytesRead != 1 || AnswerData[0] != (byte)'A')
293  {
294  throw new Exception("Received no answer or a unexpected answer while waiting for the ACK after sending the data for channel {0} of the TeensyStripController.".Build(i + 1));
295  }
296  SourcePosition += NrOfLedsOnStrip;
297  }
298  }
299 
300  CommandData = new byte[1] { (byte)'O' };
301  ComPort.Write(CommandData, 0, 1);
302 
303  BytesRead = -1;
304  AnswerData = new byte[1];
305 
306  try
307  {
308  BytesRead = ComPort.Read(AnswerData, 0, 1);
309  }
310  catch (Exception E)
311  {
312  throw new Exception("A exception occured while waiting for the ACK after sending the output command (O) to the TeensyStripController", E);
313  }
314  if (BytesRead != 1 || AnswerData[0] != (byte)'A')
315  {
316  throw new Exception("Received no answer or a unexpected answer while waiting for the ACK after sending the output command (O) to the TeensyStripController");
317  }
318 
319 
320  }
321 
322 
323  //private byte[] PackData(byte[] OutputValues, int FirstLed, int NumberOfLeds)
324  //{
325  // int OutputPosition = FirstLed * 3;
326  // int LedNr = 0;
327 
328  // if (NumberOfLeds > 0)
329  // {
330  // while (LedNr < NumberOfLeds)
331  // {
332  // if (OutputValues[OutputPosition] == OutputValues[OutputPosition + 3] && OutputValues[OutputPosition + 1] == OutputValues[OutputPosition + 4] && OutputValues[OutputPosition + 2] == OutputValues[OutputPosition + 5])
333  // {
334  // //
335  // }
336  // else
337  // {
338 
339  // }
340 
341 
342 
343 
344  // }
345 
346 
347 
348  // }
349  // else
350  // {
351  // //No data to pack
352  // return new byte[1] { 0 };
353  // }
354 
355 
356  //}
357 
358 
362  protected override void ConnectToController()
363  {
364  DisconnectFromController();
365 
366  string[] PortNames = SerialPort.GetPortNames();
367  if (!PortNames.Any(PN => PN == ComPortName))
368  {
369  throw new Exception("The specified Com-Port '{0}' does not exist. Found the following Com-Ports: {1}. Will not send data to the controller.".Build(ComPortName, string.Join(", ", PortNames)));
370  }
371 
372  ComPort = new SerialPort();
373  ComPort.ReadTimeout = ComPortTimeOutMs;
374  ComPort.WriteTimeout = ComPortTimeOutMs;
375 
376  try
377  {
378  ComPort.PortName = ComPortName;
379  }
380  catch (Exception E)
381  {
382  throw new Exception("A exception occured while setting the name of the Com-port '{0}'. Found the following Com-Ports: {1}. Will not send data to the controller.".Build(ComPortName, string.Join(", ", PortNames)), E);
383  }
384 
385  try
386  {
387  ComPort.Open();
388  }
389  catch (Exception E)
390  {
391  throw new Exception("A exception occured while trying to open the Com-port '{0}'. Found the following Com-Ports: {1}. Will not send data to the controller.".Build(ComPortName, string.Join(", ", PortNames)), E);
392  }
393 
394 
395 
396  //Make sure, the controller is in the expected state (ready to receive commands)
397  Thread.Sleep(50);
398  ComPort.ReadExisting();
399 
400  bool CommandModeOK = false;
401  for (int AttemptNr = 0; AttemptNr < 20; AttemptNr++)
402  {
403 
404  ComPort.Write(new byte[] { 0 }, 0, 1);
405  Thread.Sleep(20);
406  if (ComPort.BytesToRead > 0)
407  {
408  int Ret = ComPort.ReadByte();
409  if (Ret == (int)'A')
410  {
411  //Got a Ack, controller is ready for more commands
412  CommandModeOK = true;
413  break;
414  }
415  else if (Ret == (int)'N')
416  {
417  //Got a NACK. Controller is ready for more commands
418  CommandModeOK = true;
419  break;
420  }
421  else
422  {
423  //Must be the answer of a previous command. Just ignore.
424  }
425 
426  //Got no anwser from com port. Mostly likely we are still inside a command which is expecting more data. Send a lot of 0 bytes to get out of this situation.
427  ComPort.Write(new byte[3 * 1000], 0, 3 * 1000);
428 
429  Thread.Sleep(50);
430  //Get rid of all returned data and try again
431  ComPort.ReadExisting();
432  }
433  };
434  if (!CommandModeOK)
435  {
436  Log.Exception("Could not put the controller on com-port {0} into the commandmode. Will not send data to the controller.".Build(ComPortName));
437  DisconnectFromController();
438  return;
439  }
440 
441 
442  //If we reach this point, we know that the controller is ready to accept commands.
443 
444  //Check max number of leds per channel
445  ComPort.Write(new byte[] { (byte)'M' }, 0, 1);
446  byte[] ReceiveData = new byte[3];
447  int BytesRead = -1;
448 
449  try
450  {
451  BytesRead = ReadPortWait(ReceiveData, 0, 3);
452  }
453  catch (Exception E)
454  {
455  throw new Exception("Expected 3 bytes containing data on the max number of leds per channel, but the read operation resulted in a exception. Will not send data to the controller", E);
456  }
457 
458 
459  if (BytesRead != 3)
460  {
461  throw new Exception("The TeensyStripController did not send the expected 3 bytes containing the data on the max number of leds per channel. Received only {0} bytes. Will not send data to the controller".Build(BytesRead));
462  }
463  if (ReceiveData[2] != 'A')
464  {
465  throw new Exception("The TeensyStripController did not send a ACK after the data containing the max number of leds per channel. Will not send data to the controller");
466  }
467  int MaxNumberOfLedsPerChannel = ReceiveData[0] * 256 + ReceiveData[1];
468 
469  if (NumberOfLedsPerStrip.Any(Nr => Nr > MaxNumberOfLedsPerChannel))
470  {
471  throw new Exception("The TeensyStripController boards supports up to {0}} leds per channel, but you have defined up to {1} leds per channel. Will not send data to the controller.".Build(MaxNumberOfLedsPerChannel, NumberOfLedsPerStrip.Max()));
472  }
473 
474 
475 
476  //Set number of leds per channel
477  NumberOfLedsPerChannel = NumberOfLedsPerStrip.Max();
478  ushort NrOfLeds = (ushort)NumberOfLedsPerChannel;
479  byte[] CommandData = new byte[3] { (byte)'L', (byte)(NrOfLeds >> 8), (byte)(NrOfLeds & 255) };
480  ComPort.Write(CommandData, 0, 3);
481  ReceiveData = new byte[1];
482  BytesRead = -1;
483  try
484  {
485  BytesRead = ReadPortWait(ReceiveData, 0, 1);
486  }
487  catch (Exception E)
488  {
489  throw new Exception("Expected 1 bytes after setting the number of leds per channel, but the read operation resulted in a exception. Will not send data to the controller.", E);
490  }
491 
492  if (BytesRead != 1 || ReceiveData[0] != (byte)'A')
493  {
494  throw new Exception("Expected a Ack (A) after setting the number of leds per channel, but received no answer or a unexpected answer. Will not send data to the controller.");
495 
496  }
497 
498  //Clear the buffer and turn off the leds.
499  CommandData = new byte[1] { (byte)'C' };
500  ComPort.Write(CommandData, 0, 1);
501  ReceiveData = new byte[1];
502  BytesRead = -1;
503  try
504  {
505  BytesRead = ReadPortWait(ReceiveData, 0, 1);
506  }
507  catch (Exception E)
508  {
509  throw new Exception("Expected 1 bytes after clearing the buffer of the TeensyStripController, but the read operation resulted in a exception. Will not send data to the controller.", E);
510  }
511 
512  if (BytesRead != 1 || ReceiveData[0] != (byte)'A')
513  {
514  throw new Exception("Expected a Ack (A) after clearing the buffer of the TeensyStripController, but received no answer or a unexpected answer. Will not send data to the controller.");
515  }
516 
517  CommandData = new byte[1] { (byte)'O' };
518  ComPort.Write(CommandData, 0, 1);
519  ReceiveData = new byte[1];
520  BytesRead = -1;
521  try
522  {
523  BytesRead = ReadPortWait(ReceiveData, 0, 1);
524  }
525  catch (Exception E)
526  {
527  throw new Exception("Expected 1 bytes after outputing the buffer of the TeensyStripController to the ledstrips, but the read operation resulted in a exception. Will not send data to the controller.", E);
528  }
529 
530  if (BytesRead != 1 || ReceiveData[0] != (byte)'A')
531  {
532  throw new Exception("Expected a Ack (A) after outputing the buffer of the TeensyStripController to the ledstrips, but received no answer or a unexpected answer. Will not send data to the controller.");
533  }
534  }
535 
536 
537 
538 
542  protected override void DisconnectFromController()
543  {
544  if (ComPort != null)
545  {
546  try
547  {
548  ComPort.Close();
549 
550  }
551  finally
552  {
553  ComPort = null;
554  }
555  }
556 
557  }
558 
559 
568  private int ReadPortWait(byte[] Buffer, int BufferOffset, int NumberOfBytes)
569  {
570 
571  byte[] ReadBuffer = new byte[1];
572  for (int ByteNumber = 0; ByteNumber < NumberOfBytes; ByteNumber++)
573  {
574  int BytesRead = -1;
575 
576  try
577  {
578  BytesRead = ComPort.Read(ReadBuffer, 0, 1);
579 
580  }
581  catch (TimeoutException TE)
582  {
583  throw new Exception("A TimeoutException occured while trying to read byte {0} of {1} from Com-Port {2}.".Build(ByteNumber + 1, NumberOfBytes, ComPort.PortName), TE);
584  }
585  catch (Exception E)
586  {
587  throw new Exception("A exception occured while trying to read byte {0} of {1} from Com-Port {2}.".Build(ByteNumber + 1, NumberOfBytes, ComPort.PortName), E);
588  }
589 
590  if (BytesRead != 1)
591  {
592  throw new Exception("A exception occured while trying to read byte {0} of {1} from Com-Port {2}. Tried to read 1 byte, but received {3} bytes.".Build(new object[] { ByteNumber + 1, NumberOfBytes, ComPort.PortName, BytesRead }));
593  }
594 
595  Buffer[BufferOffset + ByteNumber] = ReadBuffer[0];
596 
597  }
598 
599  return NumberOfBytes;
600 
601  }
602 
603 
604 
605  }
606 }
override void ConnectToController()
This method is called when DOF wants to connect to the controller.
The TeensyStripController is used to control up to 8 WS2811/WS2812 based ledstrips with up to 1100 le...
override void DisconnectFromController()
Disconnects from the controller.
static void Warning(string Message)
Writes a warning message to the log.
Definition: Log.cs:134
override bool VerifySettings()
Verifies if a valid ComPortName has been set and if the number of outputs per channel is >=0...
override void UpdateOutputs(byte[] OutputValues)
This method is called whenever new data has to be sent to the output controller. Implement the commun...
A simple logger used to record important events and exceptions.
Definition: Log.cs:14
This abstract class implement the full base logic for a output controller with a separate thread for ...
void SetupOutputs()
Manages to output object of the output controller. Use the GetNumberOfConfiguredOutputs() method to d...
override int GetNumberOfConfiguredOutputs()
This method returns the sum of the number of leds configured for the 8 output channels of the Teensy ...
static void Exception(string Message, Exception E=null)
Writes a exception message to the log.
Definition: Log.cs:144