YAML based config for PI / Portduino (#2943)

* Add configuration via /etc/meshtastic/config.yaml

* Move example config, support more locations

* Fix config check

* Use access() to check for config file presence

* Throw an error and exit on radio init fail

* Adds error check for reading Bluetooth MAC

* Settle on meshtasticd, add install script

* Shell fixes

* Fine. I'll put it back and then disable you

* Get wrekt, shellchekt

* Firat attempt at adding raspbian CI build

* Tickle the workflow

* Beatings will continue til morale improves

* Permissions are overrated

---------

Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
This commit is contained in:
Ben Meadors 2023-11-18 08:12:34 -06:00 committed by GitHub
parent 9d4af1146e
commit 46bd6ca7ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 206 additions and 51 deletions

30
.github/workflows/build_raspbian.yml vendored Normal file
View File

@ -0,0 +1,30 @@
name: Build Raspbian
on: workflow_call
permissions:
contents: write
packages: write
jobs:
build-raspbian:
runs-on: [self-hosted, linux, ARM64]
steps:
- uses: actions/checkout@v3
- name: Build base
id: base
uses: ./.github/actions/setup-base
- name: Build Raspbian
run: bin/build-native.sh
- name: Get release version string
run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Store binaries as an artifact
uses: actions/upload-artifact@v3
with:
name: firmware-native-${{ steps.version.outputs.version }}.zip
path: |
release/meshtasticd_linux_arm64

View File

@ -1,7 +1,10 @@
enable=all enable=all
source-path=SCRIPTDIR source-path=SCRIPTDIR
disable=SC2154 disable=SC2154
disable=SC2248
disable=SC2250
# If you're having issues with shellcheck following source, disable the errors via: # If you're having issues with shellcheck following source, disable the errors via:
# disable=SC1090 # disable=SC1090
# disable=SC1091 # disable=SC1091
#

View File

@ -2,8 +2,8 @@
set -e set -e
VERSION=`bin/buildinfo.py long` VERSION=$(bin/buildinfo.py long)
SHORT_VERSION=`bin/buildinfo.py short` SHORT_VERSION=$(bin/buildinfo.py short)
OUTDIR=release/ OUTDIR=release/
@ -15,9 +15,13 @@ rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
platformio pkg update platformio pkg update
pio run --environment native if command -v raspi-config &>/dev/null; then
cp .pio/build/native/program $OUTDIR/meshtasticd_linux_amd64 pio run --environment raspbian
cp .pio/build/raspbian/program $OUTDIR/meshtasticd_linux_arm64
else
pio run --environment native
cp .pio/build/native/program $OUTDIR/meshtasticd_linux_amd64
fi
cp bin/device-install.* $OUTDIR cp bin/device-install.* $OUTDIR
cp bin/device-update.* $OUTDIR cp bin/device-update.* $OUTDIR

26
bin/config-dist.yaml Normal file
View File

@ -0,0 +1,26 @@
# Define your devices here.
# Use Broadcom pin numbering
#Waveshare SX126X XXXM
#USE_SX1262: true
#SX126X_DIO2_AS_RF_SWITCH: true
#SX126X_CS: 21
#SX126X_DIO1: 16
#SX126X_BUSY: 20
#SX126X_RESET: 18
#Waveshare SX1302 LISTEN ONLY AT THIS TIME!
#USE_SX1262: true
#SX126X_CS: 7
#SX126X_DIO1: 17
#SX126X_RESET: 22
#Adafruit RFM9x
#USE_RF95: true
#RF95_RESET: 25
#RF95_NSS: 7
#RF95_IRQ: 22
#RF95_DIO1: 23

9
bin/meshtasticd.service Normal file
View File

@ -0,0 +1,9 @@
[unit]
description=Meshtastic Native Daemon
[Service]
Type=simple
ExecStart=/usr/sbin/meshtasticd
[Install]
WantedBy=multi-user.target

10
bin/native-install.sh Executable file
View File

@ -0,0 +1,10 @@
#!/usr/bin/env bash
cp release/meshtasticd_linux_arm64 /usr/sbin/meshtasticd
mkdir /etc/meshtasticd
if [[ -f "/etc/meshtasticd/config.yaml" ]]; then
cp bin/config-dist.yaml /etc/meshtasticd/config-upgrade.yaml
else
cp bin/config-dist.yaml /etc/meshtasticd/config.yaml
fi
cp bin/meshtasticd.service /usr/lib/systemd/system/meshtasticd.service

View File

@ -57,8 +57,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define REQUIRE_RADIO true // If true, we will fail to start if the radio is not found #define REQUIRE_RADIO true // If true, we will fail to start if the radio is not found
/// Convert a preprocessor name into a quoted string /// Convert a preprocessor name into a quoted string
#define xstr(s) str(s) #define xstr(s) ystr(s)
#define str(s) #s #define ystr(s) #s
/// Convert a preprocessor name into a quoted string and if that string is empty use "unset" /// Convert a preprocessor name into a quoted string and if that string is empty use "unset"
#define optstr(s) (xstr(s)[0] ? xstr(s) : "unset") #define optstr(s) (xstr(s)[0] ? xstr(s) : "unset")

View File

@ -17,6 +17,9 @@
#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) #if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32)
HardwareSerial *GPS::_serial_gps = &Serial1; HardwareSerial *GPS::_serial_gps = &Serial1;
#elif defined(ARCH_RASPBERRY_PI)
// need a translation layer to make _serial_gps work with pigpio https://abyz.me.uk/rpi/pigpio/cif.html#serOpen
HardwareSerial *GPS::_serial_gps = NULL;
#else #else
HardwareSerial *GPS::_serial_gps = NULL; HardwareSerial *GPS::_serial_gps = NULL;
#endif #endif

View File

@ -69,6 +69,7 @@ NRF52Bluetooth *nrf52Bluetooth;
#ifdef ARCH_RASPBERRY_PI #ifdef ARCH_RASPBERRY_PI
#include "platform/portduino/PiHal.h" #include "platform/portduino/PiHal.h"
#include "platform/portduino/PortduinoGlue.h"
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <string> #include <string>
@ -690,15 +691,32 @@ void setup()
#endif #endif
#ifdef ARCH_RASPBERRY_PI #ifdef ARCH_RASPBERRY_PI
PiHal *RadioLibHAL = new PiHal(1); if (settingsMap[use_sx1262]) {
if (!rIf) { if (!rIf) {
rIf = new SX1262Interface((LockingArduinoHal *)RadioLibHAL, 21, 16, 18, 20); PiHal *RadioLibHAL = new PiHal(1);
if (!rIf->init()) { rIf = new SX1262Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[sx126x_cs], settingsMap[sx126x_dio1],
LOG_WARN("Failed to find SX1262 radio\n"); settingsMap[sx126x_reset], settingsMap[sx126x_busy]);
delete rIf; if (!rIf->init()) {
rIf = NULL; LOG_ERROR("Failed to find SX1262 radio\n");
} else { delete rIf;
LOG_INFO("SX1262 Radio init succeeded, using SX1262 radio\n"); exit(EXIT_FAILURE);
} else {
LOG_INFO("SX1262 Radio init succeeded, using SX1262 radio\n");
}
}
} else if (settingsMap[use_rf95]) {
if (!rIf) {
PiHal *RadioLibHAL = new PiHal(1);
rIf = new RF95Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[rf95_nss], settingsMap[rf95_irq],
settingsMap[rf95_reset], settingsMap[rf95_dio1]);
if (!rIf->init()) {
LOG_ERROR("Failed to find RF95 radio\n");
delete rIf;
rIf = NULL;
exit(EXIT_FAILURE);
} else {
LOG_INFO("RF95 Radio init succeeded, using RF95 radio\n");
}
} }
} }

