Compare commits
6 commits
3ef1f57b5d
...
8cf97e0e5a
| Author | SHA1 | Date | |
|---|---|---|---|
| 8cf97e0e5a | |||
| 84d947a3f0 | |||
| 18e8d2c8ea | |||
| 4a9cc72b6a | |||
| 52fc683fa9 | |||
| 222934c7c1 |
23 changed files with 1025 additions and 0 deletions
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
/build/
|
||||||
|
/provisioning/
|
||||||
|
/*.log
|
||||||
|
.pio/
|
||||||
|
.pio
|
||||||
|
.vscode/
|
||||||
|
*.elf
|
||||||
|
*.bin
|
||||||
|
*.map
|
||||||
|
# Emacs backup files
|
||||||
|
*~
|
||||||
|
\#*\#
|
||||||
|
.\#*
|
||||||
|
|
||||||
|
# Emacs dir locals (optional)
|
||||||
|
.dir-locals.el
|
||||||
9
.gitmodules
vendored
9
.gitmodules
vendored
|
|
@ -1,3 +1,12 @@
|
||||||
[submodule "external/microReticulum"]
|
[submodule "external/microReticulum"]
|
||||||
path = external/microReticulum
|
path = external/microReticulum
|
||||||
url = https://github.com/attermann/microReticulum.git
|
url = https://github.com/attermann/microReticulum.git
|
||||||
|
[submodule "external/DebugLog"]
|
||||||
|
path = external/DebugLog
|
||||||
|
url = https://github.com/hideakitai/DebugLog.git
|
||||||
|
[submodule "external/ArxTypeTraits"]
|
||||||
|
path = external/ArxTypeTraits
|
||||||
|
url = https://github.com/hideakitai/ArxTypeTraits.git
|
||||||
|
[submodule "external/ArxContainer"]
|
||||||
|
path = external/ArxContainer
|
||||||
|
url = https://github.com/hideakitai/ArxContainer.git
|
||||||
|
|
|
||||||
68
CMakeLists.txt
Normal file
68
CMakeLists.txt
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
project(microReticulumTbeam LANGUAGES C CXX)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Portability shims
|
||||||
|
#
|
||||||
|
# microReticulum's CMake currently links against "msgpackc-cxx" and "MsgPack"
|
||||||
|
# as if they were system libraries, which breaks on machines that don't have
|
||||||
|
# those exact libs installed.
|
||||||
|
#
|
||||||
|
# Define shim targets so CMake treats them as targets (no "-l...").
|
||||||
|
# If/when you want a real msgpack-cxx dependency, replace the shim with
|
||||||
|
# FetchContent/find_package and link to that instead.
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
if(NOT TARGET msgpackc-cxx)
|
||||||
|
add_library(msgpackc-cxx INTERFACE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT TARGET MsgPack)
|
||||||
|
add_library(MsgPack INTERFACE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
# Pull in the microReticulum submodule build
|
||||||
|
add_subdirectory(external/microReticulum)
|
||||||
|
|
||||||
|
# Provide DebugLog.h for microReticulum's MsgPack dependency
|
||||||
|
#set(DEBUGLOG_DIR ${CMAKE_SOURCE_DIR}/external/DebugLog)
|
||||||
|
|
||||||
|
#if(TARGET ReticulumShared)
|
||||||
|
# target_include_directories(ReticulumShared PUBLIC ${DEBUGLOG_DIR})
|
||||||
|
#endif()
|
||||||
|
|
||||||
|
#if(TARGET ReticulumStatic)
|
||||||
|
# target_include_directories(ReticulumStatic PUBLIC ${DEBUGLOG_DIR})
|
||||||
|
#endif()
|
||||||
|
|
||||||
|
set(DEBUGLOG_DIR ${CMAKE_SOURCE_DIR}/external/DebugLog)
|
||||||
|
set(ARX_TYPETRAITS_DIR ${CMAKE_SOURCE_DIR}/external/ArxTypeTraits)
|
||||||
|
set(ARX_CONTAINER_DIR ${CMAKE_SOURCE_DIR}/external/ArxContainer)
|
||||||
|
|
||||||
|
if(TARGET ReticulumShared)
|
||||||
|
target_include_directories(ReticulumShared PUBLIC
|
||||||
|
${DEBUGLOG_DIR}
|
||||||
|
${ARX_TYPETRAITS_DIR}
|
||||||
|
${ARX_CONTAINER_DIR}
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(TARGET ReticulumStatic)
|
||||||
|
target_include_directories(ReticulumStatic PUBLIC
|
||||||
|
${DEBUGLOG_DIR}
|
||||||
|
${ARX_TYPETRAITS_DIR}
|
||||||
|
${ARX_CONTAINER_DIR}
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# We only need the static library for host-side tooling.
|
||||||
|
# The shared lib target requires system msgpack libs on some systems.
|
||||||
|
if(TARGET ReticulumShared)
|
||||||
|
set_target_properties(ReticulumShared PROPERTIES EXCLUDE_FROM_ALL YES)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Build our host-side tools
|
||||||
|
add_subdirectory(tools)
|
||||||
0
exercises/00_usb_radio_check/README.md
Normal file
0
exercises/00_usb_radio_check/README.md
Normal file
43
exercises/00_usb_radio_check/platformio.ini
Normal file
43
exercises/00_usb_radio_check/platformio.ini
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
; 20260212 ChatGPT
|
||||||
|
; $Id$
|
||||||
|
; $HeadURL$
|
||||||
|
|
||||||
|
[platformio]
|
||||||
|
default_envs = node_a
|
||||||
|
|
||||||
|
[env]
|
||||||
|
platform = espressif32
|
||||||
|
framework = arduino
|
||||||
|
board = esp32-s3-devkitc-1
|
||||||
|
monitor_speed = 115200
|
||||||
|
lib_deps =
|
||||||
|
jgromes/RadioLib@^6.6.0
|
||||||
|
|
||||||
|
; Common build flags (pins from Meshtastic tbeam-s3-core variant.h)
|
||||||
|
build_flags =
|
||||||
|
-D LORA_CS=10
|
||||||
|
-D LORA_MOSI=11
|
||||||
|
-D LORA_SCK=12
|
||||||
|
-D LORA_MISO=13
|
||||||
|
-D LORA_RESET=5
|
||||||
|
-D LORA_DIO1=1
|
||||||
|
-D LORA_BUSY=4
|
||||||
|
-D LORA_TCXO_VOLTAGE=1.8
|
||||||
|
-D ARDUINO_USB_MODE=1
|
||||||
|
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||||
|
|
||||||
|
; Radio params for println/printf + RadioLib init
|
||||||
|
-D LORA_FREQ=915.000
|
||||||
|
-D LORA_SF=7
|
||||||
|
-D LORA_BW=125
|
||||||
|
-D LORA_CR=5
|
||||||
|
|
||||||
|
[env:node_a]
|
||||||
|
build_flags =
|
||||||
|
${env.build_flags}
|
||||||
|
-D NODE_LABEL=\"A\"
|
||||||
|
|
||||||
|
[env:node_b]
|
||||||
|
build_flags =
|
||||||
|
${env.build_flags}
|
||||||
|
-D NODE_LABEL=\"B\"
|
||||||
60
exercises/00_usb_radio_check/src/main.cpp
Normal file
60
exercises/00_usb_radio_check/src/main.cpp
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <SPI.h>
|
||||||
|
#include <RadioLib.h>
|
||||||
|
|
||||||
|
#ifndef LORA_FREQ
|
||||||
|
#define LORA_FREQ 915.000
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef LORA_SF
|
||||||
|
#define LORA_SF 7
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef LORA_BW
|
||||||
|
#define LORA_BW 125
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef LORA_CR
|
||||||
|
#define LORA_CR 5
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// SX1262 on T-Beam Supreme (tbeam-s3-core pinout)
|
||||||
|
SX1262 radio = new Module(LORA_CS, LORA_DIO1, LORA_RESET, LORA_BUSY);
|
||||||
|
int state; // = radio.begin(915.0, 125.0, 7, 5, 0x12, 14);
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
delay(2000); // give USB time to enumerate
|
||||||
|
Serial.println("Booting LoRa test...");
|
||||||
|
Serial.println();
|
||||||
|
|
||||||
|
Serial.println("Initializing radio...");
|
||||||
|
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
|
||||||
|
Serial.printf("Radio chip: SX1262\r\n");
|
||||||
|
Serial.printf("Frequency: %.3f MHz\r\n", (double)LORA_FREQ);
|
||||||
|
Serial.printf("SF: %d BW: %d CR: %d\r\n", LORA_SF, LORA_BW, LORA_CR);
|
||||||
|
|
||||||
|
int state = radio.begin(915.0, 125.0, 7, 5, 0x12, 14);
|
||||||
|
|
||||||
|
Serial.printf("radio.begin returned: %d\r\n", state);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
static uint32_t counter = 0;
|
||||||
|
Serial.printf("alive %lu\n", counter++);
|
||||||
|
|
||||||
|
Serial.println("Sending test frame...");
|
||||||
|
int tx = radio.transmit("USB RADIO CHECK");
|
||||||
|
Serial.printf("TX state: %d\r\n", tx);
|
||||||
|
|
||||||
|
// we're not expecting to receive anything, just testing that we
|
||||||
|
// can call Receive()
|
||||||
|
Serial.println("Starting receive...");
|
||||||
|
state = radio.startReceive();
|
||||||
|
Serial.printf("startReceive returned: %d\r\n", state);
|
||||||
|
delay(1000);
|
||||||
|
}
|
||||||
37
exercises/01_lora_ascii_pingpong/platformio.ini
Normal file
37
exercises/01_lora_ascii_pingpong/platformio.ini
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
; 20260212 ChatGPT
|
||||||
|
; $Id$
|
||||||
|
; $HeadURL$
|
||||||
|
|
||||||
|
[platformio]
|
||||||
|
default_envs = node_a
|
||||||
|
|
||||||
|
[env]
|
||||||
|
platform = espressif32
|
||||||
|
framework = arduino
|
||||||
|
board = esp32-s3-devkitc-1
|
||||||
|
monitor_speed = 115200
|
||||||
|
lib_deps =
|
||||||
|
jgromes/RadioLib@^6.6.0
|
||||||
|
|
||||||
|
; Common build flags (pins from Meshtastic tbeam-s3-core variant.h)
|
||||||
|
build_flags =
|
||||||
|
-D LORA_CS=10
|
||||||
|
-D LORA_MOSI=11
|
||||||
|
-D LORA_SCK=12
|
||||||
|
-D LORA_MISO=13
|
||||||
|
-D LORA_RESET=5
|
||||||
|
-D LORA_DIO1=1
|
||||||
|
-D LORA_BUSY=4
|
||||||
|
-D LORA_TCXO_VOLTAGE=1.8
|
||||||
|
-D ARDUINO_USB_MODE=1
|
||||||
|
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||||
|
|
||||||
|
[env:node_a]
|
||||||
|
build_flags =
|
||||||
|
${env.build_flags}
|
||||||
|
-D NODE_LABEL=\"A\"
|
||||||
|
|
||||||
|
[env:node_b]
|
||||||
|
build_flags =
|
||||||
|
${env.build_flags}
|
||||||
|
-D NODE_LABEL=\"B\"
|
||||||
148
exercises/01_lora_ascii_pingpong/src/main.cpp
Normal file
148
exercises/01_lora_ascii_pingpong/src/main.cpp
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
// 20260212 ChatGPT
|
||||||
|
// $Id$
|
||||||
|
// $HeadURL$
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <SPI.h>
|
||||||
|
#include <RadioLib.h>
|
||||||
|
|
||||||
|
// --- Compile-time label ---
|
||||||
|
#ifndef NODE_LABEL
|
||||||
|
#define NODE_LABEL "?"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// --- Pins injected via platformio.ini build_flags ---
|
||||||
|
#ifndef LORA_CS
|
||||||
|
#error "LORA_CS not defined"
|
||||||
|
#endif
|
||||||
|
#ifndef LORA_DIO1
|
||||||
|
#error "LORA_DIO1 not defined"
|
||||||
|
#endif
|
||||||
|
#ifndef LORA_RESET
|
||||||
|
#error "LORA_RESET not defined"
|
||||||
|
#endif
|
||||||
|
#ifndef LORA_BUSY
|
||||||
|
#error "LORA_BUSY not defined"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// SX1262 on T-Beam Supreme (tbeam-s3-core pinout)
|
||||||
|
SX1262 radio = new Module(LORA_CS, LORA_DIO1, LORA_RESET, LORA_BUSY);
|
||||||
|
|
||||||
|
static volatile bool g_rx_flag = false;
|
||||||
|
|
||||||
|
|
||||||
|
static void onDio1Rise() {
|
||||||
|
g_rx_flag = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_config() {
|
||||||
|
Serial.printf("Node=%s\n", NODE_LABEL);
|
||||||
|
Serial.printf("Pins: CS=%d DIO1=%d RST=%d BUSY=%d SCK=%d MISO=%d MOSI=%d\r\n",
|
||||||
|
(int)LORA_CS, (int)LORA_DIO1, (int)LORA_RESET, (int)LORA_BUSY,
|
||||||
|
(int)LORA_SCK, (int)LORA_MISO, (int)LORA_MOSI);
|
||||||
|
Serial.printf("LoRa: freq=915.0 BW=125 SF=7 CR=5 txp=14\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
delay(250);
|
||||||
|
Serial.println();
|
||||||
|
Serial.println("Exercise 01: LoRa ASCII ping-pong (serial only)");
|
||||||
|
|
||||||
|
// Ensure SPI pins match the variant
|
||||||
|
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
|
||||||
|
|
||||||
|
int state = radio.begin(915.0 /* MHz */, 125.0 /* kHz */, 7 /* SF */, 5 /* CR */, 0x12 /* sync */, 14 /* dBm */);
|
||||||
|
if (state != RADIOLIB_ERR_NONE) {
|
||||||
|
Serial.printf("ERROR: radio.begin failed, code=%d\r\n", state);
|
||||||
|
while (true) delay(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match Meshtastic-like wiring assumptions for SX1262 modules:
|
||||||
|
// - DIO2 used as RF switch
|
||||||
|
// - TCXO at 1.8V
|
||||||
|
//OLD radio.setDio2AsRfSwitch(true);
|
||||||
|
//OLD radio.setTcxoVoltage((float)LORA_TCXO_VOLTAGE);
|
||||||
|
// below is replacement for above 2 lines:
|
||||||
|
|
||||||
|
//maybe_setDio2AsRfSwitch(radio, true);
|
||||||
|
//maybe_setTcxoVoltage(radio, (float)LORA_TCXO_VOLTAGE);
|
||||||
|
|
||||||
|
// end of replacement
|
||||||
|
|
||||||
|
// Set up RX interrupt
|
||||||
|
radio.setDio1Action(onDio1Rise);
|
||||||
|
|
||||||
|
// Start receiving
|
||||||
|
state = radio.startReceive();
|
||||||
|
if (state != RADIOLIB_ERR_NONE) {
|
||||||
|
Serial.printf("ERROR: startReceive failed, code=%d\r\n", state);
|
||||||
|
while (true) delay(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
print_config();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
// Periodic TX (with a label-based offset to reduce collisions)
|
||||||
|
static uint32_t next_tx_ms = 0;
|
||||||
|
static uint32_t iter = 0;
|
||||||
|
uint32_t now = millis();
|
||||||
|
|
||||||
|
if (next_tx_ms == 0) {
|
||||||
|
// Offset A and B so they don't always collide
|
||||||
|
uint32_t offset = (NODE_LABEL[0] == 'A') ? 500 : 1500;
|
||||||
|
next_tx_ms = now + offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((int32_t)(now - next_tx_ms) >= 0) {
|
||||||
|
next_tx_ms = now + 2000; // 2 seconds for this smoke test
|
||||||
|
|
||||||
|
// String msg = String("I am ") + NODE_LABEL + " iter=" + String(iter++);
|
||||||
|
String msg = String(" ") + NODE_LABEL + " sends greetings. iter=" + String(iter++);
|
||||||
|
Serial.printf("TX: %s\r\n", msg.c_str());
|
||||||
|
|
||||||
|
//int tx = radio.transmit(msg);
|
||||||
|
//if (tx != RADIOLIB_ERR_NONE) {
|
||||||
|
// Serial.printf("TX ERROR code=%d\r\n", tx);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// After transmit, resume RX
|
||||||
|
//radio.startReceive();
|
||||||
|
// DIO1 triggers on TX-done as well as RX-done.
|
||||||
|
// If left armed, TX completion looks like RX.
|
||||||
|
g_rx_flag = false;
|
||||||
|
radio.clearDio1Action();
|
||||||
|
|
||||||
|
int tx = radio.transmit(msg);
|
||||||
|
if (tx != RADIOLIB_ERR_NONE) {
|
||||||
|
Serial.printf("TX ERROR code=%d\r\n", tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-arm RX interrupt and resume RX
|
||||||
|
g_rx_flag = false;
|
||||||
|
radio.setDio1Action(onDio1Rise);
|
||||||
|
radio.startReceive();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// RX handling
|
||||||
|
if (g_rx_flag) {
|
||||||
|
g_rx_flag = false;
|
||||||
|
|
||||||
|
String rx;
|
||||||
|
int state = radio.readData(rx);
|
||||||
|
if (state == RADIOLIB_ERR_NONE) {
|
||||||
|
Serial.printf("RX: %s | RSSI=%.1f SNR=%.1f\r\n",
|
||||||
|
rx.c_str(), radio.getRSSI(), radio.getSNR());
|
||||||
|
} else {
|
||||||
|
Serial.printf("RX ERROR code=%d\r\n", state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep receiving
|
||||||
|
radio.startReceive();
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(5);
|
||||||
|
}
|
||||||
|
|
||||||
76
exercises/README.md
Normal file
76
exercises/README.md
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
## Buttons
|
||||||
|
There are three buttons
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
From Antenna to USB port:
|
||||||
|
1) RESET
|
||||||
|
2) POWER
|
||||||
|
3) BOOT
|
||||||
|

|
||||||
|
|
||||||
|
## RESET
|
||||||
|
The RESET button only needs to be depressed for less than 1 second. Doing so will cause the unit to reboot. If you have a "screen" monitor on the USB device, it will be disconnected. A good way to capture boot posting messages from within the unit is to have your command in a console ready to execute by depressing the RETURN key and you depress RETURN immediately after you depress the unit's RESET button, possibly allowing 1/2 second to allow kernel to recognize the new USB device, i.e. /dev/ttyACM0
|
||||||
|
## POWER
|
||||||
|
If your unit is powered OFF, simply depress the POWER button will start the unit. If the unit is powered ON, then depressing the POWER button and holding it down for 6 second will cause the unit to power down. The OLED display will go dark when the unit has successfully been powered down.
|
||||||
|
|
||||||
|
## BOOT
|
||||||
|
A) TODO: what happens when unit is powered ON?
|
||||||
|
B) TODO: what happens when unit is powered OFF?
|
||||||
|
C) To upload new binary image: depress the BOOT button and hold down, then while holding down the BOOT button disconnect the USB line and then reconnect the USB line and then lift up on the BOOT button. This will cause the unit to await an upload of an image to be stored in its FLASH memory. See further how to flash a new image in the section "Flashing Binary". Note: although the uploader may state it is trying to reset after successful installation of the new image, it seems that software attempt does not work and you have to manually depress the unit's RESET button to force a RESET. Remember, if you want to see the boot's posting, you should have your terminal ready to run "screen" so you capture the initial postings after you click RESET.
|
||||||
|
# Exercises
|
||||||
|
These are progressve tests you can run to confirm how to access the unit's functionality and validate your workbench.
|
||||||
|
|
||||||
|
Exercise 00: 00_usb_radio_check
|
||||||
|
|
||||||
|
Exercise 01: ASCII ping-pong over LoRa (serial only)
|
||||||
|
|
||||||
|
Exercise 02: Add OLED display
|
||||||
|
|
||||||
|
Exercise 03: Add SD logging
|
||||||
|
|
||||||
|
Exercise 04: Replace ASCII payload with microR packets
|
||||||
|
|
||||||
|
Exercise 05: SD provisioning with identity.bin, peer list, beacon
|
||||||
|
|
||||||
|
Each exercise is self-contained:
|
||||||
|
|
||||||
|
its own platformio.ini
|
||||||
|
its own src/main.cpp
|
||||||
|
its own README.md with exact commands
|
||||||
|
|
||||||
|
To clean:
|
||||||
|
|
||||||
|
pio run -t clean
|
||||||
|
|
||||||
|
Nuclear option:
|
||||||
|
|
||||||
|
rm -rf .pio
|
||||||
|
|
||||||
|
Rebuild:
|
||||||
|
|
||||||
|
pio run -e node_a
|
||||||
|
|
||||||
|
pio run -e node_b
|
||||||
|
|
||||||
|
## Upload existing firmware (will not recompile if unchanged)
|
||||||
|
### Upload node A (set your port)
|
||||||
|
pio run -e node_a -t upload --upload-port /dev/ttyACM0
|
||||||
|
|
||||||
|
### Upload node B
|
||||||
|
pio run -e node_b -t upload --upload-port /dev/ttyACM1
|
||||||
|
|
||||||
|
To monitor both:
|
||||||
|
|
||||||
|
screen /dev/ttyACM0 115200
|
||||||
|
screen /dev/ttyACM1 115200
|
||||||
|
|
||||||
|
Attach antennas (transmitting without antenna will cause power feedback and likely destroy your circuits)
|
||||||
|
Keep boards at least ~30 cm apart so you don’t desense the receivers.
|
||||||
|
|
||||||
|
shared board pin headers in one place:
|
||||||
|
shared/
|
||||||
|
boards/
|
||||||
|
tbeam-s3-core_pins.h
|
||||||
|
|
||||||
1
external/ArxContainer
vendored
Submodule
1
external/ArxContainer
vendored
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit d6affcd0bc83219b863c20abf7c269214db8db2a
|
||||||
1
external/ArxTypeTraits
vendored
Submodule
1
external/ArxTypeTraits
vendored
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 702de9cc59c7e047cdc169ae3547718b289d2c02
|
||||||
1
external/DebugLog
vendored
Submodule
1
external/DebugLog
vendored
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit b581f7dde6c276c5df684e2328f406d9754d2f46
|
||||||
BIN
img/20260212_182225_Thu.png
Normal file
BIN
img/20260212_182225_Thu.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
BIN
img/20260212_182300_Thu.png
Normal file
BIN
img/20260212_182300_Thu.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 448 KiB |
BIN
img/20260212_184521_Thu.png
Normal file
BIN
img/20260212_184521_Thu.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
42
shared/boards/tbeam-s3-core/pins_arduino.h
Normal file
42
shared/boards/tbeam-s3-core/pins_arduino.h
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
#ifndef Pins_Arduino_h
|
||||||
|
#define Pins_Arduino_h
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define USB_VID 0x303a
|
||||||
|
#define USB_PID 0x1001
|
||||||
|
|
||||||
|
// Now declared in .platformio/packages/framework-arduinoespressif32/cores/esp32/Arduino.h
|
||||||
|
// #define NUM_ANALOG_INPUTS 20
|
||||||
|
// #define EXTERNAL_NUM_INTERRUPTS 46
|
||||||
|
// #define NUM_DIGITAL_PINS 48
|
||||||
|
// #define analogInputToDigitalPin(p) (((p) < 20) ? (analogChannelToDigitalPin(p)) : -1)
|
||||||
|
// #define digitalPinToInterrupt(p) (((p) < 48) ? (p) : -1)
|
||||||
|
// #define digitalPinHasPWM(p) (p < 46)
|
||||||
|
|
||||||
|
static const uint8_t TX = 43;
|
||||||
|
static const uint8_t RX = 44;
|
||||||
|
|
||||||
|
// The default Wire will be mapped to PMU and RTC
|
||||||
|
static const uint8_t SDA = 42;
|
||||||
|
static const uint8_t SCL = 41;
|
||||||
|
|
||||||
|
// Default SPI will be mapped to Radio
|
||||||
|
static const uint8_t SS = 10;
|
||||||
|
static const uint8_t MOSI = 11;
|
||||||
|
static const uint8_t MISO = 13;
|
||||||
|
static const uint8_t SCK = 12;
|
||||||
|
|
||||||
|
// Another SPI bus shares SD card and QMI8653 inertial measurement sensor
|
||||||
|
#define SPI_MOSI (35)
|
||||||
|
#define SPI_SCK (36)
|
||||||
|
#define SPI_MISO (37)
|
||||||
|
#define SPI_CS (47)
|
||||||
|
#define IMU_CS (34)
|
||||||
|
|
||||||
|
#define SDCARD_CS SPI_CS
|
||||||
|
#define IMU_INT (33)
|
||||||
|
// #define PMU_IRQ (40)
|
||||||
|
#define RTC_INT (14)
|
||||||
|
|
||||||
|
#endif /* Pins_Arduino_h */
|
||||||
26
shared/boards/tbeam-s3-core/platformio.ini
Normal file
26
shared/boards/tbeam-s3-core/platformio.ini
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
; The 1.0 release of the LilyGo TBEAM-S3-Core board
|
||||||
|
[env:tbeam-s3-core]
|
||||||
|
custom_meshtastic_hw_model = 12
|
||||||
|
custom_meshtastic_hw_model_slug = LILYGO_TBEAM_S3_CORE
|
||||||
|
custom_meshtastic_architecture = esp32-s3
|
||||||
|
custom_meshtastic_actively_supported = true
|
||||||
|
custom_meshtastic_support_level = 1
|
||||||
|
custom_meshtastic_display_name = LILYGO T-Beam Supreme
|
||||||
|
custom_meshtastic_images = tbeam-s3-core.svg
|
||||||
|
custom_meshtastic_tags = LilyGo
|
||||||
|
custom_meshtastic_requires_dfu = true
|
||||||
|
custom_meshtastic_partition_scheme = 8MB
|
||||||
|
|
||||||
|
extends = esp32s3_base
|
||||||
|
board = tbeam-s3-core
|
||||||
|
board_build.partitions = default_8MB.csv
|
||||||
|
board_check = true
|
||||||
|
|
||||||
|
lib_deps =
|
||||||
|
${esp32s3_base.lib_deps}
|
||||||
|
# renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
|
||||||
|
lewisxhe/SensorLib@0.3.4
|
||||||
|
|
||||||
|
build_flags =
|
||||||
|
${esp32s3_base.build_flags}
|
||||||
|
-I variants/esp32s3/tbeam-s3-core
|
||||||
6
shared/boards/tbeam-s3-core/provenance.txt
Normal file
6
shared/boards/tbeam-s3-core/provenance.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
2/12/2026 copied from meshastic/firmware/variants/esp32s3/tbeam-s3-core
|
||||||
|
since that appeared in the build tree of a successfully operating binary:
|
||||||
|
jlpoole@jp /usr/local/src/meshtastic/firmware $ cd /usr/local/src/meshtastic/firmware
|
||||||
|
ls -1 .pio/build 2>/dev/null | egrep -i 'tbeam|t-beam|s3' | sort
|
||||||
|
tbeam-s3-core
|
||||||
|
jlpoole@jp /usr/local/src/meshtastic/firmware $
|
||||||
11
shared/boards/tbeam-s3-core/rfswitch.h
Normal file
11
shared/boards/tbeam-s3-core/rfswitch.h
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
#include "RadioLib.h"
|
||||||
|
|
||||||
|
static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
|
||||||
|
|
||||||
|
static const Module::RfSwitchMode_t rfswitch_table[] = {
|
||||||
|
// mode DIO5 DIO6
|
||||||
|
{LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW}},
|
||||||
|
{LR11x0::MODE_TX, {LOW, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH}},
|
||||||
|
{LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}},
|
||||||
|
{LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE,
|
||||||
|
};
|
||||||
79
shared/boards/tbeam-s3-core/variant.h
Normal file
79
shared/boards/tbeam-s3-core/variant.h
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
// #define BUTTON_NEED_PULLUP // if set we need to turn on the internal CPU pullup during sleep
|
||||||
|
|
||||||
|
#define I2C_SDA1 42 // Used for PMU management and PCF8563
|
||||||
|
#define I2C_SCL1 41 // Used for PMU management and PCF8563
|
||||||
|
|
||||||
|
#define I2C_SDA 17 // For QMC6310 sensors and screens
|
||||||
|
#define I2C_SCL 18 // For QMC6310 sensors and screens
|
||||||
|
|
||||||
|
#define BUTTON_PIN 0 // The middle button GPIO on the T-Beam S3
|
||||||
|
// #define EXT_NOTIFY_OUT 13 // Default pin to use for Ext Notify Module.
|
||||||
|
|
||||||
|
#define LED_STATE_ON 0 // State when LED is lit
|
||||||
|
|
||||||
|
// TTGO uses a common pinout for their SX1262 vs RF95 modules - both can be enabled and we will probe at runtime for RF95 and if
|
||||||
|
// not found then probe for SX1262
|
||||||
|
#define USE_SX1262
|
||||||
|
#define USE_SX1268
|
||||||
|
#define USE_LR1121
|
||||||
|
|
||||||
|
#define LORA_DIO0 -1 // a No connect on the SX1262 module
|
||||||
|
#define LORA_RESET 5
|
||||||
|
#define LORA_DIO1 1 // SX1262 IRQ
|
||||||
|
#define LORA_DIO2 4 // SX1262 BUSY
|
||||||
|
#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled
|
||||||
|
|
||||||
|
#ifdef USE_SX1262
|
||||||
|
#define SX126X_CS 10 // FIXME - we really should define LORA_CS instead
|
||||||
|
#define SX126X_DIO1 LORA_DIO1
|
||||||
|
#define SX126X_BUSY LORA_DIO2
|
||||||
|
#define SX126X_RESET LORA_RESET
|
||||||
|
// Not really an E22 but TTGO seems to be trying to clone that
|
||||||
|
#define SX126X_DIO2_AS_RF_SWITCH
|
||||||
|
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
|
||||||
|
// Internally the TTGO module hooks the SX1262-DIO2 in to control the TX/RX switch (which is the default for the sx1262interface
|
||||||
|
// code)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// LR1121
|
||||||
|
#ifdef USE_LR1121
|
||||||
|
#define LR1121_IRQ_PIN 1
|
||||||
|
#define LR1121_NRESET_PIN LORA_RESET
|
||||||
|
#define LR1121_BUSY_PIN 4
|
||||||
|
#define LR1121_SPI_NSS_PIN 10
|
||||||
|
#define LR1121_SPI_SCK_PIN 12
|
||||||
|
#define LR1121_SPI_MOSI_PIN 11
|
||||||
|
#define LR1121_SPI_MISO_PIN 13
|
||||||
|
#define LR11X0_DIO3_TCXO_VOLTAGE 3.0
|
||||||
|
#define LR11X0_DIO_AS_RF_SWITCH
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Leave undefined to disable our PMU IRQ handler. DO NOT ENABLE THIS because the pmuirq can cause sperious interrupts
|
||||||
|
// and waking from light sleep
|
||||||
|
// #define PMU_IRQ 40
|
||||||
|
#define HAS_AXP2101
|
||||||
|
|
||||||
|
// PCF8563 RTC Module
|
||||||
|
#define PCF8563_RTC 0x51
|
||||||
|
|
||||||
|
// Specify the PMU as Wire1. In the t-beam-s3 core, PCF8563 and PMU share the bus
|
||||||
|
#define PMU_USE_WIRE1
|
||||||
|
#define RTC_USE_WIRE1
|
||||||
|
|
||||||
|
#define LORA_SCK 12
|
||||||
|
#define LORA_MISO 13
|
||||||
|
#define LORA_MOSI 11
|
||||||
|
#define LORA_CS 10
|
||||||
|
|
||||||
|
#define GPS_RX_PIN 9
|
||||||
|
#define GPS_TX_PIN 8
|
||||||
|
#define GPS_WAKEUP_PIN 7
|
||||||
|
#define GPS_1PPS_PIN 6
|
||||||
|
|
||||||
|
#define HAS_SDCARD // Have SPI interface SD card slot
|
||||||
|
#define SDCARD_USE_SPI1
|
||||||
|
|
||||||
|
// has 32768 Hz crystal
|
||||||
|
#define HAS_32768HZ 1
|
||||||
|
|
||||||
|
#define USE_SH1106
|
||||||
1
tools/CMakeLists.txt
Normal file
1
tools/CMakeLists.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
add_subdirectory(keygen)
|
||||||
24
tools/keygen/CMakeLists.txt
Normal file
24
tools/keygen/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
add_executable(rns-provision main.cpp)
|
||||||
|
|
||||||
|
# If microReticulum exports an include directory / target, this may be unnecessary.
|
||||||
|
# Keep this here as a pragmatic fallback:
|
||||||
|
target_include_directories(rns-provision PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/external/microReticulum/src
|
||||||
|
)
|
||||||
|
|
||||||
|
# ArduinoJson is pulled by microReticulum headers; add it to this tool's include path.
|
||||||
|
target_include_directories(rns-provision PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/external/microReticulum/src
|
||||||
|
|
||||||
|
# ArduinoJson headers:
|
||||||
|
# - ArduinoJson.h lives at the repo root
|
||||||
|
# - ArduinoJson/... lives under src/
|
||||||
|
${CMAKE_BINARY_DIR}/_deps/arduinojson-src
|
||||||
|
${CMAKE_BINARY_DIR}/_deps/arduinojson-src/src
|
||||||
|
)
|
||||||
|
|
||||||
|
# Link against the microReticulum library target.
|
||||||
|
# If this target name is wrong in your submodule, change it here.
|
||||||
|
target_link_libraries(rns-provision PRIVATE ReticulumStatic)
|
||||||
|
|
||||||
|
install(TARGETS rns-provision RUNTIME DESTINATION bin)
|
||||||
376
tools/keygen/main.cpp
Normal file
376
tools/keygen/main.cpp
Normal file
|
|
@ -0,0 +1,376 @@
|
||||||
|
// rns-provision - generate microReticulum identity keypairs + provisioning bundles
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
// ./rns-provision --quantity 6 --format tsv
|
||||||
|
// ./rns-provision -q 6 -f json --public
|
||||||
|
// ./rns-provision -q 6 --outdir provisioning/20260212_1030 --bundle both --public
|
||||||
|
//
|
||||||
|
// Notes:
|
||||||
|
// - Writes sensitive material (private keys). Protect your output directories.
|
||||||
|
// - identity.bin is raw private key bytes as returned by microReticulum Identity.
|
||||||
|
//
|
||||||
|
// $Header$
|
||||||
|
// $Id$
|
||||||
|
|
||||||
|
#include <Identity.h>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <exception>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#if __has_include(<filesystem>)
|
||||||
|
#include <filesystem>
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
#else
|
||||||
|
#error "This tool requires <filesystem> (C++17)."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static constexpr const char* TOOL_NAME = "rns-provision";
|
||||||
|
static constexpr const char* TOOL_VERSION = "0.1.0";
|
||||||
|
|
||||||
|
static void usage(const char* argv0, bool full = true) {
|
||||||
|
std::cerr << TOOL_NAME << " " << TOOL_VERSION << "\n\n";
|
||||||
|
|
||||||
|
std::cerr
|
||||||
|
<< "Usage:\n"
|
||||||
|
<< " " << argv0 << " --quantity N [options]\n\n";
|
||||||
|
|
||||||
|
if (!full) return;
|
||||||
|
|
||||||
|
std::cerr
|
||||||
|
<< "Options:\n"
|
||||||
|
<< " -q, --quantity N Number of identities to generate (required)\n"
|
||||||
|
<< " -f, --format FMT Stdout format: tsv (default) or json\n"
|
||||||
|
<< " --public Include public_key in stdout and identity.json\n"
|
||||||
|
<< " --outdir DIR Write provisioning bundle to DIR\n"
|
||||||
|
<< " --bundle MODE none|json|bin|both (default: both if --outdir)\n"
|
||||||
|
<< " --prefix NAME Device directory prefix (default: device)\n"
|
||||||
|
<< " -h, --help Show this help message and exit\n"
|
||||||
|
<< "\n"
|
||||||
|
<< "Examples:\n"
|
||||||
|
<< " " << argv0 << " -q 6\n"
|
||||||
|
<< " " << argv0 << " -q 6 --outdir provisioning/20260212_1030 --bundle both\n"
|
||||||
|
<< "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_flag(const std::string& a, const char* s) { return a == s; }
|
||||||
|
|
||||||
|
static std::string utc_now_iso8601() {
|
||||||
|
using namespace std::chrono;
|
||||||
|
auto now = system_clock::now();
|
||||||
|
std::time_t t = system_clock::to_time_t(now);
|
||||||
|
std::tm tm{};
|
||||||
|
#ifdef _WIN32
|
||||||
|
gmtime_s(&tm, &t);
|
||||||
|
#else
|
||||||
|
gmtime_r(&t, &tm);
|
||||||
|
#endif
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << std::put_time(&tm, "%Y-%m-%dT%H:%M:%SZ");
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string json_escape(const std::string& s) {
|
||||||
|
std::ostringstream o;
|
||||||
|
for (unsigned char c : s) {
|
||||||
|
switch (c) {
|
||||||
|
case '\"': o << "\\\""; break;
|
||||||
|
case '\\': o << "\\\\"; break;
|
||||||
|
case '\b': o << "\\b"; break;
|
||||||
|
case '\f': o << "\\f"; break;
|
||||||
|
case '\n': o << "\\n"; break;
|
||||||
|
case '\r': o << "\\r"; break;
|
||||||
|
case '\t': o << "\\t"; break;
|
||||||
|
default:
|
||||||
|
if (c < 0x20) {
|
||||||
|
o << "\\u" << std::hex << std::setw(4) << std::setfill('0') << int(c);
|
||||||
|
} else {
|
||||||
|
o << c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return o.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string zpad_int(int value, int width) {
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << std::setw(width) << std::setfill('0') << value;
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ensure_dir(const fs::path& p) {
|
||||||
|
std::error_code ec;
|
||||||
|
if (fs::exists(p, ec)) {
|
||||||
|
if (!fs::is_directory(p, ec)) {
|
||||||
|
throw std::runtime_error("Path exists but is not a directory: " + p.string());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!fs::create_directories(p, ec)) {
|
||||||
|
throw std::runtime_error("Failed to create directory: " + p.string() + " (" + ec.message() + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void write_text_file(const fs::path& p, const std::string& data) {
|
||||||
|
std::ofstream out(p, std::ios::out | std::ios::trunc);
|
||||||
|
if (!out) throw std::runtime_error("Failed to open for write: " + p.string());
|
||||||
|
out << data;
|
||||||
|
out.close();
|
||||||
|
if (!out) throw std::runtime_error("Failed writing file: " + p.string());
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
// Best-effort: 0600 for sensitive text files (private keys)
|
||||||
|
::chmod(p.c_str(), 0600);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void write_binary_file(const fs::path& p, const std::vector<uint8_t>& bytes) {
|
||||||
|
std::ofstream out(p, std::ios::out | std::ios::binary | std::ios::trunc);
|
||||||
|
if (!out) throw std::runtime_error("Failed to open for write: " + p.string());
|
||||||
|
out.write(reinterpret_cast<const char*>(bytes.data()), static_cast<std::streamsize>(bytes.size()));
|
||||||
|
out.close();
|
||||||
|
if (!out) throw std::runtime_error("Failed writing file: " + p.string());
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
::chmod(p.c_str(), 0600);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class BundleMode { NONE, JSON, BIN, BOTH };
|
||||||
|
|
||||||
|
static BundleMode parse_bundle_mode(const std::string& s) {
|
||||||
|
if (s == "none") return BundleMode::NONE;
|
||||||
|
if (s == "json") return BundleMode::JSON;
|
||||||
|
if (s == "bin") return BundleMode::BIN;
|
||||||
|
if (s == "both") return BundleMode::BOTH;
|
||||||
|
throw std::runtime_error("Invalid --bundle value (must be none|json|bin|both)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert microReticulum Bytes-ish type to std::vector<uint8_t>.
|
||||||
|
// This assumes the returned object supports .size() and operator[].
|
||||||
|
// If your microReticulum Bytes type differs, we’ll adjust here.
|
||||||
|
template <typename BytesT>
|
||||||
|
static std::vector<uint8_t> to_u8vec(const BytesT& b) {
|
||||||
|
std::vector<uint8_t> v;
|
||||||
|
v.reserve(static_cast<size_t>(b.size()));
|
||||||
|
for (size_t i = 0; i < static_cast<size_t>(b.size()); i++) {
|
||||||
|
v.push_back(static_cast<uint8_t>(b[i]));
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
try {
|
||||||
|
int quantity = -1;
|
||||||
|
std::string format = "tsv";
|
||||||
|
bool include_public = false;
|
||||||
|
|
||||||
|
bool do_outdir = false;
|
||||||
|
fs::path outdir;
|
||||||
|
std::string prefix = "device";
|
||||||
|
BundleMode bundle_mode = BundleMode::NONE;
|
||||||
|
bool bundle_mode_explicit = false;
|
||||||
|
|
||||||
|
for (int i = 1; i < argc; i++) {
|
||||||
|
std::string a(argv[i]);
|
||||||
|
|
||||||
|
if (is_flag(a, "-h") || is_flag(a, "--help")) {
|
||||||
|
usage(argv[0], true);
|
||||||
|
return 0;
|
||||||
|
} else if (is_flag(a, "-q") || is_flag(a, "--quantity")) {
|
||||||
|
if (i + 1 >= argc) throw std::runtime_error("Missing value for --quantity");
|
||||||
|
quantity = std::stoi(argv[++i]);
|
||||||
|
} else if (is_flag(a, "-f") || is_flag(a, "--format")) {
|
||||||
|
if (i + 1 >= argc) throw std::runtime_error("Missing value for --format");
|
||||||
|
format = argv[++i];
|
||||||
|
} else if (is_flag(a, "--public")) {
|
||||||
|
include_public = true;
|
||||||
|
} else if (is_flag(a, "--outdir")) {
|
||||||
|
if (i + 1 >= argc) throw std::runtime_error("Missing value for --outdir");
|
||||||
|
outdir = fs::path(argv[++i]);
|
||||||
|
do_outdir = true;
|
||||||
|
} else if (is_flag(a, "--bundle")) {
|
||||||
|
if (i + 1 >= argc) throw std::runtime_error("Missing value for --bundle");
|
||||||
|
bundle_mode = parse_bundle_mode(argv[++i]);
|
||||||
|
bundle_mode_explicit = true;
|
||||||
|
} else if (is_flag(a, "--prefix")) {
|
||||||
|
if (i + 1 >= argc) throw std::runtime_error("Missing value for --prefix");
|
||||||
|
prefix = argv[++i];
|
||||||
|
} else if (is_flag(a, "--version")) {
|
||||||
|
std::cout << TOOL_NAME << " " << TOOL_VERSION << "\n";
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw std::runtime_error("Unknown argument: " + a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quantity <= 0) {
|
||||||
|
usage(argv[0], true);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
if (!(format == "tsv" || format == "json")) {
|
||||||
|
throw std::runtime_error("Invalid --format (must be tsv or json)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default bundle behavior: if user set --outdir but not --bundle, write both.
|
||||||
|
if (do_outdir && !bundle_mode_explicit) {
|
||||||
|
bundle_mode = BundleMode::BOTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (do_outdir) {
|
||||||
|
ensure_dir(outdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DeviceRec {
|
||||||
|
int n;
|
||||||
|
std::string device_name;
|
||||||
|
std::string id_hex;
|
||||||
|
std::string priv_hex;
|
||||||
|
std::string pub_hex;
|
||||||
|
std::vector<uint8_t> priv_bin;
|
||||||
|
fs::path device_dir;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<DeviceRec> devices;
|
||||||
|
devices.reserve(static_cast<size_t>(quantity));
|
||||||
|
|
||||||
|
for (int n = 1; n <= quantity; n++) {
|
||||||
|
RNS::Identity ident(true);
|
||||||
|
|
||||||
|
DeviceRec rec;
|
||||||
|
rec.n = n;
|
||||||
|
rec.device_name = prefix + "_" + zpad_int(n, 3);
|
||||||
|
|
||||||
|
// These methods matched your earlier build. If microReticulum changes, we adjust here.
|
||||||
|
rec.id_hex = ident.hash().toHex();
|
||||||
|
rec.priv_hex = ident.get_private_key().toHex();
|
||||||
|
if (include_public) rec.pub_hex = ident.get_public_key().toHex();
|
||||||
|
|
||||||
|
// Write binary blob for the private key
|
||||||
|
rec.priv_bin = to_u8vec(ident.get_private_key());
|
||||||
|
|
||||||
|
if (do_outdir) {
|
||||||
|
rec.device_dir = outdir / rec.device_name;
|
||||||
|
ensure_dir(rec.device_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
devices.push_back(std::move(rec));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- stdout ----
|
||||||
|
if (format == "tsv") {
|
||||||
|
std::cout << "n\tdevice\tid_hex\tprivate_key_hex";
|
||||||
|
if (include_public) std::cout << "\tpublic_key_hex";
|
||||||
|
std::cout << "\n";
|
||||||
|
for (const auto& d : devices) {
|
||||||
|
std::cout << d.n << "\t" << d.device_name << "\t" << d.id_hex << "\t" << d.priv_hex;
|
||||||
|
if (include_public) std::cout << "\t" << d.pub_hex;
|
||||||
|
std::cout << "\n";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::cout << "[\n";
|
||||||
|
for (size_t i = 0; i < devices.size(); i++) {
|
||||||
|
const auto& d = devices[i];
|
||||||
|
std::cout << " {\n";
|
||||||
|
std::cout << " \"n\": " << d.n << ",\n";
|
||||||
|
std::cout << " \"device\": \"" << json_escape(d.device_name) << "\",\n";
|
||||||
|
std::cout << " \"id\": \"" << d.id_hex << "\",\n";
|
||||||
|
std::cout << " \"private_key\": \"" << d.priv_hex << "\"";
|
||||||
|
if (include_public) {
|
||||||
|
std::cout << ",\n \"public_key\": \"" << d.pub_hex << "\"\n";
|
||||||
|
} else {
|
||||||
|
std::cout << "\n";
|
||||||
|
}
|
||||||
|
std::cout << " }" << (i + 1 == devices.size() ? "\n" : ",\n");
|
||||||
|
}
|
||||||
|
std::cout << "]\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- bundle output ----
|
||||||
|
if (do_outdir && bundle_mode != BundleMode::NONE) {
|
||||||
|
const std::string created_utc = utc_now_iso8601();
|
||||||
|
|
||||||
|
// manifest.json
|
||||||
|
{
|
||||||
|
std::ostringstream m;
|
||||||
|
m << "{\n";
|
||||||
|
m << " \"schema_version\": 1,\n";
|
||||||
|
m << " \"created_utc\": \"" << created_utc << "\",\n";
|
||||||
|
m << " \"quantity\": " << quantity << ",\n";
|
||||||
|
m << " \"prefix\": \"" << json_escape(prefix) << "\",\n";
|
||||||
|
m << " \"devices\": [\n";
|
||||||
|
for (size_t i = 0; i < devices.size(); i++) {
|
||||||
|
const auto& d = devices[i];
|
||||||
|
m << " {\n";
|
||||||
|
m << " \"n\": " << d.n << ",\n";
|
||||||
|
m << " \"device\": \"" << json_escape(d.device_name) << "\",\n";
|
||||||
|
m << " \"id\": \"" << d.id_hex << "\"";
|
||||||
|
if (bundle_mode == BundleMode::JSON || bundle_mode == BundleMode::BOTH) {
|
||||||
|
m << ",\n \"identity_json\": \"" << json_escape((d.device_name + "/identity.json")) << "\"";
|
||||||
|
}
|
||||||
|
if (bundle_mode == BundleMode::BIN || bundle_mode == BundleMode::BOTH) {
|
||||||
|
m << ",\n \"identity_bin\": \"" << json_escape((d.device_name + "/identity.bin")) << "\"";
|
||||||
|
}
|
||||||
|
m << "\n }" << (i + 1 == devices.size() ? "\n" : ",\n");
|
||||||
|
}
|
||||||
|
m << " ]\n";
|
||||||
|
m << "}\n";
|
||||||
|
write_text_file(outdir / "manifest.json", m.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& d : devices) {
|
||||||
|
// label.txt
|
||||||
|
{
|
||||||
|
std::ostringstream l;
|
||||||
|
l << d.device_name << "\n";
|
||||||
|
l << "id: " << d.id_hex << "\n";
|
||||||
|
write_text_file(d.device_dir / "label.txt", l.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// identity.json
|
||||||
|
if (bundle_mode == BundleMode::JSON || bundle_mode == BundleMode::BOTH) {
|
||||||
|
std::ostringstream j;
|
||||||
|
j << "{\n";
|
||||||
|
j << " \"schema_version\": 1,\n";
|
||||||
|
j << " \"created_utc\": \"" << created_utc << "\",\n";
|
||||||
|
j << " \"device\": \"" << json_escape(d.device_name) << "\",\n";
|
||||||
|
j << " \"id\": \"" << d.id_hex << "\",\n";
|
||||||
|
j << " \"private_key\": \"" << d.priv_hex << "\"";
|
||||||
|
if (include_public) {
|
||||||
|
j << ",\n \"public_key\": \"" << d.pub_hex << "\"\n";
|
||||||
|
} else {
|
||||||
|
j << "\n";
|
||||||
|
}
|
||||||
|
j << "}\n";
|
||||||
|
write_text_file(d.device_dir / "identity.json", j.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// identity.bin
|
||||||
|
if (bundle_mode == BundleMode::BIN || bundle_mode == BundleMode::BOTH) {
|
||||||
|
write_binary_file(d.device_dir / "identity.bin", d.priv_bin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cerr << "Wrote provisioning bundle to: " << outdir.string() << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Error: " << e.what() << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue