/* This software is subject to the license described in the License.txt file included with this software distribution. You may not use this file except in compliance with this license. Copyright (c) Dynastream Innovations Inc. 2012 All rights reserved. */ #include "StdAfx.h" #include "BikePowerDisplay.h" /************************************************************************** * BikePowerDisplay::ANT_eventNotification * * Process ANT channel event * * ucEventCode_: code of ANT channel event * pucEventBuffer_: pointer to buffer containing data received from ANT, * or a pointer to the transmit buffer in the case of an EVENT_TX * * returns: N/A * **************************************************************************/ void BikePowerDisplay::ANT_eventNotification(UCHAR ucEventCode_, UCHAR* pucEventBuffer_) { switch(ucEventCode_) { case EVENT_RX_BURST_PACKET: case EVENT_RX_BROADCAST: case EVENT_RX_ACKNOWLEDGED: // Intentional fall thru HandleReceive((UCHAR*) pucEventBuffer_); // Decode current data break; case EVENT_TRANSFER_TX_COMPLETED: ucAckRetryCount = 0; // Reset retransmission counter ucMsgExpectingAck = 0; // Clear pending msg code UpdateDisplayAckStatus(ACK_SUCCESS); // Tx successful break; case EVENT_TRANSFER_TX_FAILED: case EVENT_ACK_TIMEOUT: // Intentional fall thru if(ucMsgExpectingAck) { if(HandleRetransmit()) UpdateDisplayAckStatus(ACK_RETRY); // Data was retransmitted else UpdateDisplayAckStatus(ACK_FAIL); // Maximum number of retries exceeded, Tx failed } break; default: break; } } /************************************************************************** * BikePowerDisplay::InitializeSim * * Initializes simulator variables * * returns: N/A * **************************************************************************/ void BikePowerDisplay::InitializeSim() { bSeenPower = FALSE; bSeenWheelTorque = FALSE; bSeenCrankTorque = FALSE; bSeenCTF = FALSE; ePrevType = BikePower::SensorType::UNKNOWN; ulAcumPowerEventCount = 0; ulAcumTorqueEventCount = 0; ulAcumTicks = 0; ulDistance100 = 0; ucWheelCircumference100 = System::Convert::ToByte(this->numericUpDown_Cal_WheelCircum->Value); // Initial value set on UI (cm) usCTFOffset = 0; // Initial uncalibrated offset bAutoZeroInvalid = FALSE; bAZOn = (BOOL) this->radioButton_Cal_AZon->Checked; // Initial value set on UI usCalSerialToSend = System::Convert::ToUInt16(this->numericUpDown_Cal_CTFSerial->Value); usCalSlopeToSend = System::Convert::ToUInt16(10 * this->numericUpDown_Cal_CTFSlope->Value); ucMsgExpectingAck = 0; bCoasting = FALSE; bStopped = FALSE; } /************************************************************************** * BikePowerDisplay::HandleReceive * * Decodes received data * * pucRxBuffer_: pointer to the buffer containing the received data * * returns: N/A * **************************************************************************/ void BikePowerDisplay::HandleReceive(UCHAR* pucRxBuffer_) { static USHORT usNoEventCount = 0; // Counter for successive transmissions with no new events // Power sensors static UCHAR ucPrevPowerEventCount = 0; // Previous value for the power event count static USHORT usPrevAcumPower = 0; // Previous value for the cumulative power (W) // Crank/wheel torque sensors static UCHAR ucPrevTorqueEventCount = 0; // Previous value for the torque event count static UCHAR ucPrevTicks = 0; // Previous number of revolutions (wheel or torque) static USHORT usPrevPeriod2048 = 0; // Previous period (wheel or torque), in 1/2048s static USHORT usPrevTorque32 = 0; // Previous torque (1/32 Nm) static UCHAR ucCurTorqueEventCount = 0; // Current torque event count static UCHAR ucCurTicks = 0; // Current number of ticks static USHORT usCurPeriod2048 = 0; // Current period static USHORT usCurTorque32 = 0; // Current torque // Crank torque frequency static UCHAR ucPrevCTFEventCount = 0; // Previous value for the crank torque frequency event count static USHORT usPrevTime2000 = 0; // Previous CTF time stamp (1/2000 s) static USHORT usPrevCTFTicks = 0; // Previous torque tick count UCHAR ucPageNum = pucRxBuffer_[0]; // Get page number // Decode common pages if available if(ucPageNum >= CommonData::PAGE80 && ucPageNum <= CommonData::PAGE82) { try { commonPages->Decode(pucRxBuffer_); } catch(CommonData::Error^ errorCommon) { } } // Decode bike power pages if available if(ucPageNum <= BikePower::PAGE_CTF) { try { bpPages->Decode(pucRxBuffer_); bAutoZeroInvalid = FALSE; bBadCalPage = FALSE; } catch(BikePower::Error^ errorBikePower) { if(errorBikePower->bUndefAutoZero) bAutoZeroInvalid = TRUE; if(ucPageNum == BikePower::PAGE_CALIBRATION) bBadCalPage = TRUE; } } // If sensor type changed, reset calculated values if(ePrevType != bpPages->eType) { ChangeSensorType(); // Update sensor type on interface bCoasting = FALSE; // Reset state variables bStopped = FALSE; bSeenCTF = FALSE; bSeenPower = FALSE; bSeenWheelTorque = FALSE; bSeenCrankTorque = FALSE; bSeenCTF = FALSE; if(bpPages->eType == BikePower::SensorType::CRANK_TORQUE_FREQ) // For CTF sensor SendMessage(BikePower::CAL_REQUEST); // Request offset to be able to do calculations } // Process bike power pages switch(ucPageNum) { case BikePower::PAGE_GET_SET_PARAMETERS: if(bpPages->ucCrankLength == 0xFF) { label_CrankLength->Text = "Invalid"; checkBox_InvalidCrankLength->Checked = true; checkBox_AutoCrank->Checked = false; } else if(bpPages->ucCrankLength == 0xFE) { label_CrankLength->Text = "Auto Crank"; checkBox_InvalidCrankLength->Checked = false; checkBox_AutoCrank->Checked = true; } else { label_CrankLength->Text = (((float)bpPages->ucCrankLength * (float)0.5) + (110.0)).ToString("F1"); checkBox_InvalidCrankLength->Checked = false; checkBox_AutoCrank->Checked = false; numericUpDown_CrankLength->Value = (Decimal)(((float)bpPages->ucCrankLength * (float)0.5) + (110.0)); } label_AutoCrankLength->Text = ((bpPages->ucSensorCapabilities & 0x01) == 0) ? "Manually Set Crank Length" : "Automatically Set Crank Length"; switch((bpPages->ucSensorStatus & 0x30) >> 4) { case BikePower::SensorStatus::UNDEFINED: label_SensorStatus->Text = "Undefined"; break; case BikePower::SensorStatus::LEFT_PRESENT: label_SensorStatus->Text = "Left Present"; break; case BikePower::SensorStatus::RIGHT_PRESENT: label_SensorStatus->Text = "Right Present"; break; case BikePower::SensorStatus::LEFT_RIGHT_PRESENT: label_SensorStatus->Text = "Left + Right Present"; break; default: label_SensorStatus->Text = "----"; break; } switch((bpPages->ucSensorStatus & 0x0C) >> 2) { case BikePower::SWMistmatchStatus::UNDEFINED: label_SWMistmatchStatus->Text = "Undefined"; break; case BikePower::SWMistmatchStatus::RIGHT_SENSOR_OLDER: label_SWMistmatchStatus->Text = "Right Sensor Older"; break; case BikePower::SWMistmatchStatus::LEFT_SENSOR_OLDER: label_SWMistmatchStatus->Text = "Left Sensor Older"; break; case BikePower::SWMistmatchStatus::SW_SAME: label_SWMistmatchStatus->Text = "No mismatch"; break; default: label_SWMistmatchStatus->Text = "----"; break; } switch(bpPages->ucSensorStatus & 0x03) { case BikePower::CrankLengthStatus::LENGTH_INVALID: label_CrankLengthStatus->Text = "Invalid"; break; case BikePower::CrankLengthStatus::DEFAULT_LENGTH_USED: label_CrankLengthStatus->Text = "Default Length Used"; break; case BikePower::CrankLengthStatus::LENGTH_MANUALLY_SET: label_CrankLengthStatus->Text = "Manually Set"; break; case BikePower::CrankLengthStatus::LENGTH_AUTOMATICALLY_SET: label_CrankLengthStatus->Text = "Automatically Set"; break; default: label_CrankLengthStatus->Text = "----"; break; } break; case BikePower::PAGE_POWER: { if(!bSeenPower) // Initialize previous values if first time the page is seen { ucPrevPowerEventCount = bpPages->ucPowEventCount; usPrevAcumPower = bpPages->usAcumPower; ulAcumPowerEventCount = 0; bSeenPower = TRUE; } // Difference between current and previous event UCHAR ucEventDiff = GetDifference(bpPages->ucPowEventCount, ucPrevPowerEventCount); USHORT usPowerDiff = GetDifference(bpPages->usAcumPower, usPrevAcumPower); // Update last event ucPrevPowerEventCount = bpPages->ucPowEventCount; usPrevAcumPower = bpPages->usAcumPower; // Cumulative values ulAcumPowerEventCount += ucEventDiff; // Cumulative power event count // Update calculation if dealing with a new event if(ucEventDiff && bpPages->eType == BikePower::SensorType::POWER_ONLY) ulAveragePower256 = (((ULONG) usPowerDiff) << 8)/ucEventDiff; // Average power (1/256W) break; } case BikePower::PAGE_WHEEL_TORQUE: ucCurTorqueEventCount = bpPages->ucWTEventCount; ucCurTicks = bpPages->ucWheelTicks; usCurPeriod2048 = bpPages->usAcumWheelPeriod2048; usCurTorque32 = bpPages->usAcumTorque32; case BikePower::PAGE_CRANK_TORQUE: // intentional fall-thru (most of the calculations for these two sensors are the same) { if(bpPages->eType == BikePower::SensorType::TORQUE_CRANK) { ucCurTorqueEventCount = bpPages->ucCTEventCount; ucCurTicks = bpPages->ucCrankTicks; usCurPeriod2048 = bpPages->usAcumCrankPeriod2048; usCurTorque32 = bpPages->usAcumTorque32; } // Initialize previous values if first time the wheel or crank torque page is seen if((!bSeenCrankTorque && bpPages->eType == BikePower::SensorType::TORQUE_CRANK) || (!bSeenWheelTorque && bpPages->eType == BikePower::SensorType::TORQUE_WHEEL)) { if(!bSeenCrankTorque && bpPages->eType == BikePower::SensorType::TORQUE_WHEEL) bSeenWheelTorque = TRUE; if(!bSeenWheelTorque && bpPages->eType == BikePower::SensorType::TORQUE_CRANK) bSeenCrankTorque = TRUE; ulAcumTorqueEventCount = 0; // Reset accumulated values ulAcumTicks = 0; usNoEventCount = 0; ulDistance100 = 0; ucPrevTorqueEventCount = ucCurTorqueEventCount; ucPrevTicks = ucCurTicks; usPrevPeriod2048 = usCurPeriod2048; usPrevTorque32 = usCurTorque32; } // Difference between current and previous event UCHAR ucTorqueEventDiff = GetDifference(ucCurTorqueEventCount, ucPrevTorqueEventCount); UCHAR ucTicksDiff = GetDifference(ucCurTicks, ucPrevTicks); USHORT usPeriodDiff = GetDifference(usCurPeriod2048, usPrevPeriod2048); USHORT usTorqueDiff = GetDifference(usCurTorque32, usPrevTorque32); // Update last event ucPrevTorqueEventCount =ucCurTorqueEventCount; ucPrevTicks = ucCurTicks; usPrevPeriod2048 = usCurPeriod2048; usPrevTorque32 = usCurTorque32; // Update cumulative values ulAcumTorqueEventCount += ucTorqueEventDiff; // Cumulative torque event count ulAcumTicks += ucTicksDiff; // Cumulative revolutions // Update calculations if dealing with a new event if(ucTorqueEventDiff) { usNoEventCount = 0; if(usPeriodDiff) { if(bpPages->eType == BikePower::SensorType::TORQUE_WHEEL) { bStopped = FALSE; ulSpeed = ((ULONG) 3600 * 2048 * ucWheelCircumference100 * ucTorqueEventDiff)/((ULONG) 100 * usPeriodDiff); // Average speed in m/h (Circumference in cm, Period in 1/2048s) } else // Crank Torque { bCoasting = FALSE; ulAverageCadence = ((ULONG) 60 * 2048 * ucTorqueEventDiff)/(ULONG) usPeriodDiff; // Average cadence (rpm) } ulAngularVelocity256 = ((ULONG) 2 * 2048 * PI256 * ucTorqueEventDiff)/(ULONG) usPeriodDiff; // Angular velocity (1/256 rad/s) -> 2 * 2048 * 256 * pi } else { if(bpPages->eType == BikePower::SensorType::TORQUE_WHEEL) { bStopped = TRUE; // No changes in wheel period if bike has stopped (time synchronous update) ulSpeed = 0; // If the bike has stopped, its speed and wheel angular velocity are zero } else // Crank Torque { bCoasting = TRUE; // No changes in crank period if the bike is coasting (time synchronous update) ulAverageCadence = 0; // If the bike is coasting, its cadence and crank angular velocity are zero } ulAngularVelocity256 = 0; } ulAverageTorque32 = (ULONG) usTorqueDiff/(ULONG) ucTorqueEventDiff; // Average torque (1/32 Nm) ulAveragePower256 = (ulAverageTorque32 * ulAngularVelocity256)/32; // Average power => (1/32Nm) * (1/256 rad/s) * 1/32 = 1/256W if(bpPages->eType == BikePower::SensorType::TORQUE_WHEEL) { ulDistance100 += (ULONG) ucWheelCircumference100 * ucTicksDiff; // Distance (cm) if(usTorqueDiff) // Check for coasting bCoasting = FALSE; else bCoasting = TRUE; // The bike is coasting if there are wheel events but the accumulated torque does not change } } else { // Event synchronous usNoEventCount++; if(usNoEventCount >= BikePower::MAX_NOEVENT) { if(bpPages->eType == BikePower::SensorType::TORQUE_WHEEL) { bStopped = TRUE; // Detect stop (no new wheel events before a preset number of messages) ulSpeed = 0; // Bike stopped, so speed and wheel angular velocity is zero } else // Crank Torque { bCoasting = TRUE; // Detect coasting (no new crank events before a preset number of messages) ulAverageCadence = 0; // If the bike is coasting, cadence and crank angular velocity are zero } ulAngularVelocity256 = 0; } } break; } case BikePower::PAGE_CTF: { if(!bSeenCTF) // Initialize previous values if first time the page has been seen { bSeenCTF = TRUE; ulAcumCTFEventCount = 0; usNoEventCount = 0; ucPrevCTFEventCount = bpPages->ucCTFEventCount; usPrevTime2000 = bpPages->usTime2000; usPrevCTFTicks = bpPages->usTorqueTicks; } // Difference between current and previous event UCHAR ucCTFEventDiff = GetDifference(bpPages->ucCTFEventCount, ucPrevCTFEventCount); USHORT usTimeDiff = GetDifference(bpPages->usTime2000, usPrevTime2000); USHORT usCTFTicksDiff = GetDifference(bpPages->usTorqueTicks, usPrevCTFTicks); // Update last event ucPrevCTFEventCount = bpPages->ucCTFEventCount; usPrevTime2000 = bpPages->usTime2000; usPrevCTFTicks = bpPages->usTorqueTicks; // Update cumulative values ulAcumCTFEventCount += ucCTFEventDiff; // Update calculations if dealing with a new event if(ucCTFEventDiff) { usNoEventCount = 0; bCoasting = FALSE; if(usTimeDiff) { ulAverageCadence = ((ULONG) 60 * 2000 * ucCTFEventDiff)/(ULONG) usTimeDiff; // Cadence (rpm) if(!bpPages->IsSlopeInvalid(bpPages->usSlope10)) { ulAverageTorque32 = ((((ULONG) 32 * 10 * 2000 * usCTFTicksDiff)/usTimeDiff) - (32 * 10 * usCTFOffset))/(ULONG)bpPages->usSlope10; // Torque (1/32 Nm): time is in 1/2000s, slope in 1/10 Hz/Nm, convert to 1/32 Nm for consistency ulAveragePower256 = (ULONG) (PI256 * ulAverageTorque32 * ulAverageCadence)/(30*32); // Average power (1/256 W) , torque is in 1/32 m if(ulAverageTorque32) ulAngularVelocity256 = ((ULONG) 32 * ulAveragePower256)/ulAverageTorque32; // Power (1/256W) / Torque (1/32 Nm) -> 1/256 rad/s } } } else { usNoEventCount++; if(usNoEventCount >= BikePower::MAX_NOEVENT) { bCoasting = TRUE; // Detect coasting ulAverageCadence = 0; ulAveragePower256 = 0; } } break; } case BikePower::PAGE_CALIBRATION: // Set calibration offset to use on calculations if it received a valid value if(bpPages->ucRxCalibrationID == BikePower::CAL_CTF && bpPages->ucRxCTFMsgID == BikePower::CTF_OFFSET) { if(!bpPages->IsOffsetInvalid(bpPages->usCalOffset)) usCTFOffset = bpPages->usCalOffset; } break; default: break; } UpdateDisplay(ucPageNum); ePrevType = bpPages->eType; // Previous detected sensor type } /************************************************************************** * BikePowerDisplay::HandleRetransmit * * Retransmits calibration message, up to the maximum retransmission number * If values are updated on the GUI while attempting to retransmit, the * newest values will be sent * * returns: TRUE if message was retransmitted * FALSE if maximum number of retransmissions was reached * **************************************************************************/ BOOL BikePowerDisplay::HandleRetransmit() { BOOL bSuccess = TRUE; if(ucMsgExpectingAck) // Message still expecting an ack { if(ucAckRetryCount++ < MAX_RETRIES) { SendMessage(ucMsgExpectingAck); } else bSuccess = FALSE; } return bSuccess; } /************************************************************************** * BikePowerDisplay::UpdateDisplay * * Shows received decoded data on GUI * * ucPageNum_: received page * * returns: N/A * **************************************************************************/ void BikePowerDisplay::UpdateDisplay(UCHAR ucPageNum_) { // Display current message received switch(ucPageNum_) { case BikePower::PAGE_POWER: this->label_Trn_PageDisplay->Text = "BAS"; this->label_Trn_UpdateCountDisplay->Text = bpPages->ucPowEventCount.ToString(); // Current power event count if(bpPages->IsCadenceInvalid(bpPages->ucCadence)) this->label_Trn_CadenceDisplay->Text = "Off"; else this->label_Trn_CadenceDisplay->Text = bpPages->ucCadence.ToString(); // Cadence (RPM) if(bpPages->ucPedalPower != BikePower::RESERVED) { UCHAR ucTemp = 0x00; ucTemp = ((bpPages->ucPedalPower) & 0x7F); //0x7F is a mask used to remove "Pedal Differentiation Bit" leaving pedal power percent value only if(ucTemp >= 0x65) // Checking pedal power percent value is within the valid range 0%-100% { this->label_Trn_PedalDisplay->Text = "---"; this->label_Trn_PedalPwrDisplay->Text = "Invalid"; } else { if((bpPages->ucPedalPower) & 0x80) // 0x80 is a mask for right pedal power contribution this->label_Trn_PedalDisplay->Text = "Right"; // Power contribution is from the right pedal else this->label_Trn_PedalDisplay->Text = "Unknown"; // Power contribution is unknown this->label_Trn_PedalPwrDisplay->Text = ucTemp.ToString(); } } else { this->label_Trn_PedalDisplay->Text = "---"; this->label_Trn_PedalPwrDisplay->Text = "Off"; } this->label_Trn_AccumPowerDisplay->Text = bpPages->usAcumPower.ToString(); // Current cumulative power (W) this->label_Trn_InstPowerDisplay->Text = bpPages->usPower.ToString(); // Power (W) this->label_Dat_InstCadenceDisplay->Text = this->label_Trn_CadenceDisplay->Text; // Cadence (RPM) this->label_Dat_InstPowerDisplay->Text = bpPages->usPower.ToString(); // Power (W) this->label_Dat_PedalPwrDisplay->Text = this->label_Trn_PedalPwrDisplay->Text; this->label_Dat_PedalDisplay->Text = this->label_Trn_PedalDisplay->Text; break; case BikePower::PAGE_WHEEL_TORQUE: this->label_Trn_PageDisplay->Text = "WHL"; this->label_Trn_UpdateCountDisplay->Text = bpPages->ucWTEventCount.ToString(); this->label_Trn_EventCountDisplay->Text = bpPages->ucWheelTicks.ToString(); if(bpPages->IsCadenceInvalid(bpPages->ucCadence)) this->label_Trn_CadenceDisplay->Text = "Off"; else this->label_Trn_CadenceDisplay->Text = bpPages->ucCadence.ToString(); // Cadence (RPM) this->label_Dat_InstCadenceDisplay->Text = this->label_Trn_CadenceDisplay->Text; // Cadence (RPM) this->label_Trn_AccumOneDisplay->Text = bpPages->usAcumWheelPeriod2048.ToString(); // Period (1/2048 s) this->label_Trn_AccumTwoDisplay->Text = bpPages->usAcumTorque32.ToString(); // Torque (1/32 Nm) break; case BikePower::PAGE_CRANK_TORQUE: this->label_Trn_PageDisplay->Text = "CRK"; this->label_Trn_UpdateCountDisplay->Text = bpPages->ucCTEventCount.ToString(); this->label_Trn_EventCountDisplay->Text = bpPages->ucCrankTicks.ToString(); if(bpPages->IsCadenceInvalid(bpPages->ucCadence)) this->label_Trn_CadenceDisplay->Text = "Off"; else this->label_Trn_CadenceDisplay->Text = bpPages->ucCadence.ToString(); // Cadence (RPM) this->label_Dat_InstCadenceDisplay->Text = this->label_Trn_CadenceDisplay->Text; // Cadence (RPM) this->label_Trn_AccumOneDisplay->Text = bpPages->usAcumCrankPeriod2048.ToString(); this->label_Trn_AccumTwoDisplay->Text = (bpPages->usAcumTorque32).ToString(); // Accum torque (1/32 Nm) break; case BikePower::PAGE_TEPS: { if(bpPages->ucLeftTorqueEffectiveness == bpPages->INVALID_TEPS) label_LeftTorqueEffectiveness->Text = "INVALID"; else label_LeftTorqueEffectiveness->Text = (bpPages->ucLeftTorqueEffectiveness / 2.0).ToString(); if(bpPages->ucRightTorqueEffectiveness == bpPages->INVALID_TEPS) label_RightTorqueEffectiveness->Text = "INVALID"; else label_RightTorqueEffectiveness->Text = (bpPages->ucRightTorqueEffectiveness / 2.0).ToString(); if(bpPages->ucLeftPedalSmoothness == bpPages->INVALID_TEPS) label_LeftPedalSmoothness->Text = "INVALID"; else label_LeftPedalSmoothness->Text = (bpPages->ucLeftPedalSmoothness / 2.0).ToString(); if(bpPages->ucRightPedalSmoothness == bpPages->INVALID_TEPS) label_RightPedalSmoothness->Text = "INVALID"; else if(bpPages->ucRightPedalSmoothness == bpPages->COMBINED_PEDAL_SMOOTHNESS) label_RightPedalSmoothness->Text = "COMBINED PEDAL SMOOTHNESS"; else label_RightPedalSmoothness->Text = (bpPages->ucRightPedalSmoothness / 2.0).ToString(); break; } case BikePower::PAGE_CTF: this->label_Trn_PageDisplay->Text = "CTF"; this->label_Trn_UpdateCountDisplay->Text = bpPages->ucCTFEventCount.ToString(); this->label_Trn_SlopeDisplay->Text = bpPages->usSlope10.ToString(); this->label_Trn_AccumOneDisplay->Text = bpPages->usTime2000.ToString(); this->label_Trn_AccumTwoDisplay->Text = bpPages->usTorqueTicks.ToString(); if(bpPages->IsSlopeInvalid(bpPages->usSlope10)) this->label_Dat_SlopeDisplay->Text = "---"; else this->label_Dat_SlopeDisplay->Text = ((double)bpPages->usSlope10/10).ToString(); break; case BikePower::PAGE_CALIBRATION: this->label_Cal_StatusDisplay->ForeColor = System::Drawing::Color::Green; if(bBadCalPage) { this->label_Cal_StatusDisplay->ForeColor = System::Drawing::Color::Red; this->label_Cal_StatusDisplay->Text = "xPAG"; } switch(bpPages->ucRxCalibrationID) { case BikePower::CAL_SUCCESS: case BikePower::CAL_FAIL: // Intentional fall thru if(!bAutoZeroInvalid) { this->button_Cal_AutoZeroSet->Enabled = (bpPages->bAutoZeroEnable == TRUE); this->radioButton_Cal_AZoff->Enabled = (bpPages->bAutoZeroEnable == TRUE); this->radioButton_Cal_AZon->Enabled = (bpPages->bAutoZeroEnable == TRUE); if(!bpPages->bAutoZeroEnable) this->listBox_Cal_AZStatus->SelectedIndex = 2; else this->listBox_Cal_AZStatus->SelectedIndex = (UCHAR) bpPages->bAutoZeroOn; } this->label_Cal_CalNumberDisplay->Text = bpPages->sCalibrationData.ToString(); if(bpPages->ucRxCalibrationID == BikePower::CAL_SUCCESS) { this->label_Cal_StatusDisplay->Text = "+CAL"; this->label_Cal_StatusDisplay->ForeColor = System::Drawing::Color::Green; } else { this->label_Cal_StatusDisplay->Text = "xCAL"; this->label_Cal_StatusDisplay->ForeColor = System::Drawing::Color::Red; } break; case BikePower::CAL_TORQUE_METER_CAPABILITIES: this->button_Cal_AutoZeroSet->Enabled = (bpPages->bAutoZeroEnable == TRUE); this->radioButton_Cal_AZoff->Enabled = (bpPages->bAutoZeroEnable == TRUE); this->radioButton_Cal_AZon->Enabled = (bpPages->bAutoZeroEnable == TRUE); if(!bpPages->bAutoZeroEnable) // Auto zero not supported this->listBox_Cal_AZStatus->SelectedIndex = 2; else this->listBox_Cal_AZStatus->SelectedIndex = (UCHAR) bpPages->bAutoZeroOn; if(bpPages->IsRawTorqueInvalid(bpPages->sRawTorque)) this->label_Cal_RawTorqueDisplay->Text = "---"; else this->label_Cal_RawTorqueDisplay->Text = bpPages->sRawTorque.ToString(); if(bpPages->IsOffsetTorqueInvalid(bpPages->sOffsetTorque)) this->label_Cal_OffsetTorqueDisplay->Text = "---"; else this->label_Cal_OffsetTorqueDisplay->Text = bpPages->sOffsetTorque.ToString(); break; case BikePower::CAL_CTF: if(bpPages->ucRxCTFMsgID == BikePower::CTF_OFFSET) { this->label_Cal_CalNumberDisplay->Text = bpPages->usCalOffset.ToString(); if(!bpPages->usCalOffset) { this->label_Cal_StatusDisplay->ForeColor = System::Drawing::Color::Yellow; this->label_Cal_StatusDisplay->Text = "ZERO"; } else if(bpPages->IsOffsetInvalid(bpPages->usCalOffset)) { this->label_Cal_StatusDisplay->ForeColor = System::Drawing::Color::Red; this->label_Cal_StatusDisplay->Text = "xOFS"; } else { this->label_Dat_CTFOffsetDisplay->Text = usCTFOffset.ToString(); this->label_Cal_StatusDisplay->Text = "+OFS"; } } if(bpPages->ucRxCTFMsgID == BikePower::CTF_ACK) { if(bpPages->ucRxCTFMsgID == BikePower::CTF_SLOPE) this->label_Cal_StatusDisplay->Text = "+SLP"; if(bpPages->ucRxCTFMsgID == BikePower::CTF_SERIAL) this->label_Cal_StatusDisplay->Text = "+SRL"; } break; default: break; } break; case CommonData::PAGE80: this->label_Glb_HardwareVerDisplay->Text = commonPages->ucHwVersion.ToString(); // Hw version this->label_Glb_ManfIDDisplay->Text = commonPages->usMfgID.ToString(); // Mfg ID this->label_Glb_ModelNumDisplay->Text = commonPages->usModelNum.ToString(); // Model number break; case CommonData::PAGE81: this->label_Glb_SoftwareVerDisplay->Text = commonPages->ucSwVersion.ToString(); // Sw version if(commonPages->ulSerialNum != 0xFFFFFFFF) this->label_Glb_SerialNumDisplay->Text = commonPages->ulSerialNum.ToString(); // Serial number else this->label_Glb_SerialNumDisplay->Text = "N/A"; break; case CommonData::PAGE82: if(commonPages->IsBatteryVoltageInvalid(commonPages->usBatVoltage256)) // Battery voltage this->label_Bat_VoltsDisplay->Text = "Invalid Voltage"; else this->label_Bat_VoltsDisplay->Text = System::Math::Round((double)commonPages->usBatVoltage256/256,4).ToString(); if(commonPages->IsBatteryStatusInvalid(commonPages->eBatStatus)) // Battery status this->listBox_Bat_Status->SelectedIndex = this->listBox_Bat_Status->Items->Count - 1; else this->listBox_Bat_Status->SelectedIndex = (UCHAR) (commonPages->eBatStatus) - 1; this->label_Bat_ElpTimeDisplay->Text = ((ULONG) (commonPages->ulOpTime & CommonData::OPERATING_TIME_MASK) * (UCHAR) commonPages->eTimeResolution).ToString(); // Operating time (s) this->label_Bat_SecPrecis->Text = System::String::Concat(((UCHAR)commonPages->eTimeResolution).ToString(), " Sec Precision"); // Time resolution break; default: break; } // Calculations are not displayed initially, so they will only be displayed once the sensor type is known // A sensor type would change from UNKNOWN to the correct type after initialization if(ePrevType == bpPages->eType) { switch(bpPages->eType) { case BikePower::SensorType::POWER_ONLY: this->label_Dat_Coasting->Visible = (bCoasting == TRUE); this->label_Dat_Stopped->Visible = (bStopped == TRUE); this->label_Dat_CalcPowerDisplay->Text = System::Math::Round((double)ulAveragePower256/256,2).ToString(); // Average power (W) this->label_Dat_UpdateCountDisplay->Text = ulAcumPowerEventCount.ToString(); // Cumulative power event count break; case BikePower::SensorType::TORQUE_WHEEL: case BikePower::SensorType::TORQUE_CRANK: // intentional fall - thru this->label_Dat_Coasting->Visible = (bCoasting == TRUE); this->label_Dat_Stopped->Visible = (bStopped == TRUE); this->label_Dat_CalcPowerDisplay->Text = System::Math::Round((double) ulAveragePower256/256,2).ToString(); // Average power (W) this->label_Dat_UpdateCountDisplay->Text = ulAcumTorqueEventCount.ToString(); // Torque event count this->label_Dat_EventCountDisplay->Text = ulAcumTicks.ToString(); // Ticks this->label_Dat_AngVelDisplay->Text = System::Math::Round((double) ulAngularVelocity256/256,2).ToString(); // Angular velocity (rad/s) this->label_Dat_TorqueDisplay->Text = System::Math::Round((double) ulAverageTorque32/32,2).ToString(); // Average torque (Nm) if(bpPages->eType == BikePower::SensorType::TORQUE_WHEEL) { this->label_Dat_SpeedDisplay->Text = System::Math::Round((double)ulSpeed/1000,2).ToString(); // Average speed (meters/h -> km/h) this->label_Dat_DistDisplay->Text = System::Math::Round((double)ulDistance100/100,2).ToString(); // Distance (m) } else // Crank Torque { this->label_Dat_CalcRPMDisplay->Text = System::Math::Round(ulAverageCadence,2).ToString(); } break; case BikePower::SensorType::CRANK_TORQUE_FREQ: if(ucPageNum_ == BikePower::PAGE_CALIBRATION && bpPages->ucRxCTFMsgID == BikePower::CTF_OFFSET) { // Blank calculations while receiving CTF offset, rather than displaying erroneous data this->label_Dat_CalcPowerDisplay->Text = "---"; this->label_Dat_UpdateCountDisplay->Text = "---"; this->label_Dat_AngVelDisplay->Text = "---"; this->label_Dat_TorqueDisplay->Text = "---"; this->label_Dat_CalcRPMDisplay->Text = "---"; } else { this->label_Dat_Coasting->Visible = (bCoasting == TRUE); this->label_Dat_Stopped->Visible = (bStopped == TRUE); this->label_Dat_CalcPowerDisplay->Text = System::Math::Round((double)ulAveragePower256/256,2).ToString(); this->label_Dat_UpdateCountDisplay->Text = ulAcumCTFEventCount.ToString(); this->label_Dat_AngVelDisplay->Text = System::Math::Round((double)ulAngularVelocity256/256,2).ToString(); this->label_Dat_TorqueDisplay->Text = System::Math::Round((double)ulAverageTorque32/32,2).ToString(); this->label_Dat_CalcRPMDisplay->Text = ulAverageCadence.ToString(); } break; default: break; } } } /************************************************************************** * BikePowerDisplay::UpdateDisplayAckStatus * * Updates display to show if acknowledged calibration messages were * transmitted successfully * * returns: N/A * **************************************************************************/ void BikePowerDisplay::UpdateDisplayAckStatus(UCHAR ucStatus_) { switch(ucStatus_) { case ACK_SUCCESS: this->label_Cal_StatusDisplay->ForeColor = System::Drawing::Color::Green; this->label_Cal_StatusDisplay->Text = "SENT"; break; case ACK_RETRY: this->label_Cal_StatusDisplay->ForeColor = System::Drawing::Color::Blue; this->label_Cal_StatusDisplay->Text = "RTRY"; break; case ACK_FAIL: this->label_Cal_StatusDisplay->ForeColor = System::Drawing::Color::Red; this->label_Cal_StatusDisplay->Text = "xSNT"; break; default: break; } } /************************************************************************** * BikePowerDisplay::ChangeSensorType * * Updates display to enable/disable elements available only on certain * type of sensors, after a change in sensor type is detected * * returns: N/A * **************************************************************************/ void BikePowerDisplay::ChangeSensorType() { switch(bpPages->eType) { case BikePower::SensorType::POWER_ONLY: this->label_Dat_PageRecDisplay->Text = "Power Only Sensor"; this->label_Trn_EventCountDisplay->Text = "---"; this->label_Trn_AccumOneDisplay->Text = "---"; this->label_Trn_AccumTwoDisplay->Text = "---"; this->label_Trn_Slope->Visible = false; this->label_Trn_SlopeDisplay->Visible = false; this->label_Dat_SlopeDisplay->Text = "---"; this->label_Dat_AngVelDisplay->Text = "---"; this->label_Dat_TorqueDisplay->Text = "---"; this->label_Dat_CalcRPMDisplay->Text = "---"; this->label_Dat_SpeedDisplay->Text = "---"; this->label_Dat_DistDisplay->Text = "---"; this->label_Dat_CTFOffsetDisplay->Text = "---"; this->numericUpDown_Cal_CTFSlope->Enabled = false; this->numericUpDown_Cal_CTFSerial->Enabled = false; this->button_Cal_CTFSerialSet->Enabled = false; this->button_Cal_CTFSlopeSet->Enabled = false; break; case BikePower::SensorType::TORQUE_WHEEL: this->label_Dat_PageRecDisplay->Text = "Wheel Torque Sensor"; this->label_Trn_AccumOne->Text = "Wheel P:"; this->label_Trn_Slope->Visible = false; this->label_Trn_SlopeDisplay->Visible = false; this->label_Trn_InstPowerDisplay->Text = "---"; this->label_Trn_AccumPowerDisplay->Text = "---"; this->label_Dat_InstPowerDisplay->Text = "---"; this->label_Dat_CTFOffsetDisplay->Text = "---"; this->label_Dat_SlopeDisplay->Text = "---"; this->numericUpDown_Cal_CTFSlope->Enabled = false; this->numericUpDown_Cal_CTFSerial->Enabled = false; this->button_Cal_CTFSerialSet->Enabled = false; this->button_Cal_CTFSlopeSet->Enabled = false; break; case BikePower::SensorType::TORQUE_CRANK: this->label_Dat_PageRecDisplay->Text = "Crank Torque Sensor"; this->label_Trn_AccumOne->Text = "Crank P:"; this->label_Trn_Slope->Visible = false; this->label_Trn_SlopeDisplay->Visible = false; this->label_Trn_InstPowerDisplay->Text = "---"; this->label_Trn_AccumPowerDisplay->Text = "---"; this->label_Dat_SlopeDisplay->Text = "---"; this->label_Dat_SpeedDisplay->Text = "---"; this->label_Dat_DistDisplay->Text = "---"; this->label_Dat_InstPowerDisplay->Text = "---"; this->label_Dat_CTFOffsetDisplay->Text = "---"; this->numericUpDown_Cal_CTFSlope->Enabled = false; this->numericUpDown_Cal_CTFSerial->Enabled = false; this->button_Cal_CTFSerialSet->Enabled = false; this->button_Cal_CTFSlopeSet->Enabled = false; break; case BikePower::SensorType::CRANK_TORQUE_FREQ: this->label_Dat_PageRecDisplay->Text = "Crank Torque Frequency Sensor"; this->label_Trn_AccumOne->Text = " Time:"; this->label_Trn_EventCountDisplay->Text = "---"; this->label_Trn_Slope->Visible = true; this->label_Trn_SlopeDisplay->Visible = true; this->label_Trn_InstPowerDisplay->Text = "---"; this->label_Trn_AccumPowerDisplay->Text = "---"; this->label_Trn_CadenceDisplay->Text = "---"; this->label_Trn_PedalDisplay->Text = "---"; this->label_Trn_PedalPwrDisplay->Text = "---"; this->label_Dat_SpeedDisplay->Text = "---"; this->label_Dat_DistDisplay->Text = "---"; this->label_Dat_InstPowerDisplay->Text = "---"; this->label_Dat_InstCadenceDisplay->Text = "---"; this->label_Dat_CTFOffsetDisplay->Text = "Unknown"; this->numericUpDown_Cal_CTFSlope->Enabled = true; this->numericUpDown_Cal_CTFSerial->Enabled = true; this->button_Cal_CTFSerialSet->Enabled = true; this->button_Cal_CTFSlopeSet->Enabled = true; this->button_Cal_AutoZeroSet->Enabled = false; this->radioButton_Cal_AZoff->Enabled = false; this->radioButton_Cal_AZon->Enabled = false; this->listBox_Cal_AZStatus->SelectedIndex = -1; break; case BikePower::SensorType::UNKNOWN: this->label_Dat_PageRecDisplay->Text = "Updating..."; this->label_Trn_EventCountDisplay->Text = "---"; this->label_Trn_AccumOneDisplay->Text = "---"; this->label_Trn_AccumTwoDisplay->Text = "---"; this->label_Trn_Slope->Visible = false; this->label_Trn_SlopeDisplay->Visible = false; this->label_Dat_SlopeDisplay->Text = "---"; this->label_Dat_AngVelDisplay->Text = "---"; this->label_Dat_TorqueDisplay->Text = "---"; this->label_Dat_CalcRPMDisplay->Text = "---"; this->label_Dat_SpeedDisplay->Text = "---"; this->label_Dat_DistDisplay->Text = "---"; this->label_Dat_CTFOffsetDisplay->Text = "---"; this->numericUpDown_Cal_CTFSlope->Enabled = false; this->numericUpDown_Cal_CTFSerial->Enabled = false; this->button_Cal_CTFSerialSet->Enabled = false; this->button_Cal_CTFSlopeSet->Enabled = false; break; default: break; } } /************************************************************************** * BikePowerDisplay::button_Cal_Calibrate_Click * * Sends a calibration request * * returns: N/A * **************************************************************************/ System::Void BikePowerDisplay::button_Cal_Calibrate_Click(System::Object^ sender, System::EventArgs^ e) { bRequestCalibration = true; SendMessage(BikePower::CAL_REQUEST); } /************************************************************************** * BikePowerDisplay::button_Cal_AutoZeroSet_Click * * Sends an Auto Zero Configuration request * * returns: N/A * **************************************************************************/ System::Void BikePowerDisplay::button_Cal_AutoZeroSet_Click(System::Object^ sender, System::EventArgs^ e) { bRequestCalibration = true; SendMessage(BikePower::CAL_AUTOZERO_CONFIG); } /************************************************************************** * BikePowerDisplay::button_Cal_CTFSerialSet_Click * * Sends a CTF defined message for serial number, to save a new serial * number to the power sensor flash * * returns: N/A * **************************************************************************/ System::Void BikePowerDisplay::button_Cal_CTFSerialSet_Click(System::Object^ sender, System::EventArgs^ e) { bRequestCalibration = true; SendMessage(BikePower::CTF_SERIAL); } /************************************************************************** * BikePowerDisplay::button_Cal_CTFSlopeSet_Click * * Sends a CTF defined message for slope to the power sensor, to save a new * value for the slope to the sensor flash * * returns: N/A * **************************************************************************/ System::Void BikePowerDisplay::button_Cal_CTFSlopeSet_Click(System::Object^ sender, System::EventArgs^ e) { bRequestCalibration = true; SendMessage(BikePower::CTF_SLOPE); } /************************************************************************** * BikePowerDisplay::numericUpDown_Cal_WheelCircum_ValueChanged * * Updates value of wheel circumference * * returns: N/A * **************************************************************************/ System::Void BikePowerDisplay::numericUpDown_Cal_WheelCircum_ValueChanged(System::Object^ sender, System::EventArgs^ e) { ucWheelCircumference100 = System::Convert::ToByte(this->numericUpDown_Cal_WheelCircum->Value); } /************************************************************************** * BikePowerDisplay::radioButton_Cal_AZ_CheckedChanged * * Updates auto zero status set on display * * returns: N/A * **************************************************************************/ System::Void BikePowerDisplay::radioButton_Cal_AZ_CheckedChanged(System::Object^ sender, System::EventArgs^ e) { bAZOn = (BOOL) this->radioButton_Cal_AZon->Checked; } /************************************************************************** * BikePowerDisplay::numericUpDown_Cal_CTFSerial_ValueChanged * * Updates serial number set on display, for CTF Serial message * * returns: N/A * **************************************************************************/ System::Void BikePowerDisplay::numericUpDown_Cal_CTFSerial_ValueChanged(System::Object^ sender, System::EventArgs^ e) { usCalSerialToSend = System::Convert::ToUInt16(this->numericUpDown_Cal_CTFSerial->Value); // serial number } /************************************************************************** * BikePowerDisplay::numericUpDown_Cal_CTFSlope_ValueChanged * * Updates slope number set on display, for CTF Slope message * * returns: N/A * **************************************************************************/ System::Void BikePowerDisplay::numericUpDown_Cal_CTFSlope_ValueChanged(System::Object^ sender, System::EventArgs^ e) { usCalSlopeToSend = System::Convert::ToUInt16(10*(this->numericUpDown_Cal_CTFSlope->Value)); // slope in 1/10 Nm/Hz } System::Void BikePowerDisplay::button_SetCrankStatus_Click(System::Object ^sender, System::EventArgs ^e) { bpPages->ucSubpageNumber = BikePower::SUBPAGE_CRANK_PARAMETERS; if(checkBox_InvalidCrankLength->Checked) bpPages->ucCrankLength = 0xFF; // Invalid else if(checkBox_AutoCrank->Checked) bpPages->ucCrankLength = 0xFE; // Request Auto Crank else bpPages->ucCrankLength = (UCHAR)(((float)numericUpDown_CrankLength->Value - (float)110.0) / (float)0.5); // Send Crank Length value bpPages->ucSensorStatus = 0x00; // Readonly byte bpPages->ucSensorCapabilities = 0x00; // Readonly byte bRequestCalibration = false; SendMessage(bpPages->PAGE_GET_SET_PARAMETERS); } System::Void BikePowerDisplay::button_GetCrankStatus_Click(System::Object ^sender, System::EventArgs ^e) { commonPages->ucDescriptorByte1 = BikePower::SUBPAGE_CRANK_PARAMETERS; // for requesting subpages commonPages->ucDescriptorByte2 = 0xFF; // Invalid byte commonPages->ucReqTransResp = System::Convert::ToByte(numericUpDown_TransResponse->Value); //Number of times to transmit, don't use ack messages commonPages->ucReqPageNum = BikePower::PAGE_GET_SET_PARAMETERS; // request Get/Set page commonPages->ucCommandType = 0x01; // For requesting a data page bRequestCalibration = false; SendMessage(commonPages->PAGE70); } System::Void BikePowerDisplay::SendMessage(UCHAR ucMsgCode_) { UCHAR aucAckBuffer[8] = {0,0,0,0,0,0,0,0}; // If a calibration request should be sent if(bRequestCalibration) { switch(ucMsgCode_) { case BikePower::CAL_REQUEST: // Manual calibration request bpPages->EncodeManualCalibrationRequest(aucAckBuffer); break; case BikePower::CAL_AUTOZERO_CONFIG: // Auto zero calibration request bpPages->EncodeAZCalibrationRequest(TRUE, bAZOn, aucAckBuffer); break; case BikePower::CTF_SLOPE: // Save slope to flash bpPages->EncodeCTFCalibrationPage(ucMsgCode_, usCalSlopeToSend, aucAckBuffer); break; case BikePower::CTF_SERIAL: // Save serial to flash bpPages->EncodeCTFCalibrationPage(ucMsgCode_, usCalSerialToSend, aucAckBuffer); break; default: break; } } // If a non calibration request should be sent else { switch(ucMsgCode_) { case BikePower::PAGE_GET_SET_PARAMETERS: bpPages->EncodeMainData(ucMsgCode_, aucAckBuffer); break; case CommonData::PAGE70: commonPages->Encode(commonPages->PAGE70, aucAckBuffer); break; default: return; } } if(!ucMsgExpectingAck) { ucAckRetryCount = 0; ucMsgExpectingAck = ucMsgCode_; } requestAckMsg(aucAckBuffer); } /************************************************************************** * BikePowerDisplay::GetDifference * * Gets the difference between the current and previous value, considering * rollover * Template allows using the function for UCHAR, USHORT and ULONG types * * CurrentVal_: Current value * PreviousVal_: Previous value * * returns: difference between the two values * **************************************************************************/ template< typename T> T BikePowerDisplay::GetDifference(T CurrentVal_, T PreviousVal_) { T MaxVal = (T) 0xFFFFFF; T Diff = CurrentVal_ - PreviousVal_; if(PreviousVal_ > CurrentVal_) Diff = MaxVal + Diff + 1; // handle rollover return Diff; } void BikePowerDisplay::checkBox_InvalidCrankLength_CheckedChanged(System::Object ^sender, System::EventArgs ^e) { if(checkBox_InvalidCrankLength->Checked) { checkBox_AutoCrank->Checked = false; numericUpDown_CrankLength->Enabled = false; } else { numericUpDown_CrankLength->Enabled = true; } } void BikePowerDisplay::checkBox_AutoCrank_CheckedChanged(System::Object ^sender, System::EventArgs ^e) { if(checkBox_AutoCrank->Checked) { checkBox_InvalidCrankLength->Checked = false; numericUpDown_CrankLength->Enabled = false; } else numericUpDown_CrankLength->Enabled = true; }