/* 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. */ #pragma once #include "StdAfx.h" #include "BikePowerSensor.h" /************************************************************************** * BikePowerSensor::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 BikePowerSensor::ANT_eventNotification(UCHAR ucEventCode_, UCHAR* pucEventBuffer_) { switch(ucEventCode_) { case EVENT_TX: HandleTransmit((UCHAR*) pucEventBuffer_); UpdateDisplay(); break; case EVENT_RX_ACKNOWLEDGED: if(pucEventBuffer_[0] == commonPages->PAGE70) { commonPages->Decode((UCHAR*)pucEventBuffer_); HandleRequest((UCHAR*)pucEventBuffer_); } else if(pucEventBuffer_[0] == bpPages->PAGE_GET_SET_PARAMETERS) { bpPages->ucCrankLength = pucEventBuffer_[4]; HandleReceive((UCHAR*)pucEventBuffer_); } else { HandleCalibration((UCHAR*) pucEventBuffer_); UpdateCalDisplay(); } break; case EVENT_TRANSFER_TX_COMPLETED: break; case EVENT_TRANSFER_TX_FAILED: break; default: break; } } /************************************************************************** * BikePowerSensor::InitializeSim * * Initializes simulator variables * * returns: N/A * **************************************************************************/ void BikePowerSensor::InitializeSim() { ulTimerInterval = 667; //90rpm ulRunTime1000 = 0; // Simulation settings bSweepAscending = TRUE; ucSimDataType = SIM_FIXED; bByCadence = TRUE; bStop = FALSE; bCoast = FALSE; bCalSuccess = TRUE; ucCurPedalPwrValue = System::Convert::ToByte(this->numericUpDown_PedalPwr_CurrOutput->Value); ucNumWheelGearTeeth = System::Convert::ToByte(this->numericUpDown_Sim_WheelGearTeeth->Value); ucNumCrankGearTeeth = System::Convert::ToByte(this->numericUpDown_Sim_CrankGearTeeth->Value); ucWheelCircumference100 = System::Convert::ToByte(this->numericUpDown_Sim_WheelCircumference->Value); // Wheel circumference (cm) usWheelTorque32 = System::Convert::ToUInt16(32 * this->numericUpDown_Sim_WheelTorque->Value); // Wheel torque (1/10 Nm) usCrankTorque32 = System::Convert::ToUInt16(32 * this->numericUpDown_Sim_CrankTorque->Value); // Crank torque (1/10 Nm) ulMinValue1000 = System::Convert::ToUInt32(1000 * this->numericUpDown_Sim_MinOutput->Value); // Min speed or cadence (resolution 1/1000) ulCurValue1000 = System::Convert::ToUInt32(1000 * this->numericUpDown_Sim_CurOutput->Value); // Current speed or cadence (resolution 1/1000) ulMaxValue1000 = System::Convert::ToUInt32(1000 * this->numericUpDown_Sim_MaxOutput->Value); // Max speed or cadence (resolution 1/1000) ulEventTime1000 = 0; sCalData = System::Convert::ToInt16(this->numericUpDown_Cal_CalNumber->Value); // Calibration data usCalOffset = System::Convert::ToUInt16(this->numericUpDown_Cal_CalNumber->Value); // CTF calibration data bpPages->usSlope10 = System::Convert::ToUInt16(10* this->numericUpDown_Ppg_SlopeConstant->Value); // Slope (1/10 Nm/Hz) bpPages->sRawTorque = System::Convert::ToInt16(this->numericUpDown_Cal_RawTorque->Value); // Raw torque (signed) bpPages->sOffsetTorque = System::Convert::ToInt16(this->numericUpDown_Cal_OffsetTorque->Value); // Offset torque (signed) this->listBox_Cal_AZStatus->SelectedIndex = 1; // Auto Zero On bpPages->bAutoZeroEnable = TRUE; bpPages->bAutoZeroOn = TRUE; // Paging bpPages->eType = BikePower::SensorType::POWER_ONLY; // Power only sensor by default bTxPage82 = TRUE; // Include battery info bTxAZ = TRUE; // Include auto zero support bTxCadence = TRUE; // Include cadence bTxPedalPower = TRUE; //Include pedal power bRightPedal = FALSE; //Unknown pedal side contribution to power ucPowerInterleave = System::Convert::ToByte(this->numericUpDown_Ppg_BasicPowerInterlvTime->Value); // Interleaving interval from UI ucEventFreq10 = System::Convert::ToByte(10 * this->numericUpDown_Ppg_TimerEventFreq->Value); // Event freq from UI eUpdateType = BikePower::UpdateType::TIME_SYNCH; // Event synchronous sensor ucMsgExpectingAck = 0; // No messages pending at initialization // Common Data (initial values set on UI) commonPages->usBatVoltage256 = (System::Convert::ToByte(this->numericUpDown_Bat_VoltInt->Value) << 8) + (System::Convert::ToByte(this->numericUpDown_Bat_VoltFrac->Value)); this->label_Voltage_Display->Text = ((double) commonPages->usBatVoltage256/256).ToString(); // Display in V commonPages->usMfgID = System::Convert::ToUInt16(this->textBox_Glb_ManfIDChange->Text); commonPages->ulSerialNum = System::Convert::ToUInt32(this->textBox_Glb_SerialNumChange->Text); commonPages->ucHwVersion = System::Convert::ToByte(this->textBox_Glb_HardwareVerChange->Text); commonPages->ucSwVersion = System::Convert::ToByte(this->textBox_Glb_SoftwareVerChange->Text); commonPages->usModelNum = System::Convert::ToUInt16(this->textBox_Glb_ModelNumChange->Text); commonPages->eTimeResolution = CommonData::TimeResolution::TWO; // Two second resolution commonPages->ulOpTime = 0; this->listBox_Bat_Status->SelectedIndex = 2; // OK commonPages->eBatStatus = CommonData::BatStatus::OK; ucBcastMessage = 0; ucBcastCount = 0; ucTransmissionCount = 0; bpPages->ucCrankLength = (UCHAR)(((float)numericUpDown_CrankLength->Value - (float)110.0) / (float)0.5); bpPages->ucSensorCapabilities = 0; bpPages->ucSensorStatus = 0x3E; bpPages->ucLeftTorqueEffectiveness = (UCHAR)(numericUpDown_LeftTorqueEffectiveness->Value * 2); bpPages->ucRightTorqueEffectiveness = (UCHAR) (numericUpDown_RightTorqueEffectiveness->Value * 2); bpPages->ucLeftPedalSmoothness = (UCHAR) (numericUpDown_LeftPedalSmoothness->Value * 2); bpPages->ucRightPedalSmoothness = (UCHAR) (numericUpDown_RightPedalSmoothness->Value * 2); } /************************************************************************** * BikePowerSensor::HandleTransmit * * Encode data generated by simulator for transmission * * pucTxBuffer_: pointer to the transmit buffer * * returns: N/A * **************************************************************************/ void BikePowerSensor::HandleTransmit(UCHAR* pucTxBuffer_) { static ULONG ulMessageCount = 0; static UCHAR ucExtMesgType = 0; UCHAR ucPageNum; if(ucBcastMessage) { switch(ucBcastMessage) { case bpPages->PAGE_GET_SET_PARAMETERS: ucBcastCount++; bpPages->EncodeMainData(ucBcastMessage, pucTxBuffer_); if(ucBcastCount > ucTransmissionCount) ucBcastMessage = 0; break; default: if((bpPages->eType == BikePower::SensorType::CRANK_TORQUE_FREQ) && (ucBcastMessage == BikePower::CAL_REQUEST)) { bpPages->EncodeCTFCalibrationPage(BikePower::CTF_OFFSET, usCalOffset, pucTxBuffer_); // CTF sensors, send calibration offset ucBcastCount++; if(ucBcastCount > BikePower::MAX_RETRY_CTF) { ucBcastMessage = 0; ucBcastCount = 0; } } else if(ucBcastMessage) { switch(ucBcastMessage) { case BikePower::CAL_REQUEST: // Manual calibration request, send calibration response if(bCalSuccess) // Other sensors, report success or failure bpPages->EncodeCalibrationResponse(BikePower::CAL_SUCCESS, bpPages->bAutoZeroEnable, bpPages->bAutoZeroOn, sCalData, pucTxBuffer_); else bpPages->EncodeCalibrationResponse(BikePower::CAL_FAIL, bpPages->bAutoZeroEnable, bpPages->bAutoZeroOn, sCalData, pucTxBuffer_); break; case BikePower::CAL_AUTOZERO_CONFIG: // Auto zero calibration request, send response (which includes auto zero status) if(bCalSuccess) bpPages->EncodeCalibrationResponse(BikePower::CAL_SUCCESS, bpPages->bAutoZeroEnable, bpPages->bAutoZeroOn, sCalData, pucTxBuffer_); else bpPages->EncodeCalibrationResponse(BikePower::CAL_FAIL, bpPages->bAutoZeroEnable, bpPages->bAutoZeroOn, sCalData, pucTxBuffer_); break; case BikePower::CTF_SLOPE: bpPages->EncodeCTFCalibrationPage(BikePower::CTF_ACK, BikePower::CTF_SLOPE, pucTxBuffer_); break; case BikePower::CTF_SERIAL: // ACK save serial to flash bpPages->EncodeCTFCalibrationPage(BikePower::CTF_ACK, BikePower::CTF_SERIAL, pucTxBuffer_); break; default: return; } ucBcastMessage = 0; ucBcastCount = 0; } break; } return; } // Figure out what page to send // Set first to primary page according to sensor type switch(bpPages->eType) { case BikePower::SensorType::POWER_ONLY: ucPageNum = BikePower::PAGE_POWER; break; case BikePower::SensorType::TORQUE_WHEEL: ucPageNum = BikePower::PAGE_WHEEL_TORQUE; break; case BikePower::SensorType::TORQUE_CRANK: ucPageNum = BikePower::PAGE_CRANK_TORQUE; break; case BikePower::SensorType::CRANK_TORQUE_FREQ: ucPageNum = BikePower::PAGE_CTF; break; default: break; } // Handle interleaving: // The page with lowest frequency takes priority when there is a conflict // Every 121st message - one of the two global data pages, or the autozero support page (except CTF) if(bpPages->eType != BikePower::SensorType::CRANK_TORQUE_FREQ) { if(ulMessageCount % BikePower::INTERVAL_COMMON_AND_AZ == 0 && ulMessageCount != 0) { if(ucExtMesgType == 0) { ucPageNum = CommonData::PAGE80; ucExtMesgType++; } else if(ucExtMesgType == 1) { ucPageNum = CommonData::PAGE81; if(bTxAZ) ucExtMesgType++; else ucExtMesgType = 0; } else if(ucExtMesgType == 2) { ucPageNum = BikePower::PAGE_CALIBRATION; ucExtMesgType = 0; } } // Every 61st message - battery status (except CTF) else if((ulMessageCount % BikePower::INTERVAL_BATTERY == 0) && bTxPage82 && ulMessageCount != 0) { ucPageNum = CommonData::PAGE82; } // Every 2-9 messages (user defined) - basic power (wheel/crank torque) else if((ulMessageCount % ucPowerInterleave == 0) && bpPages->eType != BikePower::SensorType::POWER_ONLY && ulMessageCount != 0) { ucPageNum = BikePower::PAGE_POWER; } // Every 5 messages, if enabled, toque effectiveness & pedal smoothness page else if((ulMessageCount % BikePower::INTERVAL_TEPS == 1) && bTxPage19) // offset by 1, so it doesn't overlap with basic power if sent every 5 messages { ucPageNum = BikePower::PAGE_TEPS; } ulMessageCount++; } // Main Bike Power Data Pages if(ucPageNum >= BikePower::PAGE_POWER && ucPageNum <= BikePower::PAGE_CTF) { try { bpPages->EncodeMainData(ucPageNum, pucTxBuffer_); } catch(BikePower::Error^ errorMain) { } } // Calibration (Auto Zero Support) if(ucPageNum == BikePower::PAGE_CALIBRATION) bpPages->EncodeAutoZeroSupport(pucTxBuffer_); // Common Pages if(ucPageNum >= CommonData::PAGE80 && ucPageNum <= CommonData::PAGE82) { try { commonPages->Encode(ucPageNum, pucTxBuffer_); } catch(CommonData::Error^ errorCommon) { } } } /************************************************************************** * BikePowerSensor::HandleCalibration * * Decodes recived calibration messages and sends a response if required * * pucRxBuffer_: pointer to the receive buffer * * returns: N/A * **************************************************************************/ void BikePowerSensor::HandleCalibration(UCHAR* pucRxBuffer_) { BOOL bAutoZeroInvalid = FALSE; UCHAR ucPageNum = pucRxBuffer_[0]; if(ucPageNum == BikePower::PAGE_CALIBRATION) { try { bpPages->Decode(pucRxBuffer_); } catch(BikePower::Error^ errorBikePower) { if(errorBikePower->bUndefAutoZero) bAutoZeroInvalid = TRUE; } if(bpPages->ucRxCalibrationID == BikePower::CAL_REQUEST) SendCalibrationResponse(BikePower::CAL_REQUEST); if(bpPages->ucRxCalibrationID == BikePower::CAL_AUTOZERO_CONFIG) { // The received auto zero status (if valid) is updated directly when decoding BOOL bPrevCalStatus = bCalSuccess; if(bAutoZeroInvalid) bCalSuccess = FALSE; // Report as failure if an invalid status was received, regardless of how it is set on interface SendCalibrationResponse(BikePower::CAL_AUTOZERO_CONFIG); bCalSuccess = bPrevCalStatus; } if(bpPages->ucRxCalibrationID == BikePower::CAL_CTF) { if(bpPages->ucRxCTFMsgID == BikePower::CTF_SLOPE) { // Validate and send response if(bpPages->usCalSlope > BikePower::MAX_SLOPE) bpPages->usCalSlope = BikePower::MAX_SLOPE; else if(bpPages->usCalSlope < BikePower::MIN_SLOPE) bpPages->usCalSlope = BikePower::MIN_SLOPE; bpPages->usSlope10 = bpPages->usCalSlope; // Save on flash SendCalibrationResponse(BikePower::CTF_SLOPE); } else if(bpPages->ucRxCTFMsgID == BikePower::CTF_SERIAL) { // The received serial num is also already updated by the Bike Power decoding this->commonPages->ulSerialNum = bpPages->usCalSerialNum; // Save on flash SendCalibrationResponse(BikePower::CTF_SERIAL); } } } } void BikePowerSensor::HandleRequest(UCHAR* pucRxBuffer_) { //Request pages supported so far if(commonPages->ucReqPageNum != bpPages->PAGE_GET_SET_PARAMETERS) return; if(!ucBcastMessage) { ucTransmissionCount = (commonPages->ucReqTransResp & 0x7F); ucBcastCount = 1; // Reset transmission counter ucBcastMessage = commonPages->ucReqPageNum; // Set code to message for which to transmit } } void BikePowerSensor::HandleReceive(UCHAR *pucRxBuffer_) { switch(pucRxBuffer_[0]) { case BikePower::PAGE_GET_SET_PARAMETERS: if(bpPages->ucCrankLength < 0xFE) { numericUpDown_CrankLength->Value = (Decimal)(((float)bpPages->ucCrankLength * (float)0.5) + (float)110.0); checkBox_InvalidCrankLength->Checked = false; } else if(bpPages->ucCrankLength == 0xFE) { numericUpDown_CrankLength->Value = (Decimal)172.5; bpPages->ucCrankLength = 0x7D; checkBox_InvalidCrankLength->Checked = false; } else if(bpPages->ucCrankLength == 0xFF) checkBox_InvalidCrankLength->Checked = true; break; default: break; } } /************************************************************************** * BikePowerSensor::SendCalibrationResponse * * Sends calibration message according to the message code, using the values * for the parameters previously set on the GUI * * ucMsgCode_: message ID of the calibration message requiring a response * Supported messages: * - calibration response (success or fail, or for CTF sensors, calibration offset) * - auto zero calibration response * - serial CTF ack * - slope CTF ack * * returns: N/A * **************************************************************************/ void BikePowerSensor::SendCalibrationResponse(UCHAR ucMsgCode_) { // Send acknowledged message if(!ucBcastMessage) { ucBcastCount = 1; // Reset retransmission counter ucBcastMessage = ucMsgCode_; } } /************************************************************************** * BikePowerSensor::onTimerTock * * Simulates a sensor event, updating simulator data based on this event * Modifications to the timer interval are applied immediately after this * at ANTChannel * * usEventTime_: current time (ms) * * returns: N/A * **************************************************************************/ void BikePowerSensor::onTimerTock(USHORT eventTime_) { bpPages->usTime2000 = 2 * eventTime_; // Time in intervals of 1/2000s if(bTxPage82) // Update battery page if enabled { // Update operating time ulRunTime1000 += ulTimerInterval; // in ms while(ulRunTime1000/((USHORT) 1000 * (UCHAR) commonPages->eTimeResolution)) { (commonPages->ulOpTime)++; // elapsed time is updated every 2 or 16 seconds, depending on resolution ulRunTime1000 -= ((USHORT) 1000 * (UCHAR) commonPages->eTimeResolution); } } // Calculate next value, if sweeping if(ucSimDataType == SIM_SWEEP && !bStop && !bCoast) { // Value sweeps between max and min // 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 ULONG tempOffset = ulMaxValue1000 - ulMinValue1000; tempOffset = ((tempOffset & 0x800) >> 6) + ((tempOffset & 0x400) >> 5) + ((tempOffset & 0x200) >> 4) + 0xFF; if(bSweepAscending) ulCurValue1000 += tempOffset; else ulCurValue1000 -= tempOffset; // Ensure value is more than min and less than max if(ulCurValue1000 > ulMaxValue1000) { ulMaxValue1000 = ulMaxValue1000; bSweepAscending = FALSE; } if(ulCurValue1000 < ulMinValue1000) { ulCurValue1000 = ulMinValue1000; bSweepAscending = TRUE; } } // Update cadence if(bByCadence) { bpPages->ucCadence = (UCHAR) (ulCurValue1000/1000); // We already have cadence ulSpeed1000 = CadenceToSpeed(ulCurValue1000); // Get speed from cadence } else { bpPages->ucCadence = (UCHAR) (SpeedToCadence(ulCurValue1000)/1000); // Get cadence from speed ulSpeed1000 = ulCurValue1000; // We already have speed } if(bTxPedalPower) { if(bRightPedal) { UCHAR ucTemp = 0x00; ucTemp = ucCurPedalPwrValue | 0x80; // 0x80 mask for right pedal power contribution bpPages->ucPedalPower = ucTemp; } else bpPages->ucPedalPower = ucCurPedalPwrValue; } // Calculate sensor event interval (in ms) based on the location of the sensor, wheel or crank ULONG ulEventPeriod1000; if(bpPages->eType == BikePower::SensorType::TORQUE_WHEEL) { if(bpPages->ucCadence) ulEventPeriod1000 = ((ULONG) 60000 * ucNumWheelGearTeeth)/((ULONG) bpPages->ucCadence * ucNumCrankGearTeeth); // rpm -> ms else ulEventPeriod1000 = 600000; // No events, set interval to a very large value } else // crank sensor { if(bpPages->ucCadence) ulEventPeriod1000 = (ULONG) 60000/bpPages->ucCadence; // rpm -> ms else ulEventPeriod1000 = 600000; // No events, set interval to a very large value } // Update values, depending on type of updates (event or time synchronous) if(eUpdateType == BikePower::UpdateType::EVENT_SYNCH) { ulTimerInterval = ulEventPeriod1000; // Event synchronous, the next interval corresponds to a wheel or crank rotation //If stopped and event driven, there is no update at all, because the sensor never fires //If coasting, event driven, and a crank mounted sensor there is no update at all, because the sensor never fires if(bStop || (bCoast && (bpPages->eType == BikePower::SensorType::TORQUE_CRANK || bpPages->eType == BikePower::SensorType::CRANK_TORQUE_FREQ))) return; // If there is an event, update event count switch(bpPages->eType) { case BikePower::SensorType::TORQUE_WHEEL: bpPages->ucWTEventCount++; bpPages->ucWheelTicks++; break; case BikePower::SensorType::TORQUE_CRANK: bpPages->ucCTEventCount++; bpPages->ucCrankTicks++; break; case BikePower::SensorType::CRANK_TORQUE_FREQ: bpPages->ucCTFEventCount++; break; default: // no event synch Power Only sensor break; } } else { ulTimerInterval = (ULONG) 10000/ucEventFreq10; // Time syncrhonous, fixed interval. Freq in 1/10 Hz -> ms // The data page is updated every time for time synchronous sensors, so the event count is incremented if(bpPages->eType == BikePower::SensorType::TORQUE_WHEEL) bpPages->ucWTEventCount++; else if(bpPages->eType == BikePower::SensorType::TORQUE_CRANK) bpPages->ucCTEventCount++; // If there are wheel or crank revolutions, the ticks also increment if(!bStop && (!bCoast || (bCoast && bpPages->eType == BikePower::SensorType::TORQUE_WHEEL))) { ulEventTime1000 += ulTimerInterval; UCHAR ucTicks = (UCHAR) (ulEventTime1000/ulEventPeriod1000); // Number of ticks since last sensor event ulEventTime1000 = ulEventTime1000%ulEventPeriod1000; // Update last sensor event time for next calculation switch(bpPages->eType) { case BikePower::SensorType::TORQUE_WHEEL: bpPages->ucWheelTicks += ucTicks; break; case BikePower::SensorType::TORQUE_CRANK: bpPages->ucCrankTicks += ucTicks; break; default: // no time synch CTF break; } } } //If we are not pedalling there is, theoretically, no torque and thus no power; also cadence will be zero if(bStop || bCoast) { bpPages->usPower = 0; bpPages->ucCadence = 0; bpPages->ucPedalPower = 0; } else { bpPages->usPower = (USHORT) ( (((ULONG) PI256 * bpPages->ucCadence * usCrankTorque32)/((ULONG) 30 * 32) + 128) >> 8); // 2*pi*Cadence*CrankTorque/60; convert CrankTorque from 1/32Nm to Nm, and constant PI256 = pi * 256. Adding 0.5 (128/256) for rounding. } // Basic power page is updated every time (except if CTF) if(bpPages->eType != BikePower::SensorType::CRANK_TORQUE_FREQ) { bpPages->ucPowEventCount++; bpPages->usAcumPower += bpPages->usPower; } if(bpPages->eType == BikePower::SensorType::TORQUE_WHEEL) { //Don't update the wheel period if we stopped if(!bStop) bpPages->usAcumWheelPeriod2048 += (USHORT) ((ulEventPeriod1000*2048)/1000); // If we are coasting the wheel event is happening but there is, theoretically, no torque if(!bCoast) bpPages->usAcumTorque32 += usWheelTorque32; // Update cumulative torque } else if(bpPages->eType == BikePower::SensorType::TORQUE_CRANK) { //Don't update the crank period if it is not spinning if(!bStop && !bCoast) bpPages->usAcumCrankPeriod2048 += (USHORT) ((ulEventPeriod1000*2048)/1000); // If we are coasting there is no torque if(!bCoast) bpPages->usAcumTorque32 += usCrankTorque32; // Update cumulative torque } else if(bpPages->eType == BikePower::SensorType::CRANK_TORQUE_FREQ) { //If we are coasting there is no torque if(!bCoast) bpPages->usTorqueTicks += (USHORT) ((((ULONG) usCrankTorque32 * bpPages->usSlope10)/(32 * 10) + (ULONG) usCalOffset) * ulTimerInterval/1000); } // If transmission of cadence is disabled, blank it if(!bTxCadence) bpPages->ucCadence = BikePower::INVALID_CADENCE; // If transmission of pedal power is disabled, blank it if(!bTxPedalPower) bpPages->ucPedalPower = BikePower::RESERVED; } /*************************************************************************** * BikePowerSensor::UpdateDisplay * * Updates displayed simulator data on GUI * * returns: N/A * **************************************************************************/ void BikePowerSensor::UpdateDisplay() { if(bTxPage82) { this->label_Bat_ElpTimeDisplay->Text = ((ULONG) (commonPages->ulOpTime & CommonData::OPERATING_TIME_MASK) * (UCHAR) commonPages->eTimeResolution).ToString(); } if(ucSimDataType == SIM_SWEEP) this->numericUpDown_Sim_CurOutput->Value = System::Convert::ToDecimal((double)ulCurValue1000/1000); if(bStop) { // The speed is just a display, so we zero it so the user knows the bike is stopped this->label_Trn_InstSpeedDisplay->Text = "0"; // We display zero cadence to avoid user confusion, but the value transmitted will remain the same this->label_Trn_CadenceDisplay->Text = "0"; // No power can be generated with no movement this->label_Trn_PowerDisplay->Text = "0"; if(bpPages->ucPedalPower != BikePower::RESERVED) { this->label_Trn_PedalPwrDisplay->Text = "0"; this->label_Trn_PedalDisplay->Text = "---"; } return; // no other sensor data updated } else if(bCoast && bpPages->eType == BikePower::SensorType::TORQUE_CRANK) { // We display zero cadence to avoid user confusion, but the value transmitted will remain the same this->label_Trn_CadenceDisplay->Text = "0"; //No power can be generated with no torque this->label_Trn_PowerDisplay->Text = "0"; return; // no other sensor data updated } if(bCoast) { if(bpPages->ucPedalPower != BikePower::RESERVED) { this->label_Trn_PedalPwrDisplay->Text = "0"; this->label_Trn_PedalDisplay->Text = "---"; } } // Not all values are directly transmitted by all sensor types, but they are calculated at the receiver if(!bpPages->IsCadenceInvalid(bpPages->ucCadence)) this->label_Trn_CadenceDisplay->Text = bpPages->ucCadence.ToString(); else this->label_Trn_CadenceDisplay->Text = "Off"; this->label_Trn_InstSpeedDisplay->Text = ((double) ulSpeed1000/1000).ToString(); this->label_Trn_PowerDisplay->Text = bpPages->usPower.ToString(); if(bpPages->eType != BikePower::SensorType::CRANK_TORQUE_FREQ) { if(bpPages->ucPedalPower != BikePower::RESERVED) //Pedal power byte is used { UCHAR ucTemp = 0x00; if(0x80 & bpPages->ucPedalPower) this->label_Trn_PedalDisplay->Text = "Right"; // Right pedal power contribution else this->label_Trn_PedalDisplay->Text = "Unknown"; // Unknown pedal power contribution ucTemp = ((bpPages->ucPedalPower) & 0x7F); // 0x7F is a mask to remove "Pedal Differentiation Bit" and find // out pedal power percent value this->label_Trn_PedalPwrDisplay->Text = ucTemp.ToString(); } else { this->label_Trn_PedalPwrDisplay->Text = "Off"; this->label_Trn_PedalDisplay->Text = "---"; } } // Sensor specific data switch(bpPages->eType) { case BikePower::SensorType::POWER_ONLY: this->label_Trn_UpdateCountDisplay->Text = bpPages->ucPowEventCount.ToString(); this->label_Trn_AccumOneDisplay->Text = bpPages->usAcumPower.ToString(); break; case BikePower::SensorType::TORQUE_WHEEL: this->label_Trn_UpdateCountDisplay->Text = bpPages->ucWTEventCount.ToString(); this->label_Trn_EventCountDisplay->Text = bpPages->ucWheelTicks.ToString(); this->label_Trn_AccumTwoDisplay->Text = bpPages->usAcumTorque32.ToString(); this->label_Trn_AccumOneDisplay->Text = bpPages->usAcumWheelPeriod2048.ToString(); break; case BikePower::SensorType::TORQUE_CRANK: this->label_Trn_UpdateCountDisplay->Text = bpPages->ucCTEventCount.ToString(); this->label_Trn_EventCountDisplay->Text = bpPages->ucCrankTicks.ToString(); this->label_Trn_AccumTwoDisplay->Text = bpPages->usAcumTorque32.ToString(); this->label_Trn_AccumOneDisplay->Text = bpPages->usAcumCrankPeriod2048.ToString(); break; case BikePower::SensorType::CRANK_TORQUE_FREQ: this->label_Trn_UpdateCountDisplay->Text = bpPages->ucCTFEventCount.ToString(); this->label_Trn_AccumTwoDisplay->Text = bpPages->usTorqueTicks.ToString(); break; default: break; } } /*************************************************************************** * BikePowerSensor::UpdateCalDisplay * * Updates received calibration data * * returns: N/A * **************************************************************************/ void BikePowerSensor::UpdateCalDisplay() { // Update Auto Zero status if(!bpPages->bAutoZeroEnable) this->listBox_Cal_AZStatus->SelectedIndex = 2; // Not supported else if(bpPages->bAutoZeroOn) this->listBox_Cal_AZStatus->SelectedIndex = 1; // On else this->listBox_Cal_AZStatus->SelectedIndex = 0; // Off // Update slope this->numericUpDown_Ppg_SlopeConstant->Value = (System::Decimal)bpPages->usSlope10/10; // Update serial number this->textBox_Glb_SerialNumChange->Text = commonPages->ulSerialNum.ToString(); } /*************************************************************************** * BikePowerSensor::radioButton_SensorType_CheckedChanged * * Sets simulator type and updates interface to enable sensor specific features * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::radioButton_SensorType_CheckedChanged(System::Object^ sender, System::EventArgs^ e) { // Power Only Sensor if(this->radioButton_Power->Checked) { bpPages->eType = BikePower::SensorType::POWER_ONLY; this->label_Trn_AccumOne->Text = "Acc.Power:"; this->label_Trn_AccumOne->Visible = true; this->label_Trn_AccumOneDisplay->Visible = true; this->label_Trn_AccumTwo->Visible = false; this->label_Trn_AccumTwoDisplay->Visible = false; this->checkBox_Cal_TorqAZMesg->Enabled = true; this->label_Cal_CalNum->Text = "Calibration Number To Send:"; this->numericUpDown_Cal_CalNumber->Increment = System::Decimal(gcnew cli::array< System::Int32 >(4) {1000, 0, 0, 0}); this->numericUpDown_Cal_CalNumber->Maximum = System::Decimal(gcnew cli::array< System::Int32 >(4) {32767, 0, 0, 0}); this->numericUpDown_Cal_CalNumber->Minimum = System::Decimal(gcnew cli::array< System::Int32 >(4) {32768, 0, 0, System::Int32::MinValue}); this->radioButton_Ppg_EventOnTime->Enabled = true; this->numericUpDown_Ppg_TimerEventFreq->Enabled = true; this->radioButton_Ppg_EventOnTime->Checked = true; this->radioButton_Ppg_EventOnRotation->Enabled = false; this->numericUpDown_Ppg_BasicPowerInterlvTime->Enabled = false; this->numericUpDown_Ppg_SlopeConstant->Enabled = false; this->checkBox_Bat_Status->Enabled = true; this->checkBox_Bat_Status->Checked = true; this->numericUpDown_Cal_CalNumber->Maximum = BikePower::MAX_CALDATA; this->numericUpDown_Cal_CalNumber->Minimum = BikePower::MIN_CALDATA; this->checkBox_Ppg_TxCadence->Enabled = true; this->checkBox_Ppg_TxPedalPwr->Enabled = true; this->numericUpDown_PedalPwr_CurrOutput->Enabled = true; this->checkBox_RightPedal->Enabled = true; this->tabControl_TEPS->Enabled = true; } // Wheel Torque Sensor else if(this->radioButton_WheelTorque->Checked) { bpPages->eType = BikePower::SensorType::TORQUE_WHEEL; this->label_Trn_AccumOne->Text = "Acc.Wheel P:"; this->label_Trn_AccumTwo->Text = "Acc.Torque:"; this->label_Trn_AccumOne->Visible = true; this->label_Trn_AccumOneDisplay->Visible = true; this->label_Trn_AccumTwo->Visible = true; this->label_Trn_AccumTwoDisplay->Visible = true; this->checkBox_Cal_TorqAZMesg->Enabled = true; this->label_Cal_CalNum->Text = "Calibration Number To Send:"; this->numericUpDown_Cal_CalNumber->Increment = System::Decimal(gcnew cli::array< System::Int32 >(4) {1000, 0, 0, 0}); this->numericUpDown_Cal_CalNumber->Maximum = System::Decimal(gcnew cli::array< System::Int32 >(4) {32767, 0, 0, 0}); this->numericUpDown_Cal_CalNumber->Minimum = System::Decimal(gcnew cli::array< System::Int32 >(4) {32768, 0, 0, System::Int32::MinValue}); this->radioButton_Ppg_EventOnTime->Enabled = true; this->numericUpDown_Ppg_TimerEventFreq->Enabled = true; this->radioButton_Ppg_EventOnRotation->Enabled = true; this->numericUpDown_Ppg_BasicPowerInterlvTime->Enabled = true; this->numericUpDown_Ppg_SlopeConstant->Enabled = false; this->checkBox_Bat_Status->Enabled = true; this->checkBox_Bat_Status->Checked = true; this->numericUpDown_Cal_CalNumber->Maximum = BikePower::MAX_CALDATA; this->numericUpDown_Cal_CalNumber->Minimum = BikePower::MIN_CALDATA; this->checkBox_Ppg_TxCadence->Enabled = true; this->checkBox_Ppg_TxPedalPwr->Enabled = true; this->numericUpDown_PedalPwr_CurrOutput->Enabled = true; this->checkBox_RightPedal->Enabled = true; this->tabControl_TEPS->Enabled = true; } // Crank Torque Sensor else if(this->radioButton_CrankTorque->Checked) { bpPages->eType = BikePower::SensorType::TORQUE_CRANK; this->label_Trn_AccumOne->Text = "Acc.Crank P:"; this->label_Trn_AccumTwo->Text = "Acc.Torque:"; this->label_Trn_AccumOne->Visible = true; this->label_Trn_AccumOneDisplay->Visible = true; this->label_Trn_AccumTwo->Visible = true; this->label_Trn_AccumTwoDisplay->Visible = true; this->checkBox_Cal_TorqAZMesg->Enabled = true; this->label_Cal_CalNum->Text = "Calibration Number To Send:"; this->numericUpDown_Cal_CalNumber->Increment = System::Decimal(gcnew cli::array< System::Int32 >(4) {1000, 0, 0, 0}); this->numericUpDown_Cal_CalNumber->Maximum = System::Decimal(gcnew cli::array< System::Int32 >(4) {32767, 0, 0, 0}); this->numericUpDown_Cal_CalNumber->Minimum = System::Decimal(gcnew cli::array< System::Int32 >(4) {32768, 0, 0, System::Int32::MinValue}); this->radioButton_Ppg_EventOnTime->Enabled = true; this->numericUpDown_Ppg_TimerEventFreq->Enabled = true; this->radioButton_Ppg_EventOnRotation->Enabled = true; this->numericUpDown_Ppg_BasicPowerInterlvTime->Enabled = true; this->numericUpDown_Ppg_SlopeConstant->Enabled = false; this->checkBox_Bat_Status->Enabled = true; this->checkBox_Bat_Status->Checked = true; this->numericUpDown_Cal_CalNumber->Maximum = BikePower::MAX_CALDATA; this->numericUpDown_Cal_CalNumber->Minimum = BikePower::MIN_CALDATA; this->checkBox_Ppg_TxCadence->Enabled = true; this->checkBox_Ppg_TxPedalPwr->Enabled = true; this->numericUpDown_PedalPwr_CurrOutput->Enabled = true; this->checkBox_RightPedal->Enabled = true; this->tabControl_TEPS->Enabled = true; } // Crank Torque Frequency Sensor else { bpPages->eType = BikePower::SensorType::CRANK_TORQUE_FREQ; this->label_Trn_AccumTwo->Text = "Acc.Torque:"; this->label_Trn_AccumOne->Visible = false; this->label_Trn_AccumOneDisplay->Visible = false; this->label_Trn_AccumTwo->Visible = true; this->label_Trn_AccumTwoDisplay->Visible = true; this->checkBox_Cal_TorqAZMesg->Checked = false; this->checkBox_Cal_TorqAZMesg->Enabled = false; this->label_Cal_CalNum->Text = "Current Torque Ticks Offset:"; this->numericUpDown_Cal_CalNumber->Value = 100; this->numericUpDown_Cal_CalNumber->Increment = System::Decimal(gcnew cli::array< System::Int32 >(4) {100, 0, 0, 0}); this->numericUpDown_Cal_CalNumber->Maximum = System::Decimal(gcnew cli::array< System::Int32 >(4) {65535, 0, 0, 0}); this->numericUpDown_Cal_CalNumber->Minimum = System::Decimal(gcnew cli::array< System::Int32 >(4) {0, 0, 0, 0}); this->radioButton_Ppg_EventOnRotation->Enabled = true; this->numericUpDown_Ppg_BasicPowerInterlvTime->Enabled = false; this->numericUpDown_Ppg_SlopeConstant->Enabled = true; this->radioButton_Ppg_EventOnRotation->Checked = true; this->radioButton_Ppg_EventOnTime->Enabled = false; this->numericUpDown_Ppg_TimerEventFreq->Enabled = false; this->checkBox_Bat_Status->Enabled = false; this->checkBox_Bat_Status->Checked = false; // Validate calibration offset if(this->numericUpDown_Cal_CalNumber->Value < BikePower::MIN_OFFSET) this->numericUpDown_Cal_CalNumber->Value = BikePower::MIN_OFFSET; else if(this->numericUpDown_Cal_CalNumber-> Value > BikePower::MAX_OFFSET) this->numericUpDown_Cal_CalNumber->Value = BikePower::MIN_OFFSET; this->numericUpDown_Cal_CalNumber->Maximum = BikePower::MAX_OFFSET; this->numericUpDown_Cal_CalNumber->Minimum = BikePower::MIN_OFFSET; this->checkBox_Ppg_TxCadence->Enabled = false; this->checkBox_Ppg_TxPedalPwr->Enabled = false; this->numericUpDown_PedalPwr_CurrOutput->Enabled = false; this->checkBox_RightPedal->Enabled = false; this->label_Trn_PedalDisplay->Text = "---"; this->label_Trn_PedalPwrDisplay->Text = "---"; this->tabControl_TEPS->Enabled = false; this->checkBox_EnableTEPS->Checked = false; } } /************************************************************************** * BikePowerSensor::numericUpDown_Ppg_BasicPowerInterlvTime_ValueChanged * * Updates interleaving period for basic power page * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::numericUpDown_Ppg_BasicPowerInterlvTime_ValueChanged(System::Object^ sender, System::EventArgs^ e) { ucPowerInterleave = System::Convert::ToByte(this->numericUpDown_Ppg_BasicPowerInterlvTime->Value); } /************************************************************************** * BikePowerSensor::numericUpDown_Ppg_TimerEventFreq_ValueChanged * * Updates event frequency for time synchronous simulation * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::numericUpDown_Ppg_TimerEventFreq_ValueChanged(System::Object^ sender, System::EventArgs^ e) { ucEventFreq10 = System::Convert::ToByte(10 * this->numericUpDown_Ppg_TimerEventFreq->Value); } /************************************************************************** * BikePowerSensor::radioButton_Ppg_UpdateType_CheckedChanged * * Updates type of simulation (event/time synch) * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::radioButton_Ppg_UpdateType_CheckedChanged(System::Object^ sender, System::EventArgs^ e) { if(this->radioButton_Ppg_EventOnRotation->Checked) eUpdateType = BikePower::UpdateType::EVENT_SYNCH; else eUpdateType = BikePower::UpdateType::TIME_SYNCH; } /************************************************************************** * BikePowerSensor::button_GlobalDataUpdate_Click * * Validates and updates product information, from user input (GUI) * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::button_GlobalDataUpdate_Click(System::Object^ sender, System::EventArgs^ e) { this->label_Glb_GlobalDataError->Visible = false; this->label_Glb_GlobalDataError->Text = "Error: "; // Convert and catch failed conversions try{ commonPages->usMfgID = System::Convert::ToUInt16(this->textBox_Glb_ManfIDChange->Text); } catch(...){ this->label_Glb_GlobalDataError->Text = System::String::Concat(this->label_Glb_GlobalDataError->Text, " MFID"); this->label_Glb_GlobalDataError->Visible = true; } try{ commonPages->ulSerialNum = System::Convert::ToUInt32(this->textBox_Glb_SerialNumChange->Text); } catch(...){ this->label_Glb_GlobalDataError->Text = System::String::Concat(this->label_Glb_GlobalDataError->Text, " Ser#"); this->label_Glb_GlobalDataError->Visible = true; } try{ commonPages->ucHwVersion = System::Convert::ToByte(this->textBox_Glb_HardwareVerChange->Text); } catch(...){ this->label_Glb_GlobalDataError->Text = System::String::Concat(this->label_Glb_GlobalDataError->Text, " HWVr"); this->label_Glb_GlobalDataError->Visible = true; } try{ commonPages->ucSwVersion = System::Convert::ToByte(this->textBox_Glb_SoftwareVerChange->Text); } catch(...){ this->label_Glb_GlobalDataError->Text = System::String::Concat(this->label_Glb_GlobalDataError->Text, " SWVr"); this->label_Glb_GlobalDataError->Visible = true; } try{ commonPages->usModelNum = System::Convert::ToUInt16(this->textBox_Glb_ModelNumChange->Text); } catch(...){ this->label_Glb_GlobalDataError->Text = System::String::Concat(this->label_Glb_GlobalDataError->Text, " Mdl#"); this->label_Glb_GlobalDataError->Visible = true; } } /************************************************************************** * BikePowerSensor::bcheckBox_Bat_Status_CheckedChanged * * Enables/disables transmission of battery page * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::checkBox_Bat_Status_CheckedChanged(System::Object^ sender, System::EventArgs^ e) { bTxPage82 = this->checkBox_Bat_Status->Checked; } /************************************************************************** * BikePowerSensor::radioButton_Bat_ElpUnits_CheckedChanged * * Selects time resolution (two or sixteen seconds) * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::radioButton_Bat_ElpUnits_CheckedChanged(System::Object^ sender, System::EventArgs^ e) { if(this->radioButton_Bat_Elp2Units->Checked && commonPages->eTimeResolution == CommonData::TimeResolution::SIXTEEN) { commonPages->eTimeResolution = CommonData::TimeResolution::TWO; commonPages->ulOpTime = commonPages->ulOpTime << 3; // OpTime*8: Convert from interval of 16 seconds to interval of 2 seconds } else if(this->radioButton_Bat_Elp16Units->Checked && commonPages->eTimeResolution == CommonData::TimeResolution::TWO) { commonPages->eTimeResolution = CommonData::TimeResolution::SIXTEEN; commonPages->ulOpTime = commonPages->ulOpTime >> 3; // OpTime/8: Convert from interval of 2 seconds to interval of 16 seconds } this->label_Bat_ElpTimeDisplay->Text = ((ULONG) (commonPages->ulOpTime & CommonData::OPERATING_TIME_MASK) * (UCHAR) commonPages->eTimeResolution).ToString(); ulRunTime1000 = 0; // reset run time value so that next update happens in 2 or 16 seconds } /************************************************************************** * BikePowerSensor::button_ElpTimeUpdate_Click * * Updates cumulative operating time (in seconds) * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::button_ElpTimeUpdate_Click(System::Object^ sender, System::EventArgs^ e) { this->button_Bat_ElpTimeUpdate->Text = "Update"; try{ ULONG ulTimeS = System::Convert::ToUInt32(this->textBox_Bat_ElpTimeChange->Text); // Get time in seconds if(commonPages->eTimeResolution == CommonData::TimeResolution::TWO) // Conver to two second resolution { if(ulTimeS > CommonData::MAX_2SEC) ulTimeS = CommonData::MAX_2SEC; commonPages->ulOpTime = ulTimeS >> 1; // Time/2 } else { // Convert to sixteen second resolution if(ulTimeS > CommonData::MAX_16SEC) ulTimeS = CommonData::MAX_16SEC; commonPages->ulOpTime = ulTimeS >> 4; // Time/16 } this->label_Bat_ElpTimeDisplay->Text = ulTimeS.ToString(); } catch(...){ this->button_Bat_ElpTimeUpdate->Text = "Retry"; // Invalid input, try again } } /************************************************************************** * BikePowerSensor::numericUpDown_Bat_VoltInt_ValueChanged * * Updates coarse (integer) battery voltage * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::numericUpDown_Bat_VoltInt_ValueChanged(System::Object^ sender, System::EventArgs^ e) { commonPages->usBatVoltage256 = ((USHORT) System::Convert::ToByte(this->numericUpDown_Bat_VoltInt->Value)) << 8 | (commonPages->usBatVoltage256 & 0xFF); // Integer portion in high byte this->label_Voltage_Display->Text = ((double) commonPages->usBatVoltage256/256).ToString(); // Display in V } /************************************************************************** * BikePowerSensor::numericUpDown_Bat_VoltInt_ValueChanged * * Updates fractional battery voltage * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::numericUpDown_Bat_VoltFrac_ValueChanged(System::Object^ sender, System::EventArgs^ e) { commonPages->usBatVoltage256 = System::Convert::ToByte(this->numericUpDown_Bat_VoltFrac->Value) | (commonPages->usBatVoltage256 & 0xFF00); // Fractional portion in low byte this->label_Voltage_Display->Text = ((double) commonPages->usBatVoltage256/256).ToString(); // Display in V } /************************************************************************** * BikePowerSensor::checkBox_Bat_Voltage_CheckedChanged * * Enables/disables coarse battery voltage * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::checkBox_Bat_Voltage_CheckedChanged(System::Object^ sender, System::EventArgs^ e) { this->numericUpDown_Bat_VoltInt->Enabled = this->checkBox_Bat_Voltage->Checked; this->numericUpDown_Bat_VoltFrac->Enabled = this->checkBox_Bat_Voltage->Checked; this->checkBox_Bat_FracVolt->Enabled = this->checkBox_Bat_Voltage->Checked; if(!this->checkBox_Bat_Voltage->Checked) { commonPages->usBatVoltage256 = (USHORT) CommonData::BATTERY_VOLTAGE_INVALID << 8 | (commonPages->usBatVoltage256 & 0xFF); // Integer portion in high byte this->label_Voltage_Display->Text = "Off"; } else { commonPages->usBatVoltage256 = ((USHORT) System::Convert::ToByte(this->numericUpDown_Bat_VoltInt->Value)) << 8 | (commonPages->usBatVoltage256 & 0xFF); // Integer portion in high byte this->label_Voltage_Display->Text = ((double) commonPages->usBatVoltage256/256).ToString(); // Display in V } } /************************************************************************** * BikePowerSensor::checkBox_Bat_FracVolt_CheckedChanged * * Enables/disables fractional battery voltage * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::checkBox_Bat_FracVolt_CheckedChanged(System::Object^ sender, System::EventArgs^ e) { this->numericUpDown_Bat_VoltFrac->Enabled = this->checkBox_Bat_FracVolt->Checked; if(!this->checkBox_Bat_FracVolt->Checked) commonPages->usBatVoltage256 &= 0xFF00; // Fractional portion in low byte, set to zero else commonPages->usBatVoltage256 = System::Convert::ToByte(this->numericUpDown_Bat_VoltFrac->Value) | (commonPages->usBatVoltage256 & 0xFF00); // Fractional portion in low byte, from GUI this->label_Voltage_Display->Text = ((double) commonPages->usBatVoltage256/256).ToString(); // Display in V } /************************************************************************** * BikePowerSensor::listBox_Bat_Status_SelectedIndexChanged * * Sets battery status * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::listBox_Bat_Status_SelectedIndexChanged(System::Object^ sender, System::EventArgs^ e) { if(this->listBox_Bat_Status->SelectedIndex == 5) // Set manually as indexes don't match enum (due to reserved bits) commonPages->eBatStatus = CommonData::BatStatus::INVALID; else commonPages->eBatStatus = (CommonData::BatStatus) (this->listBox_Bat_Status->SelectedIndex + 1); } /************************************************************************** * BikePowerSensor::numericUpDown_Ppg_SlopeConstant_ValueChanged * * Updates slope * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::numericUpDown_Ppg_SlopeConstant_ValueChanged(System::Object^ sender, System::EventArgs^ e) { bpPages->usSlope10 = System::Convert::ToUInt16(10 * this->numericUpDown_Ppg_SlopeConstant->Value); } /************************************************************************** * BikePowerSensor::numericUpDown_Sim_WheelCircumference_ValueChanged * * Updates wheel circumference (cm) * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::numericUpDown_Sim_WheelCircumference_ValueChanged(System::Object^ sender, System::EventArgs^ e) { ucWheelCircumference100 = System::Convert::ToByte(this->numericUpDown_Sim_WheelCircumference->Value); } /************************************************************************** * BikePowerSensor::checkBox_Sim_Coast_CheckedChanged * * Enables/disables coasting * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::checkBox_Sim_Coast_CheckedChanged(System::Object^ sender, System::EventArgs^ e) { bCoast = this->checkBox_Sim_Coast->Checked; this->checkBox_Sim_Stop->Enabled = !bCoast; } /************************************************************************** * BikePowerSensor::checkBox_Sim_Stop_CheckedChanged * * Enables/disables stopping * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::checkBox_Sim_Stop_CheckedChanged(System::Object^ sender, System::EventArgs^ e) { bStop = this->checkBox_Sim_Stop->Checked; this->checkBox_Sim_Coast->Enabled = !bStop; } /************************************************************************** * BikePowerSensor::checkBox_Sim_Sweeping_CheckedChanged * * Enables/disables sweeping * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::checkBox_Sim_Sweeping_CheckedChanged(System::Object^ sender, System::EventArgs^ e) { this->numericUpDown_Sim_CurOutput->Enabled = !this->numericUpDown_Sim_CurOutput->Enabled; this->numericUpDown_Sim_MinOutput->Enabled = !this->numericUpDown_Sim_MinOutput->Enabled; this->numericUpDown_Sim_MaxOutput->Enabled = !this->numericUpDown_Sim_MaxOutput->Enabled; if(this->checkBox_Sim_Sweeping->Checked) { ucSimDataType = SIM_SWEEP; bSweepAscending = TRUE; } else { ucSimDataType = SIM_FIXED; } } /************************************************************************** * BikePowerSensor::numericUpDown_Sim_WheelTorque_ValueChanged * * Updates wheel and crank torque * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::numericUpDown_Sim_WheelTorque_ValueChanged(System::Object^ sender, System::EventArgs^ e) { usWheelTorque32 = System::Convert::ToUInt16(32 * this->numericUpDown_Sim_WheelTorque->Value); // Update wheel torque from UI CalculateCrankTorque(); this->numericUpDown_Sim_WheelTorque->Value = (System::Decimal) usWheelTorque32/32; // Update values displayed this->numericUpDown_Sim_CrankTorque->Value = (System::Decimal) usCrankTorque32/32; } /************************************************************************** * BikePowerSensor::numericUpDown_Sim_CrankTorque_ValueChanged * * Updates wheel and crank torque * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::numericUpDown_Sim_CrankTorque_ValueChanged(System::Object^ sender, System::EventArgs^ e) { usCrankTorque32 = System::Convert::ToUInt16(32 * this->numericUpDown_Sim_CrankTorque->Value); // Update crank torque from UI CalculateWheelTorque(); this->numericUpDown_Sim_CrankTorque->Value = (System::Decimal) usCrankTorque32/32; // Update values displayed this->numericUpDown_Sim_WheelTorque->Value = (System::Decimal) usWheelTorque32/32; } /************************************************************************** * BikePowerSensor::numericUpDown_Sim_CrankGearTeeth_ValueChanged * * Updates crank gear teeth, and wheel/crank torque * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::numericUpDown_Sim_CrankGearTeeth_ValueChanged(System::Object^ sender, System::EventArgs^ e) { ucNumCrankGearTeeth = System::Convert::ToByte(this->numericUpDown_Sim_CrankGearTeeth->Value); // Update from UI CalculateWheelTorque(); this->numericUpDown_Sim_CrankTorque->Value = (System::Decimal) usCrankTorque32/32; // Update values displayed this->numericUpDown_Sim_WheelTorque->Value = (System::Decimal) usWheelTorque32/32; } /************************************************************************** * BikePowerSensor::numericUpDown_Sim_WheelGearTeeth_ValueChanged * * Updates wheel gear teeth, and wheel/crank torque * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::numericUpDown_Sim_WheelGearTeeth_ValueChanged(System::Object^ sender, System::EventArgs^ e) { ucNumWheelGearTeeth = System::Convert::ToByte(this->numericUpDown_Sim_WheelGearTeeth->Value); // Update from UI CalculateCrankTorque(); this->numericUpDown_Sim_WheelTorque->Value = (System::Decimal) usWheelTorque32/32; // Update values displayed this->numericUpDown_Sim_CrankTorque->Value = (System::Decimal) usCrankTorque32/32; } /************************************************************************** * BikePowerSensor::numericUpDown_PedalPwr_CurOutput_ValueChanged * * Updates the current power pedal value, when it changes * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::numericUpDown_PedalPwr_CurrOutput_ValueChanged(System::Object^ sender, System::EventArgs^ e) { ucCurPedalPwrValue = System::Convert::ToByte(this->numericUpDown_PedalPwr_CurrOutput->Value); //Update from UI } /************************************************************************** * BikePowerSensor::numericUpDown_Sim_CurOutput_ValueChanged * * Updates and converts units of the current speed or cadence value, when it changes * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::numericUpDown_Sim_CurOutput_ValueChanged(System::Object^ sender, System::EventArgs^ e) { // Ignore changes when sweeping if(this->numericUpDown_Sim_CurOutput->Enabled) { ulCurValue1000 = System::Convert::ToUInt32(1000 * this->numericUpDown_Sim_CurOutput->Value); // Convert to 1/1000 resolution ForceUpdate(); } } /************************************************************************** * BikePowerSensor::numericUpDown_Sim_MinMaxOutput_ValueChanged * * If the user has changed the min or max speed, validate that * minimum < current < maximum * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::numericUpDown_Sim_MinMaxOutput_ValueChanged(System::Object^ sender, System::EventArgs^ e) { ULONG ulPrevValue1000 = ulCurValue1000; // Check only if user changes values (not sweeping) if(this->numericUpDown_Sim_MinOutput->Enabled && this->numericUpDown_Sim_MaxOutput->Enabled) { // Check minnumericUpDown_Sim_MinOutput->Value < this->numericUpDown_Sim_MaxOutput->Value) { ulMinValue1000 = System::Convert::ToUInt32(1000 * this->numericUpDown_Sim_MinOutput->Value); ulMaxValue1000 = System::Convert::ToUInt32(1000 * this->numericUpDown_Sim_MaxOutput->Value); if(ulCurValue1000 > ulMaxValue1000) ulCurValue1000 = ulMaxValue1000; else if(ulCurValue1000 < ulMinValue1000) ulCurValue1000 = ulMinValue1000; if(ulCurValue1000 != ulPrevValue1000) { this->numericUpDown_Sim_CurOutput->Value = System::Convert::ToDecimal((double)ulCurValue1000/1000); ForceUpdate(); } } else { // If the values were invalid, set numeric values to last valid values this->numericUpDown_Sim_MinOutput->Value = System::Convert::ToDecimal((double)ulMinValue1000/1000); this->numericUpDown_Sim_MaxOutput->Value = System::Convert::ToDecimal((double)ulMaxValue1000/1000); } } } /************************************************************************** * BikePowerSensor::radioButton_SimByChanged * * Selects simulation by cadence or speed, and converts last value to appropiate * unit for consistency * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::radioButton_SimByChanged(System::Object^ sender, System::EventArgs^ e) { bByCadence = (BOOL) this->radioButton_Sim_SimByCadence->Checked; // Disable boxes momentarily, to keep values from being checked and modified again as they change this->numericUpDown_Sim_CurOutput->Enabled = FALSE; this->numericUpDown_Sim_MaxOutput->Enabled = FALSE; this->numericUpDown_Sim_MinOutput->Enabled = FALSE; // Update input data if(bByCadence) { // Selected cadence (switching from speed to cadence) // Latest speed and cadence values are maintained, so the values that represented speed are converted to cadence ulCurValue1000 = SpeedToCadence(ulCurValue1000); ulMinValue1000 = SpeedToCadence(ulMinValue1000); ulMaxValue1000 = SpeedToCadence(ulMaxValue1000); } else { // Selected speed (switching from cadence to speed) // Latest speed and cadence value are maintained, so values representing cadence are converted to speed ulCurValue1000 = CadenceToSpeed(ulCurValue1000); ulMinValue1000 = CadenceToSpeed(ulMinValue1000); ulMaxValue1000 = CadenceToSpeed(ulMaxValue1000); } // Validate values do not exceed max if(ulCurValue1000 > 255000) ulCurValue1000 = 255000; if(ulMinValue1000 > 255000) ulMinValue1000 = 255000; if(ulMaxValue1000 > 255000) ulMaxValue1000 = 255000; // Update output this->numericUpDown_Sim_CurOutput->Value = System::Convert::ToDecimal((double)ulCurValue1000/1000); this->numericUpDown_Sim_MinOutput->Value = System::Convert::ToDecimal((double)ulMinValue1000/1000); this->numericUpDown_Sim_MaxOutput->Value = System::Convert::ToDecimal((double)ulMaxValue1000/1000); // Enable input boxes again so that they can be modified by user if(ucSimDataType == SIM_SWEEP) { this->numericUpDown_Sim_MaxOutput->Enabled = TRUE; this->numericUpDown_Sim_MinOutput->Enabled = TRUE; } else { this->numericUpDown_Sim_CurOutput->Enabled = TRUE; } } /************************************************************************** * BikePowerSensor::checkBox_Cal_TorqAZMesg_CheckedChanged * * Enables/disables transmission of auto zero support page * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::checkBox_Cal_TorqAZMesg_CheckedChanged(System::Object^ sender, System::EventArgs^ e) { bTxAZ = (BOOL) this->checkBox_Cal_TorqAZMesg->Checked; } /************************************************************************** * BikePowerSensor::listBox_Cal_AZStatus_SelectedIndexChanged * * Sets auto zero status * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::listBox_Cal_AZStatus_SelectedIndexChanged(System::Object^ sender, System::EventArgs^ e) { switch(this->listBox_Cal_AZStatus->SelectedIndex) { case 0: bpPages->bAutoZeroEnable = TRUE; bpPages->bAutoZeroOn = FALSE; break; case 1: bpPages->bAutoZeroEnable = TRUE; bpPages->bAutoZeroOn = TRUE; break; case 2: bpPages->bAutoZeroEnable = FALSE; bpPages->bAutoZeroOn = FALSE; break; default: break; } } /************************************************************************** * BikePowerSensor::numericUpDown_Cal_RawTorque_ValueChanged * * Updates raw torque for auto zero support page * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::numericUpDown_Cal_RawTorque_ValueChanged(System::Object^ sender, System::EventArgs^ e) { bpPages->sRawTorque = System::Convert::ToInt16(this->numericUpDown_Cal_RawTorque->Value); } /************************************************************************** * BikePowerSensor::numericUpDown_Cal_OffsetTorque_ValueChanged * * Updates offset torque for auto zero support page * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::numericUpDown_Cal_OffsetTorque_ValueChanged(System::Object^ sender, System::EventArgs^ e) { bpPages->sOffsetTorque = System::Convert::ToInt16(this->numericUpDown_Cal_OffsetTorque->Value); } /************************************************************************** * BikePowerSensor::numericUpDown_Cal_CalNumber_ValueChanged * * Updates the calibration number to send, depending on the sensor type * When dealing with CTF sensors, the input is converted to an unsigned short, * and when dealing with other sensors, the input is converted to a signed * short * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::numericUpDown_Cal_CalNumber_ValueChanged(System::Object^ sender, System::EventArgs^ e) { if(bpPages->eType == BikePower::SensorType::CRANK_TORQUE_FREQ) usCalOffset = System::Convert::ToUInt16(this->numericUpDown_Cal_CalNumber->Value); else sCalData = System::Convert::ToInt16(this->numericUpDown_Cal_CalNumber->Value); } /************************************************************************** * BikePowerSensor::radioButton_Cal_Success_CheckedChanged * * Sets next calibration response to report a success or failure * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::radioButton_Cal_Success_CheckedChanged(System::Object^ sender, System::EventArgs^ e) { bCalSuccess = (BOOL) this->radioButton_Cal_Success->Checked; } /************************************************************************** * BikePowerSensor::checkBox_Ppg_TxCadence_CheckedChanged * * Enables/disables transmission of cadence in power and torque data pages * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::checkBox_Ppg_TxCadence_CheckedChanged(System::Object^ sender, System::EventArgs^ e) { bTxCadence = (BOOL) this->checkBox_Ppg_TxCadence->Checked; } /************************************************************************** * BikePowerSensor::checkBox_Ppg_TxPedalPwr_CheckedChanged * * Enables/disables transmission of pedal power in power data page * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::checkBox_Ppg_TxPedalPwr_CheckedChanged(System::Object^ sender, System::EventArgs^ e) { bTxPedalPower = (BOOL) this->checkBox_Ppg_TxPedalPwr->Checked; if(bTxPedalPower) { this->numericUpDown_PedalPwr_CurrOutput->Enabled = true; this->checkBox_RightPedal->Enabled = true; } else { this->numericUpDown_PedalPwr_CurrOutput->Enabled = false; this->checkBox_RightPedal->Enabled = false; } } /************************************************************************** * BikePowerSensor::checkBox_RightPedal_CheckedChanged * * Enables/disables right pedal % power contribution * * returns: N/A * **************************************************************************/ System::Void BikePowerSensor::checkBox_RightPedal_CheckedChanged(System::Object^ sender, System::EventArgs^ e) { bRightPedal = (BOOL) this->checkBox_RightPedal->Checked; } /************************************************************************** * BikePowerSensor::CalculateWheelTorque * * Calculates wheel torque from crank torque, and updates the crank torque * if the value is invalid * * returns: N/A * **************************************************************************/ void BikePowerSensor::CalculateWheelTorque() { usWheelTorque32 = (ucNumWheelGearTeeth * usCrankTorque32)/ucNumCrankGearTeeth; // Calculate wheel torque if(usWheelTorque32 > 3196) // 99.9 * 32 { usWheelTorque32 = 3196; usCrankTorque32 = (ucNumCrankGearTeeth * usWheelTorque32)/ucNumWheelGearTeeth; // Calculate crank torque from max wheel torque } } /************************************************************************** * BikePowerSensor::CalculateCrankTorque * * Calculates crank torque from wheel torque, and updates the wheel torque * if the value is invalid * * returns: N/A * **************************************************************************/ void BikePowerSensor::CalculateCrankTorque() { usCrankTorque32 = (ucNumCrankGearTeeth * usWheelTorque32)/ucNumWheelGearTeeth; // Calculate crank torque if(usCrankTorque32 > 3196) // 99.9 * 32 { usCrankTorque32 = 3196; usWheelTorque32 = (ucNumWheelGearTeeth * usCrankTorque32)/ucNumCrankGearTeeth; // Calculate wheel torque from max crank torque } } /************************************************************************** * BikePowerSensor::SpeedToCadence * * Converts from speed (m/h) to cadence (1/1000 rpm) * * ulSpeed1000_ : Speed (m/h) * * returns: Cadence (1/1000 rpm) * **************************************************************************/ ULONG BikePowerSensor::SpeedToCadence(ULONG ulSpeed1000_) { ULONG ulCadence1000; ulCadence1000 = 100 * ((ulSpeed1000_ * 1000 * ucNumWheelGearTeeth)/((ULONG) 60 * ucWheelCircumference100 * ucNumCrankGearTeeth)); // m/h to rpm, circumference is in cm return ulCadence1000; } /************************************************************************** * BikePowerSensor::CadenceToSpeed * * Converts from cadence (1/1000 rpm) to speed (m/h) * * ulCadence1000_ : Cadence (1/1000 rpm) * * returns: Speed (m/h) * **************************************************************************/ ULONG BikePowerSensor::CadenceToSpeed(ULONG ulCadence1000_) { ULONG ulSpeed1000; ulSpeed1000 = (ulCadence1000_/100 * 60 * ucWheelCircumference100 * ucNumCrankGearTeeth)/(1000* ucNumWheelGearTeeth); // rpm to m/h, circumference is in cm return ulSpeed1000; } /************************************************************************** * BikePowerSensor::ForceUpdate * * Causes a timer event, to force the simulator to update all calculations * * returns: N/A * **************************************************************************/ void BikePowerSensor::ForceUpdate() { // Only performed in event synchronous simulations, to prevent simulator from // getting stuck on low speed/cadence values if(eUpdateType == BikePower::UpdateType::EVENT_SYNCH) timerHandle->Interval = 250; } /************************************************************************** * BikePowerSensor::checkBox_InvalidSerial_CheckedChanged * * Handles the CheckedChanged event * * returns: N/A * **************************************************************************/ void BikePowerSensor::checkBox_InvalidSerial_CheckedChanged(System::Object ^sender, System::EventArgs ^e) { if(this->checkBox_InvalidSerial->Checked) this->textBox_Glb_SerialNumChange->Enabled = false; else this->textBox_Glb_SerialNumChange->Enabled = true; } void BikePowerSensor::button_UpdateGetSet_Click(System::Object ^sender, System::EventArgs ^e) { bpPages->ucSubpageNumber = 0x01; if(checkBox_InvalidCrankLength->Checked) bpPages->ucCrankLength = BikePower::INVALID_CRANK_LENGTH; else bpPages->ucCrankLength = (UCHAR)(((float)numericUpDown_CrankLength->Value - (float)110.0) / (float)0.5); //clear the sensor status; bpPages->ucSensorStatus = 0; if(radioButton_SensorUndefined->Checked) bpPages->ucSensorStatus |= ((UCHAR)BikePower::SensorStatus::UNDEFINED << 4); else if(radioButton_SensorLeft->Checked) bpPages->ucSensorStatus |= ((UCHAR)BikePower::SensorStatus::LEFT_PRESENT << 4); else if(radioButton_SensorRight->Checked) bpPages->ucSensorStatus |= ((UCHAR)BikePower::SensorStatus::RIGHT_PRESENT << 4); else if(radioButton_SensorBoth->Checked) bpPages->ucSensorStatus |= ((UCHAR)BikePower::SensorStatus::LEFT_RIGHT_PRESENT << 4); if(radioButton_MismatchUndefined->Checked) bpPages->ucSensorStatus |= ((UCHAR)BikePower::SWMistmatchStatus::UNDEFINED << 2); else if(radioButton_MismatchRight->Checked) bpPages->ucSensorStatus |= ((UCHAR)BikePower::SWMistmatchStatus::RIGHT_SENSOR_OLDER << 2); else if(radioButton_MismatchLeft->Checked) bpPages->ucSensorStatus |= ((UCHAR)BikePower::SWMistmatchStatus::LEFT_SENSOR_OLDER << 2); else if(radioButton_MismatchNone->Checked) bpPages->ucSensorStatus |= ((UCHAR)BikePower::SWMistmatchStatus::SW_SAME << 2); if(radioButton_CrankInvalid->Checked) bpPages->ucSensorStatus |= ((UCHAR)BikePower::CrankLengthStatus::LENGTH_INVALID); else if(radioButton_CrankDefault->Checked) bpPages->ucSensorStatus |= ((UCHAR)BikePower::CrankLengthStatus::DEFAULT_LENGTH_USED); else if(radioButton_CrankManual->Checked) bpPages->ucSensorStatus |= ((UCHAR)BikePower::CrankLengthStatus::LENGTH_MANUALLY_SET); else if(radioButton_CrankAuto->Checked) bpPages->ucSensorStatus |= ((UCHAR)BikePower::CrankLengthStatus::LENGTH_AUTOMATICALLY_SET); //Clear auto crank bit bpPages->ucSensorCapabilities &= 0xFE; // Set auto crank bit if(checkBox_AutoCrank->Checked) bpPages->ucSensorCapabilities |= 0x01; } void BikePowerSensor::checkBox_InvalidCrankLength_CheckedChanged(System::Object ^sender, System::EventArgs ^e) { if(checkBox_InvalidCrankLength->Checked) numericUpDown_CrankLength->Enabled = false; else numericUpDown_CrankLength->Enabled = true; } System::Void BikePowerSensor::checkBox_LeftTorqueEffectivenessInvalid_CheckedChanged(System::Object^ sender, System::EventArgs^ e) { numericUpDown_LeftTorqueEffectiveness->Enabled = !checkBox_LeftTorqueEffectivenessInvalid->Checked; if(checkBox_LeftTorqueEffectivenessInvalid->Checked) bpPages->ucLeftTorqueEffectiveness = bpPages->INVALID_TEPS; else bpPages->ucLeftTorqueEffectiveness = (UCHAR) (numericUpDown_LeftTorqueEffectiveness->Value * 2); } System::Void BikePowerSensor::checkBox_RightTorqueEffectivenessInvalid_CheckedChanged(System::Object^ sender, System::EventArgs^ e) { numericUpDown_RightTorqueEffectiveness->Enabled = !checkBox_RightTorqueEffectivenessInvalid->Checked; if(checkBox_RightTorqueEffectivenessInvalid->Checked) bpPages->ucRightTorqueEffectiveness = bpPages->INVALID_TEPS; else bpPages->ucRightTorqueEffectiveness = (UCHAR) (numericUpDown_RightTorqueEffectiveness->Value *2); } System::Void BikePowerSensor::checkBox_LeftPedalSmoothnessInvalid_CheckedChanged(System::Object^ sender, System::EventArgs^ e) { numericUpDown_LeftPedalSmoothness->Enabled = !checkBox_LeftPedalSmoothnessInvalid->Checked; if(checkBox_LeftPedalSmoothnessInvalid->Checked) bpPages->ucLeftPedalSmoothness = bpPages->INVALID_TEPS; else bpPages->ucLeftPedalSmoothness = (UCHAR) (numericUpDown_LeftPedalSmoothness->Value * 2); } System::Void BikePowerSensor::checkBox_RightPedalSmoothnessInvalid_CheckedChanged(System::Object^ sender, System::EventArgs^ e) { numericUpDown_RightPedalSmoothness->Enabled = !checkBox_RightPedalSmoothnessInvalid->Checked; if(checkBox_RightPedalSmoothnessInvalid->Checked) bpPages->ucRightPedalSmoothness = bpPages->INVALID_TEPS; else bpPages->ucRightPedalSmoothness = (UCHAR) (numericUpDown_RightPedalSmoothness->Value * 2); } System::Void BikePowerSensor::checkBox_Combined_CheckedChanged(System::Object^ sender, System::EventArgs^ e) { // If this is a combined system, torque effectiveness doesn't apply checkBox_LeftTorqueEffectivenessInvalid->Checked = checkBox_Combined->Checked; checkBox_RightTorqueEffectivenessInvalid->Checked = checkBox_Combined->Checked; // Pedal Smoothness is a little trickier... numericUpDown_RightPedalSmoothness->Enabled = !checkBox_Combined->Checked; checkBox_RightPedalSmoothnessInvalid->Enabled = !checkBox_Combined->Checked; if(checkBox_Combined->Checked) bpPages->ucRightPedalSmoothness = bpPages->COMBINED_PEDAL_SMOOTHNESS; else checkBox_RightPedalSmoothnessInvalid_CheckedChanged(this, nullptr); // trigger an update to check for invalid values and read numeric box } System::Void BikePowerSensor::checkBox_EnableTEPS_CheckedChanged(System::Object^ sender, System::EventArgs^ e) { bTxPage19 = checkBox_EnableTEPS->Checked; } System::Void BikePowerSensor::numericUpDown_LeftTorqueEffectiveness_ValueChanged(System::Object^ sender, System::EventArgs^ e) { if(!checkBox_LeftTorqueEffectivenessInvalid->Checked) bpPages->ucLeftTorqueEffectiveness = (UCHAR) (numericUpDown_LeftTorqueEffectiveness->Value * 2); } System::Void BikePowerSensor::numericUpDown_RightTorqueEffectiveness_ValueChanged(System::Object^ sender, System::EventArgs^ e) { if(!checkBox_RightTorqueEffectivenessInvalid->Checked) bpPages->ucRightTorqueEffectiveness = (UCHAR) (numericUpDown_RightTorqueEffectiveness->Value * 2); } System::Void BikePowerSensor::numericUpDown_LeftPedalSmoothness_ValueChanged(System::Object^ sender, System::EventArgs^ e) { if(!checkBox_LeftPedalSmoothnessInvalid->Checked) bpPages->ucLeftPedalSmoothness = (UCHAR) (numericUpDown_LeftPedalSmoothness->Value *2); } System::Void BikePowerSensor::numericUpDown_RightPedalSmoothness_ValueChanged(System::Object^ sender, System::EventArgs^ e) { if(!checkBox_RightPedalSmoothnessInvalid->Checked) bpPages->ucRightPedalSmoothness = (UCHAR) (numericUpDown_RightPedalSmoothness->Value * 2); }