Aus Raspberry Pi Geek 01/2024

WLAN-Grenzen mit LoRa überwinden (Seite 2)

Den Aufwand müssen Sie aber nicht zwangsläufig treiben, wenn die Datenrate niedrig liegt und Sie entweder großzügige Wiederholungen konfigurieren oder mit einzelnen verlorenen Nachrichten leben können. Die Beispielprogramme in den folgenden Abschnitten verwenden statt C/C++ das nicht ganz so optimale, aber ausreichende Python.

Sender

Als Programm auf der Sendeseite genügen die Zeilen aus Listing 1. Das kleine CircuitPython-Programm nutzt eine Bibliothek [6], die Adafruit für seine Breakouts entwickelt hat (Zeile 7). Prinzipiell müsste sie auch mit Breakouts anderer Hersteller funktionieren, die denselben Chip verwenden.

Die Zeilen 16 bis 28 erzeugen ein Objekt des Treibers und konfigurieren es. Zeile 24 regelt die Sendestärke. Die Zeilen 27 bis 29 konfigurieren die Wiederholversuche und die zeitlichen Abstände dazwischen. Der Wert aus Zeile 27 ist lediglich als untere Grenze zu verstehen: Die tatsächliche Pausendauer kann bis zum Doppelten des hier angegebenen Werts betragen. In unserem Beispiel liegt sie also zwischen einer halben und einer Sekunde.

Das Programm überträgt ohne weitergehende Optimierungen lediglich ein paar Messwerte (Zeitstempel ts und Temperatur temp der MCU) im CSV-Format (Zeilen 32 bis  35). Wenn Sie größere Datenmengen übertragen möchten, sollten Sie hier nachbessern – ein 16-Bit-Float braucht nur 2 Byte, wenn es nicht als Text über den Äther geht.

Listing 1

Daten senden

#!/usr/bin/python3
import board
from digitalio import DigitalInOut, Direction, Pull
import busio
import time
import microcontroller
import adafruit_rfm9x
PIN_LORA_SCK  = board.GP10
PIN_LORA_MOSI = board.GP11
PIN_LORA_MISO = board.GP8
PIN_LORA_CS   = board.GP9
PIN_LORA_RST  = board.GP7
PIN_LORA_EN   = board.GP15
spi1 = busio.SPI(PIN_LORA_SCK, PIN_LORA_MOSI, PIN_LORA_MISO)
pin_cs                = DigitalInOut(PIN_LORA_CS)
pin_reset             = DigitalInOut(PIN_LORA_RST)
pin_enable            = DigitalInOut(PIN_LORA_EN)
pin_enable.direction  = Direction.OUTPUT
pin_enable.value = 1
rfm9x = adafruit_rfm9x.RFM9x(spi1, pin_cs, pin_reset,868.0, baudrate=100000)
rfm9x.enable_crc  = True
rfm9x.tx_power    = 23
rfm9x.node        = 1
rfm9x.destination = 2
rfm9x.ack_wait    = 0.5
rfm9x.ack_retries = 3
rfm9x.sleep()
while True:
  ts = time.monotonic()
  temp = microcontroller.cpu.temperature
  csv_data = f"{ts},{temp}"
  send_ok = rfm9x.send_with_ack(bytes(csv_data, "UTF-8"))
  if send_ok:
    print("LoRa: ... successful")
  else:
    print("LoRa: ... failed")
  time.sleep(15)

Empfänger

Die Ansprüche an den Empfänger liegen normalerweise deutlich höher als die an den Sender, denn er muss die Daten von potenziell vielen Sendern entgegennehmen. Idealerweise erfolgt die Verarbeitung asynchron in einem zweiten Thread, während der Haupt-Thread sofort wieder auf Empfang geht.

Unser Beispiel in Listing 2 ist deutlich primitiver gestrickt. Das Programm fragt in Zeile 16 das Vorliegen neuer Daten ab und schreibt diese gegebenenfalls einfach heraus (Zeile 22). Es kommen wieder CircuitPython und dieselben Bibliotheken wie beim Sender zum Einsatz. Dank der Abstraktionsschicht Blinka [7] funktioniert das auf fast allen gängigen Kleinrechnern.

Für ein echtes Empfangsprogramm würde man allerdings die Verarbeitung per Interrupt steuern, denn das Modul kann bei neu verfügbaren Daten den Status eines Interrupt-Pins ändern. Ebenfalls möglich wäre die Ausgabe von rfm9x.snr (dem Signal-Rausch-Verhältnis) und rmf9x.rssi (der Empfangsstärke): Beides würde ein optimales Ausrichten der Antennen unterstützen. Ein ausführlicheres Beispiel inklusive Installationsanleitungen bietet ein Tutorial von Adafruit [8].

Listing 2

Empfänger

#!/usr/bin/python3
import time
import busio
import board
from digitalio import DigitalInOut, Direction, Pull
import adafruit_rfm9x
CS = DigitalInOut(board.CE1)
RESET = DigitalInOut(board.D25)
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 868.0)
rfm9x.node = 2
print("LoRa: waiting for data...")
while True:
  packet = rfm9x.receive(with_ack=True, timeout=0.5)
  if packet is None:
    time.sleep(0.5)
    continue
  try:
    data = packet.decode(encoding="UTF-8")
    print(f"LoRa: {data=}")
  except:
    print("LoRa: error")
  time.sleep(0.5)

Messergebnisse

Die Performance der beiden LoRa-Module überzeugt voll und ganz. Beim “Kellertest” erreichten die gesendeten Daten den Empfänger im dritten Stock problemlos durch vier massive Betondecken hindurch. Im Freien reichte der Empfang bis zum übernächsten Häuserblock, und das ohne besondere Optimierungsmaßnamen. So stand der Sender nicht etwa auf dem Balkon, sondern im Inneren der Wohnung.

Im direkten Vergleich mit einer Übertragung per WLAN (etwa über UDP) punktet LoRa aus zwei Gründen mit höherer Effizienz. Zum einen ist die Funktechnik an sich genügsamer. Zum anderen, und das fällt noch stärker ins Gewicht, spart die kürzere Aktivzeit (Batterie-)Strom: Die Anmeldung am WLAN entfällt, und der Mikrocontroller kann sich nach getaner Arbeit schneller wieder schlafen legen.

Fazit

Wenn Sie in einem Projekt mit WLAN oder Bluetooth an zu großen Distanzen oder einer schwierigen Übertragungsstrecke scheitern, sollten Sie unbedingt einen Blick auf LoRa werfen.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 4 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
RASPBERRY PI GEEK KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Raspberry Pi Geek bei Google Play Readly Logo
Nach oben