Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions boards/linux.json
Original file line number Diff line number Diff line change
@@ -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"
}
21 changes: 17 additions & 4 deletions examples/simple_repeater/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,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);
Expand Down Expand Up @@ -904,6 +904,19 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
void MyMesh::begin(FILESYSTEM *fs) {
mesh::Mesh::begin();
_fs = fs;
#if defined(ARCH_PORTDUINO)
// Apply runtime INI config as first-run defaults before loading persisted prefs.
// If /com_prefs exists, loadPrefs() below will overwrite these with the saved values.
StrHelper::strncpy(_prefs.node_name, board.config.advert_name, sizeof(_prefs.node_name));
_prefs.node_lat = board.config.lat;
_prefs.node_lon = board.config.lon;
StrHelper::strncpy(_prefs.password, board.config.admin_password, sizeof(_prefs.password));
_prefs.freq = board.config.lora_freq;
_prefs.bw = board.config.lora_bw;
_prefs.sf = board.config.lora_sf;
_prefs.cr = board.config.lora_cr;
_prefs.tx_power_dbm = board.config.lora_tx_power;
#endif
// load persisted prefs
_cli.loadPrefs(_fs);
acl.load(_fs, self_id);
Expand Down Expand Up @@ -950,6 +963,8 @@ bool MyMesh::formatFileSystem() {
return LittleFS.format();
#elif defined(ESP32)
return SPIFFS.format();
#elif defined(ARCH_PORTDUINO)
return false; // not supported on Linux
#else
#error "need to implement file system erase"
return false;
Expand Down Expand Up @@ -1082,9 +1097,7 @@ void MyMesh::formatPacketStatsReply(char *reply) {
void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) {
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
IdentityStore store(*_fs, "");
#elif defined(ESP32)
IdentityStore store(*_fs, "/identity");
#elif defined(RP2040_PLATFORM)
#elif defined(ESP32) || defined(RP2040_PLATFORM) || defined(ARCH_PORTDUINO)
IdentityStore store(*_fs, "/identity");
#else
#error "need to define saveIdentity()"
Expand Down
2 changes: 2 additions & 0 deletions examples/simple_repeater/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#include <LittleFS.h>
#elif defined(ESP32)
#include <SPIFFS.h>
#elif defined(ARCH_PORTDUINO)
#include <PortduinoFS.h>
#endif

#ifdef WITH_RS232_BRIDGE
Expand Down
8 changes: 8 additions & 0 deletions examples/simple_repeater/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ void setup() {
fs = &LittleFS;
IdentityStore store(LittleFS, "/identity");
store.begin();
#elif defined(ARCH_PORTDUINO)
if (::mkdir(board.config.data_dir, 0755) != 0 && errno != EEXIST) {
Serial.printf("WARNING: could not create data_dir '%s': %s\n", board.config.data_dir, strerror(errno));
}
portduinoVFS->mountpoint(board.config.data_dir);
fs = &PortduinoFS;
IdentityStore store(PortduinoFS, "/identity");
store.begin();
#else
#error "need to define filesystem"
#endif
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/ClientACL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/CommonCLI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,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);
Expand Down
4 changes: 2 additions & 2 deletions src/helpers/IdentityStore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/IdentityStore.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#pragma once

#if defined(ESP32) || defined(RP2040_PLATFORM)
#if defined(ESP32) || defined(RP2040_PLATFORM) || defined(ARCH_PORTDUINO)
#include <FS.h>
#define FILESYSTEM fs::FS
#elif defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/RegionMap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,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);
Expand Down
7 changes: 7 additions & 0 deletions src/helpers/TxtDataHelpers.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
#include "TxtDataHelpers.h"
#if defined(ARCH_PORTDUINO)
#include <inttypes.h>
#endif

void StrHelper::strncpy(char* dest, const char* src, size_t buf_sz) {
while (buf_sz > 1 && *src) {
Expand Down Expand Up @@ -102,7 +105,11 @@ static void _ftoa(float f, char *p, int *status)
*p++ = '0';
else
{
#if defined(ARCH_PORTDUINO)
sprintf(p, "%" PRId32, int_part);
#else
ltoa(int_part, p, 10);
#endif
while (*p)
p++;
}
Expand Down
48 changes: 48 additions & 0 deletions src/helpers/radiolib/LinuxSX1262.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#pragma once

#include <RadioLib.h>

#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;
}
};
22 changes: 22 additions & 0 deletions src/helpers/radiolib/LinuxSX1262Wrapper.h
Original file line number Diff line number Diff line change
@@ -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);
}
};
6 changes: 6 additions & 0 deletions variants/linux/99-meshcore.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# udev rules for meshcored — grant the meshcore group access to SPI and GPIO devices.
# Works on Arch Linux, Debian/Raspberry Pi OS, and other distributions.
# Install: sudo cp 99-meshcore.rules /etc/udev/rules.d/

SUBSYSTEM=="spidev", GROUP="meshcore", MODE="0660"
KERNEL=="gpiochip*", GROUP="meshcore", MODE="0660"
147 changes: 147 additions & 0 deletions variants/linux/LinuxBoard.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#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);

// strip optional surrounding quotes from string values
{
size_t vlen = strlen(value);
if (vlen >= 2 && (value[0] == '"' || value[0] == '\'') && value[vlen-1] == value[0]) {
value[vlen-1] = '\0';
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 = atoi(value) != 0;
else if (strcmp(key, "rx_boosted_gain") == 0) rx_boosted_gain = atoi(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);
else if (strcmp(key, "data_dir") == 0) data_dir = safe_copy(value, 256);
}
fclose(f);
return 0;
}
Loading