diff --git a/boards/esp32-s3-devkitc-1-myboard.json b/boards/esp32-s3-devkitc-1-myboard.json new file mode 100644 index 000000000..22a8353cd --- /dev/null +++ b/boards/esp32-s3-devkitc-1-myboard.json @@ -0,0 +1,49 @@ +{ + "build": { + "arduino":{ + "ldscript": "esp32s3_out.ld", + "partitions": "elecrow_partitions.csv" + }, + "core": "esp32", + "extra_flags": [ + "-DARDUINO_USB_MODE=1", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [ + [ + "0x303A", + "0x1001" + ] + ], + "mcu": "esp32s3", + "variant": "esp32s3" + }, + "connectivity": [ + "wifi" + ], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": [ + "esp-builtin" + ], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": [ + "arduino", + "espidf" + ], + "name": "Espressif ESP32-S3-DevKitC-1-N8 -ELECROW", + "upload": { + "flash_size": "4MB", + "maximum_ram_size": 327680, + "maximum_size": 8388608, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/esp32s3/user-guide-devkitc-1.html", + "vendor": "Espressif" +} diff --git a/elecrow_partitions.csv b/elecrow_partitions.csv new file mode 100644 index 000000000..61254fcab --- /dev/null +++ b/elecrow_partitions.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x300000, +spiffs, data, spiffs, 0x310000,0xE0000, +coredump, data, coredump,0x3F0000,0x10000, \ No newline at end of file diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index da1bac5b3..d12a23774 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -241,8 +241,10 @@ protected: } void onCommandDataRecv(const ContactInfo& from, mesh::Packet* pkt, uint32_t sender_timestamp, const char *text) override { + MESH_DEBUG_PRINTLN("onCommandDataRecv"); } void onSignedMessageRecv(const ContactInfo& from, mesh::Packet* pkt, uint32_t sender_timestamp, const uint8_t *sender_prefix, const char *text) override { + MESH_DEBUG_PRINTLN("onSignedMessageRecv"); } void onChannelMessageRecv(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t timestamp, const char *text) override { @@ -255,10 +257,12 @@ protected: } uint8_t onContactRequest(const ContactInfo& contact, uint32_t sender_timestamp, const uint8_t* data, uint8_t len, uint8_t* reply) override { + MESH_DEBUG_PRINTLN("onContactRequest"); return 0; // unknown } void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) override { + MESH_DEBUG_PRINTLN("onContactResponse"); // not supported } diff --git a/src/helpers/esp32/ESPNOWRadio.cpp b/src/helpers/esp32/ESPNOWRadio.cpp index ced19f911..ea54bdb71 100644 --- a/src/helpers/esp32/ESPNOWRadio.cpp +++ b/src/helpers/esp32/ESPNOWRadio.cpp @@ -3,12 +3,27 @@ #include #include +static constexpr uint16_t BRIDGE_PACKET_MAGIC = 0xC03E; +static const size_t MAX_ESPNOW_PACKET_SIZE = 250; +/** + * @brief Common field sizes used by bridge implementations + * + * These constants define the size of common packet fields used across bridges. + * BRIDGE_MAGIC_SIZE is used by all bridges for packet identification. + * BRIDGE_LENGTH_SIZE is used by bridges that need explicit length fields (like RS232). + * BRIDGE_CHECKSUM_SIZE is used by all bridges for Fletcher-16 checksums. + */ +static constexpr uint16_t BRIDGE_MAGIC_SIZE = sizeof(BRIDGE_PACKET_MAGIC); +static constexpr uint16_t BRIDGE_LENGTH_SIZE = sizeof(uint16_t); +static constexpr uint16_t BRIDGE_CHECKSUM_SIZE = sizeof(uint16_t); + static uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; static esp_now_peer_info_t peerInfo; static volatile bool is_send_complete = false; static esp_err_t last_send_result; static uint8_t rx_buf[256]; static uint8_t last_rx_len = 0; +static char bridge_secret[16]; // callback when data is sent static void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { @@ -16,15 +31,56 @@ static void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { ESPNOW_DEBUG_PRINTLN("Send Status: %d", (int)status); } +static void xorCrypt(uint8_t *data, size_t len) { + size_t keyLen = strlen(bridge_secret); + for (size_t i = 0; i < len; i++) { + data[i] ^= bridge_secret[i % keyLen]; + } +} + +static uint16_t fletcher16(const uint8_t *data, size_t len) { + uint8_t sum1 = 0, sum2 = 0; + + for (size_t i = 0; i < len; i++) { + sum1 = (sum1 + data[i]) % 255; + sum2 = (sum2 + sum1) % 255; + } + + return (sum2 << 8) | sum1; +} + +static bool validateChecksum(const uint8_t *data, size_t len, uint16_t received_checksum) { + uint16_t calculated_checksum = fletcher16(data, len); + return received_checksum == calculated_checksum; +} + static void OnDataRecv(const uint8_t *mac, const uint8_t *data, int len) { ESPNOW_DEBUG_PRINTLN("Recv: len = %d", len); - memcpy(rx_buf, data, len); - last_rx_len = len; + + if (len < 4) return; + + uint16_t magic = (data[0] << 8) | data[1]; + if (magic != BRIDGE_PACKET_MAGIC) return; + + uint8_t decrypted[MAX_ESPNOW_PACKET_SIZE]; + int encLen = len - 2; // everything except magic + memcpy(decrypted, data + 2, encLen); + xorCrypt(decrypted, encLen); + + uint16_t rx_cksum = (decrypted[0] << 8) | decrypted[1]; + uint8_t* meshPayload = decrypted + 2; + int meshLen = encLen - 2; + + if (!validateChecksum(meshPayload, meshLen, rx_cksum)) return; + + memcpy(rx_buf, meshPayload, meshLen); + last_rx_len = meshLen; } void ESPNOWRadio::init() { // Set device as a Wi-Fi Station WiFi.mode(WIFI_STA); + strncpy(bridge_secret, "LVSITANOS", sizeof(bridge_secret)); // Long Range mode esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_LR); @@ -72,7 +128,36 @@ uint32_t ESPNOWRadio::intID() { bool ESPNOWRadio::startSendRaw(const uint8_t* bytes, int len) { // Send message via ESP-NOW is_send_complete = false; - esp_err_t result = esp_now_send(broadcastAddress, bytes, len); + if (!bytes || len <= 0) { + Serial.println("Invalid raw mesh packet"); + return false; + } + + if (len > (MAX_ESPNOW_PACKET_SIZE - 4)) { + Serial.println("Mesh packet too large for ESP-NOW"); + return false; + } + + uint8_t out[MAX_ESPNOW_PACKET_SIZE]; + + // 1) magic + out[0] = (BRIDGE_PACKET_MAGIC >> 8) & 0xFF; + out[1] = BRIDGE_PACKET_MAGIC & 0xFF; + + // 2) checksum (πάνω στο raw mesh packet) + uint16_t cksum = fletcher16(bytes, len); + out[2] = (cksum >> 8) & 0xFF; + out[3] = cksum & 0xFF; + + // 3) payload = raw mesh packet + memcpy(out + 4, bytes, len); + + // 4) encrypt checksum + payload + xorCrypt(out + 2, len + 2); + + // 5) send via ESP-NOW + esp_err_t result = esp_now_send(broadcastAddress, out, len + 4); + if (result == ESP_OK) { n_sent++; ESPNOW_DEBUG_PRINTLN("Send success"); diff --git a/variants/elecrow_espnow/platformio.ini b/variants/elecrow_espnow/platformio.ini new file mode 100644 index 000000000..de7fcfb66 --- /dev/null +++ b/variants/elecrow_espnow/platformio.ini @@ -0,0 +1,31 @@ +; ----------- Elecrow Panels ESP32-S3 ------------ +; No LoRa module - Only ESP-NOW +; Works with repeaters with esp_now +[Elecrow_ESPNOW] +extends = esp32_base +board = esp32-s3-devkitc-1-myboard +build_flags = + ${esp32_base.build_flags} + -I variants/Elecrow_espnow + -D PIN_BOARD_SDA=-1 + -D PIN_BOARD_SCL=-1 + -D PIN_USER_BTN=0 +; -D ESPNOW_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${esp32_base.build_src_filter} + + + +<../variants/Elecrow_espnow> + +[env:Elecrow_ESPNOW_terminal_chat] +extends = Elecrow_ESPNOW +build_flags = + ${Elecrow_ESPNOW.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=8 + -D MESH_DEBUG=1 +build_src_filter = ${Elecrow_ESPNOW.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${Elecrow_ESPNOW.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/elecrow_espnow/target.cpp b/variants/elecrow_espnow/target.cpp new file mode 100644 index 000000000..6b5d4e444 --- /dev/null +++ b/variants/elecrow_espnow/target.cpp @@ -0,0 +1,44 @@ +#include +#include "target.h" +#include + +ESP32Board board; + +ESPNOWRadio radio_driver; + +ESP32RTCClock rtc_clock; +SensorManager sensors; + +bool radio_init() { + rtc_clock.begin(); + + radio_driver.init(); + + return true; // success +} + +uint32_t radio_get_rng_seed() { + return millis() + radio_driver.intID(); // TODO: where to get some entropy? +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + // no-op +} + +void radio_set_tx_power(uint8_t dbm) { + radio_driver.setTxPower(dbm); +} + +// NOTE: as we are using the WiFi radio, the ESP_IDF will have enabled hardware RNG: +// https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/random.html +class ESP_RNG : public mesh::RNG { +public: + void random(uint8_t* dest, size_t sz) override { + esp_fill_random(dest, sz); + } +}; + +mesh::LocalIdentity radio_new_identity() { + ESP_RNG rng; + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/elecrow_espnow/target.h b/variants/elecrow_espnow/target.h new file mode 100644 index 000000000..99b6f5778 --- /dev/null +++ b/variants/elecrow_espnow/target.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include + +extern ESP32Board board; +extern ESPNOWRadio radio_driver; +extern ESP32RTCClock rtc_clock; +extern SensorManager sensors; + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/generic-e22/variant.h b/variants/generic-e22/variant.h index 6c850af26..57207ed30 100644 --- a/variants/generic-e22/variant.h +++ b/variants/generic-e22/variant.h @@ -19,7 +19,7 @@ // Radio #define USE_SX1262 // E22-900M30S uses SX1262 #define USE_SX1268 // E22-400M30S uses SX1268 -#define SX126X_MAX_POWER 22 // Outputting 22dBm from SX1262 results in ~30dBm E22-900M30S output (module only uses last stage of the YP2233W PA) +#define SX126X_MAX_POWER 27 // Outputting 22dBm from SX1262 results in ~30dBm E22-900M30S output (module only uses last stage of the YP2233W PA) #define SX126X_DIO3_TCXO_VOLTAGE 1.8 // E22 series TCXO reference voltage is 1.8V #define SX126X_CS 18 // EBYTE module's NSS pin diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index c26a5bc69..d3e841697 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -26,7 +26,7 @@ build_flags = -D PIN_VEXT_EN=36 -D PIN_VEXT_EN_ACTIVE=HIGH -D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm. - -D MAX_LORA_TX_POWER=22 ; Max SX1262 output + -D MAX_LORA_TX_POWER=27 ; Max SX1262 output -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 diff --git a/variants/meshadventurer/variant.h b/variants/meshadventurer/variant.h index 356a3e172..f075afcca 100644 --- a/variants/meshadventurer/variant.h +++ b/variants/meshadventurer/variant.h @@ -19,7 +19,7 @@ // Radio #define USE_SX1262 // E22-900M30S uses SX1262 #define USE_SX1268 // E22-400M30S uses SX1268 -#define SX126X_MAX_POWER 22 // Outputting 22dBm from SX1262 results in ~30dBm E22-900M30S output (module only uses last stage of the YP2233W PA) +#define SX126X_MAX_POWER 27 // Outputting 22dBm from SX1262 results in ~30dBm E22-900M30S output (module only uses last stage of the YP2233W PA) #define SX126X_DIO3_TCXO_VOLTAGE 1.8 // E22 series TCXO reference voltage is 1.8V #define SX126X_CS 18 // EBYTE module's NSS pin