HRMSensor.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. /*
  2. This software is subject to the license described in the License.txt file
  3. included with this software distribution. You may not use this file except in compliance
  4. with this license.
  5. Copyright (c) Dynastream Innovations Inc. 2012
  6. All rights reserved.
  7. */
  8. #include "StdAfx.h"
  9. #include "HRMSensor.h"
  10. /**************************************************************************
  11. * HRMSensor::ANT_eventNotification
  12. *
  13. * Process ANT channel event
  14. *
  15. * ucEventCode_: code of ANT channel event
  16. * pucEventBuffer_: pointer to buffer containing data received from ANT,
  17. * or a pointer to the transmit buffer in the case of an EVENT_TX
  18. *
  19. * returns: N/A
  20. *
  21. **************************************************************************/
  22. void HRMSensor::ANT_eventNotification(UCHAR ucEventCode_, UCHAR* pucEventBuffer_)
  23. {
  24. switch(ucEventCode_)
  25. {
  26. case EVENT_TX:
  27. HandleTransmit((UCHAR*) pucEventBuffer_);
  28. break;
  29. default:
  30. break;
  31. }
  32. }
  33. /**************************************************************************
  34. * HRMSensor::InitializeSim
  35. *
  36. * Initializes simulator variables
  37. *
  38. * returns: N/A
  39. *
  40. **************************************************************************/
  41. void HRMSensor::InitializeSim()
  42. {
  43. ulTimerInterval = 833; // 72 bpm
  44. ucReserved = HRM_RESERVED;
  45. ulRunTime = 0;
  46. ulRunTime16000 = 0;
  47. ucBackgroundCount = 0;
  48. ucNextBackgroundPage = 1; // By default, support all defined pages
  49. ucEventCount = 0;
  50. ucMinPulse = System::Convert::ToByte(this->numericUpDown_Prm_MinPulse->Text); // Default min HR value is set on UI
  51. ucCurPulse = System::Convert::ToByte(this->numericUpDown_Prm_CurPulse->Text); // Default current HR value is set on UI
  52. ucMaxPulse = System::Convert::ToByte(this->numericUpDown_Prm_MaxPulse->Text); // Default max HR value is set on UI
  53. ucBPM = ucCurPulse; // Initial heart rate value (fixed simulation type)
  54. ulElapsedTime2 = 0;
  55. usTime1024 = 0;
  56. usPreviousTime1024 = 0;
  57. ucMfgID = System::Convert::ToByte(this->textBox_ManfIDChange->Text); // Default values set on UI
  58. ucHwVersion = System::Convert::ToByte(this->textBox_HardwareVerChange->Text);
  59. ucSwVersion = System::Convert::ToByte(this->textBox_SoftwareVerChange->Text);
  60. ucModelNum = System::Convert::ToByte(this->textBox_ModelNumChange->Text);
  61. usSerialNum = System::Convert::ToUInt16(this->textBox_SerialNumChange->Text);
  62. ucSimDataType = SIM_FIXED; // Generate single fixed heart rate value by default
  63. bLegacy = FALSE; // Disable legacy mode by default
  64. bTxMinimum = FALSE; // Disable minimum data set by default
  65. bSweepAscending = TRUE; // If sweeping, start with ascending values by default
  66. }
  67. /**************************************************************************
  68. * HRMSensor::HandleTransmit
  69. *
  70. * Encode data generated by simulator for transmission
  71. *
  72. * pucTxBuffer_: pointer to the transmit buffer
  73. *
  74. * returns: N/A
  75. *
  76. **************************************************************************/
  77. void HRMSensor::HandleTransmit(UCHAR* pucTxBuffer_)
  78. {
  79. static UCHAR ucMessageNum = 0; // Message count
  80. static UCHAR ucPageToggle = 0; // Page toggle tracker
  81. UCHAR ucPageNum; // Page number
  82. if(bLegacy || bTxMinimum)
  83. ucPageNum = HRM_PAGE0; // Send page 0 if using the minimum data set or legacy sensors
  84. else
  85. ucPageNum = HRM_PAGE4; // Send page 4 otherwise
  86. // Send background pages at defined interval (not available in legacy sensors)
  87. if(!bLegacy)
  88. {
  89. if(ucMessageNum++ == HRM_BACKGROUND_INTERVAL - 1)
  90. {
  91. ucMessageNum = 0;
  92. switch(ucNextBackgroundPage)
  93. {
  94. case 1:
  95. // Page 1 is not sent with minimum data set
  96. ucPageNum = HRM_PAGE1;
  97. ucNextBackgroundPage++;
  98. break;
  99. case 2:
  100. ucPageNum = HRM_PAGE2;
  101. if(bTxMinimum)
  102. {
  103. // If using minimum data set, background pages are sent three times to ensure they are received by displays with lower message rate
  104. if(++ucBackgroundCount > 2)
  105. {
  106. ucBackgroundCount = 0;
  107. ucNextBackgroundPage ++;
  108. }
  109. }
  110. else
  111. {
  112. // Otherwise, send only once
  113. ucNextBackgroundPage++;
  114. }
  115. break;
  116. case 3:
  117. ucPageNum = HRM_PAGE3;
  118. if(bTxMinimum)
  119. {
  120. // If using minimum data set, background pages are sent three times to ensure they are received by displays with lower message rate
  121. if(++ucBackgroundCount > 2)
  122. {
  123. ucBackgroundCount = 0;
  124. ucNextBackgroundPage = HRM_PAGE2;
  125. }
  126. }
  127. else
  128. {
  129. // Otherwise, send only once
  130. ucNextBackgroundPage = HRM_PAGE1;
  131. }
  132. break;
  133. default:
  134. break;
  135. }
  136. }
  137. }
  138. // Fill in common info
  139. pucTxBuffer_[4] = (UCHAR) (usTime1024 & 0xFF); // Low byte of time of last valid heart beat event (1/1024s)
  140. pucTxBuffer_[5] = (UCHAR) (usTime1024 >> 8) & 0xFF; // High byte of time of last valid heart beat event (1/1024s)
  141. pucTxBuffer_[6] = ucEventCount; // Heart beat count (counts)
  142. pucTxBuffer_[7] = ucBPM; // Instantaneous heart rate (bpm)
  143. // Format the required page
  144. switch(ucPageNum)
  145. {
  146. case HRM_PAGE0:
  147. // Minimum data set: Page 0
  148. if(bLegacy)
  149. pucTxBuffer_[0] = ucReserved; // Legacy sensors only, do not interpret at receiver, do not use as reference for new sensor designs
  150. else
  151. pucTxBuffer_[0] = HRM_PAGE0;
  152. pucTxBuffer_[1] = ucReserved; // Reserved, do not interpret at the receiver
  153. pucTxBuffer_[2] = ucReserved; // Reserved, do not interpret at the receiver
  154. pucTxBuffer_[3] = ucReserved; // Reserved, do not interpret at the receiver
  155. break;
  156. case HRM_PAGE1:
  157. pucTxBuffer_[0] = HRM_PAGE1;
  158. pucTxBuffer_[1] = (ulElapsedTime2 & 0xFF); // Cumulative operating time, bits 0-7 (intervals of 2s)
  159. pucTxBuffer_[2] = (ulElapsedTime2 >> 8) & 0xFF; // Cumulative operating time, bits 8-15 (intervals of 2s)
  160. pucTxBuffer_[3] = (ulElapsedTime2 >> 16) & 0xFF; // Cumulative operating time, bits 16-23 (intervals of 2s)
  161. break;
  162. case HRM_PAGE2:
  163. pucTxBuffer_[0] = HRM_PAGE2;
  164. pucTxBuffer_[1] = ucMfgID; // Manufacturing ID
  165. pucTxBuffer_[2] = usSerialNum & 0x00FF; // Low byte of serial number
  166. pucTxBuffer_[3] = (usSerialNum & 0xFF00) >>8; // High byte of serial number
  167. break;
  168. case HRM_PAGE3:
  169. pucTxBuffer_[0] = HRM_PAGE3;
  170. pucTxBuffer_[1] = ucHwVersion; // Hardware version
  171. pucTxBuffer_[2] = ucSwVersion; // Software version
  172. pucTxBuffer_[3] = ucModelNum; // Model number
  173. break;
  174. case HRM_PAGE4:
  175. pucTxBuffer_[0] = HRM_PAGE4;
  176. pucTxBuffer_[1] = ucReserved; // Reserved, do not interpret at receiver
  177. pucTxBuffer_[2] = usPreviousTime1024 & 0xFF; // Low byte of previous heart beat event (1/1024s)
  178. pucTxBuffer_[3] = (usPreviousTime1024 >> 8) & 0xFF; // High byte of previous heart beat event (1/1024s)
  179. break;
  180. default:
  181. break;
  182. }
  183. // Handle page toggle bit: toggle every four messages
  184. if(!bLegacy)
  185. {
  186. ucPageToggle += 0x20;
  187. pucTxBuffer_[0] += (ucPageToggle & HRM_TOGGLE_MASK);
  188. }
  189. }
  190. /**************************************************************************
  191. * HRMSensor::onTimerTock
  192. *
  193. * Simulates a device event, updating simulator data based on this event
  194. * Modifications to the timer interval are applied immediately after this
  195. * at ANTChannel
  196. *
  197. * usEventTime_: current time (ms)
  198. *
  199. * returns: N/A
  200. *
  201. **************************************************************************/
  202. void HRMSensor::onTimerTock(USHORT usEventTime_)
  203. {
  204. UCHAR tempOffset = 0; // Temporary variable to calculate sweeping intervals
  205. // Update event count
  206. ++ucEventCount;
  207. // Save previous event time
  208. usPreviousTime1024 = usTime1024;
  209. // Update event time
  210. ulRunTime16000 += (ulTimerInterval << 4); // Multiply by 16 to convert from ms to 1/16000s
  211. usTime1024 = (USHORT) ((ulRunTime16000 << 3) / 125); // Convert to 1/1024s - multiply by 1024/16000
  212. ulRunTime += ulTimerInterval;
  213. while(ulRunTime/2000) // 2000 ms
  214. {
  215. ++ulElapsedTime2; // elapsed time is updated every 2 seconds
  216. ulRunTime -=2000;
  217. }
  218. // Update heart rate value
  219. switch(ucSimDataType)
  220. {
  221. case SIM_FIXED:
  222. // Transmits the current pulse value
  223. ucBPM = ucCurPulse;
  224. break;
  225. case SIM_STEP:
  226. // Step: Jumps between min and max every 16 beats
  227. if(ucEventCount & 0x10)
  228. ucBPM = ucMinPulse;
  229. else
  230. ucBPM = ucMaxPulse;
  231. break;
  232. case SIM_SWEEP:
  233. // Heart rate sweeps between min and max
  234. // The jump offset is calculated versus position against the max so it won't get stuck on low values for a long time and won't speed through high values too fast
  235. tempOffset = ucMaxPulse - ucCurPulse;
  236. tempOffset = ((tempOffset & 0xC0) >> 6) + ((tempOffset & 0x20) >>5) + ((tempOffset & 0x10) >>4)+1;
  237. if(bSweepAscending)
  238. ucCurPulse += tempOffset;
  239. else
  240. ucCurPulse -= tempOffset;
  241. // Ensure value is not less than min or more than max
  242. if(ucCurPulse >= ucMaxPulse)
  243. {
  244. ucCurPulse = ucMaxPulse;
  245. bSweepAscending = FALSE;
  246. }
  247. if(ucCurPulse <= ucMinPulse)
  248. {
  249. ucCurPulse = ucMinPulse;
  250. bSweepAscending = TRUE;
  251. }
  252. ucBPM = ucCurPulse;
  253. break;
  254. default:
  255. break;
  256. }
  257. // Update timer interval (in ms)
  258. if(ucBPM)
  259. ulTimerInterval = (ULONG) 60000/ucBPM; // 60 seconds/beats per minute
  260. // Update display
  261. UpdateDisplay();
  262. }
  263. /**************************************************************************
  264. * HRMSensor::UpdateDisplay
  265. *
  266. * Updates displayed simulator data on GUI
  267. *
  268. * returns: N/A
  269. *
  270. **************************************************************************/
  271. void HRMSensor::UpdateDisplay()
  272. {
  273. label_EventCount->Text = ucEventCount.ToString(); // Event count
  274. this->label_ElapsedSecsDisplay->Text = ((ulElapsedTime2 & 0x00FFFFFF) << 1).ToString(); // Operating time (s)
  275. this->numericUpDown_Prm_CurPulse->Value = ucCurPulse; // Current BPM generated by simulator
  276. label_PulseTxd->Text = ucBPM.ToString(); // Current BPM transmitting
  277. this->label_CurrentTimeDisplay->Text = usTime1024.ToString(); // Time of last event (1/1024s)
  278. if(bTxMinimum || bLegacy) // Time of previous event (1/1024s), only when using advanced data pages
  279. this->label_LastTimeDisplay->Text = "---";
  280. else
  281. this->label_LastTimeDisplay->Text = usPreviousTime1024.ToString();
  282. }
  283. /**************************************************************************
  284. * HRMSensor::radioButton_SimTypeChanged
  285. *
  286. * Select method to generate simulator data, from user input (GUI)
  287. *
  288. * returns: N/A
  289. *
  290. **************************************************************************/
  291. void HRMSensor::radioButton_SimTypeChanged (System::Object^ sender, System::EventArgs^ e)
  292. {
  293. if(radioButton_HRFixed->Checked)
  294. {
  295. this->numericUpDown_Prm_CurPulse->Enabled = true;
  296. this->numericUpDown_Prm_MinPulse->Enabled = false;
  297. this->numericUpDown_Prm_MaxPulse->Enabled = false;
  298. ucSimDataType = SIM_FIXED;
  299. }
  300. else if(this->radioButton_HRSweep->Checked)
  301. {
  302. this->numericUpDown_Prm_CurPulse->Enabled = false;
  303. this->numericUpDown_Prm_MinPulse->Enabled = true;
  304. this->numericUpDown_Prm_MaxPulse->Enabled = true;
  305. ucSimDataType = SIM_SWEEP;
  306. bSweepAscending = TRUE;
  307. }
  308. else if(this->radioButton_HRStep->Checked)
  309. {
  310. this->numericUpDown_Prm_CurPulse->Enabled = false;
  311. this->numericUpDown_Prm_MinPulse->Enabled = true;
  312. this->numericUpDown_Prm_MaxPulse->Enabled = true;
  313. ucSimDataType = SIM_STEP;
  314. }
  315. }
  316. /**************************************************************************
  317. * HRMSensor::button_UpdateTime_Click
  318. *
  319. * Validates and updates cumulative operating time, from user input (GUI)
  320. *
  321. * returns: N/A
  322. *
  323. **************************************************************************/
  324. System::Void HRMSensor::button_UpdateTime_Click(System::Object^ sender, System::EventArgs^ e)
  325. {
  326. ULONG ulCumulativeTime = 0;
  327. label_AdvancedError->Visible = false;
  328. try{
  329. ulCumulativeTime = System::Convert::ToUInt32(this->textBox_ElpTimeChange->Text);
  330. if(ulCumulativeTime > 33554430) // Cumulative Operating Time rollover: 33554430 seconds
  331. throw "Cumulative operating time exceeds rollover value";
  332. ulElapsedTime2 = ulCumulativeTime >> 1; // Cumulative time is stored in intervals of 2 seconds
  333. // Update display
  334. label_ElapsedSecsDisplay->Text = ((ulElapsedTime2 & 0x00FFFFFF) << 1).ToString();
  335. }
  336. catch(...){
  337. label_AdvancedError->Text = "Error: Time";
  338. label_AdvancedError->Visible = true;
  339. }
  340. }
  341. /**************************************************************************
  342. * HRMSensor::button_AdvancedUpdate_Click
  343. *
  344. * Validates and updates product information, from user input (GUI)
  345. *
  346. * returns: N/A
  347. *
  348. **************************************************************************/
  349. System::Void HRMSensor::button_AdvancedUpdate_Click(System::Object^ sender, System::EventArgs^ e)
  350. {
  351. this->label_AdvancedError->Visible = false;
  352. this->label_AdvancedError->Text = "Error: ";
  353. //convert and catch failed conversions
  354. try{
  355. ucMfgID = System::Convert::ToByte(this->textBox_ManfIDChange->Text);
  356. }
  357. catch(...){
  358. this->label_AdvancedError->Text = System::String::Concat(label_AdvancedError->Text, " MFID");
  359. this->label_AdvancedError->Visible = true;
  360. }
  361. try{
  362. usSerialNum = System::Convert::ToUInt16(this->textBox_SerialNumChange->Text);
  363. }
  364. catch(...){
  365. label_AdvancedError->Text = System::String::Concat(label_AdvancedError->Text, " Ser#");
  366. label_AdvancedError->Visible = true;
  367. }
  368. try{
  369. ucHwVersion = System::Convert::ToByte(this->textBox_HardwareVerChange->Text);
  370. }
  371. catch(...){
  372. label_AdvancedError->Text = System::String::Concat(label_AdvancedError->Text, " HWVr");
  373. label_AdvancedError->Visible = true;
  374. }
  375. try{
  376. ucSwVersion = System::Convert::ToByte(this->textBox_SoftwareVerChange->Text);
  377. }
  378. catch(...){
  379. label_AdvancedError->Text = System::String::Concat(label_AdvancedError->Text, " SWVr");
  380. label_AdvancedError->Visible = true;
  381. }
  382. try{
  383. ucModelNum = System::Convert::ToByte(this->textBox_ModelNumChange->Text);
  384. }
  385. catch(...){
  386. label_AdvancedError->Text = System::String::Concat(label_AdvancedError->Text, " Mdl#");
  387. label_AdvancedError->Visible = true;
  388. }
  389. }
  390. /**************************************************************************
  391. * HRMSensor::numericUpDown_Prm_CurPulse_ValueChanged
  392. *
  393. * Validates and updates the current heart rate value, from user input (GUI)
  394. *
  395. * returns: N/A
  396. *
  397. **************************************************************************/
  398. System::Void HRMSensor::numericUpDown_Prm_CurPulse_ValueChanged(System::Object^ sender, System::EventArgs^ e)
  399. {
  400. // This value is raised whenever the value changes, even if internally
  401. // Only update the current pulse if set by the user (not sweeping)
  402. if(this->numericUpDown_Prm_CurPulse->Enabled)
  403. {
  404. ucCurPulse = System::Convert::ToByte(this->numericUpDown_Prm_CurPulse->Value);
  405. ForceUpdate();
  406. }
  407. }
  408. /**************************************************************************
  409. * HRMSensor::numericUpDown_Prm_MinMaxPulse_ValueChanged
  410. *
  411. * If the user has changed the min or max heart rate, validate that
  412. * minimum < current < maximum
  413. *
  414. * returns: N/A
  415. *
  416. **************************************************************************/
  417. System::Void HRMSensor::numericUpDown_Prm_MinMaxPulse_ValueChanged(System::Object^ sender, System::EventArgs^ e)
  418. {
  419. // This event is raised whenever the min and max value change, even if internally
  420. // Check min<max if in min/max mode, and force min<cur<max
  421. if(this->numericUpDown_Prm_MinPulse->Value < this->numericUpDown_Prm_MaxPulse->Value)
  422. {
  423. ucMinPulse = System::Convert::ToByte(this->numericUpDown_Prm_MinPulse->Value);
  424. ucMaxPulse = System::Convert::ToByte(this->numericUpDown_Prm_MaxPulse->Value);
  425. if(ucCurPulse > ucMaxPulse)
  426. {
  427. ucCurPulse = ucMaxPulse;
  428. this->numericUpDown_Prm_CurPulse->Value = ucCurPulse;
  429. ForceUpdate();
  430. }
  431. else if(ucCurPulse < ucMinPulse)
  432. {
  433. ucCurPulse = ucMinPulse;
  434. this->numericUpDown_Prm_CurPulse->Value = ucCurPulse;
  435. ForceUpdate();
  436. }
  437. }
  438. else
  439. {
  440. // If the values were invalid, set numeric values to last valid values
  441. this->numericUpDown_Prm_MinPulse->Value = ucMinPulse;
  442. this->numericUpDown_Prm_MaxPulse->Value = ucMaxPulse;
  443. }
  444. }
  445. /**************************************************************************
  446. * HRMSensor::checkBox_Legacy_CheckedChanged
  447. *
  448. * Enable simulation of legacy HRM for testing backward compatibility of receivers
  449. *
  450. * returns: N/A
  451. *
  452. **************************************************************************/
  453. System::Void HRMSensor::checkBox_Legacy_CheckedChanged(System::Object^ sender, System::EventArgs^ e)
  454. {
  455. if(checkBox_Legacy->Checked)
  456. {
  457. System::Windows::Forms::DialogResult result = MessageBox::Show(L"This option is available for backward compatibility testing.\nIt should not be used as a reference in the development of new sensors.", L"Warning", MessageBoxButtons::OKCancel);
  458. if( result == ::DialogResult::OK )
  459. {
  460. bLegacy = TRUE;
  461. // Generate random data for reserved field to simulate legacy receivers
  462. // This should not be interpreted by the receiver, and by no means should be used as a reference for new designs
  463. // This feature is avaialable only for testing backwards compatibility
  464. srand(ulElapsedTime2);
  465. ucReserved = (UCHAR) ((rand() % 240) + 16); // Generate a random number between 16 and 255
  466. checkBox_SendBasicPage->Checked = FALSE;
  467. checkBox_SendBasicPage->Enabled = FALSE;
  468. }
  469. else
  470. {
  471. bLegacy = FALSE;
  472. ucReserved = HRM_RESERVED;
  473. checkBox_Legacy->Checked = FALSE;
  474. checkBox_SendBasicPage->Enabled = TRUE;
  475. }
  476. }
  477. else
  478. {
  479. bLegacy = FALSE;
  480. ucReserved = HRM_RESERVED;
  481. checkBox_SendBasicPage->Enabled = TRUE;
  482. }
  483. }
  484. /**************************************************************************
  485. * HRMSensor::checkBox_SendBasicPage_CheckedChanged
  486. *
  487. * Enable sending basic data set: Page 0 and Background pages 2 and 3
  488. *
  489. * returns: N/A
  490. *
  491. **************************************************************************/
  492. System::Void HRMSensor::checkBox_SendBasicPage_CheckedChanged(System::Object^ sender, System::EventArgs^ e)
  493. {
  494. if(checkBox_SendBasicPage->Checked)
  495. {
  496. bTxMinimum = TRUE;
  497. ucNextBackgroundPage = 2; // Page 1 is disabled
  498. ucBackgroundCount = 0; // Reset count so that pages are sent twice from the beginning
  499. }
  500. else
  501. {
  502. bTxMinimum = FALSE;
  503. ucNextBackgroundPage = 1; // Enable Page 1
  504. ucBackgroundCount = 0; // Reset count so that pages are sent twice from the beginning
  505. }
  506. }
  507. /**************************************************************************
  508. * HRMSensor::ForceUpdate
  509. *
  510. * Causes a timer event, to force the simulator to update all calculations
  511. *
  512. * returns: N/A
  513. *
  514. **************************************************************************/
  515. void HRMSensor::ForceUpdate()
  516. {
  517. timerHandle->Interval = 250;
  518. }