View File

@ -2,6 +2,9 @@
#include "configuration.h" #include "configuration.h"
#include "error.h" #include "error.h"
#include "mesh/NodeDB.h" #include "mesh/NodeDB.h"
#ifdef ARCH_RASPBERRY_PI
#include "PortduinoGlue.h"
#endif
// Particular boards might define a different max power based on what their hardware can do, default to max power output if not // Particular boards might define a different max power based on what their hardware can do, default to max power output if not
// specified (may be dangerous if using external PA and SX126x power config forgotten) // specified (may be dangerous if using external PA and SX126x power config forgotten)
@ -74,6 +77,12 @@ template <typename T> bool SX126xInterface<T>::init()
#ifdef SX126X_DIO2_AS_RF_SWITCH #ifdef SX126X_DIO2_AS_RF_SWITCH
LOG_DEBUG("Setting DIO2 as RF switch\n"); LOG_DEBUG("Setting DIO2 as RF switch\n");
bool dio2AsRfSwitch = true; bool dio2AsRfSwitch = true;
#elif defined(ARCH_RASPBERRY_PI)
bool dio2AsRfSwitch = false;
if (settingsMap[sx126x_dio2_as_rf_switch]) {
LOG_DEBUG("Setting DIO2 as RF switch\n");
dio2AsRfSwitch = true;
}
#else #else
LOG_DEBUG("Setting DIO2 as not RF switch\n"); LOG_DEBUG("Setting DIO2 as not RF switch\n");
bool dio2AsRfSwitch = false; bool dio2AsRfSwitch = false;

