Skip to content
Draft
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ compile_commands.json
.venv/
venv/
platformio.local.ini
__pycache__/
*.pyc
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,52 @@ The repeater and room server firmwares can be setup via USB in the web config to

They can also be managed via LoRa in the mobile app by using the Remote Management feature.

### Local USB Configurator

This repo also includes a local serial configurator for MeshCore repeater and room-server firmware:

```bash
python3 tools/meshcore_configurator.py
```

It can configure radio settings, TX power, node name, passwords, GPS options, raw CLI commands, and ESP32 firmware updates without using Web Serial in a browser.

Examples:

```bash
python3 tools/meshcore_configurator.py --list-ports
python3 tools/meshcore_configurator.py --port /dev/ttyUSB0 --set radio 910.525,62.5,7,5
python3 tools/meshcore_configurator.py --port /dev/ttyUSB0 --set tx 22
python3 tools/meshcore_configurator.py --port /dev/ttyUSB0 --command "gps on"
python3 tools/meshcore_configurator.py --port /dev/ttyUSB0 --flash .pio/build/hammer_sx1262_repeater/firmware-merged.bin
```

Dependencies:

```bash
python3 -m pip install pyserial esptool
```

On Linux, the user may need serial-port permissions:

```bash
sudo usermod -aG dialout $USER
```

Then log out and back in.

To build a portable Windows executable:

```bat
tools\build_windows_exe.bat
```

The resulting executable is created at:

```text
dist\meshcore-configurator.exe
```

## 🛠 Hardware Compatibility

