diff --git a/data/index.html b/data/index.html
index f5da9fd..ca94cbb 100755
--- a/data/index.html
+++ b/data/index.html
@@ -111,6 +111,10 @@
Phobos LapTimer
+
+
+
+
diff --git a/data/script.js b/data/script.js
index 9e99ac7..3e8b60f 100644
--- a/data/script.js
+++ b/data/script.js
@@ -13,6 +13,7 @@ const ssidInput = document.getElementById("ssid");
const pwdInput = document.getElementById("pwd");
const minLapInput = document.getElementById("minLap");
const alarmThreshold = document.getElementById("alarmThreshold");
+const elrsBindPhrase = document.getElementById("elrsBindPhrase");
const freqLookup = [
[5865, 5845, 5825, 5805, 5785, 5765, 5745, 5725],
@@ -85,6 +86,7 @@ onload = function (e) {
timer.innerHTML = "00:00:00 s";
clearLaps();
createRssiChart();
+ elrsBindPhrase.value = config.elrsBindPhrase
});
};
@@ -245,6 +247,7 @@ function saveConfig() {
name: pilotNameInput.value,
ssid: ssidInput.value,
pwd: pwdInput.value,
+ elrsBindPhrase: elrsBindPhrase.value,
}),
})
.then((response) => response.json())
diff --git a/lib/CONFIG/config.cpp b/lib/CONFIG/config.cpp
index a481808..4432067 100644
--- a/lib/CONFIG/config.cpp
+++ b/lib/CONFIG/config.cpp
@@ -59,6 +59,7 @@ void Config::toJson(AsyncResponseStream& destination) {
config["name"] = conf.pilotName;
config["ssid"] = conf.ssid;
config["pwd"] = conf.password;
+ config["elrsBindPhrase"] = conf.elrsBindPhrase;
serializeJson(config, destination);
}
@@ -74,6 +75,7 @@ void Config::toJsonString(char* buf) {
config["name"] = conf.pilotName;
config["ssid"] = conf.ssid;
config["pwd"] = conf.password;
+ config["elrsBindPhrase"] = conf.elrsBindPhrase;
serializeJsonPretty(config, buf, 256);
}
@@ -118,6 +120,10 @@ void Config::fromJson(JsonObject source) {
strlcpy(conf.password, source["pwd"] | "", sizeof(conf.password));
modified = true;
}
+ if (source["elrsBindPhrase"] != conf.elrsBindPhrase) {
+ strlcpy(conf.elrsBindPhrase, source["elrsBindPhrase"] | "", sizeof(conf.elrsBindPhrase));
+ modified = true;
+ }
}
uint16_t Config::getFrequency() {
@@ -148,6 +154,10 @@ char* Config::getPassword() {
return conf.password;
}
+char* Config::getElrsBindPhrase() {
+ return conf.elrsBindPhrase;
+}
+
void Config::setDefaults(void) {
DEBUG("Setting EEPROM defaults\n");
// Reset everything to 0/false and then just set anything that zero is not appropriate
@@ -163,6 +173,7 @@ void Config::setDefaults(void) {
strlcpy(conf.ssid, "", sizeof(conf.ssid));
strlcpy(conf.password, "", sizeof(conf.password));
strlcpy(conf.pilotName, "", sizeof(conf.pilotName));
+ strlcpy(conf.elrsBindPhrase, "", sizeof(conf.elrsBindPhrase));
modified = true;
write();
}
diff --git a/lib/CONFIG/config.h b/lib/CONFIG/config.h
index e46b834..11c9750 100644
--- a/lib/CONFIG/config.h
+++ b/lib/CONFIG/config.h
@@ -81,6 +81,7 @@ typedef struct {
char pilotName[21];
char ssid[33];
char password[33];
+ char elrsBindPhrase[32];
} laptimer_config_t;
class Config {
@@ -101,6 +102,7 @@ class Config {
uint8_t getExitRssi();
char* getSsid();
char* getPassword();
+ char* getElrsBindPhrase();
private:
laptimer_config_t conf;
diff --git a/lib/LAPTIMER/laptimer.cpp b/lib/LAPTIMER/laptimer.cpp
index 8711233..4d34868 100644
--- a/lib/LAPTIMER/laptimer.cpp
+++ b/lib/LAPTIMER/laptimer.cpp
@@ -10,6 +10,7 @@ void LapTimer::init(Config *config, RX5808 *rx5808, Buzzer *buzzer, Led *l) {
rx = rx5808;
buz = buzzer;
led = l;
+ startTimeOffsetMs = 0;
filter.setMeasurementNoise(rssi_filter_q * 0.01f);
filter.setProcessNoise(rssi_filter_r * 0.0001f);
@@ -21,6 +22,7 @@ void LapTimer::init(Config *config, RX5808 *rx5808, Buzzer *buzzer, Led *l) {
void LapTimer::start() {
DEBUG("LapTimer started\n");
state = RUNNING;
+ startTimeOffsetMs = millis();
buz->beep(500);
led->on(500);
}
@@ -30,6 +32,7 @@ void LapTimer::stop() {
state = STOPPED;
lapCount = 0;
rssiCount = 0;
+ startTimeOffsetMs = 0;
memset(lapTimes, 0, sizeof(lapTimes));
buz->beep(500);
led->on(500);
@@ -75,7 +78,7 @@ void LapTimer::lapPeakCapture() {
// Check if RSSI is greater than the previous detected peak
if (rssi[rssiCount] > rssiPeak) {
rssiPeak = rssi[rssiCount];
- rssiPeakTimeMs = millis();
+ rssiPeakTimeMs = millis() - startTimeOffsetMs;
}
}
}
diff --git a/lib/LAPTIMER/laptimer.h b/lib/LAPTIMER/laptimer.h
index 42a39f9..83c7605 100644
--- a/lib/LAPTIMER/laptimer.h
+++ b/lib/LAPTIMER/laptimer.h
@@ -30,6 +30,7 @@ class LapTimer {
Buzzer *buz;
Led *led;
KalmanFilter filter;
+ uint32_t startTimeOffsetMs;
uint32_t startTimeMs;
uint8_t lapCount;
uint8_t rssiCount;
diff --git a/lib/MSP/Readme.md b/lib/MSP/Readme.md
new file mode 100644
index 0000000..c0d1cb4
--- /dev/null
+++ b/lib/MSP/Readme.md
@@ -0,0 +1 @@
+Files are copied from https://github.com/ExpressLRS/Backpack/tree/master/lib/MSP commit 476d7c5
diff --git a/lib/MSP/msp.cpp b/lib/MSP/msp.cpp
new file mode 100644
index 0000000..a7b2b07
--- /dev/null
+++ b/lib/MSP/msp.cpp
@@ -0,0 +1,314 @@
+#include "msp.h"
+#include "debug.h"
+/* ==========================================
+MSP V2 Message Structure:
+Offset: Usage: In CRC: Comment:
+======= ====== ======= ========
+0 $ Framing magic start char
+1 X 'X' in place of v1 'M'
+2 type '<' / '>' / '!' Message Type (TODO find out what ! type is)
+3 flag + uint8, flag, usage to be defined (set to zero)
+4 function + uint16 (little endian). 0 - 255 is the same function as V1 for backwards compatibility
+6 payload size + uint16 (little endian) payload size in bytes
+8 payload + n (up to 65535 bytes) payload
+n+8 checksum uint8, (n= payload size), crc8_dvb_s2 checksum
+========================================== */
+
+// CRC helper function. External to MSP class
+// TODO: Move all our CRC functions to a CRC lib
+uint8_t crc8_dvb_s2(uint8_t crc, unsigned char a)
+{
+ crc ^= a;
+ for (int ii = 0; ii < 8; ++ii) {
+ if (crc & 0x80) {
+ crc = (crc << 1) ^ 0xD5;
+ } else {
+ crc = crc << 1;
+ }
+ }
+ return crc;
+}
+
+MSP::MSP() : m_inputState(MSP_IDLE)
+{
+}
+
+bool
+MSP::processReceivedByte(uint8_t c)
+{
+ switch (m_inputState) {
+
+ case MSP_IDLE:
+ // Wait for framing char
+ if (c == '$') {
+ m_inputState = MSP_HEADER_START;
+ }
+ break;
+
+ case MSP_HEADER_START:
+ // Waiting for 'X' (MSPv2 native)
+ switch (c) {
+ case 'X':
+ m_inputState = MSP_HEADER_X;
+ break;
+ default:
+ m_inputState = MSP_IDLE;
+ break;
+ }
+ break;
+
+ case MSP_HEADER_X:
+ // Wait for the packet type (cmd or req)
+ m_inputState = MSP_HEADER_V2_NATIVE;
+
+ // Start of a new packet
+ // reset the packet, offset iterator, and CRC
+ m_packet.reset();
+ m_offset = 0;
+ m_crc = 0;
+
+ switch (c) {
+ case '<':
+ m_packet.type = MSP_PACKET_COMMAND;
+ break;
+ case '>':
+ m_packet.type = MSP_PACKET_RESPONSE;
+ break;
+ default:
+ m_packet.type = MSP_PACKET_UNKNOWN;
+ m_inputState = MSP_IDLE;
+ break;
+ }
+ break;
+
+ case MSP_HEADER_V2_NATIVE:
+ // Read bytes until we have a full header
+ m_inputBuffer[m_offset++] = c;
+ m_crc = crc8_dvb_s2(m_crc, c);
+
+ // If we've received the correct amount of bytes for a full header
+ if (m_offset == sizeof(mspHeaderV2_t)) {
+ // Copy header values into packet
+ mspHeaderV2_t* header = (mspHeaderV2_t*)&m_inputBuffer[0];
+ m_packet.payloadSize = header->payloadSize;
+ m_packet.function = header->function;
+ m_packet.flags = header->flags;
+ // reset the offset iterator for re-use in payload below
+ m_offset = 0;
+ if (m_packet.payloadSize == 0)
+ m_inputState = MSP_CHECKSUM_V2_NATIVE;
+ else
+ m_inputState = MSP_PAYLOAD_V2_NATIVE;
+ }
+ break;
+
+ case MSP_PAYLOAD_V2_NATIVE:
+ // Read bytes until we reach payloadSize
+ m_packet.payload[m_offset++] = c;
+ m_crc = crc8_dvb_s2(m_crc, c);
+
+ // If we've received the correct amount of bytes for payload
+ if (m_offset == m_packet.payloadSize) {
+ // Then we're up to the CRC
+ m_inputState = MSP_CHECKSUM_V2_NATIVE;
+ }
+ break;
+
+ case MSP_CHECKSUM_V2_NATIVE:
+ // Assert that the checksums match
+ if (m_crc == c) {
+ m_inputState = MSP_COMMAND_RECEIVED;
+ }
+ else {
+ DEBUG("CRC failure on MSP packet - Got %d expected %d", c, m_crc);
+ m_inputState = MSP_IDLE;
+ }
+ break;
+
+ default:
+ m_inputState = MSP_IDLE;
+ break;
+ }
+
+ // If we've successfully parsed a complete packet
+ // return true so the calling function knows that
+ // a new packet is ready.
+ if (m_inputState == MSP_COMMAND_RECEIVED) {
+ return true;
+ }
+ return false;
+}
+
+mspPacket_t*
+MSP::getReceivedPacket()
+{
+ return &m_packet;
+}
+
+void
+MSP::markPacketReceived()
+{
+ // Set input state to idle, ready to receive the next packet
+ // The current packet data will be discarded internally
+ m_inputState = MSP_IDLE;
+}
+
+bool
+MSP::sendPacket(mspPacket_t* packet, Stream* port)
+{
+ // Sanity check the packet before sending
+ if (packet->type != MSP_PACKET_COMMAND && packet->type != MSP_PACKET_RESPONSE) {
+ // Unsupported packet type (note: ignoring '!' until we know what it is)
+ return false;
+ }
+
+ if (packet->type == MSP_PACKET_RESPONSE && packet->payloadSize == 0) {
+ // Response packet with no payload
+ return false;
+ }
+
+ // Write out the framing chars
+ port->write('$');
+ port->write('X');
+
+ // Write out the packet type
+ if (packet->type == MSP_PACKET_COMMAND) {
+ port->write('<');
+ }
+ else if (packet->type == MSP_PACKET_RESPONSE) {
+ port->write('>');
+ }
+
+ // Subsequent bytes are contained in the crc
+ uint8_t crc = 0;
+
+ // Pack header struct into buffer
+ uint8_t headerBuffer[5];
+ mspHeaderV2_t* header = (mspHeaderV2_t*)&headerBuffer[0];
+ header->flags = packet->flags;
+ header->function = packet->function;
+ header->payloadSize = packet->payloadSize;
+
+ // Write out the header buffer, adding each byte to the crc
+ for (uint8_t i = 0; i < sizeof(mspHeaderV2_t); ++i) {
+ port->write(headerBuffer[i]);
+ crc = crc8_dvb_s2(crc, headerBuffer[i]);
+ }
+
+ // Write out the payload, adding each byte to the crc
+ for (uint16_t i = 0; i < packet->payloadSize; ++i) {
+ port->write(packet->payload[i]);
+ crc = crc8_dvb_s2(crc, packet->payload[i]);
+ }
+
+ // Write out the crc
+ port->write(crc);
+
+ return true;
+}
+uint8_t
+MSP::convertToByteArray(mspPacket_t* packet, uint8_t* byteArray)
+{
+ uint8_t bufferPos = 0;
+ // Sanity check the packet before converting
+ if (packet->type != MSP_PACKET_COMMAND && packet->type != MSP_PACKET_RESPONSE) {
+ // Unsupported packet type (note: ignoring '!' until we know what it is)
+ return 0;
+ }
+
+ if (packet->type == MSP_PACKET_RESPONSE && packet->payloadSize == 0) {
+ // Response packet with no payload
+ return 0;
+ }
+
+ // Write out the framing chars
+ byteArray[bufferPos++] = '$';
+ byteArray[bufferPos++] = 'X';
+
+ // Write out the packet type
+ if (packet->type == MSP_PACKET_COMMAND) {
+ byteArray[bufferPos++] = '<';
+ }
+ else if (packet->type == MSP_PACKET_RESPONSE) {
+ byteArray[bufferPos++] = '>';
+ }
+
+ // Subsequent bytes are contained in the crc
+ uint8_t crc = 0;
+
+ // Pack header struct into buffer
+ uint8_t headerBuffer[5];
+ mspHeaderV2_t* header = (mspHeaderV2_t*)&headerBuffer[0];
+ header->flags = packet->flags;
+ header->function = packet->function;
+ header->payloadSize = packet->payloadSize;
+
+ // Write out the header buffer, adding each byte to the crc
+ for (uint8_t i = 0; i < sizeof(mspHeaderV2_t); ++i) {
+ byteArray[bufferPos++] = headerBuffer[i];
+ crc = crc8_dvb_s2(crc, headerBuffer[i]);
+ }
+
+ // Write out the payload, adding each byte to the crc
+ for (uint16_t i = 0; i < packet->payloadSize; ++i) {
+ byteArray[bufferPos++] = packet->payload[i];
+ crc = crc8_dvb_s2(crc, packet->payload[i]);
+ }
+
+ // Write out the crc
+ byteArray[bufferPos++] = crc;
+
+ return bufferPos;
+}
+
+uint8_t
+MSP::getTotalPacketSize(mspPacket_t* packet)
+{
+ uint8_t totalSize = 0;
+
+ // framing chars
+ totalSize += sizeof('$');
+ totalSize += sizeof('X');
+
+ // packet type
+ if (packet->type == MSP_PACKET_COMMAND) {
+ totalSize += sizeof('<');
+ }
+ else if (packet->type == MSP_PACKET_RESPONSE) {
+ totalSize += sizeof('>');
+ }
+
+ // header
+ totalSize += sizeof(mspHeaderV2_t);
+
+ // payload
+ totalSize += packet->payloadSize;
+
+ // crc
+ totalSize++;
+
+ return totalSize;
+}
+
+bool
+MSP::awaitPacket(mspPacket_t* packet, Stream* port, uint32_t timeoutMillis)
+{
+ uint32_t requestTime = millis();
+
+ sendPacket(packet, port);
+
+ // wait up to milliseconds for a response, then bail out
+ while(millis() - requestTime < timeoutMillis)
+ {
+ while (port->available())
+ {
+ uint8_t data = port->read();
+ if (processReceivedByte(data))
+ {
+ return true;
+ }
+ }
+ }
+ DEBUG("MSP::awaitPacket Exceeded timeout while waiting for packet");
+ return false;
+}
\ No newline at end of file
diff --git a/lib/MSP/msp.h b/lib/MSP/msp.h
new file mode 100644
index 0000000..b8cc178
--- /dev/null
+++ b/lib/MSP/msp.h
@@ -0,0 +1,168 @@
+#pragma once
+
+#include
+#include "msptypes.h"
+
+// TODO: MSP_PORT_INBUF_SIZE should be changed to
+// dynamically allocate array length based on the payload size
+// Hardcoding payload size to 64 bytes for now, to allow enough space
+// for custom OSD text.
+#define MSP_PORT_INBUF_SIZE 64
+
+#define CHECK_PACKET_PARSING() \
+ if (packet->readError) {\
+ return;\
+ }
+
+typedef enum {
+ MSP_IDLE,
+ MSP_HEADER_START,
+ MSP_HEADER_X,
+
+ MSP_HEADER_V2_NATIVE,
+ MSP_PAYLOAD_V2_NATIVE,
+ MSP_CHECKSUM_V2_NATIVE,
+
+ MSP_COMMAND_RECEIVED
+} mspState_e;
+
+typedef enum {
+ MSP_PACKET_UNKNOWN,
+ MSP_PACKET_COMMAND,
+ MSP_PACKET_RESPONSE
+} mspPacketType_e;
+
+typedef struct __attribute__((packed)) {
+ uint8_t flags;
+ uint16_t function;
+ uint16_t payloadSize;
+} mspHeaderV2_t;
+
+typedef struct {
+ mspPacketType_e type;
+ uint8_t flags;
+ uint16_t function;
+ uint16_t payloadSize;
+ uint8_t payload[MSP_PORT_INBUF_SIZE];
+ uint16_t payloadReadIterator;
+ bool readError;
+ // Function that replace function enum with string
+ const char *get_func_name()
+ {
+ switch (function)
+ {
+ case MSP_ELRS_FUNC:
+ return "MSP_ELRS_FUNC";
+ case MSP_SET_RX_CONFIG:
+ return "MSP_SET_RX_CONFIG";
+ case MSP_VTX_CONFIG:
+ return "MSP_VTX_CONFIG";
+ case MSP_SET_VTX_CONFIG:
+ return "MSP_SET_VTX_CONFIG";
+ case MSP_EEPROM_WRITE:
+ return "MSP_EEPROM_WRITE";
+ case MSP_ELRS_RF_MODE:
+ return "MSP_ELRS_RF_MODE";
+ case MSP_ELRS_TX_PWR:
+ return "MSP_ELRS_TX_PWR";
+ case MSP_ELRS_TLM_RATE:
+ return "MSP_ELRS_TLM_RATE";
+ case MSP_ELRS_BIND:
+ return "MSP_ELRS_BIND";
+ case MSP_ELRS_MODEL_ID:
+ return "MSP_ELRS_MODEL_ID";
+ case MSP_ELRS_REQU_VTX_PKT:
+ return "MSP_ELRS_REQU_VTX_PKT";
+ case MSP_ELRS_SET_TX_BACKPACK_WIFI_MODE:
+ return "MSP_ELRS_SET_TX_BACKPACK_WIFI_MODE";
+ case MSP_ELRS_SET_VRX_BACKPACK_WIFI_MODE:
+ return "MSP_ELRS_SET_VRX_BACKPACK_WIFI_MODE";
+ case MSP_ELRS_SET_RX_WIFI_MODE:
+ return "MSP_ELRS_SET_RX_WIFI_MODE";
+ case MSP_ELRS_SET_RX_LOAN_MODE:
+ return "MSP_ELRS_SET_RX_LOAN_MODE";
+ case MSP_ELRS_GET_BACKPACK_VERSION:
+ return "MSP_ELRS_GET_BACKPACK_VERSION";
+ case MSP_ELRS_BACKPACK_CRSF_TLM:
+ return "MSP_ELRS_BACKPACK_CRSF_TLM";
+ case MSP_ELRS_SET_SEND_UID:
+ return "MSP_ELRS_SET_SEND_UID";
+ case MSP_ELRS_SET_OSD:
+ return "MSP_ELRS_SET_OSD";
+ case MSP_ELRS_BACKPACK_CONFIG:
+ return "MSP_ELRS_BACKPACK_CONFIG";
+ case MSP_ELRS_BACKPACK_CONFIG_TLM_MODE:
+ return "MSP_ELRS_BACKPACK_CONFIG_TLM_MODE";
+ case MSP_ELRS_BACKPACK_GET_CHANNEL_INDEX:
+ return "MSP_ELRS_BACKPACK_GET_CHANNEL_INDEX";
+ case MSP_ELRS_BACKPACK_SET_CHANNEL_INDEX:
+ return "MSP_ELRS_BACKPACK_SET_CHANNEL_INDEX";
+ case MSP_ELRS_BACKPACK_GET_FREQUENCY:
+ return "MSP_ELRS_BACKPACK_GET_FREQUENCY";
+ case MSP_ELRS_BACKPACK_GET_RECORDING_STATE:
+ return "MSP_ELRS_BACKPACK_GET_RECORDING_STATE";
+ default:
+ static char buffer[32];
+ snprintf(buffer, sizeof(buffer), "Unknown function: %u", function);
+ return buffer;
+ }
+ }
+
+ void reset()
+ {
+ type = MSP_PACKET_UNKNOWN;
+ flags = 0;
+ function = 0;
+ payloadSize = 0;
+ payloadReadIterator = 0;
+ readError = false;
+ }
+
+ void addByte(uint8_t b)
+ {
+ payload[payloadSize++] = b;
+ }
+
+ void makeResponse()
+ {
+ type = MSP_PACKET_RESPONSE;
+ }
+
+ void makeCommand()
+ {
+ type = MSP_PACKET_COMMAND;
+ }
+
+ uint8_t readByte()
+ {
+ if (payloadReadIterator >= payloadSize) {
+ // We are trying to read beyond the length of the payload
+ readError = true;
+ return 0;
+ }
+
+ return payload[payloadReadIterator++];
+ }
+} mspPacket_t;
+
+/////////////////////////////////////////////////
+
+class MSP
+{
+public:
+ MSP();
+ bool processReceivedByte(uint8_t c);
+ mspPacket_t* getReceivedPacket();
+ void markPacketReceived();
+ bool sendPacket(mspPacket_t* packet, Stream* port);
+ uint8_t convertToByteArray(mspPacket_t* packet, uint8_t* byteArray);
+ uint8_t getTotalPacketSize(mspPacket_t* packet);
+ bool awaitPacket(mspPacket_t* packet, Stream* port, uint32_t timeoutMillis);
+
+private:
+ mspState_e m_inputState;
+ uint16_t m_offset;
+ uint8_t m_inputBuffer[MSP_PORT_INBUF_SIZE];
+ mspPacket_t m_packet;
+ uint8_t m_crc;
+};
\ No newline at end of file
diff --git a/lib/MSP/msptypes.h b/lib/MSP/msptypes.h
new file mode 100644
index 0000000..0cda911
--- /dev/null
+++ b/lib/MSP/msptypes.h
@@ -0,0 +1,58 @@
+#pragma once
+
+#define MSP_ELRS_FUNC 0x4578 // ['E','x']
+
+#define MSP_SET_RX_CONFIG 45
+#define MSP_VTX_CONFIG 88 //out message Get vtx settings - betaflight
+#define MSP_SET_VTX_CONFIG 89 //in message Set vtx settings - betaflight
+#define MSP_EEPROM_WRITE 250 //in message no param
+
+// ELRS specific opcodes
+#define MSP_ELRS_RF_MODE 0x06
+#define MSP_ELRS_TX_PWR 0x07
+#define MSP_ELRS_TLM_RATE 0x08
+#define MSP_ELRS_BIND 0x09
+#define MSP_ELRS_MODEL_ID 0x0A
+#define MSP_ELRS_REQU_VTX_PKT 0x0B
+#define MSP_ELRS_SET_TX_BACKPACK_WIFI_MODE 0x0C
+#define MSP_ELRS_SET_VRX_BACKPACK_WIFI_MODE 0x0D
+#define MSP_ELRS_SET_RX_WIFI_MODE 0x0E
+#define MSP_ELRS_SET_RX_LOAN_MODE 0x0F
+#define MSP_ELRS_GET_BACKPACK_VERSION 0x10
+#define MSP_ELRS_BACKPACK_CRSF_TLM 0x11
+#define MSP_ELRS_SET_SEND_UID 0x00B5
+#define MSP_ELRS_SET_OSD 0x00B6
+
+// Config opcodes
+#define MSP_ELRS_BACKPACK_CONFIG 0x30
+#define MSP_ELRS_BACKPACK_CONFIG_TLM_MODE 0x31
+
+// CRSF encapsulated msp defines
+#define ENCAPSULATED_MSP_PAYLOAD_SIZE 4
+#define ENCAPSULATED_MSP_FRAME_LEN 8
+
+// ELRS backpack protocol opcodes
+// See: https://docs.google.com/document/d/1u3c7OTiO4sFL2snI-hIo-uRSLfgBK4h16UrbA08Pd6U/edit#heading=h.1xw7en7jmvsj
+
+// outgoing, packets originating from the backpack or forwarded from the TX backpack to the VRx
+#define MSP_ELRS_BACKPACK_GET_CHANNEL_INDEX 0x0300
+#define MSP_ELRS_BACKPACK_SET_CHANNEL_INDEX 0x0301
+#define MSP_ELRS_BACKPACK_GET_FREQUENCY 0x0302
+#define MSP_ELRS_BACKPACK_SET_FREQUENCY 0x0303
+#define MSP_ELRS_BACKPACK_GET_RECORDING_STATE 0x0304
+#define MSP_ELRS_BACKPACK_SET_RECORDING_STATE 0x0305
+#define MSP_ELRS_BACKPACK_GET_VRX_MODE 0x0306
+#define MSP_ELRS_BACKPACK_SET_VRX_MODE 0x0307
+#define MSP_ELRS_BACKPACK_GET_RSSI 0x0308
+#define MSP_ELRS_BACKPACK_GET_BATTERY_VOLTAGE 0x0309
+#define MSP_ELRS_BACKPACK_GET_FIRMWARE 0x030A
+#define MSP_ELRS_BACKPACK_SET_BUZZER 0x030B
+#define MSP_ELRS_BACKPACK_SET_OSD_ELEMENT 0x030C
+#define MSP_ELRS_BACKPACK_SET_HEAD_TRACKING 0x030D // enable/disable head-tracking forwarding packets to the TX
+#define MSP_ELRS_BACKPACK_SET_RTC 0x030E
+
+// incoming, packets originating from the VRx
+#define MSP_ELRS_BACKPACK_SET_MODE 0x0380 // enable wifi/binding mode
+#define MSP_ELRS_BACKPACK_GET_VERSION 0x0381 // get the bacpack firmware version
+#define MSP_ELRS_BACKPACK_GET_STATUS 0x0382 // get the status of the backpack
+#define MSP_ELRS_BACKPACK_SET_PTR 0x0383 // forwarded back to TX backpack
\ No newline at end of file
diff --git a/lib/WEBSERVER/webserver.cpp b/lib/WEBSERVER/webserver.cpp
index b042ff2..eb2b184 100644
--- a/lib/WEBSERVER/webserver.cpp
+++ b/lib/WEBSERVER/webserver.cpp
@@ -5,7 +5,9 @@
#include
#include
#include
-
+#include
+#include "msp.h"
+#include "msptypes.h"
#include "debug.h"
static const uint8_t DNS_PORT = 53;
@@ -20,6 +22,129 @@ static const char *wifi_ap_ssid_prefix = "PhobosLT";
static const char *wifi_ap_password = "phoboslt";
static const char *wifi_ap_address = "20.0.0.1";
String wifi_ap_ssid;
+esp_now_peer_info_t peerInfo;
+uint8_t targetAddress[6];
+uint8_t uniAddress[6];
+MSP msp;
+
+int sendMSPViaEspnow(mspPacket_t *packet)
+{
+ int esp_err = -1;
+ uint8_t packetSize = msp.getTotalPacketSize(packet);
+ uint8_t nowDataOutput[packetSize];
+
+ uint8_t result = msp.convertToByteArray(packet, nowDataOutput);
+ DEBUG("Sending MSP data: ");
+ for (uint16_t i = 0; i < packetSize; ++i) {
+ DEBUG("%u ", nowDataOutput[i]);
+ }
+ DEBUG("\n");
+
+ if (!result)
+ {
+ return esp_err;
+ }
+
+ esp_err = esp_now_send(targetAddress, (uint8_t *) &nowDataOutput, packetSize);
+ if (esp_err != ESP_OK) {
+ DEBUG("Error sending ESP-NOW data: %s\n", esp_err_to_name(esp_err));
+ } else {
+ DEBUG("ESP-NOW data sent successfully\n");
+ }
+
+ return esp_err;
+}
+
+void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status)
+{
+ if (status == ESP_NOW_SEND_SUCCESS)
+ DEBUG("SENT OK\n");
+ else
+ DEBUG("SENT FAIL\n");
+}
+
+#define MSP_ELRS_SET_OSD_BEAT 0x01
+#define MSP_ELRS_SET_OSD_CLEAR 0x02
+#define MSP_ELRS_SET_OSD_WRITE 0x03
+#define MSP_ELRS_SET_OSD_SHOW 0x04
+
+mspPacket_t packet;
+void sendMSPOSDMessage(byte subfunction, char msg[]){
+
+ packet.reset();
+ packet.makeCommand();
+ packet.function = MSP_ELRS_SET_OSD;
+
+ packet.addByte(subfunction);
+ packet.addByte(3);
+ packet.addByte(3);
+ packet.addByte(0);
+ packet.addByte(0);
+ if (subfunction == MSP_ELRS_SET_OSD_WRITE)
+ {
+ for (int i = 0; i < strlen(msg); i++)
+ {
+ packet.addByte(msg[i]);
+ }
+ }
+ packet.addByte(0);
+
+ sendMSPViaEspnow(&packet);
+ usleep(100);
+}
+
+void sendElrsHello(){
+ char buf[] = "PLT TIMER CONNECTED!";
+ sendMSPOSDMessage(MSP_ELRS_SET_OSD_CLEAR, buf);
+ sendMSPOSDMessage(MSP_ELRS_SET_OSD_WRITE, buf);
+ sendMSPOSDMessage(MSP_ELRS_SET_OSD_SHOW, buf);
+}
+
+void getLapString(uint32_t lapTime, char *output) {
+ uint32_t min = lapTime / 1000 / 60;
+ uint32_t seconds = lapTime / 1000;
+ uint32_t milis = lapTime % 1000;
+
+ sprintf(output, "%02u:%02u.%03u", min, seconds, milis);
+}
+
+void sendElrsEvent(uint32_t lapTime) {
+ char lapTimeMsg[10];
+ getLapString(lapTime, lapTimeMsg);
+
+ char buf[20];
+ snprintf(buf, sizeof(buf), "TIME: %s", lapTimeMsg);
+ DEBUG("Sending ELRS OSD message: %s\n", buf);
+ sendMSPOSDMessage(MSP_ELRS_SET_OSD_CLEAR, buf);
+ sendMSPOSDMessage(MSP_ELRS_SET_OSD_WRITE, buf);
+ sendMSPOSDMessage(MSP_ELRS_SET_OSD_SHOW, buf);
+}
+
+void getBindingUID(char *phrase, uint8_t *bindingUID)
+{
+ String bindingPhraseFull = String("-DMY_BINDING_PHRASE=\"") + phrase + "\"";
+ int len = bindingPhraseFull.length();
+ char bindingPhrase[len + 1];
+ bindingPhraseFull.toCharArray(bindingPhrase, len + 1);
+
+ unsigned char md5Hash[16];
+ MD5Builder md5;
+ md5.begin();
+ md5.add(bindingPhrase);
+ md5.calculate();
+ md5.getBytes(md5Hash);
+
+ uint8_t uidBytes[6];
+ memcpy(uidBytes, md5Hash, 6);
+ DEBUG("\nBinding phrase uid: ");
+ for (int i = 0; i < 6; i++)
+ {
+ bindingUID[i] = uidBytes[i];
+ DEBUG("%u,", uidBytes[i]);
+ }
+ DEBUG(". Hello :) \n");
+}
+
void Webserver::init(Config *config, LapTimer *lapTimer, BatteryMonitor *batMonitor, Buzzer *buzzer, Led *l) {
@@ -56,6 +181,7 @@ void Webserver::sendRssiEvent(uint8_t rssi) {
events.send(buf, "rssi");
}
+
void Webserver::sendLaptimeEvent(uint32_t lapTime) {
if (!servicesStarted) return;
char buf[16];
@@ -66,6 +192,7 @@ void Webserver::sendLaptimeEvent(uint32_t lapTime) {
void Webserver::handleWebUpdate(uint32_t currentTimeMs) {
if (timer->isLapAvailable()) {
sendLaptimeEvent(timer->getLapTime());
+ sendElrsEvent(timer->getLapTime());
}
if (sendRssi && ((currentTimeMs - rssiSentMs) > WEB_RSSI_SEND_TIMEOUT_MS)) {
@@ -115,12 +242,45 @@ void Webserver::handleWebUpdate(uint32_t currentTimeMs) {
DEBUG("Changing to WiFi AP mode\n");
WiFi.disconnect();
- wifiMode = WIFI_AP;
- WiFi.setHostname(wifi_hostname); // hostname must be set before the mode is set to STA
- WiFi.mode(wifiMode);
- changeTimeMs = currentTimeMs;
+ WiFi.setTxPower(WIFI_POWER_19_5dBm);
+ WiFi.setHostname(wifi_hostname);
WiFi.softAPConfig(ipAddress, ipAddress, netMsk);
+ esp_wifi_set_channel(1, WIFI_SECOND_CHAN_NONE);
+ esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N | WIFI_PROTOCOL_LR);
WiFi.softAP(wifi_ap_ssid.c_str(), wifi_ap_password);
+ WiFi.mode(WIFI_AP_STA);
+ if (conf->getElrsBindPhrase()[0] != 0)
+ {
+
+ DEBUG("Enablind ESP NOW for backpack\n");
+ uint8_t bindingUID[6];
+ getBindingUID(conf->getElrsBindPhrase(), bindingUID);
+
+ memcpy(targetAddress, bindingUID, sizeof(targetAddress));
+ memcpy(uniAddress, bindingUID, sizeof(uniAddress));
+ uniAddress[0] = uniAddress[0] & ~0x01;
+
+ if (esp_now_init() != 0)
+ {
+ DEBUG("Error initializing ESP-NOW\n");
+ return;
+ }
+ esp_wifi_set_mac(WIFI_IF_STA, uniAddress);
+ memset(&peerInfo, 0, sizeof(peerInfo));
+ memcpy(peerInfo.peer_addr, targetAddress, 6);
+ peerInfo.channel = 1;
+ peerInfo.encrypt = false;
+ if (esp_now_add_peer(&peerInfo) != ESP_OK)
+ {
+ DEBUG("ESP-NOW failed to add peer\n");
+ return;
+ }
+
+ esp_now_register_send_cb(OnDataSent);
+ sendElrsHello();
+ }
+
+ DEBUG("Start services\n");
startServices();
buz->beep(1000);
led->on(1000);
diff --git a/src/main.cpp b/src/main.cpp
index 2190eec..ada129a 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -32,6 +32,7 @@ static void initParallelTask() {
xTaskCreatePinnedToCore(parallelTask, "parallelTask", 3000, NULL, 0, &xTimerTask, 0);
}
+
void setup() {
DEBUG_INIT;
config.init();
diff --git a/targets/PhobosLT.ini b/targets/PhobosLT.ini
index e5978a2..1557e0a 100644
--- a/targets/PhobosLT.ini
+++ b/targets/PhobosLT.ini
@@ -16,3 +16,4 @@ lib_deps =
build_flags =
-DCONFIG_ASYNC_TCP_EVENT_QUEUE_SIZE=256
-DELEGANTOTA_USE_ASYNC_WEBSERVER=1
+ -DDEBUG_OUT=1