View File

@ -9,7 +9,14 @@
#include <assert.h> #include <assert.h>
#ifdef ARCH_RASPBERRY_PI #ifdef ARCH_RASPBERRY_PI
#include "PortduinoGlue.h"
#include "pigpio.h" #include "pigpio.h"
#include "yaml-cpp/yaml.h"
#include <iostream>
#include <map>
#include <unistd.h>
std::map<int, int> settingsMap;
#else #else
#include <linux/gpio/LinuxGPIOPin.h> #include <linux/gpio/LinuxGPIOPin.h>
@ -27,7 +34,7 @@ void cpuDeepSleep(uint32_t msecs)
} }
void updateBatteryLevel(uint8_t level) NOT_IMPLEMENTED("updateBatteryLevel"); void updateBatteryLevel(uint8_t level) NOT_IMPLEMENTED("updateBatteryLevel");
#ifndef ARCH_RASPBERRY_PI
/** a simulated pin for busted IRQ hardware /** a simulated pin for busted IRQ hardware
* Porduino helper class to do this i2c based polling: * Porduino helper class to do this i2c based polling:
*/ */
@ -54,7 +61,7 @@ class PolledIrqPin : public GPIOPin
}; };
static GPIOPin *loraIrq; static GPIOPin *loraIrq;
#endif
int TCPPort = 4403; int TCPPort = 4403;
static error_t parse_opt(int key, char *arg, struct argp_state *state) static error_t parse_opt(int key, char *arg, struct argp_state *state)
@ -94,6 +101,48 @@ void portduinoSetup()
printf("Setting up Meshtastic on Portduino...\n"); printf("Setting up Meshtastic on Portduino...\n");
#ifdef ARCH_RASPBERRY_PI #ifdef ARCH_RASPBERRY_PI
YAML::Node yamlConfig;
if (access("config.yaml", R_OK) == 0) {
try {
yamlConfig = YAML::LoadFile("config.yaml");
} catch (YAML::Exception e) {
std::cout << "*** Exception " << e.what() << std::endl;
exit(EXIT_FAILURE);
}
} else if (access("/etc/meshtasticd/config.yaml", R_OK) == 0) {
try {
yamlConfig = YAML::LoadFile("/etc/meshtasticd/config.yaml");
} catch (YAML::Exception e) {
std::cout << "*** Exception " << e.what() << std::endl;
exit(EXIT_FAILURE);
}
} else {
std::cout << "No 'config.yaml' found, exiting." << std::endl;
exit(EXIT_FAILURE);
}
try {
settingsMap[use_sx1262] = yamlConfig["USE_SX1262"].as<bool>(false);
settingsMap[sx126x_dio2_as_rf_switch] = yamlConfig["SX126X_DIO2_AS_RF_SWITCH"].as<bool>(false);
settingsMap[sx126x_cs] = yamlConfig["SX126X_CS"].as<int>(RADIOLIB_NC);
settingsMap[sx126x_dio1] = yamlConfig["SX126X_DIO1"].as<int>(RADIOLIB_NC);
settingsMap[sx126x_busy] = yamlConfig["SX126X_BUSY"].as<int>(RADIOLIB_NC);
settingsMap[sx126x_reset] = yamlConfig["SX126X_RESET"].as<int>(RADIOLIB_NC);
settingsMap[use_rf95] = yamlConfig["USE_RF95"].as<bool>(false);
settingsMap[rf95_nss] = yamlConfig["RF95_NSS"].as<int>(RADIOLIB_NC);
settingsMap[rf95_irq] = yamlConfig["RF95_IRQ"].as<int>(RADIOLIB_NC);
settingsMap[rf95_reset] = yamlConfig["RF95_RESET"].as<int>(RADIOLIB_NC);
settingsMap[rf95_dio1] = yamlConfig["RF95_DIO1"].as<int>(RADIOLIB_NC);
} catch (YAML::Exception e) {
std::cout << "*** Exception " << e.what() << std::endl;
exit(EXIT_FAILURE);
}
if (access("/sys/kernel/debug/bluetooth/hci0/identity", R_OK) != 0) {
std::cout << "Cannot read Bluetooth MAC Address. Please run as root" << std::endl;
exit(EXIT_FAILURE);
}
return; return;
#endif #endif
@ -121,7 +170,7 @@ void portduinoSetup()
gpioBind(loraCs); gpioBind(loraCs);
} else } else
#endif #endif
#ifndef ARCH_RASPBERRY_PI
{ {
// Set the random seed equal to TCPPort to have a different seed per instance // Set the random seed equal to TCPPort to have a different seed per instance
randomSeed(TCPPort); randomSeed(TCPPort);
@ -140,4 +189,5 @@ void portduinoSetup()
} }
// gpioBind((new SimGPIOPin(LORA_RESET, "LORA_RESET"))); // gpioBind((new SimGPIOPin(LORA_RESET, "LORA_RESET")));
// gpioBind((new SimGPIOPin(RF95_NSS, "RF95_NSS"))->setSilent()); // gpioBind((new SimGPIOPin(RF95_NSS, "RF95_NSS"))->setSilent());
#endif
} }

