Close

Day 4

A project log for Low Power LoRaWAN Gateway

Low power, low cost, low parts and low channel count USB talking LoRaWAN gateway (without concentrator) for OpenWRT routers and pals

morphmorph 02/16/2023 at 20:490 Comments

Life is a struggle, and I've been struggling for the past few days with this project and many other things.

I've spent most of my free time with CNC path operations for a small boat plug that I'm building out of XPS foam layers at school, but thats another story.

With this project I've mostly had problems with passing/converting the serial output of the e5 mini 'gateway' to UDP packets that Chirpstack would understand.

Nevertheless, I've succeeded in forwarding messages with my own python script. I thought that I'm too cool for the legacy Semtech Packet Forwarder so I wrote my own. Well... it took me ages, I'm a bit rusty with my boat adventures and everything happening lately. First I wanted to test the packet forwarder functionality with netcat, but ran into encoding issues. Then I tried C and got lost. Finally python solved the problem, python is not my forte, but it just worked. Next I need to implement downlink functionality to my UDP socket python script.

import serial
import socket

serial_port = serial.Serial(port="/dev/ttyUSB0", baudrate=115200,
                            bytesize=8, timeout=2, stopbits=serial.STOPBITS_ONE)

remote = ("hush.super.secret", 1700)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while (1):
    if (serial_port.in_waiting > 0):
        serial_buffer = serial_port.readline()
        sock.sendto(serial_buffer, remote)
        # data, addr = sock.recvfrom(1024)  # buffer size is 1024 bytes
        # print("received message: %s" % data)

Python packet forwarder

#include "STM32WL_LoRaRadio.h"
#include "events/EventQueue.h"
#include "jems.h"
#include "mbed.h"

#define MAX_LEVEL 10 // how deeply nested the JSON structures can get
static jems_level_t jems_levels[MAX_LEVEL];
static jems_t jems;

#define FREQUENCY 868000000
#define TX_POWER 22 // max should be 22 in lib
#define BANDWIDTH 0
#define PREAMBLE_LENGTH 8
#define DOWNLINK_MAX_LENGTH 51 // keep sf12 max until we settle on a better number
#define FREQ_HOP_ON 0
#define HOP_PERIOD 4

BufferedSerial router(USBTX, USBRX);
static STM32WL_LoRaRadio radio;
static EventQueue ev_queue(10 * EVENTS_EVENT_SIZE);

DigitalOut led1(LED1);

static void write_char(char ch, uintptr_t arg)
{
    // fputc(ch, (FILE *)arg);
    router.write(&ch, 1);
}

//  Bytes  | Function
// :------:|---------------------------------------------------------------------
//  0      | protocol version = 2
//  1-2    | random token
//  3      | PUSH_DATA identifier 0x00
//  4-11   | Gateway unique identifier (MAC address)
//  12-end | JSON object, starting with {, ending with }, see section 4

static void forward_packet(const uint8_t *payload, uint16_t size, int16_t rssi,
                           int8_t snr)
{
    // char string_payload[(size << 1) + 1];
    // for (size_t i = 0; i < size; i++)
    // {
    //     sprintf(&string_payload[i * 2], "%02X", payload[i]);
    // }
    char gw_id[] = {0x02, 0xCF, 0x9A, 0x00, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB};
    router.write(gw_id, 12);
    jems_init(&jems, jems_levels, MAX_LEVEL, write_char, (uintptr_t)stdout);
    jems_object_open(&jems);
    jems_string(&jems, "rxpk");
    jems_array_open(&jems);
    jems_object_open(&jems);
    // jems_string(&jems, "time");
    // jems_string(&jems, "2022-02-15T22:17:00.000000Z");
    // jems_string(&jems, "tmst");
    // jems_number(&jems, 3512348611);
    // jems_string(&jems, "chan");
    // jems_number(&jems, 2);
    // jems_string(&jems, "rfch");
    // jems_number(&jems, 0);
    jems_string(&jems, "freq");
    jems_number(&jems, FREQUENCY);
    jems_string(&jems, "stat");
    jems_number(&jems, 1);
    jems_string(&jems, "modu");
    jems_string(&jems, "LORA");
    jems_string(&jems, "datr");
    jems_string(&jems, "SF9BW125");
    jems_string(&jems, "codr");
    jems_string(&jems, "4/5");
    jems_string(&jems, "rssi");
    jems_number(&jems, rssi);
    jems_string(&jems, "lsnr");
    jems_number(&jems, snr);
    jems_string(&jems, "size");
    jems_number(&jems, size);
    jems_string(&jems, "data");
    jems_string(&jems, "APtfAPB+1bNwjr3JaVJHXQAFUIADzW0=");
    jems_object_close(&jems);
    jems_array_close(&jems);
    jems_object_close(&jems);
}

