From 53d53fbb34f578b1bbcdfa86a3b4f8d9d8b3eb16 Mon Sep 17 00:00:00 2001 From: Grzegorz Godlewski Date: Sun, 16 Nov 2025 21:49:44 +0100 Subject: [PATCH] Add portduino arch --- boards/rpi_zero_2w.json | 22 +++++ build.sh | 4 + examples/companion_radio/DataStore.cpp | 20 ++-- examples/companion_radio/MyMesh.h | 2 + examples/companion_radio/main.cpp | 12 +++ examples/simple_repeater/MyMesh.cpp | 12 ++- examples/simple_repeater/MyMesh.h | 2 + examples/simple_repeater/main.cpp | 4 + examples/simple_room_server/MyMesh.cpp | 8 +- examples/simple_room_server/MyMesh.h | 2 + examples/simple_room_server/main.cpp | 4 + examples/simple_secure_chat/main.cpp | 14 ++- examples/simple_sensor/SensorMesh.cpp | 6 +- examples/simple_sensor/SensorMesh.h | 2 + examples/simple_sensor/main.cpp | 4 + src/helpers/ClientACL.cpp | 2 +- src/helpers/CommonCLI.cpp | 2 +- src/helpers/IdentityStore.cpp | 4 +- src/helpers/IdentityStore.h | 5 +- src/helpers/RegionMap.cpp | 4 +- src/helpers/TxtDataHelpers.cpp | 2 +- src/helpers/bridges/RS232Bridge.cpp | 2 +- src/helpers/radiolib/LinuxSX1262.h | 59 ++++++++++++ src/helpers/radiolib/LinuxSX1262Wrapper.h | 22 +++++ variants/linux/LinuxBoard.cpp | 108 ++++++++++++++++++++++ variants/linux/LinuxBoard.h | 86 +++++++++++++++++ variants/linux/README.md | 89 ++++++++++++++++++ variants/linux/meshcored.ini | 14 +++ variants/linux/meshcored.service | 29 ++++++ variants/linux/platformio.ini | 60 ++++++++++++ variants/linux/target.cpp | 54 +++++++++++ variants/linux/target.h | 32 +++++++ variants/portduino/platformio.ini | 40 ++++++++ 33 files changed, 702 insertions(+), 30 deletions(-) create mode 100644 boards/rpi_zero_2w.json create mode 100644 src/helpers/radiolib/LinuxSX1262.h create mode 100644 src/helpers/radiolib/LinuxSX1262Wrapper.h create mode 100644 variants/linux/LinuxBoard.cpp create mode 100644 variants/linux/LinuxBoard.h create mode 100644 variants/linux/README.md create mode 100644 variants/linux/meshcored.ini create mode 100644 variants/linux/meshcored.service create mode 100644 variants/linux/platformio.ini create mode 100644 variants/linux/target.cpp create mode 100644 variants/linux/target.h create mode 100644 variants/portduino/platformio.ini diff --git a/boards/rpi_zero_2w.json b/boards/rpi_zero_2w.json new file mode 100644 index 000000000..30de9187a --- /dev/null +++ b/boards/rpi_zero_2w.json @@ -0,0 +1,22 @@ +{ + "build": { + "arduino": { + }, + "core": "linux", + "extra_flags": [ + ], + "hwids": [], + "mcu": "arm64", + "variant": "raspberry-pi-zero-2-w" + }, + "connectivity": ["wifi", "bluetooth"], + "debug": {}, + "frameworks": ["portduino", "linux"], + "name": "Raspberry Pi Zero 2 W", + "upload": { + "maximum_ram_size": 0, + "maximum_size": 0 + }, + "url": "https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#raspberry-pi-zero-2-w", + "vendor": "RaspberryPi" +} 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 eac027b89..98171cba3 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(ARCH_PORTDUINO) 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(ARCH_PORTDUINO) 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(ARCH_PORTDUINO) 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(ARCH_PORTDUINO) identity_store.begin(); #endif @@ -67,6 +67,8 @@ void DataStore::begin() { #include #elif defined(RP2040_PLATFORM) #include +#elif defined(ARCH_PORTDUINO) + #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(ARCH_PORTDUINO) 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(ARCH_PORTDUINO) 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(ARCH_PORTDUINO) 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(ARCH_PORTDUINO) 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(ARCH_PORTDUINO) + 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 927ec65eb..b7380a9ef 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(ARCH_PORTDUINO) +#include #elif defined(ESP32) #include #endif diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 82c8c21d9..9e913d909 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(ARCH_PORTDUINO) + #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(ARCH_PORTDUINO) + 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 4136818ce..91815931c 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -299,7 +299,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(ARCH_PORTDUINO) return _fs->open(fname, "a"); #else return _fs->open(fname, "a", true); @@ -687,11 +687,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; @@ -753,6 +755,10 @@ bool MyMesh::formatFileSystem() { return InternalFS.format(); #elif defined(RP2040_PLATFORM) return LittleFS.format(); +#elif defined(ARCH_PORTDUINO) + return true; +#elif defined(ARCH_PORTDUINO) + return true; #elif defined(ESP32) return SPIFFS.format(); #else @@ -787,7 +793,7 @@ void MyMesh::updateFloodAdvertTimer() { } void MyMesh::dumpLogFile() { -#if defined(RP2040_PLATFORM) +#if defined(RP2040_PLATFORM) || defined(ARCH_PORTDUINO) File f = _fs->open(PACKET_LOG_FILE, "r"); #else File f = _fs->open(PACKET_LOG_FILE); @@ -880,7 +886,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(ARCH_PORTDUINO) 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 d8a20486a..8ffafafbd 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(ARCH_PORTDUINO) + #include #elif defined(ESP32) #include #endif diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 7387e77e7..e8cd6a65f 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(ARCH_PORTDUINO) + 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 de06b4c67..82ae58bfa 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(ARCH_PORTDUINO) return _fs->open(fname, "a"); #else return _fs->open(fname, "a", true); @@ -661,6 +661,8 @@ bool MyMesh::formatFileSystem() { return InternalFS.format(); #elif defined(RP2040_PLATFORM) return LittleFS.format(); +#elif defined(ARCH_PORTDUINO) + return true; #elif defined(ESP32) return SPIFFS.format(); #else @@ -694,7 +696,7 @@ void MyMesh::updateFloodAdvertTimer() { } void MyMesh::dumpLogFile() { -#if defined(RP2040_PLATFORM) +#if defined(RP2040_PLATFORM) || defined(ARCH_PORTDUINO) File f = _fs->open(PACKET_LOG_FILE, "r"); #else File f = _fs->open(PACKET_LOG_FILE); @@ -719,7 +721,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(ARCH_PORTDUINO) 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 8641caaf9..6517bf7a3 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(ARCH_PORTDUINO) + #include #elif defined(ESP32) #include #endif diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 1a3b4d6e0..c53b47e12 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(ARCH_PORTDUINO) + 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..e2d23cd43 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(ARCH_PORTDUINO) + #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(ARCH_PORTDUINO) 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(ARCH_PORTDUINO) 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(ARCH_PORTDUINO) 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(ARCH_PORTDUINO) 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(ARCH_PORTDUINO) 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(ARCH_PORTDUINO) + 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 20d9921b9..c71d36506 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(ARCH_PORTDUINO) return _fs->open(fname, "a"); #else return _fs->open(fname, "a", true); @@ -750,6 +750,8 @@ bool SensorMesh::formatFileSystem() { return InternalFS.format(); #elif defined(RP2040_PLATFORM) return LittleFS.format(); +#elif defined(ARCH_PORTDUINO) + return true; #elif defined(ESP32) return SPIFFS.format(); #else @@ -764,7 +766,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(ARCH_PORTDUINO) 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 00d9c698a..f13d3dbe4 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(ARCH_PORTDUINO) +#include #elif defined(ESP32) #include #endif diff --git a/examples/simple_sensor/main.cpp b/examples/simple_sensor/main.cpp index a5fcc1484..f4dd4240c 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(ARCH_PORTDUINO) + 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..0b1417876 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(ARCH_PORTDUINO) 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 93773ccee..ba5d82108 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -102,7 +102,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(ARCH_PORTDUINO) 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..0a9700f0a 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(ARCH_PORTDUINO) 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(ARCH_PORTDUINO) 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..67823b827 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(ARCH_PORTDUINO) #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(ARCH_PORTDUINO) + #include + #define FILESYSTEM fs::FS #endif #include diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index 368446157..8788e4de4 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(ARCH_PORTDUINO) 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(ARCH_PORTDUINO) 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 09e86c676..fc74ce605 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 554e8fcce..841969785 100644 --- a/src/helpers/bridges/RS232Bridge.cpp +++ b/src/helpers/bridges/RS232Bridge.cpp @@ -17,7 +17,7 @@ void RS232Bridge::begin() { ((HardwareSerial *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); #elif defined(NRF52_PLATFORM) ((HardwareSerial *)_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..5866b49af --- /dev/null +++ b/src/helpers/radiolib/LinuxSX1262.h @@ -0,0 +1,59 @@ +#pragma once + +#include + +#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received +#define SX126X_IRQ_PREAMBLE_DETECTED 0x04 + +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, LORA_TX_POWER, 16, 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, LORA_TX_POWER, 16, 0.0f); + } + if (status != RADIOLIB_ERR_NONE) { + Serial.print("ERROR: radio init failed: "); + Serial.println(status); + return false; // fail + } + + setCRC(1); + + #ifdef SX126X_CURRENT_LIMIT + setCurrentLimit(SX126X_CURRENT_LIMIT); + #endif + #ifdef SX126X_DIO2_AS_RF_SWITCH + setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); + #endif + #ifdef SX126X_RX_BOOSTED_GAIN + setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); + #endif + #if defined(SX126X_RXEN) || defined(SX126X_TXEN) + #ifndef SX126X_RXEN + #define SX126X_RXEN RADIOLIB_NC + #endif + #ifndef SX126X_TXEN + #define SX126X_TXEN RADIOLIB_NC + #endif + setRfSwitchPins(SX126X_RXEN, SX126X_TXEN); + #endif + + return true; // success + } + + bool isReceiving() { + uint16_t irq = getIrqFlags(); + bool detected = (irq & SX126X_IRQ_HEADER_VALID) || (irq & SX126X_IRQ_PREAMBLE_DETECTED); + return detected; + } +}; \ No newline at end of file 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..09791820a --- /dev/null +++ b/variants/linux/LinuxBoard.cpp @@ -0,0 +1,108 @@ +#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); + if (config.lora_irq_pin != -1) { + initGPIOPin(config.lora_irq_pin, "gpiochip0", config.lora_irq_pin); + } + if (config.lora_reset_pin != -1) { + initGPIOPin(config.lora_reset_pin, "gpiochip0", config.lora_reset_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_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, "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..ec7ec7895 --- /dev/null +++ b/variants/linux/LinuxBoard.h @@ -0,0 +1,86 @@ +#pragma once + +#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 = -1; + uint32_t lora_reset_pin = -1; + + char* spidev = "/dev/spidev0.0"; + +#ifdef SX126X_DIO3_TCXO_VOLTAGE + float lora_tcxo = SX126X_DIO3_TCXO_VOLTAGE; +#else + float lora_tcxo = 1.6f; +#endif + + 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..0648d61d7 --- /dev/null +++ b/variants/linux/README.md @@ -0,0 +1,89 @@ +# 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 rpi_zero_2w_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 /etc/systemd/system/meshcored.service + +# Enable and start the service + +sudo systemctl daemon-reload +sudo systemctl enable --now meshcored.service +``` + +## Logs + +TODO: add more logging + +``` +journalctl -u meshcored +``` diff --git a/variants/linux/meshcored.ini b/variants/linux/meshcored.ini new file mode 100644 index 000000000..ba2e4b211 --- /dev/null +++ b/variants/linux/meshcored.ini @@ -0,0 +1,14 @@ +advert_name = "Sample Router" +admin_password = "password" +lat = 0.0 +lon = 0.0 + +lora_irq_pin = 22 +lora_reset_pin = 13 + +spidev = /dev/spidev0.0 +lora_freq = 869.618 +lora_bw = 62.5 +lora_sf = 8 +lora_cr = 8 +lora_tcxo = 1.8 diff --git a/variants/linux/meshcored.service b/variants/linux/meshcored.service new file mode 100644 index 000000000..3b3714b77 --- /dev/null +++ b/variants/linux/meshcored.service @@ -0,0 +1,29 @@ +# /etc/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/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..ff88a4b6a --- /dev/null +++ b/variants/linux/platformio.ini @@ -0,0 +1,60 @@ +[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} + +build_src_filter = ${linux_base.build_src_filter} + +<../examples/simple_repeater> + +lib_deps = + ${linux_base.lib_deps} + +[env:rpi_zero_2w_repeater] +extends = env:linux_repeater +build_flags = + ${env:linux_repeater.build_flags} + -D P_LORA_NSS=-1 ; SS pin handled by RPI + -D P_LORA_BUSY=-1 ; Seems to be unused + -D RADIO_CLASS=LinuxSX1262 + -D WRAPPER_CLASS=LinuxSX1262Wrapper + -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=50 + -D USE_CUSTOM_SX1262_WRAPPER + -D LORA_TX_POWER=22 + -D SX126X_POWER_EN=37 + -D SX126X_DIO3_TCXO_VOLTAGE=1.8f + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + +lib_deps = + ${env:linux_repeater.lib_deps} diff --git a/variants/linux/target.cpp b/variants/linux/target.cpp new file mode 100644 index 000000000..a59fd6638 --- /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, P_LORA_NSS, -1, -1, P_LORA_BUSY); +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, P_LORA_NSS, board.config.lora_irq_pin, board.config.lora_reset_pin, P_LORA_BUSY); + 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..906b15ed9 --- /dev/null +++ b/variants/portduino/platformio.ini @@ -0,0 +1,40 @@ +[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 + -DRADIOLIB_EEPROM_UNSUPPORTED + -DPORTDUINO_LINUX_HARDWARE + -fPIC + -lpthread + -lstdc++fs + -lbluetooth + -lgpiod + -li2c + -luv + -std=gnu17 + -std=c++17