diff --git a/companion/src/simulation/CMakeLists.txt b/companion/src/simulation/CMakeLists.txt index 5dcfe27be..6816540c9 100644 --- a/companion/src/simulation/CMakeLists.txt +++ b/companion/src/simulation/CMakeLists.txt @@ -14,6 +14,7 @@ set(${PROJECT_NAME}_NAMES telemetrysimu telemetryprovidercrossfire telemetryproviderfrsky + telemetryproviderfrskyhub trainersimu widgets/lcdwidget widgets/radiowidget diff --git a/companion/src/simulation/simulatorinterface.h b/companion/src/simulation/simulatorinterface.h index 8546910da..9f820840c 100644 --- a/companion/src/simulation/simulatorinterface.h +++ b/companion/src/simulation/simulatorinterface.h @@ -41,6 +41,7 @@ enum SimulatorTelemetryProtocol { SIMU_TELEMETRY_PROTOCOL_FRSKY_SPORT = 0, SIMU_TELEMETRY_PROTOCOL_FRSKY_HUB, SIMU_TELEMETRY_PROTOCOL_CROSSFIRE, + SIMU_TELEMETRY_PROTOCOL_FRSKY_HUB_OOB, SIMU_TELEMETRY_PROTOCOL_COUNT }; diff --git a/companion/src/simulation/telemetryproviderfrskyhub.cpp b/companion/src/simulation/telemetryproviderfrskyhub.cpp new file mode 100644 index 000000000..6e9063ffe --- /dev/null +++ b/companion/src/simulation/telemetryproviderfrskyhub.cpp @@ -0,0 +1,450 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include + +#include "appdata.h" +#include "telemetryproviderfrskyhub.h" +#include "ui_telemetryproviderfrskyhub.h" +#include "telem_data.h" +#include "radio/src/telemetry/frsky_defs.h" + +template t LIMIT(t mi, t x, t ma) { return std::min(std::max(mi, x), ma); } + +TelemetryProviderFrSkyHub::TelemetryProviderFrSkyHub(QWidget * parent): + QWidget(parent), + ui(new Ui::TelemetryProviderFrSkyHub) +{ + ui->setupUi(this); + + // Set default values from UI definition into GPS + gps.setLatLon(ui->input_gps->text()); + gps.setCourseDegrees(ui->input_hdg->value()); + gps.setSpeedKMH(ui->input_gspd->value()); + gps.setAltitude(ui->input_galt->value()); + gps.setDateTime(ui->input_date->dateTime()); + + connect(ui->input_hdg, QOverload::of(&QDoubleSpinBox::valueChanged), &gps, &SimulatedGPS::setCourseDegrees); + connect(ui->input_gps, &QLineEdit::textChanged, &gps, &SimulatedGPS::setLatLon); + connect(ui->input_gspd, QOverload::of(&QDoubleSpinBox::valueChanged), &gps, &SimulatedGPS::setSpeedKMH); + connect(ui->input_galt, QOverload::of(&QDoubleSpinBox::valueChanged), &gps, &SimulatedGPS::setAltitude); + + connect(&gps, &SimulatedGPS::positionChanged, ui->input_gps, &QLineEdit::setText); + connect(&gps, &SimulatedGPS::courseDegreesChanged, ui->input_hdg, QOverload::of(&QDoubleSpinBox::setValue)); + connect(&gps, QOverload::of(&SimulatedGPS::dateTimeChanged), ui->input_date, &QDateTimeEdit::setDateTime); + + // Create this once + supportedLogItems.clear(); + supportedLogItems.insert("RSSI", ""); + supportedLogItems.insert("A1", "V"); + supportedLogItems.insert("A2", "V"); + supportedLogItems.insert("TRSS", "dB"); + supportedLogItems.insert("RQly", ""); + supportedLogItems.insert("TQly", ""); + supportedLogItems.insert("RPM", "rpm"); + supportedLogItems.insert("Fuel", "%"); + supportedLogItems.insert("Tmp1", "°C"); + supportedLogItems.insert("Tmp2", "°C"); + supportedLogItems.insert("VFAS", "V"); + supportedLogItems.insert("Curr", "A"); + supportedLogItems.insert("VSpd", "m/s"); + supportedLogItems.insert("Alt", "m"); + supportedLogItems.insert("GPS", ""); + supportedLogItems.insert("GSpd", "kts"); + supportedLogItems.insert("Hdg", "°"); + supportedLogItems.insert("GAlt", "m"); + supportedLogItems.insert("Date", ""); + supportedLogItems.insert("AccX", "g"); + supportedLogItems.insert("AccY", "g"); + supportedLogItems.insert("AccZ", "g"); + +} + +TelemetryProviderFrSkyHub::~TelemetryProviderFrSkyHub() +{ + delete ui; +} + +QHash * TelemetryProviderFrSkyHub::getSupportedLogItems() +{ + return &supportedLogItems; +} + +QString TelemetryProviderFrSkyHub::getLogfileIdentifier() +{ + return QString("TELEMETRY_DATA: FrSky Hub"); +} + +void TelemetryProviderFrSkyHub::loadItemFromLog(QString item, QString value) +{ + if (item == "RSSI") ui->input_rssi->setValue(value.toInt()); + if (item == "A1") ui->input_a1->setValue(value.toDouble()); + if (item == "A2") ui->input_a2->setValue(value.toDouble()); + if (item == "TRSS") ui->input_trss->setValue(value.toInt()); + if (item == "RQly") ui->input_rqly->setValue(value.toInt()); + if (item == "TQly") ui->input_tqly->setValue(value.toInt()); + if (item == "RPM") ui->input_rpm->setValue(value.toInt()); + if (item == "Fuel") ui->input_fuel->setValue(value.toInt()); + if (item == "Tmp1") ui->input_tmp1->setValue(value.toInt()); + if (item == "Tmp2") ui->input_tmp2->setValue(value.toInt()); + if (item == "VFAS") ui->input_vfas->setValue(value.toDouble()); + if (item == "Curr") ui->input_curr->setValue(value.toDouble()); + if (item == "VSpd") ui->input_vspd->setValue(value.toDouble()); + if (item == "Alt") ui->input_alt->setValue(value.toDouble()); + if (item == "GPS") ui->input_gps->setText(value); + if (item == "GSpd") ui->input_gspd->setValue(value.toDouble()); + if (item == "Hdg") ui->input_hdg->setValue(value.toInt()); + if (item == "GAlt") ui->input_galt->setValue(value.toDouble()); + if (item == "Date") ui->input_date->setDateTime(QDateTime::fromString(value)); + if (item == "AccX") ui->input_accx->setValue(value.toDouble()); + if (item == "AccY") ui->input_accy->setValue(value.toDouble()); + if (item == "AccZ") ui->input_accz->setValue(value.toDouble()); +} + +void TelemetryProviderFrSkyHub::resetRssi() +{ + sendLinkFrame(0, 0, 0, 0, 0, 0); +} + +void TelemetryProviderFrSkyHub::loadUiFromSimulator(SimulatorInterface * simulator) +{ + // Nothing to do for FrSky Hub +} + +void TelemetryProviderFrSkyHub::generateTelemetryFrame(SimulatorInterface *simulator) +{ + static int item = 0; + + switch (item++) { + case 0: + // Always generate link stats + if (true) { + // just want a block here to put these variables in + int rssi = ui->input_rssi->value(); + double a1 = ui->input_a1->value(); + double a2 = ui->input_a2->value(); + int trss = ui->input_trss->value(); + int rqly = ui->input_rqly->value(); + int tqly = ui->input_tqly->value(); + + sendLinkFrame(rssi, a1, a2, trss, rqly, tqly); + } + break; + case 1: + if (ui->enabled_rpm->isChecked()) { + sendRPMFrame(ui->input_rpm->value()); + } + case 2: + if (ui->enabled_fuel->isChecked()) { + sendFuelFrame(ui->input_fuel->value()); + } + case 3: + if (ui->enabled_temps->isChecked()) { + sendTempsFrame(ui->input_tmp1->value(), ui->input_tmp2->value()); + } + case 4: + if (ui->enabled_battery->isChecked()) { + sendBatteryFrame(ui->input_vfas->value(), ui->input_curr->value()); + } + case 5: + if (ui->enabled_baro->isChecked()) { + sendBaroFrame(ui->input_vspd->value(), ui->input_alt->value()); + } + case 6: + if (ui->enabled_accel->isChecked()) { + sendAccelFrame(ui->input_accx->value(), ui->input_accy->value(), ui->input_accz->value()); + } + case 7: + if (ui->enabled_gps->isChecked()) { + sendGPSFrame(gps); + } + default: + item = 0; + return; + } +} + +void TelemetryProviderFrSkyHub::sendLongLinkFrame(int rssi, double a1, double a2, int trss, int rqly, int tqly) +{ + // A1 & A2 are an unsigned byte measuring 0V (0) to 13.2V (255) + uint8_t a1_encoded = static_cast((a1 / 13.2) * 255); + uint8_t a2_encoded = static_cast((a2 / 13.2) * 255); + uint8_t rssi_encoded = static_cast(rssi); + uint8_t trss_encoded = static_cast(trss); + uint8_t rqly_encoded = static_cast(rqly); + uint8_t tqly_encoded = static_cast(tqly); + + uint8_t packet[7] = { 0xfe, a1_encoded, a2_encoded, rssi_encoded, trss_encoded, rqly_encoded, tqly_encoded}; + QByteArray ba((char *)packet, 7); + emit telemetryDataChanged(SIMU_TELEMETRY_PROTOCOL_FRSKY_HUB, ba); +} + +void TelemetryProviderFrSkyHub::sendShortLinkFrame(int rssi, double a1, double a2) +{ + // A1 & A2 are an unsigned byte measuring 0V (0) to 13.2V (255) + uint8_t a1_encoded = static_cast((a1 / 13.2) * 255); + uint8_t a2_encoded = static_cast((a2 / 13.2) * 255); + uint8_t rssi_encoded = static_cast(rssi); + + uint8_t packet[4] = { 0xfe, a1_encoded, a2_encoded, rssi_encoded}; + QByteArray ba((char *)packet, 4); + emit telemetryDataChanged(SIMU_TELEMETRY_PROTOCOL_FRSKY_HUB, ba); +} + +void TelemetryProviderFrSkyHub::sendLinkFrame(int rssi, double a1, double a2, int trss, int rqly, int tqly) +{ + if (ui->enabled_multi->isChecked()) + sendLongLinkFrame(rssi, a1, a2, trss << 1, rqly, tqly); + else + sendShortLinkFrame(rssi, a1, a2); +} + +void TelemetryProviderFrSkyHub::sendSensorPacket(uint8_t id, uint16_t value) +{ + uint8_t packet[3] = { id, static_cast(value & 0xff), static_cast((value >> 8) & 0xff) }; + QByteArray ba((char *)packet, 3); + emit telemetryDataChanged(SIMU_TELEMETRY_PROTOCOL_FRSKY_HUB_OOB, ba); +} + +void TelemetryProviderFrSkyHub::sendRPMFrame(int rpm) +{ + uint16_t rpm_60 = rpm / 60; + sendSensorPacket(RPM_ID, rpm_60); +} + +void TelemetryProviderFrSkyHub::sendFuelFrame(int fuelpercent) +{ + sendSensorPacket(FUEL_ID, fuelpercent); +} + +void TelemetryProviderFrSkyHub::sendTempsFrame(int temp1, int temp2) +{ + sendSensorPacket(TEMP1_ID, temp1); + sendSensorPacket(TEMP2_ID, temp2); +} + +void TelemetryProviderFrSkyHub::sendBatteryFrame(double vfas, double curr) +{ + /* + // There are a couple of voltage sensors that could be used in a + // FrSky Hub setup. We will use the high precision one as that's + // easier, but if you _really_ want to simulate VFAS from the sensor + // that reports in units of 0.52381 volts, go ahead and use this + // snippet instead. + + double scaled_vfas = vfas * (110.0 / 210.0); + int16_t bp = scaled_vfas; + int16_t ap = (scaled_vfas - bp) * 10; + + sendSensorPacket(VOLTS_BP_ID, bp); + sendSensorPacket(VOLTS_AP_ID, ap); + */ + + sendSensorPacket(VFAS_ID, 2000 + (vfas * 100)); + sendSensorPacket(CURRENT_ID, curr * 10); +} + +void TelemetryProviderFrSkyHub::sendBaroFrame(double vspd, double alt) +{ + sendSensorPacket(VARIO_ID, vspd * 100); + sendSensorPacket(BARO_ALT_BP_ID, alt); + sendSensorPacket(BARO_ALT_AP_ID, (alt - floor(alt)) * 10); +} + +void TelemetryProviderFrSkyHub::sendAccelFrame(double accx, double accy, double accz) +{ + sendSensorPacket(ACCEL_X_ID, accx * 1000); + sendSensorPacket(ACCEL_Y_ID, accy * 1000); + sendSensorPacket(ACCEL_Z_ID, accz * 1000); +} + +void TelemetryProviderFrSkyHub::sendGPSFrame(SimulatedGPS & gps) +{ + double speedKnots = gps.speedKMH * 0.539957; + uint16_t bp, ap; + + uint16_t degrees; + double decimal_minutes; + + degrees = floor(abs(gps.lat)); + decimal_minutes = (abs(gps.lat) - degrees) * 60; + bp = degrees * 100 + floor(decimal_minutes); + ap = (decimal_minutes - floor(decimal_minutes)) * 10000; + + // MUST send these in BP, AP, NS order + sendSensorPacket(GPS_LAT_BP_ID, bp); + sendSensorPacket(GPS_LAT_AP_ID, ap); + sendSensorPacket(GPS_LAT_NS_ID, gps.lat < 0 ? 'S' : 'N'); + + degrees = floor(abs(gps.lon)); + decimal_minutes = (abs(gps.lon) - degrees) * 60; + bp = degrees * 100 + floor(decimal_minutes); + ap = (decimal_minutes - floor(decimal_minutes)) * 10000; + + // MUST send these in BP, AP, EW order + sendSensorPacket(GPS_LONG_BP_ID, bp); + sendSensorPacket(GPS_LONG_AP_ID, ap); + sendSensorPacket(GPS_LONG_EW_ID, gps.lon < 0 ? 'W' : 'E'); + + // The _after decimal point_ packets are ignored by the telemetry parser, so don't bother sending them. + sendSensorPacket(GPS_ALT_BP_ID, gps.altitude); + sendSensorPacket(GPS_SPEED_BP_ID, speedKnots); + sendSensorPacket(GPS_COURS_BP_ID, gps.courseDegrees); + + // Date and Time + QDate date = gps.dt.date(); + QTime time = gps.dt.time(); + sendSensorPacket(GPS_YEAR_ID, date.year()); + sendSensorPacket(GPS_DAY_MONTH_ID, (date.month() << 8) + date.day()); + sendSensorPacket(GPS_HOUR_MIN_ID, (time.minute() << 8) + time.hour()); + sendSensorPacket(GPS_SEC_ID, time.second()); +} + +void TelemetryProviderFrSkyHub::on_button_gpsRunStop_clicked() +{ + if (gps.running) { + gps.stop(); + ui->button_gpsRunStop->setText(tr("Run")); + } else { + gps.start(); + ui->button_gpsRunStop->setText(tr("Stop")); + } +} + +void TelemetryProviderFrSkyHub::on_button_saveTelemetryValues_clicked() +{ + QString fldr = g.backupDir().trimmed(); + if (fldr.isEmpty()) + fldr = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + + QString idFileNameAndPath = QFileDialog::getSaveFileName(this, tr("Save Telemetry"), fldr % "/telemetry.tlm", tr(".tlm Files (*.tlm)")); + if (idFileNameAndPath.isEmpty()) + return; + + QFile file(idFileNameAndPath); + if (!file.open(QIODevice::WriteOnly)){ + QMessageBox::critical(this, CPN_STR_APP_NAME, tr("Unable to open file for writing.\n%1").arg(file.errorString())); + return; + } + QTextStream out(&file); + + out << getLogfileIdentifier() << "\r\n"; + out << ui->input_rssi->text() << "\r\n"; + out << ui->input_a1->text() << "\r\n"; + out << ui->input_a2->text() << "\r\n"; + out << ui->input_trss->text() << "\r\n"; + out << ui->input_rqly->text() << "\r\n"; + out << ui->input_tqly->text() << "\r\n"; + out << ui->input_rpm->text() << "\r\n"; + out << ui->input_fuel->text() << "\r\n"; + out << ui->input_tmp1->text() << "\r\n"; + out << ui->input_tmp2->text() << "\r\n"; + out << ui->input_vfas->text() << "\r\n"; + out << ui->input_curr->text() << "\r\n"; + out << ui->input_vspd->text() << "\r\n"; + out << ui->input_alt->text() << "\r\n"; + out << ui->input_gps->text() << "\r\n"; + out << ui->input_gspd->text() << "\r\n"; + out << ui->input_hdg->text() << "\r\n"; + out << ui->input_galt->text() << "\r\n"; + out << ui->input_date->dateTime().toString(Qt::ISODate) << "\r\n"; + out << ui->input_accx->text() << "\r\n"; + out << ui->input_accy->text() << "\r\n"; + out << ui->input_accz->text() << "\r\n"; + + out << ui->enabled_multi->isChecked() << "\r\n"; + out << ui->enabled_rpm->isChecked() << "\r\n"; + out << ui->enabled_fuel->isChecked() << "\r\n"; + out << ui->enabled_temps->isChecked() << "\r\n"; + out << ui->enabled_battery->isChecked() << "\r\n"; + out << ui->enabled_baro->isChecked() << "\r\n"; + out << ui->enabled_gps->isChecked() << "\r\n"; + out << ui->enabled_accel->isChecked() << "\r\n"; + + file.flush(); + file.close(); +} + +void TelemetryProviderFrSkyHub::on_button_loadTelemetryValues_clicked() +{ + QString fldr = g.backupDir().trimmed(); + if (fldr.isEmpty()) + fldr = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + + QString idFileNameAndPath = QFileDialog::getOpenFileName(this, tr("Open Telemetry File"), fldr % "/telemetry.tlm", tr(".tlm Files (*.tlm)")); + if (idFileNameAndPath.isEmpty()) + return; + + QFile file(idFileNameAndPath); + + if (!file.open(QIODevice::ReadOnly)){ + QMessageBox::critical(this, CPN_STR_APP_NAME, tr("Unable to open file for reading.\n%1").arg(file.errorString())); + return; + } + + QTextStream in(&file); + + QString inputText; + QDateTime inputDatetime; + double inputDouble; + int inputInt; + + inputText = in.readLine(); + if (inputText != getLogfileIdentifier()) { + QMessageBox::critical(this, CPN_STR_APP_NAME, tr("Not a FrSky Hub telemetry values file.")); + return; + } + + inputText = in.readLine(); inputInt = inputText.toInt(); ui->input_rssi->setValue(inputInt); + inputText = in.readLine(); inputDouble = inputText.toDouble(); ui->input_a1->setValue(inputDouble); + inputText = in.readLine(); inputDouble = inputText.toDouble(); ui->input_a2->setValue(inputDouble); + inputText = in.readLine(); inputInt = inputText.toInt(); ui->input_trss->setValue(inputInt); + inputText = in.readLine(); inputInt = inputText.toInt(); ui->input_rqly->setValue(inputInt); + inputText = in.readLine(); inputInt = inputText.toInt(); ui->input_tqly->setValue(inputInt); + inputText = in.readLine(); inputInt = inputText.toInt(); ui->input_rpm->setValue(inputInt); + inputText = in.readLine(); inputInt = inputText.toInt(); ui->input_fuel->setValue(inputInt); + inputText = in.readLine(); inputInt = inputText.toInt(); ui->input_tmp1->setValue(inputInt); + inputText = in.readLine(); inputInt = inputText.toInt(); ui->input_tmp2->setValue(inputInt); + inputText = in.readLine(); inputDouble = inputText.toDouble(); ui->input_vfas->setValue(inputDouble); + inputText = in.readLine(); inputDouble = inputText.toDouble(); ui->input_curr->setValue(inputDouble); + inputText = in.readLine(); inputDouble = inputText.toDouble(); ui->input_vspd->setValue(inputDouble); + inputText = in.readLine(); inputDouble = inputText.toDouble(); ui->input_alt->setValue(inputDouble); + inputText = in.readLine(); ui->input_gps->setText(inputText); + inputText = in.readLine(); inputDouble = inputText.toDouble(); ui->input_gspd->setValue(inputDouble); + inputText = in.readLine(); inputDouble = inputText.toDouble(); ui->input_hdg->setValue(inputDouble); + inputText = in.readLine(); inputDouble = inputText.toDouble(); ui->input_galt->setValue(inputDouble); + inputText = in.readLine(); inputDatetime = QDateTime::fromString(inputText, Qt::ISODate); ui->input_date->setDateTime(inputDatetime); + inputText = in.readLine(); inputDouble = inputText.toDouble(); ui->input_accx->setValue(inputDouble); + inputText = in.readLine(); inputDouble = inputText.toDouble(); ui->input_accy->setValue(inputDouble); + inputText = in.readLine(); inputDouble = inputText.toDouble(); ui->input_accz->setValue(inputDouble); + + inputText = in.readLine(); inputInt = inputText.toInt(); ui->enabled_multi->setChecked(inputInt); + inputText = in.readLine(); inputInt = inputText.toInt(); ui->enabled_rpm->setChecked(inputInt); + inputText = in.readLine(); inputInt = inputText.toInt(); ui->enabled_fuel->setChecked(inputInt); + inputText = in.readLine(); inputInt = inputText.toInt(); ui->enabled_temps->setChecked(inputInt); + inputText = in.readLine(); inputInt = inputText.toInt(); ui->enabled_battery->setChecked(inputInt); + inputText = in.readLine(); inputInt = inputText.toInt(); ui->enabled_baro->setChecked(inputInt); + inputText = in.readLine(); inputInt = inputText.toInt(); ui->enabled_gps->setChecked(inputInt); + inputText = in.readLine(); inputInt = inputText.toInt(); ui->enabled_accel->setChecked(inputInt); + + file.close(); +} diff --git a/companion/src/simulation/telemetryproviderfrskyhub.h b/companion/src/simulation/telemetryproviderfrskyhub.h new file mode 100644 index 000000000..af69e976a --- /dev/null +++ b/companion/src/simulation/telemetryproviderfrskyhub.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#pragma once + +#include +#include + +#include "simulatorinterface.h" +#include "telemetryprovider.h" +#include "simulatedgps.h" + +// Max telemetry packet size +#define FRSKY_HUB_MAX_PACKET_SIZE 16 + +namespace Ui { + class TelemetryProviderFrSkyHub; +} + +class TelemetryProviderFrSkyHub : public QWidget, public TelemetryProvider +{ + Q_OBJECT + + public: + + explicit TelemetryProviderFrSkyHub(QWidget * parent); + virtual ~TelemetryProviderFrSkyHub(); + + signals: + void telemetryDataChanged(const quint8 protocol, const QByteArray data); + + public slots: + void resetRssi(); + QHash * getSupportedLogItems(); + void loadItemFromLog(QString itemName, QString value); + QString getLogfileIdentifier(); + + protected slots: + void generateTelemetryFrame(SimulatorInterface * simulator); + void loadUiFromSimulator(SimulatorInterface * simulator); + + protected: + Ui::TelemetryProviderFrSkyHub * ui; + SimulatedGPS gps; + QHash supportedLogItems; + + private slots: + void on_button_gpsRunStop_clicked(); + void on_button_saveTelemetryValues_clicked(); + void on_button_loadTelemetryValues_clicked(); + + void sendLongLinkFrame(int rssi, double a1, double a2, int trss, int rqly, int tqly); + void sendShortLinkFrame(int rssi, double a1, double a2); + void sendLinkFrame(int rssi, double a1, double a2, int trss, int rqly, int tqly); + void sendSensorPacket(uint8_t id, uint16_t value); + void sendRPMFrame(int rpm); + void sendFuelFrame(int fuelpercent); + void sendTempsFrame(int temp1, int temp2); + void sendBatteryFrame(double vfas, double curr); + void sendBaroFrame(double vspd, double alt); + void sendAccelFrame(double accx, double accy, double accz); + void sendGPSFrame(SimulatedGPS & gps); +}; diff --git a/companion/src/simulation/telemetryproviderfrskyhub.ui b/companion/src/simulation/telemetryproviderfrskyhub.ui new file mode 100644 index 000000000..52a396d02 --- /dev/null +++ b/companion/src/simulation/telemetryproviderfrskyhub.ui @@ -0,0 +1,1287 @@ + + + TelemetryProviderFrSkyHub + + + + 0 + 0 + 311 + 961 + + + + + 0 + 0 + + + + Form + + + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + 8 + + + + -99.000000000000000 + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + 13.199999999999999 + + + 5.100000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + Fuel + + + + + + + + 8 + + + + RPM + + + + + + + + 0 + 0 + + + + + 8 + + + + G + + + + + + + + 8 + + + + Baro + + + + + + + + 0 + 0 + + + + + 8 + + + + Tmp2 + + + + + + + + 8 + + + + Run + + + + + + + + 0 + 0 + + + + + 8 + + + + VSpd + + + + + + + + 0 + 0 + + + + + 8 + + + + Hdg + + + + + + + + 8 + + + + °C + + + + + + + + 0 + 0 + + + + + 8 + + + + 0 + + + 100 + + + 79 + + + + + + + + 0 + 0 + + + + + 8 + + + + G + + + + + + + + 0 + 0 + + + + + 8 + + + + V + + + + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + 0 + + + 255 + + + 21 + + + + + + + + 8 + + + + Date + + + + + + + + 0 + 0 + + + + + 8 + + + + m/s + + + + + + + + 0 + 0 + + + + + 8 + + + + RPM + + + + + + + + 0 + 0 + + + + + 8 + + + + A2 + + + + + + + + 0 + 0 + + + + + 8 + + + + 255 + + + 65 + + + + + + + + 0 + 0 + + + + + 8 + + + + -99.000000000000000 + + + 0.200000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + AccX + + + + + + + + 8 + + + + Accel + + + + + + + + 8 + + + + Batt + + + + + + + + 0 + 0 + + + + + 8 + + + + GAlt + + + + + + + + 0 + 0 + + + + + 8 + + + + A1 + + + + + + + + 8 + + + + Temp + + + + + + + + 0 + 0 + + + + + 8 + + + + RSSI + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + + 8 + + + + 13.199999999999999 + + + 3.290000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + VFAS + + + + + + + + 8 + + + + °C + + + + + + + + 0 + 0 + + + + + 8 + + + + RPM + + + + + + + + 0 + 0 + + + + + 8 + + + + Tmp1 + + + + + + + + 0 + 0 + + + + + 8 + + + + % + + + + + + + + 0 + 0 + + + + + 8 + + + + 1 + + + 2.500000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + Alt + + + + + + + + 0 + 0 + + + + + 8 + + + + AccZ + + + + + + + + 8 + + + + Degrees + + + + + + + + 0 + 0 + + + + + 8 + + + + m + + + + + + + + 8 + + + + 74 + + + + + + + + 0 + 0 + + + + + 8 + + + + Curr + + + + + + + + 8 + + + + 98 + + + + + + + + 8 + + + + Fuel + + + + + + + + 8 + + + + + + + + + 0 + 0 + + + + + 8 + + + + A + + + + + + + + 0 + 0 + + + + + 8 + + + + m + + + + + + + + 0 + 0 + + + + + 8 + + + + 1 + + + -1000.000000000000000 + + + 32768.000000000000000 + + + 33.000000000000000 + + + + + + + + 8 + + + + Load Telemetry Values + + + + + + + + 8 + + + + Save Telemetry Values + + + + + + + + 0 + 0 + + + + + 8 + + + + GSpd + + + + + + + + 0 + 0 + + + + + 8 + + + + 31.000000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + 49.640000000000001 + + + 1.909090000000000 + + + 12.100000000000000 + + + + + + + + 8 + + + + GPS + + + + + + + + 0 + 0 + + + + + 8 + + + + knots + + + + + + + + 0 + 0 + + + + + 8 + + + + Lat,Lon +(dec.deg.) + + + false + + + + + + + + 8 + + + + 1 + + + 1200.000000000000000 + + + 31.000000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + G + + + + + + + + 8 + + + + GPS Sim + + + + + + + + 0 + 0 + + + + + 8 + + + + V + + + + + + + + 0 + 0 + + + + + 8 + + + + GPS + + + + + + + + 0 + 0 + + + + + 8 + + + + 30000 + + + 60 + + + 1260 + + + + + + + + 0 + 0 + + + + + 8 + + + + V + + + + + + + + 8 + + + + 25.9973,-97.1572 + + + + + + + + 0 + 0 + + + + + 8 + + + + -99.000000000000000 + + + -0.100000000000000 + + + + + + + + 0 + 0 + + + + + 8 + + + + AccY + + + + + + + + 1 + 0 + + + + + 8 + + + + 0 + + + 255 + + + 97 + + + + + + + + 8 + + + + 0 + + + 127 + + + 1 + + + 0 + + + + + + + + 8 + + + + 360.000000000000000 + + + 340.000000000000000 + + + + + + + + 8 + + + + MultiModule + + + + + + + + 8 + + + + TRSS + + + + + + + + 8 + + + + RQly + + + + + + + + 8 + + + + TQly + + + + + + + + 8 + + + + dB + + + + + + + + diff --git a/companion/src/simulation/telemetrysimu.cpp b/companion/src/simulation/telemetrysimu.cpp index 281931159..312cdaf47 100644 --- a/companion/src/simulation/telemetrysimu.cpp +++ b/companion/src/simulation/telemetrysimu.cpp @@ -26,6 +26,7 @@ #include "telem_data.h" #include "radio/src/telemetry/frsky_defs.h" #include "telemetryproviderfrsky.h" +#include "telemetryproviderfrskyhub.h" #include "telemetryprovidercrossfire.h" #include @@ -159,6 +160,28 @@ TelemetryProvider * TelemetrySimulator::newTelemetryProviderFromDropdownChoice(i case 0: return NULL; case 1: + { + TelemetryProviderCrossfire * newProvider = new TelemetryProviderCrossfire(parent); + parent->setWidget(newProvider); + if (isExternal) { + connect(newProvider, &TelemetryProviderCrossfire::telemetryDataChanged, this, &TelemetrySimulator::onExternalTelemetryProviderDataChanged); + } else { + connect(newProvider, &TelemetryProviderCrossfire::telemetryDataChanged, this, &TelemetrySimulator::onInternalTelemetryProviderDataChanged); + } + return newProvider; + } + case 2: + { + TelemetryProviderFrSkyHub * newProvider = new TelemetryProviderFrSkyHub(parent); + parent->setWidget(newProvider); + if (isExternal) { + connect(newProvider, &TelemetryProviderFrSkyHub::telemetryDataChanged, this, &TelemetrySimulator::onExternalTelemetryProviderDataChanged); + } else { + connect(newProvider, &TelemetryProviderFrSkyHub::telemetryDataChanged, this, &TelemetrySimulator::onInternalTelemetryProviderDataChanged); + } + return newProvider; + } + case 3: { TelemetryProviderFrSky * newProvider = new TelemetryProviderFrSky(parent); parent->setWidget(newProvider); @@ -169,18 +192,6 @@ TelemetryProvider * TelemetrySimulator::newTelemetryProviderFromDropdownChoice(i } // FrSky provider needs to set up sensor instances from the model's telemetry configuration newProvider->loadUiFromSimulator(simulator); - - return newProvider; - } - case 2: - { - TelemetryProviderCrossfire * newProvider = new TelemetryProviderCrossfire(parent); - parent->setWidget(newProvider); - if (isExternal) { - connect(newProvider, &TelemetryProviderCrossfire::telemetryDataChanged, this, &TelemetrySimulator::onExternalTelemetryProviderDataChanged); - } else { - connect(newProvider, &TelemetryProviderCrossfire::telemetryDataChanged, this, &TelemetrySimulator::onInternalTelemetryProviderDataChanged); - } return newProvider; } default: diff --git a/companion/src/simulation/telemetrysimu.ui b/companion/src/simulation/telemetrysimu.ui index 39b9739b8..44cdaa3f4 100644 --- a/companion/src/simulation/telemetrysimu.ui +++ b/companion/src/simulation/telemetrysimu.ui @@ -495,12 +495,17 @@ Timestamp - FrSky S.Port + CRSF / ELRS - CRSF / ELRS + FrSky Hub + + + + + FrSky S.Port @@ -598,12 +603,17 @@ Timestamp - FrSky S.Port + CRSF / ELRS - CRSF / ELRS + FrSky Hub + + + + + FrSky S.Port diff --git a/radio/src/targets/simu/opentxsimulator.cpp b/radio/src/targets/simu/opentxsimulator.cpp index 44a33c6b7..2614402bc 100644 --- a/radio/src/targets/simu/opentxsimulator.cpp +++ b/radio/src/targets/simu/opentxsimulator.cpp @@ -419,6 +419,25 @@ void OpenTxSimulator::sendTelemetry(const uint8_t module, const uint8_t protocol (uint8_t *)data.constData(), data.count()); break; + case SIMU_TELEMETRY_PROTOCOL_FRSKY_HUB_OOB: + // FrSky D telemetry is a stream which can span multiple + // packets. The telemetry parser _could_ be in the middle of a + // user packet when we want to inject telemetry, so we can't just + // call frskyDProcessPacket() with a USRPKT (at least not safely!) + // Instead we will bypass the telemetry stream parser and inject + // out of band into processHubPacket(). This way we also don't + // have to mess with byte stuffing and variable length packets. + // + // Note this doesn't take into account which module it's from, but + // that's how it works in the radio too if you have two frsky D + // modules running at once, so ¯\_(ツ)_/¯ + { + uint8_t id = data[0]; + uint16_t value = ((uint8_t)(data[2]) << 8) + (uint8_t)(data[1]); + + processHubPacket(id, value); + } + break; case SIMU_TELEMETRY_PROTOCOL_CROSSFIRE: processCrossfireTelemetryFrame(module, (uint8_t *)data.constData(),