static void stat_message()
{
    char gw_id[] = {0x02, 0xCF, 0x9A, 0x00, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB};
    router.write(gw_id, 12);
    jems_init(&jems, jems_levels, MAX_LEVEL, write_char, (uintptr_t)stdout);
    jems_object_open(&jems);
    jems_string(&jems, "stat");
    jems_object_open(&jems);
    // jems_string(&jems, "time");
    // jems_string(&jems, "2023-02-16.18:51:48.GMT");
    // jems_string(&jems, "rxnb");
    // jems_number(&jems, 0);
    // jems_string(&jems, "rxok");
    // jems_number(&jems, 0);
    // jems_string(&jems, "rxfw");
    // jems_number(&jems, 0);
    // jems_string(&jems, "ackr");
    // jems_number(&jems, 0);
    // jems_string(&jems, "dwnb");
    // jems_number(&jems, 0);
    // jems_string(&jems, "txnb");
    // jems_number(&jems, 0);
    jems_object_close(&jems);
    jems_object_close(&jems);
}

static void cad_done(bool channel_busy)
{
    ev_queue.call(debug, "cad_done in context %p\n", ThisThread::get_id());
}

static void fhss_change_channel(uint8_t current_channel)
{
    ev_queue.call(debug, "fhss_done in context %p\n", ThisThread::get_id());
}

static void rx_done(const uint8_t *payload, uint16_t size, int16_t rssi,
                    int8_t snr)
{
    ev_queue.call(forward_packet, payload, size, rssi, snr);
}

static void rx_error(void)
{
    ev_queue.call(debug, "rx_error in context %p\n", ThisThread::get_id());
}

static void rx_timeout(void)
{
    ev_queue.call(debug, "rx_timeout in context %p\n", ThisThread::get_id());
}

static void tx_done(void)
{
    radio.receive();
    ev_queue.call(debug, "tx_done in context %p, starting rx\n",
                  ThisThread::get_id());
}

static void tx_timeout(void)
{
    ev_queue.call(debug, "tx_timeout in context %p\n", ThisThread::get_id());
}

static radio_events_t RadioEvents = {.cad_done = cad_done,
                                     .fhss_change_channel = fhss_change_channel,
                                     .rx_done = rx_done,
                                     .rx_error = rx_error,
                                     .rx_timeout = rx_timeout,
                                     .tx_done = tx_done,
                                     .tx_timeout = tx_timeout};

// static void onSerialReceived()
// {
//     char buff;
//     while (router.readable())
//     {
//         router.read(&buff, 1);
//         router.write(&buff, 1);
//     }
// }

// void onSigio(void)
// {
//     ev_queue.call(onSerialReceived);
// }

int main()
{
    led1 = 1;
    radio.init_radio(&RadioEvents);
    radio.set_channel(FREQUENCY);
    radio.set_tx_config(MODEM_LORA, TX_POWER, 0, BANDWIDTH, LORA_SF9, LORA_CR_4_5,
                        PREAMBLE_LENGTH, LORA_PACKET_EXPLICIT, LORA_CRC_ON,
                        FREQ_HOP_ON, HOP_PERIOD, LORA_IQ_INVERTED, 0);
    radio.set_rx_config(MODEM_LORA, BANDWIDTH, LORA_SF9, LORA_CR_4_5, 0,
                        PREAMBLE_LENGTH, 5, LORA_PACKET_EXPLICIT, 0, LORA_CRC_ON,
                        FREQ_HOP_ON, HOP_PERIOD, LORA_IQ_NORMAL, 1);
    radio.set_public_network(true);
    radio.set_max_payload_length(MODEM_LORA, DOWNLINK_MAX_LENGTH);
    radio.receive();
    //    router.sigio(callback(onSigio));
    ev_queue.call_every(30s, stat_message);
    ev_queue.dispatch_forever();
}

Mbed code. Need to base64 encode the payload and do a bunch of other stuff, but progress is progress.

The pieces are there, just need to headbang my way through the never ending walls of challenges, that's how I roll.

Discussions