PageRenderTime 58ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/SharpBoard.NET 0.3.10.0401/WiiDeviceLibrary/Interface/ReportDevice.cs

#
C# | 508 lines | 412 code | 58 blank | 38 comment | 39 complexity | a5e48256d8ea0d9b2224e9242eab79c0 MD5 | raw file
  1. // Copyright 2009 Wii Device Library authors
  2. //
  3. // This file is part of Wii Device Library.
  4. //
  5. // Wii Device Library is free software: you can redistribute it and/or modify
  6. // it under the terms of the GNU General Public License as published by
  7. // the Free Software Foundation, either version 3 of the License, or
  8. // (at your option) any later version.
  9. //
  10. // Wii Device Library is distributed in the hope that it will be useful,
  11. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. // GNU General Public License for more details.
  14. //
  15. // You should have received a copy of the GNU General Public License
  16. // along with Wii Device Library. If not, see <http://www.gnu.org/licenses/>.
  17. //#define DEBUG_REPORTS
  18. using System;
  19. using System.IO;
  20. using System.Collections.Generic;
  21. using System.Threading;
  22. namespace WiiDeviceLibrary
  23. {
  24. public abstract class ReportDevice : IDevice
  25. {
  26. #region Fields
  27. private static TimeSpan ReportTimeout = TimeSpan.FromSeconds(2);
  28. private const int maximalReportLength = 22;
  29. private byte[] outputBuffer = new byte[maximalReportLength];
  30. private static readonly byte[] reportLengths = new byte[0x40];
  31. private Stream communicationStream;
  32. private Thread readingThread = null;
  33. private object reportLocker = new object();
  34. private object readReportLocker = new object();
  35. private int reportConsumers = 0;
  36. private Stack<byte[]> returningReports = new Stack<byte[]>();
  37. private bool isConnected = true;
  38. #endregion
  39. #region Protected Properties
  40. protected byte[] OutputBuffer
  41. {
  42. get { return outputBuffer; }
  43. }
  44. // Delfo
  45. public bool IsConnected
  46. {
  47. get { return isConnected; }
  48. }
  49. #endregion
  50. #region Constructors
  51. protected ReportDevice(IDeviceInfo deviceInfo, Stream communicationStream)
  52. {
  53. _DeviceInfo = deviceInfo;
  54. InitializeCommunication(communicationStream);
  55. }
  56. static ReportDevice()
  57. {
  58. reportLengths[0x10] = 2; // Unknown
  59. reportLengths[0x11] = 2; // Player LEDs
  60. reportLengths[0x12] = 3; // Data Reporting mode
  61. reportLengths[0x13] = 2; // IR Camera Enable
  62. reportLengths[0x14] = 2; // Speaker Enable
  63. reportLengths[0x15] = 2; // Status Information Request
  64. reportLengths[0x16] = 22; // Write Memory and Registers
  65. reportLengths[0x17] = 7; // Read Memory and Registers
  66. reportLengths[0x18] = 22; // Speaker Data
  67. reportLengths[0x19] = 2; // Speaker Mute
  68. reportLengths[0x1a] = 2; // IR Camera Enable 2
  69. reportLengths[0x20] = 7; // Status Information
  70. reportLengths[0x21] = 22; // Read Memory and Registers Data
  71. reportLengths[0x22] = 5; // Write Memory and Registers Status
  72. reportLengths[0x30] = 3; // Data reports
  73. reportLengths[0x31] = 6; // Data reports
  74. reportLengths[0x32] = 11; // Data reports
  75. reportLengths[0x33] = 18; // Data reports
  76. reportLengths[0x34] = 21; // Data reports
  77. reportLengths[0x35] = 22; // Data reports
  78. reportLengths[0x36] = 22; // Data reports
  79. reportLengths[0x37] = 22; // Data reports
  80. reportLengths[0x38] = 22; // Data reports
  81. reportLengths[0x39] = 22; // Data reports
  82. reportLengths[0x3a] = 22; // Data reports
  83. reportLengths[0x3b] = 22; // Data reports
  84. reportLengths[0x3c] = 22; // Data reports
  85. reportLengths[0x3d] = 22; // Data reports
  86. reportLengths[0x3e] = 22; // Data reports
  87. reportLengths[0x3f] = 22; // Data reports
  88. }
  89. #endregion
  90. #region Abstract Members
  91. public abstract void Initialize();
  92. #endregion
  93. #region IDevice Members
  94. private IDeviceInfo _DeviceInfo;
  95. public IDeviceInfo DeviceInfo
  96. {
  97. get { return _DeviceInfo; }
  98. }
  99. private ReportingMode _ReportingMode = ReportingMode.Buttons;
  100. public ReportingMode ReportingMode
  101. {
  102. get { return _ReportingMode; }
  103. protected set { _ReportingMode = value; }
  104. }
  105. private byte _BatteryLevel;
  106. public byte BatteryLevel
  107. {
  108. get { return _BatteryLevel; }
  109. protected set { _BatteryLevel = value; }
  110. }
  111. #endregion
  112. #region IDevice Members
  113. public void UpdateStatus()
  114. {
  115. CreateReport(OutputReport.GetStatus);
  116. OutputBuffer[1] = 0x00;
  117. SendAndReturnReport(InputReport.GetStatusResult);
  118. }
  119. // Change by Delfo
  120. public virtual void SetReportingMode(ReportingMode reportMode, CameraSensitivity irMode)
  121. {
  122. SetReportingMode(reportMode);
  123. }
  124. public virtual void SetReportingMode(ReportingMode reportMode)
  125. {
  126. if (reportMode == ReportingMode.None)
  127. throw new ArgumentException("The ReportingMode cannot be set to None.", "reportMode");
  128. CreateReport(OutputReport.SetDataReportMode);
  129. OutputBuffer[1] = 0x04;
  130. OutputBuffer[2] = (byte)reportMode;
  131. SendReport();
  132. }
  133. public void ReadMemory(uint address, byte[] buffer, int offset, short count)
  134. {
  135. if (count + offset > buffer.Length)
  136. throw new ArgumentException("The specified buffer cannot hold the requested amount of bytes.", "buffer");
  137. CreateReport(OutputReport.ReadMemory);
  138. // Write the address.
  139. OutputBuffer[1] = (byte)(((address & 0xff000000) >> 24));
  140. OutputBuffer[2] = (byte)((address & 0x00ff0000) >> 16);
  141. OutputBuffer[3] = (byte)((address & 0x0000ff00) >> 8);
  142. OutputBuffer[4] = (byte)(address & 0x000000ff);
  143. // Write the byte-count.
  144. OutputBuffer[5] = (byte)((count & 0xff00) >> 8);
  145. OutputBuffer[6] = (byte)(count & 0x00ff);
  146. int i = 0;
  147. using (IReportInterceptor reportInterceptor = CreateReportInterceptor())
  148. {
  149. SendReport();
  150. byte[] report = null;
  151. // Since ReadMemory can have multiple response-reports we have to intercept
  152. // all ReadDataResult-reports that follow the ReadMemory-report.
  153. while ((report = reportInterceptor.Intercept()) != null)
  154. {
  155. if (report[0] == (byte)InputReport.ReadDataResult)
  156. {
  157. int size = (report[3] >> 4) + 1;
  158. // The data from ReadDataResult is added to the buffer.
  159. Array.Copy(report, 6, buffer, offset + i * 16, size);
  160. i++;
  161. if (size != 16 || i * 16 == count)
  162. break;
  163. byte errorCode = (byte)(report[3] & 0x0f);
  164. switch (errorCode)
  165. {
  166. case 0:
  167. break;
  168. case 8:
  169. throw new ArgumentException("The specified range to read contains non-existent addresses.", "address");
  170. case 7:
  171. throw new ArgumentException("The specified range to read contains write-only registers.", "address");
  172. default:
  173. throw new InvalidDataException("The wiimote returned an unknown errorcode.");
  174. }
  175. }
  176. }
  177. }
  178. }
  179. public void WriteMemory(uint address, byte[] buffer, int offset, byte count)
  180. {
  181. if (count > 16)
  182. throw new ArgumentException("Memory can only be written in blocks of 16 bytes and lower.", "count");
  183. CreateReport(OutputReport.WriteMemory);
  184. OutputBuffer[1] = (byte)((address & 0xff000000) >> 24);
  185. OutputBuffer[2] = (byte)((address & 0x00ff0000) >> 16);
  186. OutputBuffer[3] = (byte)((address & 0x0000ff00) >> 8);
  187. OutputBuffer[4] = (byte)(address & 0x000000ff);
  188. OutputBuffer[5] = count;
  189. Array.Copy(buffer, offset, OutputBuffer, 6, count);
  190. byte[] writeResultReport = SendAndReturnReport(InputReport.WriteDataResult);
  191. switch (writeResultReport[4])
  192. {
  193. case 0x00:
  194. // No error.
  195. break;
  196. case 0x04:
  197. // When WriteMemory is send 2 times, where no confirmation is send back yet, errorcode 4 will be send.
  198. // throw new InvalidOperationException("WriteMemory was called when there was no confirmation send back. This should not happen, unless you are using WriteMemory (directly or indirectly) from the Updated eventhandler.");
  199. break;
  200. case 0x05:
  201. // Occurs when WriteMemory in InitializeWiimote is called. The reason for the error is unknown.
  202. // Seems to happen only with Bluesoleil.
  203. throw new InvalidOperationException("WriteMemory could not be executed.");
  204. case 0x07:
  205. // Ignore this exception for now.
  206. // throw new ArgumentException("The specified address is not accesible.");
  207. break;
  208. case 0x08:
  209. throw new ArgumentException("The specified address and bytes overlaps unwritable memory.");
  210. default:
  211. throw new InvalidOperationException(string.Format("The WriteMemory-operation resulted in an unknown error ({0}).", writeResultReport[4]));
  212. }
  213. }
  214. #endregion
  215. #region Communication
  216. private void InitializeCommunication(Stream communicationStream)
  217. {
  218. if (communicationStream == null)
  219. throw new ArgumentNullException("communicationStream");
  220. this.communicationStream = communicationStream;
  221. BeginReadReport();
  222. }
  223. private void BeginReadReport()
  224. {
  225. byte[] buffer = new byte[maximalReportLength];
  226. communicationStream.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(OnReadReport), buffer);
  227. }
  228. private void OnReadReport(IAsyncResult result)
  229. {
  230. int bytesRead = 0;
  231. readingThread = Thread.CurrentThread;
  232. byte[] buffer = (byte[])result.AsyncState;
  233. try
  234. {
  235. bytesRead = communicationStream.EndRead(result);
  236. }
  237. catch (OperationCanceledException)
  238. {
  239. Disconnect();
  240. return;
  241. }
  242. catch (IOException)
  243. {
  244. Disconnect();
  245. return;
  246. }
  247. catch (NullReferenceException)
  248. {
  249. // Although the documentation says this can never happen, Reflector tells us otherwise.
  250. // This happened at one of Maato's test-systems where this exception was thrown.
  251. Disconnect();
  252. return;
  253. }
  254. if (bytesRead <= 0)
  255. {
  256. Disconnect();
  257. return;
  258. }
  259. OnReportReceived(buffer);
  260. if (IsConnected)
  261. BeginReadReport();
  262. }
  263. protected abstract bool ParseReport(byte[] report);
  264. protected void OnReportReceived(byte[] report)
  265. {
  266. #if DEBUG_REPORTS
  267. Console.Write("< ");
  268. Console.WriteLine(ToHexadecimal(report));
  269. #endif
  270. ParseReport(report);
  271. // Enter lock for reports.
  272. Monitor.Enter(reportLocker);
  273. if (reportConsumers > 0)
  274. {
  275. returningReports.Push(report);
  276. Monitor.Enter(readReportLocker);
  277. // Signal the consumer to stop waiting.
  278. Monitor.Pulse(reportLocker);
  279. // Consumer will stop waiting after:
  280. Monitor.Exit(reportLocker);
  281. // Wait for consumer to handle the report.
  282. Monitor.Wait(readReportLocker);
  283. // Remove the report.
  284. if (!(returningReports.Peek() == report))
  285. throw new InvalidProgramException();
  286. returningReports.Pop();
  287. Monitor.Exit(readReportLocker);
  288. }
  289. else
  290. Monitor.Exit(reportLocker);
  291. }
  292. public void Disconnect()
  293. {
  294. if (isConnected)
  295. {
  296. isConnected = false;
  297. communicationStream.Close();
  298. OnDisconnected(EventArgs.Empty);
  299. }
  300. }
  301. #endregion
  302. #region Report Helper Methods
  303. public static string ToHexadecimal(byte[] value)
  304. {
  305. System.Text.StringBuilder builder = new System.Text.StringBuilder();
  306. for (int i = 0; i < value.Length; i++)
  307. {
  308. builder.Append(value[i].ToString("x2"));
  309. if (i < value.Length - 1)
  310. builder.Append(" ");
  311. }
  312. return builder.ToString();
  313. }
  314. protected void CreateReport(OutputReport reportType)
  315. {
  316. Array.Clear(OutputBuffer, 0, maximalReportLength);
  317. OutputBuffer[0] = (byte)reportType;
  318. }
  319. protected virtual void SendReport()
  320. {
  321. #if DEBUG_REPORTS
  322. Console.Write("> ");
  323. Console.WriteLine(ToHexadecimal(OutputBuffer));
  324. #endif
  325. SendReport(this.OutputBuffer);
  326. }
  327. public void SendReport(byte[] report)
  328. {
  329. int reportLength = reportLengths[report[0]];
  330. WriteReport(report, 0, reportLength);
  331. }
  332. private void WriteReport(byte[] buffer, int offset, int count)
  333. {
  334. communicationStream.Write(buffer, offset, count);
  335. }
  336. private byte[] ReadReport()
  337. {
  338. byte[] report = new byte[maximalReportLength];
  339. int result = communicationStream.Read(report, 0, report.Length);
  340. if (result > 0)
  341. {
  342. OnReportReceived(report);
  343. return report;
  344. }
  345. return null;
  346. }
  347. protected byte[] SendAndReturnReport(InputReport returnReportType)
  348. {
  349. return SendAndReturnReport(returnReportType, ReportTimeout);
  350. }
  351. protected byte[] SendAndReturnReport(InputReport returnReportType, TimeSpan timeout)
  352. {
  353. byte[] report = null;
  354. using (IReportInterceptor reportInterceptor = CreateReportInterceptor())
  355. {
  356. SendReport();
  357. DateTime start = DateTime.Now;
  358. while ((report = reportInterceptor.Intercept()) != null)
  359. {
  360. if (report[0] == (byte)returnReportType)
  361. break;
  362. if (DateTime.Now - start > timeout)
  363. {
  364. report = null;
  365. break;
  366. }
  367. }
  368. }
  369. if (report == null)
  370. throw new TimeoutException("Could not retrieve result-report.");
  371. return report;
  372. }
  373. #region Report Intercepting
  374. protected IReportInterceptor CreateReportInterceptor()
  375. {
  376. if (readingThread == Thread.CurrentThread)
  377. return new SyncReportInterceptor(this);
  378. else
  379. return new AsyncReportInterceptor(this);
  380. }
  381. protected interface IReportInterceptor : IDisposable
  382. {
  383. byte[] Intercept();
  384. }
  385. protected class SyncReportInterceptor : IReportInterceptor
  386. {
  387. ReportDevice device;
  388. public SyncReportInterceptor(ReportDevice device)
  389. {
  390. this.device = device;
  391. }
  392. public byte[] Intercept()
  393. {
  394. return device.ReadReport();
  395. }
  396. public void Dispose()
  397. {
  398. }
  399. }
  400. protected class AsyncReportInterceptor : IReportInterceptor
  401. {
  402. ReportDevice device;
  403. TimeSpan timeout;
  404. public AsyncReportInterceptor(ReportDevice device)
  405. : this(device, ReportTimeout)
  406. {
  407. }
  408. public AsyncReportInterceptor(ReportDevice device, TimeSpan timeout)
  409. {
  410. this.device = device;
  411. this.timeout = timeout;
  412. if (device.readingThread != Thread.CurrentThread)
  413. {
  414. Monitor.Enter(device.reportLocker);
  415. device.reportConsumers++;
  416. }
  417. }
  418. public byte[] Intercept()
  419. {
  420. byte[] report = null;
  421. if (Monitor.Wait(device.reportLocker, timeout))
  422. {
  423. Monitor.Enter(device.readReportLocker);
  424. report = device.returningReports.Peek();
  425. Monitor.Pulse(device.readReportLocker);
  426. Monitor.Exit(device.readReportLocker);
  427. }
  428. return report;
  429. }
  430. public void Dispose()
  431. {
  432. device.reportConsumers--;
  433. Monitor.Exit(device.reportLocker);
  434. }
  435. }
  436. #endregion
  437. #endregion
  438. #region Events
  439. #region Disconnected Event
  440. protected virtual void OnDisconnected(EventArgs e)
  441. {
  442. if (Disconnected == null)
  443. return;
  444. Disconnected(this, e);
  445. }
  446. public event EventHandler Disconnected;
  447. #endregion
  448. #endregion
  449. }
  450. }