diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 06c56a7a44..db4846b879 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -37,6 +37,8 @@ static File openWrite(FILESYSTEM* fs, const char* filename) { return fs->open(filename, FILE_O_WRITE); #elif defined(RP2040_PLATFORM) return fs->open(filename, "w"); +#elif defined(ARDULINUX_PLATFORM) + return fs->open(filename, "w"); #else return fs->open(filename, "w", true); #endif @@ -140,6 +142,8 @@ File DataStore::openRead(const char* filename) { return _fs->open(filename, FILE_O_READ); #elif defined(RP2040_PLATFORM) return _fs->open(filename, "r"); +#elif defined(ARDULINUX_PLATFORM) + return _fs->open(filename, "r"); #else return _fs->open(filename, "r", false); #endif @@ -150,6 +154,8 @@ File DataStore::openRead(FILESYSTEM* fs, const char* filename) { return fs->open(filename, FILE_O_READ); #elif defined(RP2040_PLATFORM) return fs->open(filename, "r"); +#elif defined(ARDULINUX_PLATFORM) + return fs->open(filename, "r"); #else return fs->open(filename, "r", false); #endif @@ -176,6 +182,10 @@ bool DataStore::formatFileSystem() { bool fs_success = ((fs::SPIFFSFS *)_fs)->format(); esp_err_t nvs_err = nvs_flash_erase(); // no need to reinit, will be done by reboot return fs_success && (nvs_err == ESP_OK); +#elif defined(ARDULINUX_PLATFORM) + // Wiping is handled by the daemon's --erase startup flag (ArduLinux core + // clears the VFS root before any open). The runtime "erase" command is a no-op. + return false; #else #error "need to implement format()" #endif diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 3b98a4f674..4b4491283f 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -21,6 +21,8 @@ #include #elif defined(ESP32) #include +#elif defined(ARDULINUX_PLATFORM) +#include #endif #include "DataStore.h" diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 89f0e6cb9f..2234d168fe 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -85,8 +85,17 @@ MultiSerialInterface interface_manager; #elif defined(ESP32) #include DataStore store(SPIFFS, rtc_clock); +#elif defined(ARDULINUX_PLATFORM) + DataStore store(ArduLinuxFS, rtc_clock); #endif +// include linux native interface +#if defined(ARDULINUX_PLATFORM) + #include + #include + ArduinoSerialInterface linux_serial_interface; + LinuxTcpInterface tcp_interface; +#endif /* GLOBAL OBJECTS */ #ifdef DISPLAY_CLASS #include "UITask.h" @@ -180,6 +189,17 @@ void setup() { false #endif ); +#elif defined(ARDULINUX_PLATFORM) + // the VFS root is established by the ArduLinux core from --fsdir + // (default: the XDG data dir, e.g. ~/.local/share/meshcored/default) + store.begin(); + the_mesh.begin( + #ifdef DISPLAY_CLASS + disp != NULL + #else + false + #endif + ); #else #error "need to define filesystem" #endif @@ -230,6 +250,22 @@ void setup() { interface_manager.addInterface(InterfaceType::HardwareSerial, &hardware_serial_interface); #endif +// add linux native interface: the TCP listener when companion_tcp_port is set +// and binds, otherwise the stdio transport (ArduLinux maps Serial to stdin/stdout) +#if defined(ARDULINUX_PLATFORM) + if (board.config.companion_tcp_port != 0 + && tcp_interface.begin(board.config.companion_tcp_port, board.config.companion_tcp_bind)) { + interface_manager.addInterface(InterfaceType::WiFi, &tcp_interface); + fprintf(stderr, "Companion: TCP listener on %s:%u\n", + board.config.companion_tcp_bind, board.config.companion_tcp_port); + } else { + linux_serial_interface.begin(Serial); + interface_manager.addInterface(InterfaceType::USB, &linux_serial_interface); + fprintf(stderr, "Companion: stdin/stdout transport (companion_tcp_port = %u)\n", + board.config.companion_tcp_port); + } +#endif + the_mesh.startInterface(interface_manager); sensors.begin(); diff --git a/src/helpers/LinuxTcpInterface.cpp b/src/helpers/LinuxTcpInterface.cpp new file mode 100644 index 0000000000..46758f3ecb --- /dev/null +++ b/src/helpers/LinuxTcpInterface.cpp @@ -0,0 +1,232 @@ +#ifdef ARDULINUX_PLATFORM + +#include "LinuxTcpInterface.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include // millis() + +LinuxTcpInterface::LinuxTcpInterface() + : _server_fd(-1), _client_fd(-1), _enabled(false), + _device_connected(false), _port(0), _last_write(0), + _send_queue_len(0), _rx_len(0) { + _received_frame_header.type = 0; + _received_frame_header.length = 0; +} + +LinuxTcpInterface::~LinuxTcpInterface() { + closeClient(); + if (_server_fd >= 0) ::close(_server_fd); +} + +bool LinuxTcpInterface::begin(uint16_t port, const char* bind_addr) { + if (port == 0) return false; + + _server_fd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); + if (_server_fd < 0) { + fprintf(stderr, "LinuxTcpInterface: socket() failed: %s\n", strerror(errno)); + return false; + } + + int one = 1; + (void)::setsockopt(_server_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + if (::inet_pton(AF_INET, bind_addr, &addr.sin_addr) != 1) { + fprintf(stderr, "LinuxTcpInterface: inet_pton(%s) failed (not a valid IPv4 address)\n", bind_addr); + ::close(_server_fd); + _server_fd = -1; + return false; + } + + if (::bind(_server_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + fprintf(stderr, "LinuxTcpInterface: bind(%s:%u) failed: %s\n", bind_addr, port, strerror(errno)); + ::close(_server_fd); + _server_fd = -1; + return false; + } + + if (::listen(_server_fd, 1) < 0) { + fprintf(stderr, "LinuxTcpInterface: listen() failed: %s\n", strerror(errno)); + ::close(_server_fd); + _server_fd = -1; + return false; + } + + _port = port; + return true; +} + +void LinuxTcpInterface::enable() { + if (_enabled) return; + _enabled = true; + _send_queue_len = 0; + _rx_len = 0; + resetReceivedFrameHeader(); +} + +void LinuxTcpInterface::disable() { + _enabled = false; + closeClient(); +} + +void LinuxTcpInterface::closeClient() { + if (_client_fd >= 0) { + ::close(_client_fd); + _client_fd = -1; + } + _device_connected = false; + _rx_len = 0; + resetReceivedFrameHeader(); +} + +void LinuxTcpInterface::acceptIfNeeded() { + if (_server_fd < 0) return; + + int new_fd = ::accept4(_server_fd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); + if (new_fd < 0) return; // EAGAIN/EWOULDBLOCK = no incoming connection + + // Single-client semantics: a new connection kicks the previous one, + // mirroring SerialWifiInterface::checkRecvFrame()'s replace-on-new behavior. + closeClient(); + _client_fd = new_fd; +} + +bool LinuxTcpInterface::hasReceivedFrameHeader() const { + return _received_frame_header.type != 0 && _received_frame_header.length != 0; +} + +void LinuxTcpInterface::resetReceivedFrameHeader() { + _received_frame_header.type = 0; + _received_frame_header.length = 0; +} + +size_t LinuxTcpInterface::writeFrame(const uint8_t src[], size_t len) { + if (len == 0 || len > MAX_FRAME_SIZE) return 0; + if (!_device_connected) return 0; + if (_send_queue_len >= FRAME_QUEUE_SIZE) return 0; + + _send_queue[_send_queue_len].len = (uint8_t)len; + memcpy(_send_queue[_send_queue_len].buf, src, len); + _send_queue_len++; + return len; +} + +void LinuxTcpInterface::drainSendQueue() { + while (_send_queue_len > 0 && _device_connected) { + int len = _send_queue[0].len; + uint8_t pkt[3 + MAX_FRAME_SIZE]; + pkt[0] = '>'; // same framing as serial / SerialWifiInterface so meshcli can delimit + pkt[1] = (uint8_t)(len & 0xFF); + pkt[2] = (uint8_t)((len >> 8) & 0xFF); + memcpy(&pkt[3], _send_queue[0].buf, len); + + ssize_t n = ::send(_client_fd, pkt, 3 + len, MSG_NOSIGNAL | MSG_DONTWAIT); + if (n < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) return; // try next tick + closeClient(); + return; + } + // Partial writes are rare on local TCP with small frames; on the unlikely + // partial we drop the frame to keep the protocol aligned rather than try + // to track partial-write state across ticks. + _last_write = millis(); + _send_queue_len--; + for (int i = 0; i < _send_queue_len; i++) { + _send_queue[i] = _send_queue[i + 1]; + } + } +} + +size_t LinuxTcpInterface::readFrameFromBuffer(uint8_t dest[]) { + // Parse header if we don't have one yet + if (!hasReceivedFrameHeader()) { + const size_t HDR_LEN = 3; + if (_rx_len < HDR_LEN) return 0; + + _received_frame_header.type = _rx_buf[0]; + _received_frame_header.length = (uint16_t)_rx_buf[1] | ((uint16_t)_rx_buf[2] << 8); + + memmove(_rx_buf, _rx_buf + HDR_LEN, _rx_len - HDR_LEN); + _rx_len -= HDR_LEN; + } + + uint16_t frame_length = _received_frame_header.length; + uint8_t frame_type = _received_frame_header.type; + + // '<' (0x3c) is the only valid app→radio frame type + if (frame_type != '<') { + fprintf(stderr, "LinuxTcpInterface: bad frame type 0x%02x, resyncing\n", frame_type); + _rx_len = 0; + resetReceivedFrameHeader(); + return 0; + } + + if (frame_length > MAX_FRAME_SIZE) { + fprintf(stderr, "LinuxTcpInterface: frame length %u > MAX_FRAME_SIZE %d, resyncing\n", + frame_length, MAX_FRAME_SIZE); + _rx_len = 0; + resetReceivedFrameHeader(); + return 0; + } + + if (_rx_len < frame_length) return 0; // need more bytes + + memcpy(dest, _rx_buf, frame_length); + memmove(_rx_buf, _rx_buf + frame_length, _rx_len - frame_length); + _rx_len -= frame_length; + resetReceivedFrameHeader(); + return frame_length; +} + +size_t LinuxTcpInterface::checkRecvFrame(uint8_t dest[]) { + if (!_enabled) return 0; + + acceptIfNeeded(); // kicks the previous client if a new one connects + + if (_client_fd < 0) { + if (_device_connected) _device_connected = false; + return 0; + } + + if (!_device_connected) { + _device_connected = true; + fprintf(stderr, "Companion TCP: client connected\n"); + } + + drainSendQueue(); + if (!_device_connected) return 0; // closed during drain + + // Top up rx buffer from socket + if (_rx_len < sizeof(_rx_buf)) { + ssize_t n = ::recv(_client_fd, _rx_buf + _rx_len, sizeof(_rx_buf) - _rx_len, MSG_DONTWAIT); + if (n == 0) { + fprintf(stderr, "Companion TCP: client disconnected\n"); + closeClient(); + return 0; + } + if (n < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + fprintf(stderr, "Companion TCP: recv error: %s\n", strerror(errno)); + closeClient(); + return 0; + } + } else { + _rx_len += (size_t)n; + } + } + + return readFrameFromBuffer(dest); +} + +#endif // ARDULINUX_PLATFORM diff --git a/src/helpers/LinuxTcpInterface.h b/src/helpers/LinuxTcpInterface.h new file mode 100644 index 0000000000..e49870fc67 --- /dev/null +++ b/src/helpers/LinuxTcpInterface.h @@ -0,0 +1,58 @@ +#pragma once + +#ifdef ARDULINUX_PLATFORM + +#include "BaseSerialInterface.h" +#include +#include + +class LinuxTcpInterface : public BaseSerialInterface { + int _server_fd; + int _client_fd; + bool _enabled; + bool _device_connected; + uint16_t _port; + unsigned long _last_write; + + struct FrameHeader { + uint8_t type; + uint16_t length; + }; + + struct Frame { + uint8_t len; + uint8_t buf[MAX_FRAME_SIZE]; + }; + + FrameHeader _received_frame_header; + + static const int FRAME_QUEUE_SIZE = 4; + int _send_queue_len; + Frame _send_queue[FRAME_QUEUE_SIZE]; + + uint8_t _rx_buf[MAX_FRAME_SIZE + 16]; + size_t _rx_len; + + void closeClient(); + void acceptIfNeeded(); + void drainSendQueue(); + size_t readFrameFromBuffer(uint8_t dest[]); + bool hasReceivedFrameHeader() const; + void resetReceivedFrameHeader(); + +public: + LinuxTcpInterface(); + ~LinuxTcpInterface(); + + bool begin(uint16_t port, const char* bind_addr); + + void enable() override; + void disable() override; + bool isEnabled() const override { return _enabled; } + bool isConnected() const override { return _device_connected; } + bool isWriteBusy() const override { return false; } + size_t writeFrame(const uint8_t src[], size_t len) override; + size_t checkRecvFrame(uint8_t dest[]) override; +}; + +#endif // ARDULINUX_PLATFORM diff --git a/variants/linux/LinuxBoard.cpp b/variants/linux/LinuxBoard.cpp index 8788758ae1..0c320f242a 100644 --- a/variants/linux/LinuxBoard.cpp +++ b/variants/linux/LinuxBoard.cpp @@ -54,7 +54,9 @@ void LinuxBoard::begin() { exit(1); #endif - config.load("/etc/meshcored/meshcored.ini"); + if (config.load("/etc/meshcored/meshcored.ini") < 0) { + fprintf(stderr, "warning: /etc/meshcored/meshcored.ini not found, using built-in defaults\n"); + } printf("SPI begin %s\n", config.spidev); SPI.begin(config.spidev, 2000000); @@ -94,12 +96,12 @@ void LinuxBoard::begin() { } 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 *start = str; + while (isspace((unsigned char)*start)) start++; + char *end = start + strlen(start); + while (end > start && isspace((unsigned char)end[-1])) end--; + *end = '\0'; + if (start != str) memmove(str, start, end - start + 1); } char *safe_copy(char *value, size_t maxlen) { @@ -151,6 +153,8 @@ int LinuxConfig::load(const char *filename) { if (strcmp(key, "spidev") == 0) spidev = safe_copy(value, 32); else if (strcmp(key, "lora_gpiochip") == 0) lora_gpiochip = safe_copy(value, 32); else if (strcmp(key, "console_path") == 0) console_path = safe_copy(value, 108); + else if (strcmp(key, "companion_tcp_port") == 0) companion_tcp_port = (uint16_t)atoi(value); + else if (strcmp(key, "companion_tcp_bind") == 0) companion_tcp_bind = 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); diff --git a/variants/linux/LinuxBoard.h b/variants/linux/LinuxBoard.h index ccfe11ccf6..e7bb246109 100644 --- a/variants/linux/LinuxBoard.h +++ b/variants/linux/LinuxBoard.h @@ -39,6 +39,8 @@ class LinuxConfig { // ($XDG_RUNTIME_DIR/meshcore/console, else /tmp/meshcore-/console). // Connect with `meshcore-cli -r -s `. char* console_path = ""; + uint16_t companion_tcp_port = 5000; // linux env only; 0 = stdin/stdout fallback + char* companion_tcp_bind = "127.0.0.1"; // 0.0.0.0 to expose on the network float lora_tcxo = 1.8f; diff --git a/variants/linux/README.md b/variants/linux/README.md index 7b25c58fe9..862dffd5fc 100644 --- a/variants/linux/README.md +++ b/variants/linux/README.md @@ -2,6 +2,17 @@ Native Linux support for MeshCore, targeting Raspberry Pi (Zero, 3, 4, 5) and similar SBCs with an SX1262 LoRa radio attached over SPI. Uses [ArduLinux, Arduino API for Linux](https://github.com/l5yth/ardulinux) to run the same firmware codebase on Linux without modification to the core library. +## Roles + +Two PlatformIO envs build different MeshCore roles from the same Linux/ArduLinux base: + +| Env | Role | Host interface | +|-----|------|----------------| +| `linux_repeater` | Simple repeater (re-advertises and forwards mesh traffic) | Text CLI on stdin/stdout (serial-style commands like `set name`, `set freq`) | +| `linux` | Companion radio (exposes the binary control protocol used by phone / desktop client apps) | Binary frames on stdin/stdout; bridge via `socat` or a future TCP/BLE wrapper | + +Both envs share `variants/linux/{LinuxBoard,target}.*`, the same `/etc/meshcored/meshcored.ini` hardware config, and the same VFS layout under `--fsdir` — so only one role should run at a time against a given state dir. + ## Hardware - Raspberry Pi (any model with SPI) @@ -39,17 +50,21 @@ sudo pacman -S platformio-core # or: pipx install platformio pipx install platformio # or: pip install --user platformio ``` -**Build with `build.sh`** (recommended, embeds version and commit hash): +**Build with `build.sh`** (recommended, embeds version and commit hash) — pick the env that matches the role you want: ```sh FIRMWARE_VERSION=dev ./build.sh build-firmware linux_repeater # binary: .pio/build/linux_repeater/meshcored + +FIRMWARE_VERSION=dev ./build.sh build-firmware linux +# binary: .pio/build/linux/meshcored (companion radio) ``` Alternatively, build directly with PlatformIO (no version metadata): ```sh FIRMWARE_VERSION=dev pio run -e linux_repeater +FIRMWARE_VERSION=dev pio run -e linux ``` ## Setup @@ -86,6 +101,8 @@ Key settings: |-----|---------|-------| | `spidev` | `/dev/spidev0.0` | SPI device node | | `lora_gpiochip` | `gpiochip0` | Name of the `/dev/gpiochip*` device (or kernel label). `gpiochip0` is correct for Pi 3/4/Zero 2W; Pi 5 may need `gpiochip4` or `pinctrl-rp1` depending on kernel | +| `companion_tcp_port` | `5000` | `linux` env only. TCP port the companion binary protocol listens on. `0` disables TCP and falls back to stdin/stdout for `socat`/wrapper setups. Ignored by `linux_repeater` | +| `companion_tcp_bind` | `127.0.0.1` | `linux` env only. Address the companion listens on. Default is localhost-only; set to `0.0.0.0` to expose on the network — **put auth in front of it**, the companion protocol is unauthenticated | | `lora_irq_pin` | (none) | GPIO line number for IRQ | | `lora_reset_pin` | (none) | GPIO line number for RESET | | `lora_nss_pin` | (none) | GPIO line number for NSS/CS (if not handled by the SPI driver) | @@ -105,6 +122,10 @@ Key settings: | `admin_password` | `"password"` | Admin password, **change this**, first-run default only | | `lat` / `lon` | `0.0` | GPS coordinates for advertisement, first-run default only | +> **Boolean keys** (`dio2_as_rf_switch`, `rx_boosted_gain`) are parsed as integers, use `1` / `0`. `true` / `false` are silently treated as `0`. +> +> **GPIO pins** are looked up on the chip named by `lora_gpiochip` (default `gpiochip0`) with the pin number used directly as the line index. This fits the Raspberry Pi header (BCM numbering); for other SBCs, override `lora_gpiochip` to match your hardware. + ### 3. Enable SPI and GPIO access First make sure the SPI interface is actually enabled, the radio needs a @@ -198,6 +219,12 @@ sudo journalctl -u meshcored -f > (the unit's `RuntimeDirectory` provides `/run/meshcored`). See > [§5](#5-reconfiguring-after-first-run). +> **Mesh time-sync wall-clock writes are no-ops under this unit.** The service +> runs as `meshcore` with `NoNewPrivileges=yes` and no `CAP_SYS_TIME`, so +> `settimeofday()` returns `EPERM` and the system clock is not updated from +> mesh peers. This is the safe default, keep the wall clock synced via +> `systemd-timesyncd` / NTP. + ### 5. Reconfiguring after first run `meshcored` exposes a local CLI at the path set by `console_path` in @@ -259,7 +286,7 @@ sudo systemctl start meshcored ## Known Gaps / TODO - **Config path is hardcoded**, meshcored always loads `/etc/meshcored/meshcored.ini`; there is no flag to point it elsewhere. (The data *path* is separate and configurable: it is the ArduLinux VFS root, set with `--fsdir`.) -- **Only repeater firmware**, there is no `linux_companion` target yet; companion radio support (BLE/serial interface to a phone app) is not implemented for Linux. +- **Companion BLE transport** isn't implemented for Linux yet. The `linux` env exposes the binary control protocol over a native TCP listener (`companion_tcp_port`, default `127.0.0.1:5000`) — speaks the same framing as the embedded serial/WiFi companions, so `meshcore-cli -t ` connects directly. For a phone app over BLE you'd still need a BlueZ GATT server wrapping `meshcored`, which is the natural follow-up. - **Serial `erase` command is a no-op**, `formatFileSystem()` returns `false` on Linux, so the interactive serial `erase` command reports failure. To wipe the filesystem, use the `--erase` *startup* flag (or clear the VFS dir) instead, see step 5. - **No power management**, `board.sleep()` is a no-op; the power-saving loop in `main.cpp` never actually sleeps. - **Upstream-sync fragility**, the radio wrapper (`LinuxSX1262Wrapper`) implements the `RadioLibWrapper` interface by hand, so it can drift from upstream in two ways: a new **pure-virtual** method breaks the Linux build (e.g. `setParams()`), and a new **virtual-with-default** method silently no-ops on Linux until overridden (e.g. `set`/`getRxBoostedGainMode()`, which reported and applied the wrong state until added). Mirror `CustomSX1262Wrapper` when syncing. diff --git a/variants/linux/meshcored.ini b/variants/linux/meshcored.ini index 836bdc2be4..c20de101c0 100644 --- a/variants/linux/meshcored.ini +++ b/variants/linux/meshcored.ini @@ -24,7 +24,9 @@ lora_reset_pin = 13 #lora_txen_pin spidev = /dev/spidev0.0 -# lora_gpiochip = gpiochip0 # Pi 3/4/Zero 2W default; Pi 5 may need gpiochip4 or pinctrl-rp1 +# lora_gpiochip = gpiochip0 # Pi 3/4/Zero 2W default; Pi 5 may need gpiochip4 or pinctrl-rp1 +# companion_tcp_port = 5000 # linux env only; 0 = stdin/stdout fallback +# companion_tcp_bind = 127.0.0.1 # 0.0.0.0 to expose on the network (no auth!) lora_freq = 869.618 lora_bw = 62.5 lora_sf = 8 diff --git a/variants/linux/meshcored.ini.pow-sx1262 b/variants/linux/meshcored.ini.pow-sx1262 index 96b5040a9f..7888e2157d 100644 --- a/variants/linux/meshcored.ini.pow-sx1262 +++ b/variants/linux/meshcored.ini.pow-sx1262 @@ -3,7 +3,9 @@ # Hardware config (read on every startup): spidev = /dev/spidev0.0 -# lora_gpiochip = gpiochip0 # Pi 3/4/Zero 2W default; Pi 5 may need gpiochip4 or pinctrl-rp1 +# lora_gpiochip = gpiochip0 # Pi 3/4/Zero 2W default; Pi 5 may need gpiochip4 or pinctrl-rp1 +# companion_tcp_port = 5000 # linux env only; 0 = stdin/stdout fallback +# companion_tcp_bind = 127.0.0.1 # 0.0.0.0 to expose on the network (no auth!) lora_irq_pin = 22 lora_reset_pin = 13 # lora_nss_pin - SS is handled by the SPI driver; not needed diff --git a/variants/linux/meshcored.ini.waveshare b/variants/linux/meshcored.ini.waveshare index 45b03bf82f..faae07e897 100644 --- a/variants/linux/meshcored.ini.waveshare +++ b/variants/linux/meshcored.ini.waveshare @@ -3,7 +3,9 @@ # Hardware config (read on every startup): spidev = /dev/spidev0.0 -# lora_gpiochip = gpiochip0 # Pi 3/4/Zero 2W default; Pi 5 may need gpiochip4 or pinctrl-rp1 +# lora_gpiochip = gpiochip0 # Pi 3/4/Zero 2W default; Pi 5 may need gpiochip4 or pinctrl-rp1 +# companion_tcp_port = 5000 # linux env only; 0 = stdin/stdout fallback +# companion_tcp_bind = 127.0.0.1 # 0.0.0.0 to expose on the network (no auth!) lora_irq_pin = 16 lora_reset_pin = 18 lora_nss_pin = 21 diff --git a/variants/linux/meshcored.service b/variants/linux/meshcored.service index 8fdfe44aa3..f3cdead632 100644 --- a/variants/linux/meshcored.service +++ b/variants/linux/meshcored.service @@ -1,4 +1,4 @@ -# /var/lib/systemd/system/meshcored.service +# /etc/systemd/system/meshcored.service [Unit] Description=Meshcore Daemon (meshcored) After=network.target diff --git a/variants/linux/platformio.ini b/variants/linux/platformio.ini index 8f42ce46b1..bdd41f7c87 100644 --- a/variants/linux/platformio.ini +++ b/variants/linux/platformio.ini @@ -1,8 +1,27 @@ [env:linux] extends = linux_base -build_flags = ${linux_base.build_flags} +; Companion-radio role. Transport: ArduinoSerialInterface(Serial), which +; ArduLinux maps to stdin/stdout. Pipe binary control frames in/out, or +; bridge via socat / a future TCP/BLE wrapper. +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 MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 + -D LORA_TX_POWER=22 !pkg-config --cflags --libs libbsd-overlay --silence-errors || : +build_src_filter = ${linux_base.build_src_filter} + +<../examples/companion_radio/*.cpp> + +lib_deps = + ${linux_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + [env:linux_repeater] extends = linux_base build_flags =