bool srxlFrameGpsLoc(sbuf_t *dst, timeUs_t currentTimeUs) { UNUSED(currentTimeUs); gpsCoordinateDDDMMmmmm_t coordinate; uint32_t latitudeBcd, longitudeBcd, altitudeLo; uint16_t altitudeLoBcd, groundCourseBcd, hdop; uint8_t hdopBcd, gpsFlags; if (!featureIsEnabled(FEATURE_GPS) || !STATE(GPS_FIX) || gpsSol.numSat < 6) { return false; } // lattitude GPStoDDDMM_MMMM(gpsSol.llh.lat, &coordinate); latitudeBcd = (dec2bcd(coordinate.dddmm) << 16) | dec2bcd(coordinate.mmmm); // longitude GPStoDDDMM_MMMM(gpsSol.llh.lon, &coordinate); longitudeBcd = (dec2bcd(coordinate.dddmm) << 16) | dec2bcd(coordinate.mmmm); // altitude (low order) altitudeLo = ABS(gpsSol.llh.altCm) / 10; altitudeLoBcd = dec2bcd(altitudeLo % 100000); // Ground course groundCourseBcd = dec2bcd(gpsSol.groundCourse); // HDOP hdop = gpsSol.hdop / 10; hdop = (hdop > 99) ? 99 : hdop; hdopBcd = dec2bcd(hdop); // flags gpsFlags = GPS_FLAGS_GPS_DATA_RECEIVED_BIT | GPS_FLAGS_GPS_FIX_VALID_BIT | GPS_FLAGS_3D_FIX_BIT; gpsFlags |= (gpsSol.llh.lat > 0) ? GPS_FLAGS_IS_NORTH_BIT : 0; gpsFlags |= (gpsSol.llh.lon > 0) ? GPS_FLAGS_IS_EAST_BIT : 0; gpsFlags |= (gpsSol.llh.altCm < 0) ? GPS_FLAGS_NEGATIVE_ALT_BIT : 0; gpsFlags |= (gpsSol.llh.lon / GPS_DEGREES_DIVIDER > 99) ? GPS_FLAGS_LONGITUDE_GREATER_99_BIT : 0; // SRXL frame sbufWriteU8(dst, SRXL_FRAMETYPE_GPS_LOC); sbufWriteU8(dst, SRXL_FRAMETYPE_SID); sbufWriteU16(dst, altitudeLoBcd); sbufWriteU32(dst, latitudeBcd); sbufWriteU32(dst, longitudeBcd); sbufWriteU16(dst, groundCourseBcd); sbufWriteU8(dst, hdopBcd); sbufWriteU8(dst, gpsFlags); return true; }
static bool srxlFrameVTX(sbuf_t *dst, timeUs_t currentTimeUs) { static timeUs_t lastTimeSentVtx = 0; static spektrumVtx_t vtxSent; spektrumVtx_t vtx; collectVtxTmData(&vtx); if ((vtxDeviceType != VTXDEV_UNKNOWN) && vtxDeviceType != VTXDEV_UNSUPPORTED) { convertVtxTmData(&vtx); if ((memcmp(&vtxSent, &vtx, sizeof(spektrumVtx_t)) != 0) || ((currentTimeUs - lastTimeSentVtx) > VTX_KEEPALIVE_TIME_OUT) ) { // Fill in the VTX tm structure sbufWriteU8(dst, TELE_DEVICE_VTX); sbufWriteU8(dst, SRXL_FRAMETYPE_SID); sbufWriteU8(dst, vtx.band); sbufWriteU8(dst, vtx.channel); sbufWriteU8(dst, vtx.pitMode); sbufWriteU8(dst, vtx.power); sbufWriteU16(dst, vtx.powerValue); sbufWriteU8(dst, vtx.region); sbufFill(dst, STRU_TELE_VTX_EMPTY_VALUE, STRU_TELE_VTX_EMPTY_COUNT); memcpy(&vtxSent, &vtx, sizeof(spektrumVtx_t)); lastTimeSentVtx = currentTimeUs; return true; } } return false; }
void crc16_ccitt_sbuf_append(sbuf_t *dst, uint8_t *start) { uint16_t crc = 0; const uint8_t * const end = sbufPtr(dst); for (const uint8_t *ptr = start; ptr < end; ++ptr) { crc = crc16_ccitt(crc, *ptr); } sbufWriteU16(dst, crc); }
bool srxlFrameFlightPackCurrent(sbuf_t *dst, timeUs_t currentTimeUs) { uint16_t amps = getAmperage() / 10; uint16_t mah = getMAhDrawn(); static uint16_t sentAmps; static uint16_t sentMah; static timeUs_t lastTimeSentFPmAh = 0; timeUs_t keepAlive = currentTimeUs - lastTimeSentFPmAh; if ( amps != sentAmps || mah != sentMah || keepAlive > FP_MAH_KEEPALIVE_TIME_OUT ) { sbufWriteU8(dst, SRXL_FRAMETYPE_TELE_FP_MAH); sbufWriteU8(dst, SRXL_FRAMETYPE_SID); sbufWriteU16(dst, amps); sbufWriteU16(dst, mah); sbufWriteU16(dst, SPEKTRUM_TEMP_UNUSED); // temp A sbufWriteU16(dst, SPEKTRUM_AMPS_UNUSED); // Amps B sbufWriteU16(dst, SPEKTRUM_AMPH_UNUSED); // mAH B sbufWriteU16(dst, SPEKTRUM_TEMP_UNUSED); // temp B sbufFill(dst, STRU_TELE_FP_EMPTY_FIELDS_VALUE, STRU_TELE_FP_EMPTY_FIELDS_COUNT); sentAmps = amps; sentMah = mah; lastTimeSentFPmAh = currentTimeUs; return true; } return false; }
bool srxlFrameGpsStat(sbuf_t *dst, timeUs_t currentTimeUs) { UNUSED(currentTimeUs); uint32_t timeBcd; uint16_t speedKnotsBcd, speedTmp; uint8_t numSatBcd, altitudeHighBcd; bool timeProvided = false; if (!featureIsEnabled(FEATURE_GPS) || !STATE(GPS_FIX) || gpsSol.numSat < 6) { return false; } // Number of sats and altitude (high bits) numSatBcd = (gpsSol.numSat > 99) ? dec2bcd(99) : dec2bcd(gpsSol.numSat); altitudeHighBcd = dec2bcd(gpsSol.llh.altCm / 100000); // Speed (knots) speedTmp = gpsSol.groundSpeed * 1944 / 1000; speedKnotsBcd = (speedTmp > 9999) ? dec2bcd(9999) : dec2bcd(speedTmp); #ifdef USE_RTC_TIME dateTime_t dt; // RTC if (rtcHasTime()) { rtcGetDateTime(&dt); timeBcd = dec2bcd(dt.hours); timeBcd = timeBcd << 8; timeBcd = timeBcd | dec2bcd(dt.minutes); timeBcd = timeBcd << 8; timeBcd = timeBcd | dec2bcd(dt.seconds); timeBcd = timeBcd << 4; timeBcd = timeBcd | dec2bcd(dt.millis / 100); timeProvided = true; } #endif timeBcd = (timeProvided) ? timeBcd : SPEKTRUM_TIME_UNKNOWN; // SRXL frame sbufWriteU8(dst, SRXL_FRAMETYPE_GPS_STAT); sbufWriteU8(dst, SRXL_FRAMETYPE_SID); sbufWriteU16(dst, speedKnotsBcd); sbufWriteU32(dst, timeBcd); sbufWriteU8(dst, numSatBcd); sbufWriteU8(dst, altitudeHighBcd); sbufFill(dst, STRU_TELE_GPS_STAT_EMPTY_FIELDS_VALUE, STRU_TELE_GPS_STAT_EMPTY_FIELDS_COUNT); return true; }
int mspServerCommandHandler(mspPacket_t *cmd, mspPacket_t *reply) { sbuf_t *src = &cmd->buf; sbuf_t *dst = &reply->buf; int len = sbufBytesRemaining(src); switch (cmd->cmd) { case MSP_API_VERSION: sbufWriteU8(dst, MSP_PROTOCOL_VERSION); sbufWriteU8(dst, API_VERSION_MAJOR); sbufWriteU8(dst, API_VERSION_MINOR); break; case MSP_FC_VARIANT: sbufWriteData(dst, flightControllerIdentifier, FLIGHT_CONTROLLER_IDENTIFIER_LENGTH); break; case MSP_FC_VERSION: sbufWriteU8(dst, FC_VERSION_MAJOR); sbufWriteU8(dst, FC_VERSION_MINOR); sbufWriteU8(dst, FC_VERSION_PATCH_LEVEL); break; case MSP_BOARD_INFO: sbufWriteData(dst, boardIdentifier, BOARD_IDENTIFIER_LENGTH); sbufWriteU16(dst, 0); // hardware revision sbufWriteU8(dst, 1); // 0 == FC, 1 == OSD break; case MSP_BUILD_INFO: sbufWriteData(dst, buildDate, BUILD_DATE_LENGTH); sbufWriteData(dst, buildTime, BUILD_TIME_LENGTH); sbufWriteData(dst, shortGitRevision, GIT_SHORT_REVISION_LENGTH); break; // DEPRECATED - Use MSP_API_VERSION case MSP_IDENT: sbufWriteU8(dst, MW_VERSION); sbufWriteU8(dst, 0); // mixer mode sbufWriteU8(dst, MSP_PROTOCOL_VERSION); sbufWriteU32(dst, CAP_DYNBALANCE); // "capability" break; case MSP_STATUS_EX: case MSP_STATUS: sbufWriteU16(dst, cycleTime); #ifdef USE_I2C sbufWriteU16(dst, i2cGetErrorCounter()); #else sbufWriteU16(dst, 0); #endif sbufWriteU16(dst, 0); // sensors sbufWriteU32(dst, 0); // flight mode flags sbufWriteU8(dst, 0); // profile index if(cmd->cmd == MSP_STATUS_EX) { sbufWriteU16(dst, averageSystemLoadPercent); } break; case MSP_DEBUG: // output some useful QA statistics // debug[x] = ((hse_value / 1000000) * 1000) + (SystemCoreClock / 1000000); // XX0YY [crystal clock : core clock] for (int i = 0; i < DEBUG16_VALUE_COUNT; i++) sbufWriteU16(dst, debug[i]); // 4 variables are here for general monitoring purpose break; case MSP_UID: sbufWriteU32(dst, U_ID_0); sbufWriteU32(dst, U_ID_1); sbufWriteU32(dst, U_ID_2); break; case MSP_VOLTAGE_METER_CONFIG: for (int i = 0; i < MAX_VOLTAGE_METERS; i++) { // FIXME update for multiple voltage sources i.e. use `i` and support at least OSD VBAT, OSD 12V, OSD 5V sbufWriteU8(dst, batteryConfig()->vbatscale); sbufWriteU8(dst, batteryConfig()->vbatmincellvoltage); sbufWriteU8(dst, batteryConfig()->vbatmaxcellvoltage); sbufWriteU8(dst, batteryConfig()->vbatwarningcellvoltage); } break; case MSP_CURRENT_METER_CONFIG: sbufWriteU16(dst, batteryConfig()->currentMeterScale); sbufWriteU16(dst, batteryConfig()->currentMeterOffset); sbufWriteU8(dst, batteryConfig()->currentMeterType); sbufWriteU16(dst, batteryConfig()->batteryCapacity); break; case MSP_CF_SERIAL_CONFIG: for (int i = 0; i < serialGetAvailablePortCount(); i++) { if (!serialIsPortAvailable(serialConfig()->portConfigs[i].identifier)) { continue; }; sbufWriteU8(dst, serialConfig()->portConfigs[i].identifier); sbufWriteU16(dst, serialConfig()->portConfigs[i].functionMask); sbufWriteU8(dst, serialConfig()->portConfigs[i].baudRates[BAUDRATE_MSP_SERVER]); sbufWriteU8(dst, serialConfig()->portConfigs[i].baudRates[BAUDRATE_MSP_CLIENT]); sbufWriteU8(dst, serialConfig()->portConfigs[i].baudRates[BAUDRATE_RESERVED1]); sbufWriteU8(dst, serialConfig()->portConfigs[i].baudRates[BAUDRATE_RESERVED2]); } break; case MSP_BF_BUILD_INFO: sbufWriteData(dst, buildDate, 11); // MMM DD YYYY as ascii, MMM = Jan/Feb... etc sbufWriteU32(dst, 0); // future exp sbufWriteU32(dst, 0); // future exp break; case MSP_DATAFLASH_SUMMARY: // FIXME update GUI and remove this. sbufWriteU8(dst, 0); // FlashFS is neither ready nor supported sbufWriteU32(dst, 0); sbufWriteU32(dst, 0); sbufWriteU32(dst, 0); break; case MSP_BATTERY_STATES: // write out battery states, once for each battery sbufWriteU8(dst, (uint8_t)getBatteryState() == BATTERY_NOT_PRESENT ? 0 : 1); // battery connected - 0 not connected, 1 connected sbufWriteU8(dst, (uint8_t)constrain(vbat, 0, 255)); sbufWriteU16(dst, (uint16_t)constrain(mAhDrawn, 0, 0xFFFF)); // milliamp hours drawn from battery break; case MSP_CURRENT_METERS: // write out amperage, once for each current meter. sbufWriteU16(dst, (uint16_t)constrain(amperage * 10, 0, 0xFFFF)); // send amperage in 0.001 A steps. Negative range is truncated to zero break; case MSP_VOLTAGE_METERS: // write out voltage, once for each meter. for (int i = 0; i < 3; i++) { // FIXME hack that needs cleanup, see issue #2221 // This works for now, but the vbat scale also changes the 12V and 5V readings. switch(i) { case 0: sbufWriteU8(dst, (uint8_t)constrain(vbat, 0, 255)); break; case 1: sbufWriteU8(dst, (uint8_t)constrain(batteryAdcToVoltage(adcGetChannel(ADC_12V)), 0, 255)); break; case 2: sbufWriteU8(dst, (uint8_t)constrain(batteryAdcToVoltage(adcGetChannel(ADC_5V)), 0, 255)); break; } } break; case MSP_OSD_VIDEO_CONFIG: sbufWriteU8(dst, osdVideoConfig()->videoMode); // 0 = NTSC, 1 = PAL break; case MSP_RESET_CONF: resetEEPROM(); readEEPROM(); break; case MSP_EEPROM_WRITE: writeEEPROM(); readEEPROM(); break; case MSP_SET_VOLTAGE_METER_CONFIG: { uint8_t i = sbufReadU8(src); if (i >= MAX_VOLTAGE_METERS) { return -1; } // FIXME use `i`, see MSP_VOLTAGE_METER_CONFIG batteryConfig()->vbatscale = sbufReadU8(src); // actual vbatscale as intended batteryConfig()->vbatmincellvoltage = sbufReadU8(src); // vbatlevel_warn1 in MWC2.3 GUI batteryConfig()->vbatmaxcellvoltage = sbufReadU8(src); // vbatlevel_warn2 in MWC2.3 GUI batteryConfig()->vbatwarningcellvoltage = sbufReadU8(src); // vbatlevel when buzzer starts to alert break; } case MSP_SET_CURRENT_METER_CONFIG: batteryConfig()->currentMeterScale = sbufReadU16(src); batteryConfig()->currentMeterOffset = sbufReadU16(src); batteryConfig()->currentMeterType = sbufReadU8(src); batteryConfig()->batteryCapacity = sbufReadU16(src); break; case MSP_SET_CF_SERIAL_CONFIG: { int portConfigSize = sizeof(uint8_t) + sizeof(uint16_t) + (sizeof(uint8_t) * 4); if (len % portConfigSize != 0) return -1; while (sbufBytesRemaining(src) >= portConfigSize) { uint8_t identifier = sbufReadU8(src); serialPortConfig_t *portConfig = serialFindPortConfiguration(identifier); if (!portConfig) return -1; portConfig->identifier = identifier; portConfig->functionMask = sbufReadU16(src); portConfig->baudRates[BAUDRATE_MSP_SERVER] = sbufReadU8(src); portConfig->baudRates[BAUDRATE_MSP_CLIENT] = sbufReadU8(src); portConfig->baudRates[BAUDRATE_RESERVED1] = sbufReadU8(src); portConfig->baudRates[BAUDRATE_RESERVED2] = sbufReadU8(src); } break; } case MSP_REBOOT: mspPostProcessFn = mspRebootFn; break; case MSP_SET_OSD_VIDEO_CONFIG: osdVideoConfig()->videoMode = sbufReadU8(src); mspPostProcessFn = mspApplyVideoConfigurationFn; break; default: // we do not know how to handle the message return 0; } return 1; // message was handled successfully }