diff --git a/boards/linux.json b/boards/linux.json new file mode 100644 index 000000000..21b5cdc33 --- /dev/null +++ b/boards/linux.json @@ -0,0 +1,21 @@ +{ + "build": { + "arduino": { + }, + "core": "linux", + "extra_flags": [ + ], + "hwids": [], + "mcu": "arm64", + "variant": "linux" + }, + "connectivity": ["wifi", "bluetooth"], + "debug": {}, + "frameworks": ["portduino", "linux"], + "name": "Linux", + "upload": { + "maximum_ram_size": 0, + "maximum_size": 0 + }, + "vendor": "Linux" +} diff --git a/build.sh b/build.sh index f21279417..713a62bbb 100755 --- a/build.sh +++ b/build.sh @@ -119,6 +119,10 @@ build_firmware() { cp .pio/build/$1/firmware.uf2 out/${FIRMWARE_FILENAME}.uf2 2>/dev/null || true cp .pio/build/$1/firmware.zip out/${FIRMWARE_FILENAME}.zip 2>/dev/null || true + if [ -f .pio/build/$1/program ]; then + cp .pio/build/$1/program out/meshcored 2>/dev/null || true + fi + } # firmwares containing $1 will be built diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 7f5761f3c..3a3c44e65 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -10,7 +10,7 @@ DataStore::DataStore(FILESYSTEM& fs, mesh::RTCClock& clock) : _fs(&fs), _fsExtra(nullptr), _clock(&clock), #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) identity_store(fs, "") -#elif defined(RP2040_PLATFORM) +#elif defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) identity_store(fs, "/identity") #else identity_store(fs, "/identity") @@ -22,7 +22,7 @@ DataStore::DataStore(FILESYSTEM& fs, mesh::RTCClock& clock) : _fs(&fs), _fsExtra DataStore::DataStore(FILESYSTEM& fs, FILESYSTEM& fsExtra, mesh::RTCClock& clock) : _fs(&fs), _fsExtra(&fsExtra), _clock(&clock), #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) identity_store(fs, "") -#elif defined(RP2040_PLATFORM) +#elif defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) identity_store(fs, "/identity") #else identity_store(fs, "/identity") @@ -35,7 +35,7 @@ static File openWrite(FILESYSTEM* fs, const char* filename) { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) fs->remove(filename); return fs->open(filename, FILE_O_WRITE); -#elif defined(RP2040_PLATFORM) +#elif defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) return fs->open(filename, "w"); #else return fs->open(filename, "w", true); @@ -47,7 +47,7 @@ static File openWrite(FILESYSTEM* fs, const char* filename) { #endif void DataStore::begin() { -#if defined(RP2040_PLATFORM) +#if defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) identity_store.begin(); #endif @@ -67,6 +67,8 @@ void DataStore::begin() { #include #elif defined(RP2040_PLATFORM) #include +#elif defined(PORTDUINO_PLATFORM) + #include #elif defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) #if defined(QSPIFLASH) #include @@ -102,7 +104,7 @@ lfs_ssize_t _getLfsUsedBlockCount(FILESYSTEM* fs) { uint32_t DataStore::getStorageUsedKb() const { #if defined(ESP32) return SPIFFS.usedBytes() / 1024; -#elif defined(RP2040_PLATFORM) +#elif defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) FSInfo info; info.usedBytes = 0; _fs->info(info); @@ -120,7 +122,7 @@ uint32_t DataStore::getStorageUsedKb() const { uint32_t DataStore::getStorageTotalKb() const { #if defined(ESP32) return SPIFFS.totalBytes() / 1024; -#elif defined(RP2040_PLATFORM) +#elif defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) FSInfo info; info.totalBytes = 0; _fs->info(info); @@ -137,7 +139,7 @@ uint32_t DataStore::getStorageTotalKb() const { File DataStore::openRead(const char* filename) { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) return _fs->open(filename, FILE_O_READ); -#elif defined(RP2040_PLATFORM) +#elif defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) return _fs->open(filename, "r"); #else return _fs->open(filename, "r", false); @@ -147,7 +149,7 @@ File DataStore::openRead(const char* filename) { File DataStore::openRead(FILESYSTEM* fs, const char* filename) { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) return fs->open(filename, FILE_O_READ); -#elif defined(RP2040_PLATFORM) +#elif defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) return fs->open(filename, "r"); #else return fs->open(filename, "r", false); @@ -171,6 +173,8 @@ bool DataStore::formatFileSystem() { } #elif defined(RP2040_PLATFORM) return LittleFS.format(); +#elif defined(PORTDUINO_PLATFORM) + return true; #elif defined(ESP32) return ((fs::SPIFFSFS *)_fs)->format(); #else diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 1fcc5697d..6bfa8b7d6 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -19,6 +19,8 @@ #include #elif defined(RP2040_PLATFORM) #include +#elif defined(PORTDUINO_PLATFORM) +#include #elif defined(ESP32) #include #endif diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 82c8c21d9..7667fd258 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -29,6 +29,9 @@ static uint32_t _atoi(const char* sp) { #elif defined(RP2040_PLATFORM) #include DataStore store(LittleFS, rtc_clock); +#elif defined(PORTDUINO_PLATFORM) + #include + DataStore store(PortduinoFS, rtc_clock); #elif defined(ESP32) #include DataStore store(SPIFFS, rtc_clock); @@ -184,6 +187,15 @@ void setup() { serial_interface.begin(Serial); #endif the_mesh.startInterface(serial_interface); +#elif defined(PORTDUINO_PLATFORM) + store.begin(); + the_mesh.begin( + #ifdef DISPLAY_CLASS + disp != NULL + #else + false + #endif + ); #elif defined(ESP32) SPIFFS.begin(true); store.begin(); diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 6ae6ac0a8..313eda703 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -309,7 +309,7 @@ mesh::Packet *MyMesh::createSelfAdvert() { File MyMesh::openAppend(const char *fname) { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) return _fs->open(fname, FILE_O_WRITE); -#elif defined(RP2040_PLATFORM) +#elif defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) return _fs->open(fname, "a"); #else return _fs->open(fname, "a", true); @@ -697,11 +697,13 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.node_lat = ADVERT_LAT; _prefs.node_lon = ADVERT_LON; StrHelper::strncpy(_prefs.password, ADMIN_PASSWORD, sizeof(_prefs.password)); +#ifndef SKIP_CONFIG_OVERWRITE _prefs.freq = LORA_FREQ; _prefs.sf = LORA_SF; _prefs.bw = LORA_BW; _prefs.cr = LORA_CR; _prefs.tx_power_dbm = LORA_TX_POWER; +#endif _prefs.advert_interval = 1; // default to 2 minutes for NEW installs _prefs.flood_advert_interval = 12; // 12 hours _prefs.flood_max = 64; @@ -767,6 +769,8 @@ bool MyMesh::formatFileSystem() { return InternalFS.format(); #elif defined(RP2040_PLATFORM) return LittleFS.format(); +#elif defined(PORTDUINO_PLATFORM) + return true; #elif defined(ESP32) return SPIFFS.format(); #else @@ -801,7 +805,7 @@ void MyMesh::updateFloodAdvertTimer() { } void MyMesh::dumpLogFile() { -#if defined(RP2040_PLATFORM) +#if defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) File f = _fs->open(PACKET_LOG_FILE, "r"); #else File f = _fs->open(PACKET_LOG_FILE); @@ -894,7 +898,7 @@ void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) { IdentityStore store(*_fs, ""); #elif defined(ESP32) IdentityStore store(*_fs, "/identity"); -#elif defined(RP2040_PLATFORM) +#elif defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) IdentityStore store(*_fs, "/identity"); #else #error "need to define saveIdentity()" diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index ed9f0c5fc..4b4c7526c 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -9,6 +9,8 @@ #include #elif defined(RP2040_PLATFORM) #include +#elif defined(PORTDUINO_PLATFORM) + #include #elif defined(ESP32) #include #endif diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 7387e77e7..d11081884 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -54,6 +54,10 @@ void setup() { fs = &LittleFS; IdentityStore store(LittleFS, "/identity"); store.begin(); +#elif defined(PORTDUINO_PLATFORM) + fs = &PortduinoFS; + IdentityStore store(PortduinoFS, "/identity"); + store.begin(); #else #error "need to define filesystem" #endif diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 60dd18407..ac71496b6 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -122,7 +122,7 @@ mesh::Packet *MyMesh::createSelfAdvert() { File MyMesh::openAppend(const char *fname) { #if defined(NRF52_PLATFORM) return _fs->open(fname, FILE_O_WRITE); -#elif defined(RP2040_PLATFORM) +#elif defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) return _fs->open(fname, "a"); #else return _fs->open(fname, "a", true); @@ -667,6 +667,8 @@ bool MyMesh::formatFileSystem() { return InternalFS.format(); #elif defined(RP2040_PLATFORM) return LittleFS.format(); +#elif defined(PORTDUINO_PLATFORM) + return true; #elif defined(ESP32) return SPIFFS.format(); #else @@ -700,7 +702,7 @@ void MyMesh::updateFloodAdvertTimer() { } void MyMesh::dumpLogFile() { -#if defined(RP2040_PLATFORM) +#if defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) File f = _fs->open(PACKET_LOG_FILE, "r"); #else File f = _fs->open(PACKET_LOG_FILE); @@ -725,7 +727,7 @@ void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) { IdentityStore store(*_fs, ""); #elif defined(ESP32) IdentityStore store(*_fs, "/identity"); -#elif defined(RP2040_PLATFORM) +#elif defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) IdentityStore store(*_fs, "/identity"); #else #error "need to define saveIdentity()" diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index e7f1fee83..2471d5514 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -7,6 +7,8 @@ #include #elif defined(RP2040_PLATFORM) #include +#elif defined(PORTDUINO_PLATFORM) + #include #elif defined(ESP32) #include #endif diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 1a3b4d6e0..5599a8aa9 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -47,6 +47,10 @@ void setup() { fs = &LittleFS; IdentityStore store(LittleFS, "/identity"); store.begin(); +#elif defined(PORTDUINO_PLATFORM) + fs = &PortduinoFS; + IdentityStore store(PortduinoFS, "/identity"); + store.begin(); #elif defined(ESP32) SPIFFS.begin(true); fs = &SPIFFS; diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index da1bac5b3..cd0acfd1f 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -5,6 +5,8 @@ #include #elif defined(RP2040_PLATFORM) #include +#elif defined(PORTDUINO_PLATFORM) + #include #elif defined(ESP32) #include #endif @@ -90,7 +92,7 @@ class MyMesh : public BaseChatMesh, ContactVisitor { void loadContacts() { if (_fs->exists("/contacts")) { - #if defined(RP2040_PLATFORM) + #if defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) File file = _fs->open("/contacts", "r"); #else File file = _fs->open("/contacts"); @@ -129,7 +131,7 @@ class MyMesh : public BaseChatMesh, ContactVisitor { #if defined(NRF52_PLATFORM) _fs->remove("/contacts"); File file = _fs->open("/contacts", FILE_O_WRITE); -#elif defined(RP2040_PLATFORM) +#elif defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) File file = _fs->open("/contacts", "w"); #else File file = _fs->open("/contacts", "w", true); @@ -299,7 +301,7 @@ class MyMesh : public BaseChatMesh, ContactVisitor { #if defined(NRF52_PLATFORM) IdentityStore store(fs, ""); - #elif defined(RP2040_PLATFORM) + #elif defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) IdentityStore store(fs, "/identity"); store.begin(); #else @@ -324,7 +326,7 @@ class MyMesh : public BaseChatMesh, ContactVisitor { // load persisted prefs if (_fs->exists("/node_prefs")) { - #if defined(RP2040_PLATFORM) + #if defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) File file = _fs->open("/node_prefs", "r"); #else File file = _fs->open("/node_prefs"); @@ -343,7 +345,7 @@ class MyMesh : public BaseChatMesh, ContactVisitor { #if defined(NRF52_PLATFORM) _fs->remove("/node_prefs"); File file = _fs->open("/node_prefs", FILE_O_WRITE); -#elif defined(RP2040_PLATFORM) +#elif defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) File file = _fs->open("/node_prefs", "w"); #else File file = _fs->open("/node_prefs", "w", true); @@ -569,6 +571,8 @@ void setup() { #elif defined(RP2040_PLATFORM) LittleFS.begin(); the_mesh.begin(LittleFS); +#elif defined(PORTDUINO_PLATFORM) + the_mesh.begin(PortduinoFS); #elif defined(ESP32) SPIFFS.begin(true); the_mesh.begin(SPIFFS); diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 4995c55fc..daa654824 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -66,7 +66,7 @@ static File openAppend(FILESYSTEM* _fs, const char* fname) { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) return _fs->open(fname, FILE_O_WRITE); - #elif defined(RP2040_PLATFORM) + #elif defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) return _fs->open(fname, "a"); #else return _fs->open(fname, "a", true); @@ -756,6 +756,8 @@ bool SensorMesh::formatFileSystem() { return InternalFS.format(); #elif defined(RP2040_PLATFORM) return LittleFS.format(); +#elif defined(PORTDUINO_PLATFORM) + return true; #elif defined(ESP32) return SPIFFS.format(); #else @@ -770,7 +772,7 @@ void SensorMesh::saveIdentity(const mesh::LocalIdentity& new_id) { IdentityStore store(*_fs, ""); #elif defined(ESP32) IdentityStore store(*_fs, "/identity"); -#elif defined(RP2040_PLATFORM) +#elif defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) IdentityStore store(*_fs, "/identity"); #else #error "need to define saveIdentity()" diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index c320eb447..505cf0e0e 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -9,6 +9,8 @@ #include #elif defined(RP2040_PLATFORM) #include +#elif defined(PORTDUINO_PLATFORM) +#include #elif defined(ESP32) #include #endif diff --git a/examples/simple_sensor/main.cpp b/examples/simple_sensor/main.cpp index a5fcc1484..6d1c30d3a 100644 --- a/examples/simple_sensor/main.cpp +++ b/examples/simple_sensor/main.cpp @@ -84,6 +84,10 @@ void setup() { fs = &LittleFS; IdentityStore store(LittleFS, "/identity"); store.begin(); +#elif defined(PORTDUINO_PLATFORM) + fs = &PortduinoFS; + IdentityStore store(PortduinoFS, "/identity"); + store.begin(); #else #error "need to define filesystem" #endif diff --git a/src/helpers/ClientACL.cpp b/src/helpers/ClientACL.cpp index 4ea19fd29..e2eaee7f5 100644 --- a/src/helpers/ClientACL.cpp +++ b/src/helpers/ClientACL.cpp @@ -4,7 +4,7 @@ static File openWrite(FILESYSTEM* _fs, const char* filename) { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) _fs->remove(filename); return _fs->open(filename, FILE_O_WRITE); - #elif defined(RP2040_PLATFORM) + #elif defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) return _fs->open(filename, "w"); #else return _fs->open(filename, "w", true); diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index a3de990aa..bd153efbf 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -104,7 +104,7 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) fs->remove("/com_prefs"); File file = fs->open("/com_prefs", FILE_O_WRITE); -#elif defined(RP2040_PLATFORM) +#elif defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) File file = fs->open("/com_prefs", "w"); #else File file = fs->open("/com_prefs", "w", true); diff --git a/src/helpers/IdentityStore.cpp b/src/helpers/IdentityStore.cpp index dc85d69cd..ba9489a0a 100644 --- a/src/helpers/IdentityStore.cpp +++ b/src/helpers/IdentityStore.cpp @@ -49,7 +49,7 @@ bool IdentityStore::save(const char *name, const mesh::LocalIdentity& id) { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) _fs->remove(filename); File file = _fs->open(filename, FILE_O_WRITE); -#elif defined(RP2040_PLATFORM) +#elif defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) File file = _fs->open(filename, "w"); #else File file = _fs->open(filename, "w", true); @@ -71,7 +71,7 @@ bool IdentityStore::save(const char *name, const mesh::LocalIdentity& id, const #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) _fs->remove(filename); File file = _fs->open(filename, FILE_O_WRITE); -#elif defined(RP2040_PLATFORM) +#elif defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) File file = _fs->open(filename, "w"); #else File file = _fs->open(filename, "w", true); diff --git a/src/helpers/IdentityStore.h b/src/helpers/IdentityStore.h index d0d7ee457..36ae31caa 100644 --- a/src/helpers/IdentityStore.h +++ b/src/helpers/IdentityStore.h @@ -1,6 +1,6 @@ #pragma once -#if defined(ESP32) || defined(RP2040_PLATFORM) +#if defined(ESP32) || defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) #include #define FILESYSTEM fs::FS #elif defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) @@ -8,6 +8,9 @@ #define FILESYSTEM Adafruit_LittleFS using namespace Adafruit_LittleFS_Namespace; +#elif defined(PORTDUINO_PLATFORM) + #include + #define FILESYSTEM fs::FS #endif #include diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index 368446157..e49482545 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -17,7 +17,7 @@ static File openWrite(FILESYSTEM* _fs, const char* filename) { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) _fs->remove(filename); return _fs->open(filename, FILE_O_WRITE); - #elif defined(RP2040_PLATFORM) + #elif defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) return _fs->open(filename, "w"); #else return _fs->open(filename, "w", true); @@ -26,7 +26,7 @@ static File openWrite(FILESYSTEM* _fs, const char* filename) { bool RegionMap::load(FILESYSTEM* _fs) { if (_fs->exists("/regions2")) { - #if defined(RP2040_PLATFORM) + #if defined(RP2040_PLATFORM) || defined(PORTDUINO_PLATFORM) File file = _fs->open("/regions2", "r"); #else File file = _fs->open("/regions2"); diff --git a/src/helpers/TxtDataHelpers.cpp b/src/helpers/TxtDataHelpers.cpp index d327931fd..19718ce02 100644 --- a/src/helpers/TxtDataHelpers.cpp +++ b/src/helpers/TxtDataHelpers.cpp @@ -102,7 +102,7 @@ static void _ftoa(float f, char *p, int *status) *p++ = '0'; else { - ltoa(int_part, p, 10); + snprintf(p, 20, "%d", int_part); while (*p) p++; } diff --git a/src/helpers/bridges/RS232Bridge.cpp b/src/helpers/bridges/RS232Bridge.cpp index 0024f6f2f..1fa18fca3 100644 --- a/src/helpers/bridges/RS232Bridge.cpp +++ b/src/helpers/bridges/RS232Bridge.cpp @@ -18,7 +18,7 @@ void RS232Bridge::begin() { #elif defined(NRF52_PLATFORM) // Tested with RAK_4631 and T114 ((Uart *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); -#elif defined(RP2040_PLATFORM) +#elif defined(RP2040_PLATFORM) || defined(ARCH_PORTDUINO) ((SerialUART *)_serial)->setRX(WITH_RS232_BRIDGE_RX); ((SerialUART *)_serial)->setTX(WITH_RS232_BRIDGE_TX); #elif defined(STM32_PLATFORM) diff --git a/src/helpers/radiolib/LinuxSX1262.h b/src/helpers/radiolib/LinuxSX1262.h new file mode 100644 index 000000000..8d5977e8b --- /dev/null +++ b/src/helpers/radiolib/LinuxSX1262.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received +#define SX126X_IRQ_PREAMBLE_DETECTED 0x04 +#define SX126X_PREAMBLE_LENGTH 16 + +extern LinuxBoard board; + +class LinuxSX1262 : public SX1262 { + public: + LinuxSX1262(Module *mod) : SX1262(mod) { } + + bool std_init(SPIClass* spi = NULL) + { + LinuxConfig config = board.config; + + Serial.printf("Radio begin %f %f %d %d %f\n", config.lora_freq, config.lora_bw, config.lora_sf, config.lora_cr, config.lora_tcxo); + int status = begin(config.lora_freq, config.lora_bw, config.lora_sf, config.lora_cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, config.lora_tx_power, SX126X_PREAMBLE_LENGTH, config.lora_tcxo); + // if radio init fails with -707/-706, try again with tcxo voltage set to 0.0f + if (status == RADIOLIB_ERR_SPI_CMD_FAILED || status == RADIOLIB_ERR_SPI_CMD_INVALID) { + status = begin(config.lora_freq, config.lora_bw, config.lora_sf, config.lora_cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, config.lora_tx_power, SX126X_PREAMBLE_LENGTH, 0.0f); + } + if (status != RADIOLIB_ERR_NONE) { + Serial.print("ERROR: radio init failed: "); + Serial.println(status); + return false; // fail + } + + setCRC(1); + + setCurrentLimit(config.current_limit); + setDio2AsRfSwitch(config.dio2_as_rf_switch); + setRxBoostedGainMode(config.rx_boosted_gain); + if (config.lora_rxen_pin != RADIOLIB_NC || config.lora_txen_pin != RADIOLIB_NC) { + setRfSwitchPins(config.lora_rxen_pin, config.lora_txen_pin); + } + + return true; + } + + bool isReceiving() { + uint16_t irq = getIrqFlags(); + bool detected = (irq & SX126X_IRQ_HEADER_VALID) || (irq & SX126X_IRQ_PREAMBLE_DETECTED); + return detected; + } +}; diff --git a/src/helpers/radiolib/LinuxSX1262Wrapper.h b/src/helpers/radiolib/LinuxSX1262Wrapper.h new file mode 100644 index 000000000..1d9675854 --- /dev/null +++ b/src/helpers/radiolib/LinuxSX1262Wrapper.h @@ -0,0 +1,22 @@ +#pragma once + +#include "LinuxSX1262.h" +#include "RadioLibWrappers.h" + +class LinuxSX1262Wrapper : public RadioLibWrapper { +public: + LinuxSX1262Wrapper(LinuxSX1262& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { } + bool isReceivingPacket() override { + return ((LinuxSX1262 *)_radio)->isReceiving(); + } + float getCurrentRSSI() override { + return ((LinuxSX1262 *)_radio)->getRSSI(false); + } + float getLastRSSI() const override { return ((LinuxSX1262 *)_radio)->getRSSI(); } + float getLastSNR() const override { return ((LinuxSX1262 *)_radio)->getSNR(); } + + float packetScore(float snr, int packet_len) override { + int sf = ((LinuxSX1262 *)_radio)->spreadingFactor; + return packetScoreInt(snr, sf, packet_len); + } +}; diff --git a/variants/linux/LinuxBoard.cpp b/variants/linux/LinuxBoard.cpp new file mode 100644 index 000000000..0feb95d2b --- /dev/null +++ b/variants/linux/LinuxBoard.cpp @@ -0,0 +1,137 @@ +#include +#include +#include +#include +#include "linux/gpio/LinuxGPIOPin.h" +#include "LinuxBoard.h" + +int initGPIOPin(uint8_t pinNum, const std::string gpioChipName, uint8_t line) +{ +#ifdef PORTDUINO_LINUX_HARDWARE + char gpio_name[32]; + snprintf(gpio_name, sizeof(gpio_name), "GPIO%d", pinNum); + + try { + GPIOPin *csPin; + csPin = new LinuxGPIOPin(pinNum, gpioChipName.c_str(), line, gpio_name); + csPin->setSilent(); + gpioBind(csPin); + return 0; + } catch (...) { + MESH_DEBUG_PRINTLN("Warning, cannot claim pin %d", pinNum); + return 1; + } +#else + return 0; +#endif +} + +void portduinoSetup() { +} + +void LinuxBoard::begin() { + config.load("/etc/meshcored/meshcored.ini"); + + Serial.printf("SPI begin %s\n", config.spidev); + SPI.begin(config.spidev); + + Serial.printf("LoRa pins NSS=%d BUSY=%d IRQ=%d RESET=%d TX=%d RX=%d\n", + (int)config.lora_nss_pin, + (int)config.lora_busy_pin, + (int)config.lora_irq_pin, + (int)config.lora_reset_pin, + (int)config.lora_rxen_pin, + (int)config.lora_txen_pin); + + if (config.lora_nss_pin != RADIOLIB_NC) { + initGPIOPin(config.lora_nss_pin, "gpiochip0", config.lora_nss_pin); + } + if (config.lora_busy_pin != RADIOLIB_NC) { + initGPIOPin(config.lora_busy_pin, "gpiochip0", config.lora_busy_pin); + } + if (config.lora_irq_pin != RADIOLIB_NC) { + initGPIOPin(config.lora_irq_pin, "gpiochip0", config.lora_irq_pin); + } + if (config.lora_reset_pin != RADIOLIB_NC) { + initGPIOPin(config.lora_reset_pin, "gpiochip0", config.lora_reset_pin); + } + if (config.lora_rxen_pin != RADIOLIB_NC) { + initGPIOPin(config.lora_rxen_pin, "gpiochip0", config.lora_rxen_pin); + } + if (config.lora_txen_pin != RADIOLIB_NC) { + initGPIOPin(config.lora_txen_pin, "gpiochip0", config.lora_txen_pin); + } +} + +void trim(char *str) { + char *end; + while (isspace((unsigned char)*str)) str++; + if (*str == 0) { *str = 0; return; } + end = str + strlen(str) - 1; + while (end > str && isspace((unsigned char)*end)) end--; + end[1] = '\0'; +} + +char *safe_copy(char *value, size_t maxlen) { + char *retval; + size_t length = strlen(value) + 1; + if (length > maxlen) length = maxlen; + + retval = (char *)malloc(length); + strncpy(retval, value, length - 1); + retval[length - 1] = '\0'; + return retval; +} + +int LinuxConfig::load(const char *filename) { + FILE *f = fopen(filename, "r"); + if (!f) return -1; + + char line[512]; + while (fgets(line, sizeof(line), f)) { + char *p = line; + // skip whitespace + while (isspace(*p)) p++; + // skip empty lines and comments + if (*p == '\0' || *p == '#' || *p == ';') continue; + + char *key = p; + while (*p && !isspace(*p) && *p != '=') p++; + if (*p == '\0') continue; + *p++ = '\0'; + + while (*p && (isspace(*p) || *p == '=')) p++; + char *value = p; + p = value; + while (*p && *p != '\n' && *p != '\r' && *p != '#' && *p != ';') p++; + *p = '\0'; + + trim(key); + trim(value); + + if (strcmp(key, "spidev") == 0) spidev = safe_copy(value, 32); + else if (strcmp(key, "lora_freq") == 0) lora_freq = atof(value); + else if (strcmp(key, "lora_bw") == 0) lora_bw = atof(value); + else if (strcmp(key, "lora_sf") == 0) lora_sf = (uint8_t)atoi(value); + else if (strcmp(key, "lora_cr") == 0) lora_cr = (uint8_t)atoi(value); + else if (strcmp(key, "lora_tcxo") == 0) lora_tcxo = atof(value); + else if (strcmp(key, "lora_tx_power") == 0) lora_tx_power = atoi(value); + else if (strcmp(key, "current_limit") == 0) current_limit = atof(value); + else if (strcmp(key, "dio2_as_rf_switch") == 0) dio2_as_rf_switch = value != 0; + else if (strcmp(key, "rx_boosted_gain") == 0) rx_boosted_gain = value != 0; + + else if (strcmp(key, "lora_irq_pin") == 0) lora_irq_pin = atoi(value); + else if (strcmp(key, "lora_reset_pin") == 0) lora_reset_pin = atoi(value); + else if (strcmp(key, "lora_nss_pin") == 0) lora_nss_pin = atoi(value); + else if (strcmp(key, "lora_busy_pin") == 0) lora_busy_pin = atoi(value); + else if (strcmp(key, "lora_rxen_pin") == 0) lora_rxen_pin = atoi(value); + else if (strcmp(key, "lora_txen_pin") == 0) lora_txen_pin = atoi(value); + + else if (strcmp(key, "advert_name") == 0) advert_name = safe_copy(value, 100); + else if (strcmp(key, "admin_password") == 0) admin_password = safe_copy(value, 100); + else if (strcmp(key, "lat") == 0) lat = atof(value); + else if (strcmp(key, "lon") == 0) lon = atof(value); + } + fclose(f); + return 0; +} diff --git a/variants/linux/LinuxBoard.h b/variants/linux/LinuxBoard.h new file mode 100644 index 000000000..c7ae7501c --- /dev/null +++ b/variants/linux/LinuxBoard.h @@ -0,0 +1,92 @@ +#pragma once + +#include +#include +#include +#include + +class LinuxConfig { +public: + float lora_freq = LORA_FREQ; + float lora_bw = LORA_BW; + uint8_t lora_sf = LORA_SF; +#ifdef LORA_CR + uint8_t lora_cr = LORA_CR; +#else + uint8_t lora_cr = 5; +#endif + + uint32_t lora_irq_pin = RADIOLIB_NC; + uint32_t lora_reset_pin = RADIOLIB_NC; + uint32_t lora_nss_pin = RADIOLIB_NC; + uint32_t lora_busy_pin = RADIOLIB_NC; + uint32_t lora_rxen_pin = RADIOLIB_NC; + uint32_t lora_txen_pin = RADIOLIB_NC; + + int8_t lora_tx_power = 22; + float current_limit = 140; + bool dio2_as_rf_switch = false; + bool rx_boosted_gain = true; + + char* spidev = "/dev/spidev0.0"; + + float lora_tcxo = 1.8f; + + char *advert_name = "Linux Repeater"; + char *admin_password = "password"; + float lat = 0.0f; + float lon = 0.0f; + + int load(const char *filename); +}; + +class LinuxBoard : public mesh::MainBoard { +protected: + uint8_t startup_reason; + uint8_t btn_prev_state; + +public: + void begin(); + + uint16_t getBattMilliVolts() override { + return 0; + } + + uint8_t getStartupReason() const override { return startup_reason; } + + const char* getManufacturerName() const override { + return "Linux"; + } + + int buttonStateChanged() { + return 0; + } + + void powerOff() override { + exit(0); + } + + void reboot() override { + exit(0); + } + + LinuxConfig config; +}; + +class LinuxRTCClock : public mesh::RTCClock { +public: + LinuxRTCClock() { } + void begin() { + } + uint32_t getCurrentTime() override { + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec; + } + void setCurrentTime(uint32_t time) override { + struct timeval tv; + tv.tv_sec = time; + tv.tv_usec = 0; + settimeofday(&tv, NULL); + } +}; diff --git a/variants/linux/README.md b/variants/linux/README.md new file mode 100644 index 000000000..11fef2d1e --- /dev/null +++ b/variants/linux/README.md @@ -0,0 +1,95 @@ +# Meshcore linux target + +Based on https://github.com/meshtastic/framework-portduino by geeksville +RPI docs: https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#raspberry-pi-zero-2-w + +## RPI setup + +The pinout: https://pinout.xyz/ + +Enable SPI in /boot/firmware/config.txt + +``` +dtparam=spi=on +dtoverlay=spi0-0cs +``` + +In case you use second SPI (CE1 pin on RPI GPIO) use: `spi0-0cs` + +In case you use both SPI (CE0 and CE1 pin on RPI GPIO) use: `spi0-0cs` +I use it for hybrid meshtastic / meshcore setup in single box. + +## Building + +``` +sudo apt-get install -y libbluetooth-dev libgpiod-dev openssl libssl-dev libusb-1.0-0-dev libi2c-dev libuv1-dev + +python3 -m venv venv +source venv/bin/activate +pip install --no-cache-dir -U platformio + +FIRMWARE_VERSION=dev ./build.sh build-firmware linux_repeater +``` + +The output should be in `./out/meshcored` + +### Limitations + +Currently following options DO NOT WORK: + +``` +advert_name = "Sample Router" +admin_password = "password" +lat = 0.0 +lon = 0.0 +``` + +It requires significant refactor in lots of files. It can be done in another PR. + +You need to set them on compile stage with: + +``` +-D ADVERT_NAME='"Linux Repeater"' +-D ADVERT_LAT=0.0 +-D ADVERT_LON=0.0 +-D ADMIN_PASSWORD='"password"' +``` + +## Meshcored – Systemd service installation + +```bash +# Install the binary + +sudo cp ./variants/linux/meshcored.ini /etc/meshcored/meshcored.ini +sudo install -m 755 ./out/meshcored /usr/bin/meshcored + +# Create a dedicated system user (recommended for security) + +sudo useradd --system --no-create-home --shell /usr/sbin/nologin meshcore +sudo usermod -aG gpio,spi,dialout,plugdev meshcore +sudo mkdir -p /var/lib/meshcore +sudo chown meshcore:meshcore /var/lib/meshcore + +# Install the systemd service file + +sudo cp ./variants/linux/meshcored.service /usr/lib/systemd/system/meshcored.service + +# Enable and start the service + +sudo systemctl daemon-reload +sudo systemctl enable --now meshcored.service +``` + +The default `meshcored.ini` includes optional GPIO pins you can override without rebuilding: +``` +lora_nss_pin = 21 +lora_busy_pin = 20 +lora_irq_pin = 16 +lora_reset_pin = 18 +``` + +## Logs + +``` +journalctl -u meshcored +``` diff --git a/variants/linux/meshcored.ini b/variants/linux/meshcored.ini new file mode 100644 index 000000000..6fb346fbb --- /dev/null +++ b/variants/linux/meshcored.ini @@ -0,0 +1,28 @@ +advert_name = "Sample Router" +admin_password = "password" +lat = 0.0 +lon = 0.0 + +# Waveshare LoRa hat +#lora_irq_pin = 16 +#lora_reset_pin = 18 +#lora_nss_pin = 21 +#lora_busy_pin = 20 + +lora_irq_pin = 22 +lora_reset_pin = 13 +#lora_nss_pin = # SS pin handled by RPI +#lora_busy_pin = # Seems to be unused? +#lora_rxen_pin +#lora_txen_pin + +spidev = /dev/spidev0.0 +lora_freq = 869.618 +lora_bw = 62.5 +lora_sf = 8 +lora_cr = 8 +lora_tcxo = 1.8 +#lora_tx_power = 22 +#current_limit = 140 +#dio2_as_rf_switch = 1 +#rx_boosted_gain = 1 diff --git a/variants/linux/meshcored.service b/variants/linux/meshcored.service new file mode 100644 index 000000000..345a10724 --- /dev/null +++ b/variants/linux/meshcored.service @@ -0,0 +1,29 @@ +# /var/lib/systemd/system/meshcored.service +[Unit] +Description=Meshcore Daemon (meshcored) +After=network.target +Wants=network.target + +[Service] +Type=simple +User=meshcore +Group=meshcore +ExecStart=/usr/bin/stdbuf -oL /usr/bin/meshcored --fsdir /var/lib/meshcore +WorkingDirectory=/var/lib/meshcore +Restart=on-failure +RestartSec=5 +LimitNOFILE=65535 + +# Security hardening +ProtectSystem=strict +ProtectHome=yes +PrivateTmp=yes +NoNewPrivileges=yes +ReadWritePaths=/var/lib/meshcore # allow writing only to its own data dir + +# Create data dir with correct ownership if it doesn't exist +ExecStartPre=/bin/mkdir -p /var/lib/meshcore +ExecStartPre=/bin/chown meshcore:meshcore /var/lib/meshcore + +[Install] +WantedBy=multi-user.target diff --git a/variants/linux/platformio.ini b/variants/linux/platformio.ini new file mode 100644 index 000000000..d60a50fe7 --- /dev/null +++ b/variants/linux/platformio.ini @@ -0,0 +1,47 @@ +[linux_base] +extends = portduino_base +build_flags = ${portduino_base.build_flags} + -I variants/linux + -I /usr/include +board = cross_platform +board_level = extra +lib_deps = + ${portduino_base.lib_deps} + melopero/Melopero RV3028@^1.1.0 + +build_src_filter = ${portduino_base.build_src_filter} + +<../variants/linux> + - + - + - + - + - + +[env:linux] +extends = linux_base +; The pkg-config commands below optionally add link flags. +; the || : is just a "or run the null command" to avoid returning an error code +build_flags = ${linux_base.build_flags} + !pkg-config --cflags --libs libbsd-overlay --silence-errors || : + +[env:linux_repeater] +extends = linux_base +build_flags = + ${linux_base.build_flags} + -D RADIO_CLASS=LinuxSX1262 + -D WRAPPER_CLASS=LinuxSX1262Wrapper + -D USE_CUSTOM_SX1262_WRAPPER + -D SKIP_CONFIG_OVERWRITE=1 + -D ADVERT_NAME='"Linux Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=100 + -D LORA_TX_POWER=22 + -D MESH_DEBUG=1 + +build_src_filter = ${linux_base.build_src_filter} + +<../examples/simple_repeater> + +lib_deps = + ${linux_base.lib_deps} diff --git a/variants/linux/target.cpp b/variants/linux/target.cpp new file mode 100644 index 000000000..0f5941f89 --- /dev/null +++ b/variants/linux/target.cpp @@ -0,0 +1,54 @@ +#include +#include "target.h" + +class PortduinoHal : public ArduinoHal +{ +public: + PortduinoHal(SPIClass &spi, SPISettings spiSettings) : ArduinoHal(spi, spiSettings){}; + + void spiTransfer(uint8_t *out, size_t len, uint8_t *in) { + spi->transfer(out, in, len); + } +}; + +LinuxBoard board; + +SPISettings spiSettings = SPISettings(2000000, MSBFIRST, SPI_MODE0); +ArduinoHal *hal = new PortduinoHal(SPI, spiSettings); +RADIO_CLASS radio = new Module(hal, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC); +WRAPPER_CLASS radio_driver(radio, board); + +LinuxRTCClock rtc_clock; +EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +bool radio_init() { + rtc_clock.begin(); + + radio = new Module(hal, board.config.lora_nss_pin, board.config.lora_irq_pin, board.config.lora_reset_pin, board.config.lora_busy_pin); + return radio.std_init(&SPI); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/linux/target.h b/variants/linux/target.h new file mode 100644 index 000000000..1f5539ca9 --- /dev/null +++ b/variants/linux/target.h @@ -0,0 +1,32 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #include + #include +#endif + +#if (USE_CUSTOM_SX1262_WRAPPER) +#include +#endif + +extern LinuxBoard board; +extern WRAPPER_CLASS radio_driver; +extern LinuxRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +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/portduino/platformio.ini b/variants/portduino/platformio.ini new file mode 100644 index 000000000..5785e965a --- /dev/null +++ b/variants/portduino/platformio.ini @@ -0,0 +1,41 @@ +[portduino_base] +platform = + # renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop + https://github.com/meshtastic/platform-native/archive/f566d364204416cdbf298e349213f7d551f793d9.zip +framework = arduino + +build_src_filter = + ${env.build_src_filter} + - + - + - + - + - + - + - + - + - + +lib_deps = + ${env.lib_deps} + rweather/Crypto@0.4.0 + adafruit/Adafruit seesaw Library@1.7.9 + electroniccats/CayenneLPP @ 1.6.1 + adafruit/RTClib @ ^2.1.3 + jgromes/RadioLib@7.4.0 + +build_flags = + ${arduino_base.build_flags} + -DARCH_PORTDUINO + -DPORTDUINO_PLATFORM + -DRADIOLIB_EEPROM_UNSUPPORTED + -DPORTDUINO_LINUX_HARDWARE + -fPIC + -lpthread + -lstdc++fs + -lbluetooth + -lgpiod + -li2c + -luv + -std=gnu17 + -std=c++17