I went to our friend Chatgpt and got the info below for building a UART board. I ran the script and it shows the RX flashing (which is much better than what I got before...)
Here is the Code and conversation, hope it helps. I am not a coder so I did it all thru AI.
1 - Patch to integrate the driver into pyMC_Repeater
For the project pyMC_Repeater the typical structure looks like:
5️⃣ Start the repeater
python3 repeater.py --config config.json
Expected output:
Initializing UART SX1262 radio
Radio configured
RX thread started ⚠️6️⃣ The hidden problem with Waveshare UART boards
This is the important part.
Those boards are not a raw LoRa radio.
Internally they contain:
SX1262 + microcontroller + serial firmware
So communication becomes:
pyMC → UART → firmware → SX1262
instead of the ideal:
pyMC → SX1262 directly
That firmware layer introduces several issues.
Problem 1 — Modified frames
The firmware sometimes sends packets formatted like:
[ADDR_H][ADDR_L][CHANNEL][PAYLOAD...]
Meaning the payload arrives modified.
But pyMC expects:
payload only
This can break:
packet parsing
signatures
mesh routing
Problem 2 — Packet size limits
Many UART LoRa modules limit payload size to roughly:
200 bytes
or
240 bytes
If pyMC sends something larger:
packet gets truncated
Problem 3 — Internal buffering
The firmware often does this:
LoRa RX
↓
internal buffer
↓
UART TX
This introduces extra latency:
10–80 ms
In a mesh network that can cause:
collisions
timeouts
unnecessary retransmissions
Problem 4 — Fake or missing RSSI
Some UART firmware implementations:
do not provide RSSI
provide averaged RSSI
return invalid values
But pyMC uses RSSI for:
routing
link quality estimation
So the mesh can make incorrect routing decisions.
Problem 5 — Transparent mode
Many modules start in:
transparent serial mode
Where:
UART → LoRa broadcast
But with:
no metadata
no RSSI
no ACK handling
For a mesh network this is very limited.
👍 Recommended configuration
If you use the UART module, configure it for:
fixed transmission mode
no address header
raw payload
Meaning:
LoRa payload = UART payload
This makes it much easier to integrate with pyMC.
💡 Raspberry Pi tip
On a Raspberry Pi 4, /dev/serial0 may change depending on Bluetooth usage.
It is usually safer to use:
/dev/ttyAMA0
and disable Bluetooth.
Edit:
/boot/config.txt
Add:
dtoverlay=disable-bt
enable_uart=1
Then reboot.
🚀 One more useful thing
If you want, I can also show you how to build a much better driver for these UART boards that:
removes the internal module headers
provides real RSSI
adds collision buffering
improves pyMC mesh stability
In small LoRa mesh networks this often improves reliability by 3× to 5×.
I went to our friend Chatgpt and got the info below for building a UART board. I ran the script and it shows the RX flashing (which is much better than what I got before...)
Here is the Code and conversation, hope it helps. I am not a coder so I did it all thru AI.
1 - Patch to integrate the driver into pyMC_Repeater
For the project pyMC_Repeater the typical structure looks like:
pyMC_Repeater/
├─ pymc_core/
│ ├─ hardware/
│ │ ├─ sx1262_radio.py
│ │ └─ uart_sx1262_radio.py ← new file
│ ├─ radio_factory.py
You add the UART driver file and then modify the radio factory so the repeater can create the radio.
2️⃣ Add the new driver
Create the file:
pymc_core/hardware/uart_sx1262_radio.py
and place the UART driver code there.
UART Driver Code:
**CODE START:**
import serial
import threading
import time
import struct
class UARTSX1262Radio:
"""
UART driver for Waveshare SX1262 LoRa HAT (UART version)
Designed for Raspberry Pi / pyMC repeater environments
"""
def __init__(
self,
port="/dev/serial0",
baudrate=9600,
freq=868000000,
channel=23,
power=22,
air_rate=2400
):
self.port = port
self.baudrate = baudrate
self.freq = freq
self.channel = channel
self.power = power
self.air_rate = air_rate
self.serial = serial.Serial(
port=self.port,
baudrate=self.baudrate,
timeout=0.1
)
self.running = False
self.rx_callback = None
self.rx_thread = None
self._rx_buffer = bytearray()
self.configure_radio()
# --------------------------------------------------
# LOW LEVEL SERIAL
# --------------------------------------------------
def _write(self, data):
self.serial.write(data)
self.serial.flush()
def _read_all(self):
return self.serial.read(self.serial.in_waiting or 1)
def send_command(self, cmd):
self._write(cmd)
time.sleep(0.05)
resp = self._read_all()
return resp
# --------------------------------------------------
# RADIO CONFIG
# --------------------------------------------------
def configure_radio(self):
print("Initializing UART SX1262 radio")
self.set_channel(self.channel)
self.set_power(self.power)
print("Radio configured:")
print(" channel:", self.channel)
print(" power:", self.power)
def set_channel(self, channel):
"""
Channel mapping used by Waveshare firmware
"""
self.channel = channel
cmd = bytes([
0xC0,
0x00,
0x09,
0x17,
channel
])
return self.send_command(cmd)
def set_power(self, power):
"""
TX power (0-22 dBm typical)
"""
self.power = power
cmd = bytes([
0xC0,
0x00,
0x09,
0x18,
power
])
return self.send_command(cmd)
# --------------------------------------------------
# TX
# --------------------------------------------------
def send_frame(self, payload):
"""
Send LoRa payload
"""
if isinstance(payload, str):
payload = payload.encode()
length = len(payload)
frame = bytes([0xC1, length]) + payload
self._write(frame)
# --------------------------------------------------
# RX
# --------------------------------------------------
def _parse_frames(self, data):
"""
Very simple frame parser
"""
frames = []
self._rx_buffer.extend(data)
while True:
if len(self._rx_buffer) < 2:
break
length = self._rx_buffer[1]
if len(self._rx_buffer) < length + 2:
break
payload = self._rx_buffer[2:2 + length]
frames.append(payload)
del self._rx_buffer[:length + 2]
return frames
def _read_radio(self):
data = self.serial.read(self.serial.in_waiting or 1)
if not data:
return []
return self._parse_frames(data)
# --------------------------------------------------
# RSSI (mock / firmware dependent)
# --------------------------------------------------
def get_rssi(self):
"""
Request RSSI if supported by firmware
"""
cmd = bytes([0xC1, 0x00, 0x02])
resp = self.send_command(cmd)
if len(resp) >= 1:
return resp[-1]
return None
# --------------------------------------------------
# RX THREAD
# --------------------------------------------------
def _rx_loop(self):
while self.running:
frames = self._read_radio()
for frame in frames:
if self.rx_callback:
self.rx_callback(frame)
time.sleep(0.01)
def start_rx(self, callback):
self.rx_callback = callback
self.running = True
self.rx_thread = threading.Thread(target=self._rx_loop)
self.rx_thread.daemon = True
self.rx_thread.start()
print("RX thread started")
def stop(self):
self.running = False
if self.rx_thread:
self.rx_thread.join(timeout=1)
self.serial.close()
# --------------------------------------------------
# STATUS
# --------------------------------------------------
def get_status(self):
return {
"port": self.port,
"baudrate": self.baudrate,
"channel": self.channel,
"power": self.power
}
**CODE END:**
3️⃣ Modify radio_factory.py
Edit:
pymc_core/radio_factory.py
Patch
@@
from pymc_core.hardware.sx1262_radio import SX1262Radio
+from pymc_core.hardware.uart_sx1262_radio import UARTSX1262Radio
def create_radio(config):
radio_type = config.get("radio_type", "sx1262")
if radio_type == "sx1262":
return SX1262Radio(
bus_id=config.get("spi_bus", 0),
cs_pin=config.get("cs_pin", 21),
reset_pin=config.get("reset_pin", 18),
busy_pin=config.get("busy_pin", 20),
irq_pin=config.get("irq_pin", 16)
)
+ elif radio_type == "uart-sx1262":
+
+ return UARTSX1262Radio(
+ port=config.get("serial_port", "/dev/serial0"),
+ baudrate=config.get("baudrate", 9600),
+ channel=config.get("channel", 23),
+ power=config.get("power", 22)
+ )
else:
raise Exception("Unsupported radio type")
4️⃣ Example repeater configuration
Example config.json:
{
"radio_type": "uart-sx1262",
"serial_port": "/dev/serial0",
"baudrate": 9600,
"channel": 23,
"power": 22
}
5️⃣ Start the repeater
python3 repeater.py --config config.json
Expected output:
Initializing UART SX1262 radio
Radio configured
RX thread started
⚠️ 6️⃣ The hidden problem with Waveshare UART boards
This is the important part.
Those boards are not a raw LoRa radio.
Internally they contain:
SX1262 + microcontroller + serial firmware
So communication becomes:
pyMC → UART → firmware → SX1262
instead of the ideal:
pyMC → SX1262 directly
That firmware layer introduces several issues.
Problem 1 — Modified frames
The firmware sometimes sends packets formatted like:
[ADDR_H][ADDR_L][CHANNEL][PAYLOAD...]
Meaning the payload arrives modified.
But pyMC expects:
payload only
This can break:
packet parsing
signatures
mesh routing
Problem 2 — Packet size limits
Many UART LoRa modules limit payload size to roughly:
200 bytes
or
240 bytes
If pyMC sends something larger:
packet gets truncated
Problem 3 — Internal buffering
The firmware often does this:
LoRa RX
↓
internal buffer
↓
UART TX
This introduces extra latency:
10–80 ms
In a mesh network that can cause:
collisions
timeouts
unnecessary retransmissions
Problem 4 — Fake or missing RSSI
Some UART firmware implementations:
do not provide RSSI
provide averaged RSSI
return invalid values
But pyMC uses RSSI for:
routing
link quality estimation
So the mesh can make incorrect routing decisions.
Problem 5 — Transparent mode
Many modules start in:
transparent serial mode
Where:
UART → LoRa broadcast
But with:
no metadata
no RSSI
no ACK handling
For a mesh network this is very limited.
👍 Recommended configuration
If you use the UART module, configure it for:
fixed transmission mode
no address header
raw payload
Meaning:
LoRa payload = UART payload
This makes it much easier to integrate with pyMC.
💡 Raspberry Pi tip
On a Raspberry Pi 4, /dev/serial0 may change depending on Bluetooth usage.
It is usually safer to use:
/dev/ttyAMA0
and disable Bluetooth.
Edit:
/boot/config.txt
Add:
dtoverlay=disable-bt
enable_uart=1
Then reboot.
🚀 One more useful thing
If you want, I can also show you how to build a much better driver for these UART boards that:
removes the internal module headers
provides real RSSI
adds collision buffering
improves pyMC mesh stability
In small LoRa mesh networks this often improves reliability by 3× to 5×.
<!-- github-sync-id:4036485715 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
I went to our friend Chatgpt and got the info below for building a UART board. I ran the script and it shows the RX flashing (which is much better than what I got before...)
Here is the Code and conversation, hope it helps. I am not a coder so I did it all thru AI.
1 - Patch to integrate the driver into pyMC_Repeater
For the project pyMC_Repeater the typical structure looks like:
pyMC_Repeater/
├─ pymc_core/
│ ├─ hardware/
│ │ ├─ sx1262_radio.py
│ │ └─ uart_sx1262_radio.py ← new file
│ ├─ radio_factory.py
You add the UART driver file and then modify the radio factory so the repeater can create the radio.
2️⃣ Add the new driver
Create the file:
pymc_core/hardware/uart_sx1262_radio.py
and place the UART driver code there.
UART Driver Code:
CODE START:
import serial
import threading
import time
import struct
class UARTSX1262Radio:
CODE END:
3️⃣ Modify radio_factory.py
Edit:
pymc_core/radio_factory.py
Patch
@@
from pymc_core.hardware.sx1262_radio import SX1262Radio
+from pymc_core.hardware.uart_sx1262_radio import UARTSX1262Radio
def create_radio(config):
elif radio_type == "uart-sx1262":
else:
raise Exception("Unsupported radio type")
4️⃣ Example repeater configuration
Example config.json:
{
"radio_type": "uart-sx1262",
"serial_port": "/dev/serial0",
"baudrate": 9600,
"channel": 23,
"power": 22
}
5️⃣ Start the repeater
python3 repeater.py --config config.json
Expected output:
Initializing UART SX1262 radio
Radio configured
RX thread started
⚠️ 6️⃣ The hidden problem with Waveshare UART boards
This is the important part.
Those boards are not a raw LoRa radio.
Internally they contain:
SX1262 + microcontroller + serial firmware
So communication becomes:
pyMC → UART → firmware → SX1262
instead of the ideal:
pyMC → SX1262 directly
That firmware layer introduces several issues.
Problem 1 — Modified frames
The firmware sometimes sends packets formatted like:
[ADDR_H][ADDR_L][CHANNEL][PAYLOAD...]
Meaning the payload arrives modified.
But pyMC expects:
payload only
This can break:
packet parsing
signatures
mesh routing
Problem 2 — Packet size limits
Many UART LoRa modules limit payload size to roughly:
200 bytes
or
240 bytes
If pyMC sends something larger:
packet gets truncated
Problem 3 — Internal buffering
The firmware often does this:
LoRa RX
↓
internal buffer
↓
UART TX
This introduces extra latency:
10–80 ms
In a mesh network that can cause:
collisions
timeouts
unnecessary retransmissions
Problem 4 — Fake or missing RSSI
Some UART firmware implementations:
do not provide RSSI
provide averaged RSSI
return invalid values
But pyMC uses RSSI for:
routing
link quality estimation
So the mesh can make incorrect routing decisions.
Problem 5 — Transparent mode
Many modules start in:
transparent serial mode
Where:
UART → LoRa broadcast
But with:
no metadata
no RSSI
no ACK handling
For a mesh network this is very limited.
👍 Recommended configuration
If you use the UART module, configure it for:
fixed transmission mode
no address header
raw payload
Meaning:
LoRa payload = UART payload
This makes it much easier to integrate with pyMC.
💡 Raspberry Pi tip
On a Raspberry Pi 4, /dev/serial0 may change depending on Bluetooth usage.
It is usually safer to use:
/dev/ttyAMA0
and disable Bluetooth.
Edit:
/boot/config.txt
Add:
dtoverlay=disable-bt
enable_uart=1
Then reboot.
🚀 One more useful thing
If you want, I can also show you how to build a much better driver for these UART boards that:
removes the internal module headers
provides real RSSI
adds collision buffering
improves pyMC mesh stability
In small LoRa mesh networks this often improves reliability by 3× to 5×.