LoRa, MQTT and Oled

This commit is contained in:
2026-07-16 16:32:50 +02:00
parent 1df9d878ca
commit aa60f5bca1
31 changed files with 3367 additions and 21 deletions
+183
View File
@@ -0,0 +1,183 @@
import sys
import time
IS_MICROPYTHON = sys.implementation.name == 'micropython'
if IS_MICROPYTHON:
import _thread
from machine import Pin, SPI
import ubinascii
import ujson as json
# --- PILOTE SPI DIRECT (ESP32 / Heltec V3) ---
class LoraHardwareSPI:
def __init__(self, spi_bus=1, clk=9, mosi=10, miso=11, cs=8, irq=14, rst=12, gpio=13):
from sx1262 import SX1262
self.lora = SX1262(
spi_bus=spi_bus, clk=clk, mosi=mosi, miso=miso,
cs=cs, irq=irq, rst=rst, gpio=gpio
)
self.default_group = 2 # On définit le groupe par défaut ici
self.lock = _thread.allocate_lock() # Création du verrou
def configure(self, freq=868.1, bw=125.0, sf=7, cr=5, power=14):
self.lora.begin(
freq=freq, bw=bw, sf=sf, cr=cr, power=power,
useRegulatorLDO=False, crcOn=True, preambleLength=8, implicit=False
)
self.lora.setSyncWord(0x14)
def send(self, payload, group=None):
"""Encode la payload en JSON si nécessaire, et injecte automatiquement l'octet de groupe."""
with self.lock:
if group is None:
group = self.default_group
# Si c'est un dictionnaire ou une liste, on le convertit en JSON textuel
if isinstance(payload, (dict, list)):
payload = json.dumps(payload)
if isinstance(payload, str):
payload = payload.encode('utf-8')
# Insertion automatique de l'octet de groupe au tout début de la trame physique
paquet_physique = bytes([group]) + payload
self.lora.send(paquet_physique)
def receive_packet(self, timeout_ms=1000):
"""Écoute, nettoie, extrait le groupe, gère le HEX et parse le JSON."""
with self.lock:
data, state = self.lora.recv(len=0, timeout_en=True, timeout_ms=timeout_ms)
if state == 0 and len(data) > 1:
group = data[0]
payload_brute = data[1:].strip(b'\x00 \r\n\t')
try:
text = payload_brute.decode('utf-8').strip('\x00 \r\n\t')
except UnicodeError:
return None
if text.startswith('{') or text.startswith('['):
decoded_text = text
elif text.lower().startswith('7b') or text.lower().startswith('5b'):
try:
decoded_text = ubinascii.unhexlify(text).decode('utf-8').strip('\x00 \r\n\t')
except Exception:
decoded_text = text
else:
decoded_text = text
try:
parsed_json = json.loads(decoded_text)
return {"group": group, "data": parsed_json, "raw": False}
except ValueError:
return {"group": group, "data": decoded_text, "raw": True}
return None
else:
import threading
import serial
import json
# --- PILOTE SÉRIE (Raspberry Pi / Dragino LA66) ---
class LoraSerialAT:
def __init__(self, port):
self.port = port
self.ser = serial.Serial(
port=self.port,
baudrate=9600,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=0.1
)
self.ser.reset_input_buffer()
self.ser.reset_output_buffer()
self.lock = threading.Lock()
def configure(self, **kwargs):
pass
def send(self, payload):
"""Encode automatiquement la payload en HEX pour l'envoi via la clé."""
with self.lock:
if isinstance(payload, (dict, list)):
payload = json.dumps(payload)
if isinstance(payload, str):
payload = payload.encode('utf-8')
hex_payload = payload.hex()
self.ser.reset_input_buffer()
# La clé ajoute d'elle-même l'octet de groupe configuré dans ses registres
cmd = f"AT+SEND=1,{hex_payload},1,3\r\n"
print(f"RPI : Envoi de la commande HEX -> AT+SEND=1,[HEX_DATA],1,3")
self.ser.write(cmd.encode('utf-8'))
time.sleep(0.2)
response = ""
start_wait = time.time()
while (time.time() - start_wait) < 1.5:
if self.ser.in_waiting > 0:
response += self.ser.readline().decode('utf-8', errors='ignore')
time.sleep(0.05)
print(f"[RPI LA66 TX STATUS] :\n{response.strip()}")
def receive_packet(self, timeout_ms=5000):
with self.lock:
start_time = time.time()
timeout_s = timeout_ms / 1000.0
while (time.time() - start_time) < timeout_s:
if self.ser.in_waiting > 0:
line = self.ser.readline().decode('utf-8', errors='ignore').strip()
if line:
payload_bytes = None
if "(HEX:)" in line:
hex_part = line.split("(HEX:)")[1].strip().replace(" ", "")
try:
payload_bytes = bytes.fromhex(hex_part)
except ValueError:
pass
elif "Data:" in line:
payload_bytes = line.split("Data:")[1].strip().encode('utf-8')
if payload_bytes and len(payload_bytes) > 1:
group = payload_bytes[0]
payload_clean = payload_bytes[1:].strip(b'\x00 \r\n\t')
try:
text = payload_clean.decode('utf-8').strip('\x00 \r\n\t')
except UnicodeError:
continue
if text.startswith('{') or text.startswith('['):
decoded_text = text
elif text.lower().startswith('7b') or text.lower().startswith('5b'):
try:
decoded_text = bytes.fromhex(text).decode('utf-8').strip('\x00 \r\n\t')
except Exception:
decoded_text = text
else:
decoded_text = text
try:
parsed_json = json.loads(decoded_text)
return {"group": group, "data": parsed_json, "raw": False}
except json.JSONDecodeError:
return {"group": group, "data": decoded_text, "raw": True}
time.sleep(0.01)
return None
def get_lora_device(port_or_pins=None):
if IS_MICROPYTHON:
pins = port_or_pins if port_or_pins else {}
return LoraHardwareSPI(**pins)
else:
port = port_or_pins if port_or_pins else "/dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0"
return LoraSerialAT(port)