View File

@ -0,0 +1,21 @@
#pragma once
#ifdef ARCH_RASPBERRY_PI
#include <map>
extern std::map<int, int> settingsMap;
enum {
use_sx1262,
sx126x_cs,
sx126x_dio1,
sx126x_busy,
sx126x_reset,
sx126x_dio2_as_rf_switch,
use_rf95,
rf95_nss,
rf95_irq,
rf95_reset,
rf95_dio1
};
#endif

View File

@ -16,7 +16,7 @@ build_src_filter = ${portduino_base.build_src_filter}
; The Raspberry Pi actually has accessible SPI and GPIO, so we can support real hardware there. ; The Raspberry Pi actually has accessible SPI and GPIO, so we can support real hardware there.
[env:raspbian] [env:raspbian]
extends = portduino_base extends = portduino_base
build_flags = ${portduino_base.build_flags} -O0 -lgpiod -I variants/portduino -DARCH_RASPBERRY_PI -DRADIOLIB_DEBUG -lpigpio build_flags = ${portduino_base.build_flags} -O0 -lgpiod -I variants/portduino -DARCH_RASPBERRY_PI -DRADIOLIB_DEBUG -lpigpio -lyaml-cpp
board = linux_arm board = linux_arm
lib_deps = ${portduino_base.lib_deps} lib_deps = ${portduino_base.lib_deps}
build_src_filter = ${portduino_base.build_src_filter} build_src_filter = ${portduino_base.build_src_filter}

View File

@ -1,34 +1,6 @@
#if defined(ARCH_RASPBERRY_PI) #if defined(ARCH_RASPBERRY_PI)
#define HAS_RADIO 1
#define GPIOD_CHIP_LABEL "pinctrl-bcm2711"
// define USE_RF95
#define USE_SX1262
#define SX126X_TXEN 6
#define SX126X_DIO2_AS_RF_SWITCH
#define NO_SCREEN #define NO_SCREEN
#define RF95_SCK 11
#define RF95_MISO 9
#define RF95_MOSI 10
#define RF95_NSS RADIOLIB_NC
// #define LORA_DIO0 4 // a No connect on the SX1262 module
// #define LORA_DIO0_LABEL "GPIO_GCLK"
#define LORA_RESET 18
#define LORA_RESET_LABEL "GPIO18"
#define LORA_DIO1 16 // SX1262 IRQ, called DIO0 on pinelora schematic, pin 7 on ch341f "ack" - FIXME, enable hwints in linux
// #define LORA_DIO2 20 // SX1262 BUSY, actually connected to "DIO5" on pinelora schematic, pin 8 on ch341f "slct"
// #define LORA_DIO3 6 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled
#ifdef USE_SX1262
#define SX126X_CS 21
#define SX126X_DIO1 16
#define SX126X_BUSY 20
#define SX126X_RESET LORA_RESET
// HOPE RFM90 does not have a TCXO therefore not SX126X_E22
#endif
#else // Pine64 mode. #else // Pine64 mode.
// Pine64 uses a common pinout for their SX1262 vs RF95 modules - both can be enabled and we will probe at runtime for RF95 and if // Pine64 uses a common pinout for their SX1262 vs RF95 modules - both can be enabled and we will probe at runtime for RF95 and if