WIP
DirectOutput framework for virtual pinball cabinets WIP
Go to:
Overview 
FT245RBitbangController.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.Threading;
6 
7 namespace DirectOutput.Cab.Out.FTDIChip
8 {
9 
17  {
18  private string _SerialNumber = "";
19 
26  public string SerialNumber
27  {
28  get { return _SerialNumber; }
29  set { _SerialNumber = value; }
30  }
31 
36  public override void Init(Cabinet Cabinet)
37  {
38  if (SerialNumber.IsNullOrWhiteSpace())
39  {
40  Log.Exception("Could not initialize FT245RBitbangController {0}. SerialNumber has not been set.".Build(Name));
41  return;
42  }
43  AddOutputs();
44  InitUpdaterThread();
45  Log.Write("FT245RBitbangController {0} with serial number {1} has been initialized and the updater thread has been started.".Build(Name, SerialNumber));
46  }
47 
52  public override void Finish()
53  {
54  FinishUpdaterThread();
55  Log.Write("FT245RBitbangController {0} with serial number {1} has been finished and the updater thread has been terminated.".Build(Name, SerialNumber));
56  }
57 
61  public override void Update()
62  {
63  if (NewValue != CurrentValue)
64  {
65  UpdaterThreadSignal();
66  }
67  }
68 
73  private void AddOutputs()
74  {
75  for (int i = 1; i <= 8; i++)
76  {
77  if (!Outputs.Any(x => x.Number == i))
78  {
79  Outputs.Add(new Output() { Name = "{0}.{1:00}".Build(Name, i), Number = i });
80  }
81  }
82  }
83 
84 
85 
96  protected override void OnOutputValueChanged(IOutput Output)
97  {
98 
99  IOutput ON = Output;
100 
101  if (!ON.Number.IsBetween(1, 64))
102  {
103  throw new Exception("FT245RBitbangController output numbers must be in the range of 1-8. The supplied output number {0} for FT245RBitbangController with serial number {1} is out of range.".Build(ON.Number, SerialNumber));
104  }
105 
106  if (ON.Value > 0)
107  {
108  lock (ValueChangeLocker)
109  {
110  NewValue |= (byte)(1 << (ON.Number - 1));
111  }
112  }
113  else
114  {
115  lock (ValueChangeLocker)
116  {
117  NewValue &= (byte)~(1 << (ON.Number - 1));
118  }
119  }
120  }
121 
122 
123 
124 
125  #region UpdaterThread
126  private void InitUpdaterThread()
131  {
132 
133  if (!UpdaterThreadIsActive)
134  {
135  KeepUpdaterThreadAlive = true;
136  try
137  {
138  UpdaterThread = new Thread(UpdaterThreadDoIt);
139  UpdaterThread.Name = "FT245RBitbangController {0} named {1} updater thread ".Build(SerialNumber, Name);
140  UpdaterThread.Start();
141  }
142  catch (Exception E)
143  {
144  Log.Exception("FT245RBitbangController {0} named {1} updater thread could not start.".Build(SerialNumber, Name), E);
145  throw new Exception("FT245RBitbangController {0} named {1} updater thread could not start.".Build(SerialNumber, Name), E);
146  }
147  }
148  }
149 
154  private void FinishUpdaterThread()
155  {
156  if (UpdaterThread != null)
157  {
158  try
159  {
160  KeepUpdaterThreadAlive = false;
161  UpdaterThreadSignal();
162  if (!UpdaterThread.Join(1000))
163  {
164  UpdaterThread.Abort();
165  }
166  UpdaterThread = null;
167  }
168  catch (Exception E)
169  {
170  Log.Exception("A error occured during termination of FT245RBitbangController ({0}) updater thread.".Build(SerialNumber), E);
171  throw new Exception("A error occured during termination of FT245RBitbangController ({0}) updater thread.".Build(SerialNumber, E));
172  }
173  }
174  }
175 
176 
180  public bool UpdaterThreadIsActive
181  {
182  get
183  {
184  if (UpdaterThread != null)
185  {
186  if (UpdaterThread.IsAlive)
187  {
188  return true;
189  }
190  }
191  return false;
192  }
193  }
194 
198  private void UpdaterThreadSignal()
199  {
200  lock (UpdaterThreadLocker)
201  {
202  Monitor.Pulse(UpdaterThreadLocker);
203  }
204  }
205 
206  private object ValueChangeLocker = new object();
207  private byte NewValue = 0;
208  private byte CurrentValue = 255;
209 
210 
211  private Thread UpdaterThread { get; set; }
212  private object UpdaterThreadLocker = new object();
213  private bool KeepUpdaterThreadAlive = true;
214  private int FirstTryFailCnt = 0;
215  private void UpdaterThreadDoIt()
216  {
217 
218  Connect();
219  if (FTDI == null)
220  {
221  Log.Warning("No connection to FTDI chip {0}. Updater thread will terminate.".Build(SerialNumber));
222  return;
223  }
224 
225  try
226  {
227  SendUpdate(0);
228  }
229  catch (Exception E)
230  {
231  Log.Exception("Could not send initial update to FTDI chip {0}. Updater thread will terminate.".Build(SerialNumber));
232  Disconnect();
233  return;
234  }
235 
236 
237  while (KeepUpdaterThreadAlive)
238  {
239  lock (ValueChangeLocker)
240  {
241  CurrentValue = NewValue;
242  }
243 
244  try
245  {
246  //Try to send the update
247  SendUpdate(CurrentValue);
248  }
249  catch (Exception E)
250  {
251  //Log error for first 5 exceptions
252  if (FirstTryFailCnt < 5)
253  {
254  Log.Exception("Could not send update to FTDI chip {0} on first try. Will reconnect and send update again.".Build(SerialNumber), E);
255  FirstTryFailCnt++;
256  if (FirstTryFailCnt == 5)
257  {
258  Log.Warning("Will not log futher warnings on first try failures when sending data to FTDI chip {0}.".Build(SerialNumber));
259  }
260  }
261 
262 
263  try
264  {
265  //Try to reconnect if update fails
266  Disconnect();
267  Connect();
268  }
269  catch (Exception EC)
270  {
271  //Reconnect failed
272  Log.Exception("Could not send update to FTDI chip {0}. Tried to reconnect to device but failed. Updater thread will terminate.".Build(SerialNumber), EC);
273  break;
274  }
275 
276  if (FTDI != null)
277  {
278  //Reconnected OK. Try again to send value.
279  try
280  {
281  SendUpdate(CurrentValue);
282  }
283  catch (Exception EE)
284  {
285  //Sending value failed again
286  Log.Exception("Could not send update to FTDI chip {0}. Reconnect to device worked, but sending the update did fail again. Updater thread will terminate.".Build(SerialNumber), EE);
287  break;
288  }
289 
290  }
291  else
292  {
293  //Reconnect failed
294  Log.Exception("Could not send update to FTDI chip {0}. Tried to reconnect to device but failed. Updater thread will terminate.".Build(SerialNumber));
295  break;
296  }
297  }
298 
299  if (KeepUpdaterThreadAlive)
300  {
301  lock (UpdaterThreadLocker)
302  {
303  while (NewValue == CurrentValue && KeepUpdaterThreadAlive)
304  {
305  Monitor.Wait(UpdaterThreadLocker, 50); // Lock is released while we’re waiting
306  }
307  }
308  }
309  }
310 
311  try
312  {
313  SendUpdate(0);
314  }
315  catch (Exception E)
316  {
317  Log.Exception("Final update to turn off all output fo FTDI chip {0} failed.".Build(SerialNumber), E);
318  }
319  Disconnect();
320  }
321 
322  #endregion
323 
324  #region FT245R Communication
325 
326  object FTDILocker = new object();
327  FTDI FTDI = null;
328 
329  private void Connect()
330  {
331  lock (FTDILocker)
332  {
333 
334  if (FTDI != null)
335  {
336 
337  Disconnect();
338  }
339 
340  if (SerialNumber.IsNullOrWhiteSpace())
341  {
342  Log.Exception("The SerialNumber has not been set for the FT245RBitbangController named {0}. Cant connect to device.".Build(Name));
343  return;
344  }
345 
346  FTDI = new FTDI();
347 
348  //Try to open the device
349  try
350  {
351  FTDI.ErrorHandler(FTDI.OpenBySerialNumber(SerialNumber));
352  }
353  catch (Exception E)
354  {
355  Log.Exception("Could not open the connection to FTDI chip with serial number {0}.".Build(SerialNumber), E);
356  FTDI = null;
357  return;
358  }
359 
360  // Set FT245RL to synchronous bit-bang mode, used on sainsmart relay board
361  try
362  {
363  FTDI.ErrorHandler(FTDI.SetBitMode(0xFF, FTDI.FT_BIT_MODES.FT_BIT_MODE_SYNC_BITBANG));
364  }
365  catch (Exception E)
366  {
367  Log.Exception("Could set the bitmode to bitbang for FTDI chip with serial number {0}.".Build(SerialNumber), E);
368  Disconnect();
369  FTDI = null;
370  return;
371  }
372 
373  Log.Write("Connection to FTDI chip {0} established.".Build(SerialNumber));
374  }
375 
376 
377 
378 
379  }
380 
381 
382  private void SendUpdate(byte OutputData)
383  {
384  lock (FTDILocker)
385  {
386  if (FTDI != null)
387  {
388  uint numBytes = 0;
389  byte[] Out = { OutputData };
390 
391 
392  FTDI.FT_STATUS Status = FTDI.Write(Out, 1, ref numBytes);
393  if (Status != FTDI.FT_STATUS.FT_OK)
394  {
395 
396  FTDI.ErrorHandler(Status);
397  }
398  if (numBytes != 1)
399  {
400  throw new Exception("Wrong number of written bytes (expected 1, received {0} was returned when sending data to the FTDI chip {1}".Build(numBytes, SerialNumber));
401  }
402  }
403  }
404  }
405 
406  private void Disconnect()
407  {
408  lock (FTDILocker)
409  {
410  if (FTDI != null)
411  {
412 
413  try
414  {
415  SendUpdate(0);
416  }
417  catch { }
418 
419  if (FTDI.IsOpen)
420  {
421  try
422  {
423  FTDI.ErrorHandler(FTDI.Close());
424  }
425  catch (Exception E)
426  {
427  Log.Exception("A exception occured when closing the FTDI chip {0}.".Build(SerialNumber));
428  }
429  }
430  FTDI = null;
431  Log.Write("Connection to FTDI chip {0} closed.".Build(SerialNumber));
432 
433  }
434 
435  }
436 
437  }
438 
439 
440 
441  #endregion
442 
443 
448  {
449  Outputs = new OutputList();
450  }
451 
452  }
453 }
This is a generic output controller class which are based on the FT245R chip (http://www.ftdichip.com/Products/ICs/FT245R.htm). Only units using the chip in bitbang mode are supported by this output controller class. The SainSmart USB relay boards (http://www.sainsmart.com/arduino-compatibles-1/relay/usb-relay.html) are compatible with this output controller, but other hardware which is based on the same controller chip might be compatible as well. Generally controller units which is exclusively using the FT245R (no extra cpu on board) and having max. 8 output ports are likely to be compatible. Please let me know, if you have tested other hardware successfully, so I can ammend the docu.
The Cabinet object describes the parts of a pinball cabinet (toys, outputcontrollers, outputs and more).
Definition: Cabinet.cs:17
int Number
Gets or sets the number of the Output object.
Definition: IOutput.cs:27
FT245RBitbangController()
Initializes a new instance of the FT245RBitbangController class.
override void Update()
Signals the workerthread that all pending updates for the FT245RBitbangController should be sent to t...
override void Finish()
Finishes the FT245RBitbangController object. Finish does also terminate the workerthread.
byte Value
Value of the output.
Definition: IOutput.cs:18
static void Warning(string Message)
Writes a warning message to the log.
Definition: Log.cs:134
List of IOutput objects
Definition: OutputList.cs:13
static void Write(string Message)
Writes the specified message to the logfile.
Definition: Log.cs:99
A simple logger used to record important events and exceptions.
Definition: Log.cs:14
Common interface for outputs of any output controller. The Output class implements this interface and...
Definition: IOutput.cs:10
Abstract OutputController base class to be used for IOutputController implementations. Implements IOutputController.
Common interface for all outputcontrollers. Only classes implementing this interface can be used as o...
override void OnOutputValueChanged(IOutput Output)
This method is called whenever the value of a output in the Outputs property changes its value...
override void Init(Cabinet Cabinet)
Initializes the FT245RBitbangController and starts the updater thread.
static void Exception(string Message, Exception E=null)
Writes a exception message to the log.
Definition: Log.cs:144
Basic IOutput implementation.
Definition: Output.cs:14