2 using System.Collections.Generic;
4 using System.Runtime.InteropServices;
6 using Microsoft.Win32.SafeHandles;
8 using System.Text.RegularExpressions;
45 private object NumberUpdateLocker =
new object();
46 private int _Number = -1;
61 get {
return _Number; }
64 if (!value.IsBetween(1, 16))
66 throw new Exception(
"Pinscape Unit Numbers must be between 1-16. The supplied number {0} is out of range.".Build(value));
68 lock (NumberUpdateLocker)
75 if (Name.IsNullOrWhiteSpace() || Name ==
"Pinscape Controller {0:00}".Build(_Number))
77 Name =
"Pinscape Controller {0:00}".Build(value);
84 this.Dev = Devices.First(D => D.UnitNo() == Number);
85 this.NumberOfOutputs = this.Dev.NumOutputs();
86 this.OldOutputValues = Enumerable.Repeat((byte)255, this.NumberOfOutputs).ToArray();
95 private int _MinCommandIntervalMs = 1;
96 private bool MinCommandIntervalMsSet =
false;
116 public int MinCommandIntervalMs
118 get {
return _MinCommandIntervalMs; }
121 _MinCommandIntervalMs = value.Limit(0, 1000);
122 MinCommandIntervalMsSet =
true;
126 #region IOutputcontroller implementation
156 #region OutputControllerFlexCompleteBase implementation
183 for (
int i = 0; i < NumberOfOutputs; i += 7, ++pfx)
186 int lim = Math.Min(i + 7, NumberOfOutputs);
187 for (
int j = i; j < lim; ++j)
190 if (NewOutputValues[j] != OldOutputValues[j])
194 byte[] buf =
new byte[9];
197 Array.Copy(NewOutputValues, i, buf, 2, lim - i);
201 Array.Copy(NewOutputValues, i, OldOutputValues, i, lim - i);
207 byte[] OldOutputValues;
209 private DateTime LastUpdate = DateTime.Now;
210 private void UpdateDelay()
212 int Ms = (int)DateTime.Now.Subtract(LastUpdate).TotalMilliseconds;
213 if (Ms < MinCommandIntervalMs)
214 Thread.Sleep((MinCommandIntervalMs - Ms).Limit(0, MinCommandIntervalMs));
215 LastUpdate = DateTime.Now;
235 #region USB Communications
241 return name +
" (unit " + UnitNo() +
")";
254 public Device(IntPtr fp,
string path,
string name,
short vendorID,
short productID,
short version)
260 this.vendorID = vendorID;
261 this.productID = productID;
262 this.version = version;
263 this.plungerEnabled =
true;
266 this.numOutputs = 32;
272 if ((ushort)vendorID == 0xFAFA && (productID & 0xFFF0) == 0x00F0)
273 this.unitNo = (short)((productID & 0x000f) + 1);
278 byte[] buf = ReadUSB();
282 this.plungerEnabled = (buf[1] & 0x01) != 0;
287 for (
int i = 0; i < 8; ++i)
292 if (buf != null && (buf[2] & 0xF8) == 0x88)
295 this.numOutputs = (int)buf[3] | (((
int)buf[4]) << 8);
298 unitNo = (short)(((ushort)buf[5] | (((ushort)buf[6]) << 8)) + 1);
306 if (fp.ToInt32() != 0 && fp.ToInt32() != -1)
310 private System.Threading.NativeOverlapped ov;
313 for (
int tries = 0; tries < 3; ++tries)
315 const int rptLen = 15;
316 byte[] buf =
new byte[rptLen];
319 if (
HIDImports.ReadFile(fp, buf, rptLen, out actual, ref ov) == 0)
322 if (TryReopenHandle())
325 Log.
Write(
"Pinscape Controller USB error reading from device: " + GetLastWin32ErrMsg());
328 else if (actual != rptLen)
330 Log.
Write(
"Pinscape Controller USB error reading from device: not all bytes received");
341 private IntPtr OpenFile()
345 IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
348 private bool TryReopenHandle()
351 if (Marshal.GetLastWin32Error() == 6)
354 Console.WriteLine(
"invalid handle on read - trying to reopen handle");
355 IntPtr fp2 = OpenFile();
374 int errno = Marshal.GetLastWin32Error();
375 return String.Format(
"{0} (Win32 error {1})",
387 byte[] buf =
new byte[9];
391 return WriteUSB(buf);
396 for (
int tries = 0; tries < 3; ++tries)
399 if (
HIDImports.WriteFile(fp, buf, 9, out actual, ref ov) == 0)
402 if (TryReopenHandle())
405 Log.
Write(
"Pinscape Controller USB error sending request to device: " + GetLastWin32ErrMsg());
408 else if (actual != 9)
410 Log.
Write(
"Pinscape Controller USB error sending request: not all bytes sent");
435 #region Device enumeration
446 private static List<Device> FindDevices()
449 List<Device> devices =
new List<Device>();
454 IntPtr hdev =
HIDImports.SetupDiGetClassDevs(ref guid, null, IntPtr.Zero,
HIDImports.DIGCF_DEVICEINTERFACE);
458 diData.cbSize = Marshal.SizeOf(diData);
462 HIDImports.SetupDiEnumDeviceInterfaces(hdev, IntPtr.Zero, ref guid, i, ref diData);
467 HIDImports.SetupDiGetDeviceInterfaceDetail(hdev, ref diData, IntPtr.Zero, 0, out size, IntPtr.Zero);
471 diDetail.cbSize = (IntPtr.Size == 8) ? (uint)8 : (uint)5;
472 if (
HIDImports.SetupDiGetDeviceInterfaceDetail(hdev, ref diData, ref diDetail, size, out size, IntPtr.Zero))
477 IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
481 attrs.
Size = Marshal.SizeOf(attrs);
482 if (
HIDImports.HidD_GetAttributes(fp, ref attrs))
488 String name =
"<not available>";
489 byte[] nameBuf =
new byte[128];
490 if (
HIDImports.HidD_GetProductString(fp, nameBuf, 128))
491 name =
System.Text.Encoding.Unicode.GetString(nameBuf).TrimEnd(
'\0');
496 bool isLW = ((ushort)attrs.VendorID == 0xFAFA && (attrs.ProductID >= 0x00F0 && attrs.ProductID <= 0x00FF));
497 bool isPS = ((ushort)attrs.VendorID == 0x1209 && ((ushort)attrs.ProductID == 0xEAEA));
498 ok &= ((isLW || isPS)
499 && Regex.IsMatch(name,
@"\b(?i)pinscape\b")
500 && attrs.VersionNumber >= 7);
512 if (ok &&
HIDImports.HidD_GetPreparsedData(fp, out ppdata))
524 ok &= (caps.UsagePage == 1 && caps.Usage == 4);
535 devices.Add(
new Device(fp, diDetail.DevicePath, name, attrs.VendorID, attrs.ProductID, attrs.VersionNumber));
543 if (fp.ToInt32() != 0 && fp.ToInt32() != -1)
544 HIDImports.CloseHandle(fp);
555 private static List<Device> Devices;
569 Devices = FindDevices();
586 this.Number = Number;
The Cabinet object describes the parts of a pinball cabinet (toys, outputcontrollers, outputs and more).
bool WriteUSB(byte[] buf)
Pinscape(int Number)
Initializes a new instance of the Pinscape class with a given unit number.
ICabinetOwner Owner
Gets or sets the owner or the cabinet.
String GetLastWin32ErrMsg()
static void Write(string Message)
Writes the specified message to the logfile.
Device(IntPtr fp, string path, string name, short vendorID, short productID, short version)
A simple logger used to record important events and exceptions.
override void Init(Cabinet Cabinet)
Initializes the Pinscape object. This method does also start the workerthread which does the actual ...
const uint GENERIC_READ_WRITE
override void DisconnectFromController()
Disconnect from the controller.
override void UpdateOutputs(byte[] NewOutputValues)
Send updated outputs to the physical device.
static bool CloseHandle(IntPtr hObject)
Dictionary< string, object > ConfigurationSettings
Gets the configuration settings. This dict can contain settings which are used by the output controll...
override void ConnectToController()
Connect to the controller.
override string ToString()
bool SpecialRequest(byte id)
const uint SHARE_READ_WRITE
static List< Device > AllDevices()
Get the list of all Pinscape devices discovered in the system from the Windows USB device scan...
Pinscape()
Initializes a new instance of the Pinscape class with no unit number set. The unit number must be set...
override void Finish()
Finishes the Pinscape object. Finish does also terminate the workerthread for updates.
The Pinscape Controller is an open-source software/hardware project based on the inexpensive and powe...
override bool VerifySettings()
Verify settings. Returns true if settings are valid, false otherwise. In the current implementation...