MeshCore is designed for devices listed in the [MeshCore Flasher](https://meshcore.io/flasher)
Expand Down
6 changes: 6 additions & 0 deletions examples/companion_radio/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ static uint32_t _atoi(const char* sp) {
#elif defined(BLE_PIN_CODE)
#include <helpers/esp32/SerialBLEInterface.h>
SerialBLEInterface serial_interface;
#elif defined(HAS_ETHERNET)
#include <helpers/esp32/SerialEthernetInterface.h>
extern SerialEthernetInterface eth_interface;
SerialEthernetInterface& serial_interface = eth_interface;
#elif defined(SERIAL_RX)
#include <helpers/ArduinoSerialInterface.h>
ArduinoSerialInterface serial_interface;
Expand Down Expand Up @@ -199,6 +203,8 @@ void setup() {
serial_interface.begin(TCP_PORT);
#elif defined(BLE_PIN_CODE)
serial_interface.begin(BLE_NAME_PREFIX, the_mesh.getNodePrefs()->node_name, the_mesh.getBLEPin());
#elif defined(HAS_ETHERNET)
board.setInhibitSleep(true); // prevent sleep while Ethernet is active
#elif defined(SERIAL_RX)
companion_serial.setPins(SERIAL_RX, SERIAL_TX);
companion_serial.begin(115200);
Expand Down
184 changes: 184 additions & 0 deletions src/helpers/esp32/SerialEthernetInterface.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// Ethernet support for Broken Circuit Ranch POE Ethernet
// http://www.brokencircuitranch.com
// Kent Andersen

#ifdef HAS_ETHERNET

#include "SerialEthernetInterface.h"
#include <Ethernet3.h>
#include <SPI.h>

bool SerialEthernetInterface::begin(SPIClass& spi, int port, int cs_pin, int rst_pin, uint8_t mac[6]) {

// Set CS pin before anything else
Ethernet.setCsPin(cs_pin);

// Reset W5500 if reset pin provided
if (rst_pin >= 0) {
Ethernet.setRstPin(rst_pin);
Ethernet.hardreset();
delay(200);
}

// Try DHCP
ETH_DEBUG_PRINTLN("Starting DHCP...");
int result = Ethernet.begin(mac);
if (result == 0) {
ETH_DEBUG_PRINTLN("DHCP failed, using static IP 192.168.1.200");
IPAddress staticIP(192, 168, 1, 200);
IPAddress subnet(255, 255, 255, 0);
IPAddress gateway(192, 168, 1, 1);
IPAddress dns(8, 8, 8, 8);
Ethernet.begin(mac, staticIP, subnet, gateway, dns);
}

ETH_DEBUG_PRINTLN("Ethernet IP: %s", Ethernet.localIP().toString().c_str());

// Start TCP server
server = new ConcreteEthernetServer(port);
server->begin(port);
_ethInitialized = true;

ETH_DEBUG_PRINTLN("TCP server started on port %d", port);
return true;
}

void SerialEthernetInterface::enable() {
if (_isEnabled) return;
_isEnabled = true;
clearBuffers();
}

void SerialEthernetInterface::disable() {
_isEnabled = false;
}

size_t SerialEthernetInterface::writeFrame(const uint8_t src[], size_t len) {
if (len > MAX_FRAME_SIZE) {
ETH_DEBUG_PRINTLN("writeFrame(), frame too big, len=%d", len);
return 0;
}

if (deviceConnected && len > 0) {
if (send_queue_len >= ETH_FRAME_QUEUE_SIZE) {
ETH_DEBUG_PRINTLN("writeFrame(), send_queue is full!");
return 0;
}
send_queue[send_queue_len].len = len;
memcpy(send_queue[send_queue_len].buf, src, len);
send_queue_len++;
return len;
}
return 0;
}

bool SerialEthernetInterface::isWriteBusy() const {
return false;
}

bool SerialEthernetInterface::hasReceivedFrameHeader() {
return received_frame_header.type != 0 && received_frame_header.length != 0;
}

void SerialEthernetInterface::resetReceivedFrameHeader() {
received_frame_header.type = 0;
received_frame_header.length = 0;
}

size_t SerialEthernetInterface::checkRecvFrame(uint8_t dest[]) {

if (!_ethInitialized || !server) return 0;

// Maintain DHCP lease
Ethernet.maintain();

// Check for new client
EthernetClient newClient = server->available();
if (newClient) {
deviceConnected = false;
client.stop();
client = newClient;
resetReceivedFrameHeader();
}

if (client.connected()) {
if (!deviceConnected) {
ETH_DEBUG_PRINTLN("Client connected");
deviceConnected = true;
}
} else {
if (deviceConnected) {
deviceConnected = false;
ETH_DEBUG_PRINTLN("Client disconnected");
}
}

if (deviceConnected) {
if (send_queue_len > 0) {
_last_write = millis();
int len = send_queue[0].len;

uint8_t pkt[3 + len];
pkt[0] = '>';
pkt[1] = (len & 0xFF);
pkt[2] = (len >> 8);
memcpy(&pkt[3], send_queue[0].buf, len);
client.write(pkt, 3 + len);

send_queue_len--;
for (int i = 0; i < send_queue_len; i++) {
send_queue[i] = send_queue[i + 1];
}
} else {
if (!hasReceivedFrameHeader()) {
if (client.available() >= 3) {
client.readBytes(&received_frame_header.type, 1);
client.readBytes((uint8_t*)&received_frame_header.length, 2);
}
}

if (hasReceivedFrameHeader()) {
int available = client.available();
int frame_type = received_frame_header.type;
int frame_length = received_frame_header.length;

if (frame_length > available) {
ETH_DEBUG_PRINTLN("Waiting for %d more bytes", frame_length - available);
return 0;
}

if (frame_length > MAX_FRAME_SIZE) {
ETH_DEBUG_PRINTLN("Skipping oversized frame: %d bytes", frame_length);
while (frame_length > 0) {
uint8_t skip[1];
frame_length -= client.read(skip, 1);
}
resetReceivedFrameHeader();
return 0;
}

if (frame_type != '<') {
ETH_DEBUG_PRINTLN("Skipping unexpected frame type: 0x%x", frame_type);
while (frame_length > 0) {
uint8_t skip[1];
frame_length -= client.read(skip, 1);
}
resetReceivedFrameHeader();
return 0;
}

client.readBytes(dest, frame_length);
resetReceivedFrameHeader();
return frame_length;
}
}
}

return 0;
}

bool SerialEthernetInterface::isConnected() const {
return deviceConnected;
}

#endif
92 changes: 92 additions & 0 deletions src/helpers/esp32/SerialEthernetInterface.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#pragma once
// Ethernet support for Broken Circuit Ranch POE Ethernet
// http://www.brokencircuitranch.com
// Kent Andersen

#ifdef HAS_ETHERNET

#include "../BaseSerialInterface.h"
#include <Ethernet3.h>
#include <SPI.h>

// Workaround: ESP32 Arduino core Server.h declares begin(uint16_t) as pure virtual
// but Ethernet3 only implements begin() with no args.
// This subclass satisfies the compiler by implementing the missing override.
class ConcreteEthernetServer : public EthernetServer {
public:
ConcreteEthernetServer(uint16_t port) : EthernetServer(port) {}
void begin(uint16_t port) override { EthernetServer::begin(); }
};

class SerialEthernetInterface : public BaseSerialInterface {
bool deviceConnected;
bool _isEnabled;
bool _ethInitialized;
unsigned long _last_write;

ConcreteEthernetServer* server;
EthernetClient client;

struct FrameHeader {
uint8_t type;
uint16_t length;
};

struct Frame {
uint8_t len;
uint8_t buf[MAX_FRAME_SIZE];
};

FrameHeader received_frame_header;

#define ETH_FRAME_QUEUE_SIZE 4
int recv_queue_len;
Frame recv_queue[ETH_FRAME_QUEUE_SIZE];
int send_queue_len;
Frame send_queue[ETH_FRAME_QUEUE_SIZE];

void clearBuffers() { recv_queue_len = 0; send_queue_len = 0; }

public:
SerialEthernetInterface() : server(nullptr), client(EthernetClient()) {
deviceConnected = false;
_isEnabled = false;
_ethInitialized = false;
_last_write = 0;
send_queue_len = recv_queue_len = 0;
received_frame_header.type = 0;
received_frame_header.length = 0;
}

// spi: shared SPI bus (vspi from target.cpp)
// port: TCP port to listen on
// cs_pin: W5500 chip select pin
// rst_pin: W5500 reset pin (-1 if not used)
// mac: 6-byte MAC address
bool begin(SPIClass& spi, int port, int cs_pin, int rst_pin, uint8_t mac[6]);

void enable() override;
void disable() override;
bool isEnabled() const override { return _isEnabled; }

bool isConnected() const override;
bool isWriteBusy() const override;

size_t writeFrame(const uint8_t src[], size_t len) override;
size_t checkRecvFrame(uint8_t dest[]) override;

bool hasReceivedFrameHeader();
void resetReceivedFrameHeader();
bool isEthernetInitialized() const { return _ethInitialized; }
};

#if ETH_DEBUG_LOGGING && ARDUINO
#include <Arduino.h>
#define ETH_DEBUG_PRINT(F, ...) Serial.printf("ETH: " F, ##__VA_ARGS__)
#define ETH_DEBUG_PRINTLN(F, ...) Serial.printf("ETH: " F "\n", ##__VA_ARGS__)
#else
#define ETH_DEBUG_PRINT(...) {}
#define ETH_DEBUG_PRINTLN(...) {}
#endif

#endif
11 changes: 11 additions & 0 deletions tools/build_windows_exe.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@echo off
setlocal

cd /d "%~dp0\.."

python -m pip install --upgrade pip
python -m pip install pyinstaller pyserial esptool
python -m PyInstaller --onefile --console --name meshcore-configurator tools\meshcore_configurator.py

echo.
echo Built: dist\meshcore-configurator.exe
Loading