WIP
DirectOutput framework for virtual pinball cabinets WIP
Go to:
Overview 
PacLed64.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.Runtime.InteropServices;
6 using System.Threading;
7 using System.Xml.Serialization;
9 
10 namespace DirectOutput.Cab.Out.Pac
11 {
24  {
25  #region Id
26 
27 
28  private object IdUpdateLocker = new object();
29  private int _Id = -1;
30 
42  public int Id
43  {
44  get { return _Id; }
45  set
46  {
47  if (!value.IsBetween(1, 4))
48  {
49  throw new Exception("PacLed64 Ids must be between 1-4. The supplied Id {0} is out of range.".Build(value));
50  }
51  lock (IdUpdateLocker)
52  {
53  if (_Id != value)
54  {
55 
56  if (Name.IsNullOrWhiteSpace() || Name == "PacLed64 {0:0}".Build(_Id))
57  {
58  Name = "PacLed64 {0:0}".Build(value);
59  }
60 
61  _Id = value;
62 
63  }
64  }
65  }
66  }
67 
68  #endregion
69 
70  private int _MinUpdateIntervalMs = 0;
71 
84  public int MinUpdateIntervalMs
85  {
86  get { return _MinUpdateIntervalMs; }
87  set { _MinUpdateIntervalMs = value.Limit(0, 1000); }
88  }
89 
90 
91  private int _FullUpdateThreshold = 30;
92 
107  public int FullUpdateThreshold
108  {
109  get { return _FullUpdateThreshold; }
110  set { _FullUpdateThreshold = value.Limit(0, 64); }
111  }
112 
113 
114 
115 
116 
117  #region IOutputcontroller implementation
118  public override void Update()
122  {
123  PacLed64Units[Id].TriggerPacLed64UpdaterThread();
124  }
125 
126 
133  public override void Init(Cabinet Cabinet)
134  {
135  AddOutputs();
136  PacLed64Units[Id].FullUpdateThreshold = FullUpdateThreshold;
137  PacLed64Units[Id].MinUpdateInterval = TimeSpan.FromMilliseconds(MinUpdateIntervalMs);
138  PacLed64Units[Id].Init(Cabinet);
139  Log.Write("PacLed64 Id:{0} initialized and updater thread started.".Build(Id));
140 
141  }
142 
147  public override void Finish()
148  {
149  PacLed64Units[Id].Finish();
150  PacLed64Units[Id].ShutdownLighting();
151  Log.Write("PacLed64 Id:{0} finished and updater thread stopped.".Build(Id));
152 
153  }
154  #endregion
155 
156 
157 
158  #region Outputs
159 
160 
165  private void AddOutputs()
166  {
167  for (int i = 1; i <= 64; i++)
168  {
169  if (!Outputs.Any(x => (x).Number == i))
170  {
171  Outputs.Add(new Output() { Name = "{0}.{1:00}".Build(Name, i), Number = i });
172  }
173  }
174  }
175 
176 
177 
178 
189  protected override void OnOutputValueChanged(IOutput Output)
190  {
191 
192  IOutput ON = Output;
193 
194  if (!ON.Number.IsBetween(1, 64))
195  {
196  throw new Exception("PacLed64 output numbers must be in the range of 1-64. The supplied output number {0} is out of range.".Build(ON.Number));
197  }
198 
199  PacLed64Unit S = PacLed64Units[this.Id];
200  S.UpdateValue(ON);
201  }
202 
203 
204 
205  #endregion
206 
207 
208 
209 
210 
211 
212 
213 
214 
215  //~PacLed64()
216  //{
217  // Dispose(false);
218  //}
219 
220  //#region Dispose
221 
222 
223 
224  //public void Dispose()
225  //{
226 
227  // Dispose(true);
228  // GC.SuppressFinalize(this); // remove this from gc finalizer list
229  //}
230  //protected virtual void Dispose(bool disposing)
231  //{
232  // // Check to see if Dispose has already been called.
233  // if (!this.disposed)
234  // {
235  // // If disposing equals true, dispose all managed
236  // // and unmanaged resources.
237  // if (disposing)
238  // {
239  // // Dispose managed resources.
240 
241  // }
242 
243  // // Call the appropriate methods to clean up
244  // // unmanaged resources here.
245  // // If disposing is false,
246  // // only the following code is executed.
247 
248  // TerminatePacLed64();
249 
250 
251  // // Note disposing has been done.
252  // disposed = true;
253 
254  // }
255  //}
256  //private bool disposed = false;
257 
258  //#endregion
259 
260 
261 
262  #region Constructor
263 
264 
268  static PacLed64()
269  {
270 
271  PacLed64Units = new Dictionary<int, PacLed64Unit>();
272  for (int i = 1; i <= 4; i++)
273  {
274  PacLed64Units.Add(i, new PacLed64Unit(i));
275 
276  }
277 
278  }
279 
283  public PacLed64()
284  {
285 
286  Outputs = new OutputList();
287 
288  }
289 
290 
291 
296  public PacLed64(int Id)
297  : this()
298  {
299  this.Id = Id;
300  }
301 
302  #endregion
303 
304 
305 
306 
307 
308 
309  #region Internal class for PacLed64 output states and update methods
310 
311  private static Dictionary<int, PacLed64Unit> PacLed64Units = new Dictionary<int, PacLed64Unit>();
312 
313  private class PacLed64Unit
314  {
315  //private Pinball Pinball;
316 
317 
318  private const int MaxUpdateFailCount = 5;
319 
320 
321  public int Id { get; private set; }
322 
323  public int FullUpdateThreshold { get; set; }
324  public TimeSpan MinUpdateInterval { get; set; }
325 
326  private int Index { get; set; }
327 
328  private PacDriveSingleton PDSingleton;
329 
330  private byte[] NewValue = new byte[64];
331  private byte[] CurrentValue = new byte[64];
332 
333 
334  private byte[] LastValueSent = new byte[64];
335  private bool[] LastStateSent = new bool[64];
336 
337  public bool UpdateRequired = true;
338 
339  public object PacLed64UpdateLocker = new object();
340  public object ValueChangeLocker = new object();
341 
342  public Thread PacLed64Updater;
343  public bool KeepPacLed64UpdaterAlive = false;
344  public object PacLed64UpdaterThreadLocker = new object();
345 
346 
347  public void Init(Cabinet Cabinet)
348  {
349  //this.Pinball = Cabinet.Pinball;
350  //if (!Pinball.TimeSpanStatistics.Contains("PacLed64 {0:0} update calls".Build(Id)))
351  //{
352  // UpdateTimeStatistics = new TimeSpanStatisticsItem() { Name = "PacLed64 {0:0} update calls".Build(Id), GroupName = "OutputControllers - PacLed64" };
353  // Pinball.TimeSpanStatistics.Add(UpdateTimeStatistics);
354  //}
355  //else
356  //{
357  // UpdateTimeStatistics = Pinball.TimeSpanStatistics["PacLed64 {0:0} update calls".Build(Id)];
358  //}
359  //if (!Pinball.TimeSpanStatistics.Contains("PacLed64 {0:0} PWM updates".Build(Id)))
360  //{
361  // PWMUpdateTimeStatistics = new TimeSpanStatisticsItem() { Name = "PacLed64 {0:0} PWM updates".Build(Id), GroupName = "OutputControllers - PacLed64" };
362  // Pinball.TimeSpanStatistics.Add(PWMUpdateTimeStatistics);
363  //}
364  //else
365  //{
366  // PWMUpdateTimeStatistics = Pinball.TimeSpanStatistics["PacLed64 {0:0} PWM updates".Build(Id)];
367  //}
368  //if (!Pinball.TimeSpanStatistics.Contains("PacLed64 {0:0} OnOff updates".Build(Id)))
369  //{
370  // OnOffUpdateTimeStatistics = new TimeSpanStatisticsItem() { Name = "PacLed64 {0:0} OnOff updates".Build(Id), GroupName = "OutputControllers - PacLed64" };
371  // Pinball.TimeSpanStatistics.Add(OnOffUpdateTimeStatistics);
372  //}
373  //else
374  //{
375  // OnOffUpdateTimeStatistics = Pinball.TimeSpanStatistics["PacLed64 {0:0} OnOff updates".Build(Id)];
376  //}
377  StartPacLed64UpdaterThread();
378  }
379 
380  public void Finish()
381  {
382 
383  TerminatePacLed64UpdaterThread();
384  ShutdownLighting();
385 
386  }
387 
388  public void UpdateValue(IOutput Output)
389  {
390  //Skip update on output numbers which are out of range
391  if (!Output.Number.IsBetween(1, 64)) return;
392 
393  int ZeroBasedOutputNumber = Output.Number - 1;
394  lock (ValueChangeLocker)
395  {
396  if (NewValue[ZeroBasedOutputNumber] != Output.Value)
397  {
398  NewValue[ZeroBasedOutputNumber] = Output.Value;
399  UpdateRequired = true;
400  }
401  }
402 
403  }
404  private void CopyNewToCurrent()
405  {
406  lock (ValueChangeLocker)
407  {
408  Array.Copy(NewValue, CurrentValue, NewValue.Length);
409 
410  }
411  }
412 
413  public bool IsUpdaterThreadAlive
414  {
415  get
416  {
417  if (PacLed64Updater != null)
418  {
419  return PacLed64Updater.IsAlive;
420  }
421  return false;
422  }
423  }
424 
425  public void StartPacLed64UpdaterThread()
426  {
427  lock (PacLed64UpdaterThreadLocker)
428  {
429  if (!IsUpdaterThreadAlive)
430  {
431  KeepPacLed64UpdaterAlive = true;
432  PacLed64Updater = new Thread(PacLed64UpdaterDoIt);
433  PacLed64Updater.Name = "PacLed64 {0:0} updater thread".Build(Id);
434  PacLed64Updater.Start();
435  }
436  }
437  }
438  public void TerminatePacLed64UpdaterThread()
439  {
440  lock (PacLed64UpdaterThreadLocker)
441  {
442  if (PacLed64Updater != null)
443  {
444  try
445  {
446  KeepPacLed64UpdaterAlive = false;
447  TriggerPacLed64UpdaterThread();
448  if (!PacLed64Updater.Join(1000))
449  {
450  PacLed64Updater.Abort();
451  }
452 
453  }
454  catch (Exception E)
455  {
456  Log.Exception("A error occurd during termination of {0}.".Build(PacLed64Updater.Name), E);
457  throw new Exception("A error occurd during termination of {0}.".Build(PacLed64Updater.Name), E);
458  }
459  PacLed64Updater = null;
460  }
461  }
462  }
463 
464  bool TriggerUpdate = false;
465  public void TriggerPacLed64UpdaterThread()
466  {
467  TriggerUpdate = true;
468  lock (PacLed64UpdaterThreadLocker)
469  {
470  Monitor.Pulse(PacLed64UpdaterThreadLocker);
471  }
472  }
473 
474 
475  //TODO: Check if thread should really terminate on failed updates
476  private void PacLed64UpdaterDoIt()
477  {
478  //Pinball.ThreadInfoList.HeartBeat("PacLed64 {0:0}".Build(Id));
479 
480  try
481  {
482  ResetFadeTime();
483 
484  }
485  catch (Exception E)
486  {
487  Log.Exception("A exception occured while setting the fadetime for pacled64 {0} to 0.".Build(Index), E);
488  throw;
489  }
490  int FailCnt = 0;
491  while (KeepPacLed64UpdaterAlive)
492  {
493  try
494  {
495  if (IsPresent)
496  {
497 
498  SendPacLed64Update();
499 
500  }
501  FailCnt = 0;
502  }
503  catch (Exception E)
504  {
505  Log.Exception("A error occured when updating PacLed64 {0}".Build(Id), E);
506  //Pinball.ThreadInfoList.RecordException(E);
507  FailCnt++;
508 
509  if (FailCnt > MaxUpdateFailCount)
510  {
511  Log.Exception("More than {0} consecutive updates failed for PacLed64 {1}. Updater thread will terminate.".Build(MaxUpdateFailCount, Id));
512  KeepPacLed64UpdaterAlive = false;
513  }
514  }
515  //Pinball.ThreadInfoList.HeartBeat();
516  if (KeepPacLed64UpdaterAlive)
517  {
518  lock (PacLed64UpdaterThreadLocker)
519  {
520  while (!TriggerUpdate && KeepPacLed64UpdaterAlive)
521  {
522  Monitor.Wait(PacLed64UpdaterThreadLocker, 50); // Lock is released while we’re waiting
523  //Pinball.ThreadInfoList.HeartBeat();
524  }
525 
526  }
527 
528  }
529  TriggerUpdate = false;
530  }
531  //Pinball.ThreadInfoList.ThreadTerminates();
532  }
533 
534 
535 
536 
537  private bool ForceFullUpdate = true;
538  private void SendPacLed64Update()
539  {
540  if (IsPresent)
541  {
542 
543  lock (PacLed64UpdateLocker)
544  {
545  lock (ValueChangeLocker)
546  {
547  if (!UpdateRequired && !ForceFullUpdate) return;
548 
549  CopyNewToCurrent();
550 
551  UpdateRequired = false;
552  }
553 
554  byte IntensityUpdatesRequired = 0;
555  byte StateUpdatesRequired = 0;
556  if (!ForceFullUpdate)
557  {
558 
559  for (int g = 0; g < 8; g++)
560  {
561 
562  bool StateUpdateRequired = false;
563  for (int p = 0; p < 8; p++)
564  {
565  int o = g << 3 | p;
566  if (CurrentValue[o] > 0)
567  {
568  if (CurrentValue[o] != LastValueSent[o])
569  {
570  IntensityUpdatesRequired++;
571  }
572  else if (!LastStateSent[o])
573  {
574  StateUpdateRequired = true;
575  }
576  }
577  else if (LastStateSent[o])
578  {
579  StateUpdateRequired = true;
580  }
581 
582  }
583  if (StateUpdateRequired) StateUpdatesRequired++;
584  StateUpdateRequired = false;
585  }
586  }
587  if (ForceFullUpdate || (IntensityUpdatesRequired + StateUpdatesRequired) >= FullUpdateThreshold)
588  {
589  //more than FullUpdateThreshold update calls required. Will send intensity updates for all outputs.
590  EnforceMinUpdateInterval();
591  PDSingleton.PacLed64SetLEDIntensities(Index, CurrentValue);
592  Array.Copy(CurrentValue, LastValueSent, CurrentValue.Length);
593  for (int i = 0; i < 64; i++)
594  {
595  LastStateSent[i] = (LastValueSent[i] > 0);
596  }
597  }
598  else
599  {
600  //Will send separate intensity and state updates.
601  for (int g = 0; g < 8; g++)
602  {
603  int Mask = 0;
604  bool StateUpdateRequired = false;
605  for (int p = 0; p < 8; p++)
606  {
607  int o = g << 3 | p;
608  if (CurrentValue[o] > 0)
609  {
610  if (CurrentValue[o] != LastValueSent[o])
611  {
612  EnforceMinUpdateInterval();
613  PDSingleton.PacLed64SetLEDIntensity(Index, o, CurrentValue[o]);
614  LastStateSent[o] = true;
615  LastValueSent[o] = CurrentValue[o];
616  }
617  else if (!LastStateSent[o])
618  {
619 
620  StateUpdateRequired = true;
621  LastStateSent[o] = true;
622  }
623  Mask |= (1 << p);
624  }
625  else if (LastStateSent[o])
626  {
627  StateUpdateRequired = true;
628  LastStateSent[o] = false;
629  LastValueSent[o] = 0;
630  }
631 
632  }
633  if (StateUpdateRequired)
634  {
635  EnforceMinUpdateInterval();
636  PDSingleton.PacLed64SetLEDStates(Index, g + 1, (byte)Mask);
637  StateUpdateRequired = false;
638  }
639 
640  }
641  }
642 
643 
644  }
645  ForceFullUpdate = false;
646  }
647  else
648  {
649  ForceFullUpdate = true;
650  }
651  }
652 
653 
654  public void ShutdownLighting()
655  {
656  for (int g = 0; g <= 8; g++)
657  {
658  EnforceMinUpdateInterval();
659  PDSingleton.PacLed64SetLEDStates(0, g, 0);
660  }
661 
662  LastStateSent.Fill(false);
663  }
664 
665  private void ResetFadeTime()
666  {
667  EnforceMinUpdateInterval();
668  PDSingleton.PacLed64SetLEDFadeTime(Index, 0);
669 
670  }
671 
672 
673  DateTime LastCommand = DateTime.MinValue;
674  private void EnforceMinUpdateInterval()
675  {
676  if (MinUpdateInterval != TimeSpan.Zero)
677  {
678  TimeSpan Interval = DateTime.Now - LastCommand;
679  LastCommand = DateTime.Now;
680  if (Interval < MinUpdateInterval)
681  {
682  int Sleepperiod = ((int)(MinUpdateInterval - Interval).TotalMilliseconds).Limit(0,1000);
683  if (Sleepperiod > 0)
684  {
685  Thread.Sleep(Sleepperiod);
686  }
687  }
688  }
689  }
690 
691 
692 
693  private bool IsPresent
694  {
695  get
696  {
697  if (!Id.IsBetween(1, 4)) return false;
698  return Index >= 0;
699  }
700  }
701 
702 
703  void Instance_OnPacRemoved(int Index)
704  {
705  this.Index = PDSingleton.PacLed64GetIndexForDeviceId(Id);
706  }
707 
708  void Instance_OnPacAttached(int Index)
709  {
710  this.Index = PDSingleton.PacLed64GetIndexForDeviceId(Id);
711  InitUnit();
712  TriggerPacLed64UpdaterThread();
713  }
714 
715  private void InitUnit()
716  {
717  if (Index >= 0)
718  {
719  LastValueSent.Fill((byte)0);
720 
721  PDSingleton.PacLed64SetLEDIntensities(Index, LastValueSent);
722  LastStateSent.Fill(false);
723  LastValueSent.Fill((byte)0);
724  }
725  }
726 
727 
728  public PacLed64Unit(int Id)
729  {
730  this.Id = Id;
731 
732  PDSingleton = PacDriveSingleton.Instance;
733  PDSingleton.OnPacAttached += new PacDriveSingleton.PacAttachedDelegate(Instance_OnPacAttached);
734  PDSingleton.OnPacRemoved += new PacDriveSingleton.PacRemovedDelegate(Instance_OnPacRemoved);
735 
736  this.Index = PacDriveSingleton.Instance.PacLed64GetIndexForDeviceId(Id);
737 
738  NewValue.Fill((byte)0);
739 
740  InitUnit();
741 
742 
743  }
744 
745 
746  }
747 
748 
749 
750  #endregion
751 
752 
753 
754 
755  }
756 }
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
override void Finish()
Finishes the PacLed64 object. Finish does also terminate the workerthread.
Definition: PacLed64.cs:147
override void OnOutputValueChanged(IOutput Output)
This method is called whenever the value of a output in the Outputs property changes its value...
Definition: PacLed64.cs:189
List of IOutput objects
Definition: OutputList.cs:13
override void Init(Cabinet Cabinet)
Initializes the PacLed64 object. This method does also start the workerthread which does the actual ...
Definition: PacLed64.cs:133
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
PacLed64(int Id)
Initializes a new instance of the PacLed64 class.
Definition: PacLed64.cs:296
The PacLed64 is a output controller with 64 outputs all supporting 256 pwm levels with a PWM frequenc...
Definition: PacLed64.cs:23
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...
PacLed64()
Initializes a new instance of the PacLed64 class.
Definition: PacLed64.cs:283
The namespace DirectOutput.General contains classes for general use.
Basic IOutput implementation.
Definition: Output.cs:14