Private LORA sensors and server

Well, it’s been a hot minute and I have been busy busy busy…

Where were we? I think the last time we spoke we left off with a functioning gate sensor. Right… Well, I gave it an UPGRADE! I started reading about LORA which stands for Long Range (Wireless communication). The thing that caught my eye was the low power consumption, but long range aspect. They claim it has a range of around ten miles. What? 10 MILES? The thing that I didn’t like is that they all seem to be centered around IOT devices which connect and store data to some cloud. I am no tin foil wearing, UFO fearing freak, but ain’t no one needs to know when my gate is opened except my wife and me, and I certainly don’t need to track it online when I can track it at home.

So I went down the rabbit hole. I ordered some cheap LORA modules, connected them with a Raspberry Pi Pico and tried a point to point Micropython program, one sending data and the other receiving data. That worked, pretty simple. Then I started looking in to LORAWan. What I found was, the products like Chirpstack say they are used for local only but, like I said above, they all require some connection to a cloud and that wouldn’t work for me. There is only one thing to do I guess…

Yeah, I may have went a bit overboard on the enclosure.

Building a private LORA server… Right… That’s what you thought wasn’t it?

Actually, not as difficult as I thought it would be. The Adafruit LORA modules were cheap, but not as cheap as I wanted. I like for my projects to be as cheap as possible in case my wife finds out what I am tinkering with. She thinks everything I build is to spy on her for some reason. Maybe I have given her a complex by saying her name through the cameras while she is out weed eating. Wait, what were we talking about? oh yeah, LORA servers! So I found the LORA chips way cheaper and I designed a board to mount the chip, a Raspberry Pi Pico and an antenna jack. I added a couple of GPIO pin connections for 5V LEDs because who doesn’t like flashy LED lights on DIY electronic projects? Am I right?

I added a couple of current limiting resistors for the LEDs but I had some 3.3v LEDs in my stash of misfit electronic toys so I just jumperd those suckers.

Okay, I have a platform, lets get coding… I had to get the Micropython library for that chip which was really the hardest part of this project. https://github.com/wybiral/micropython-lora I got that and started coding. I created a server that listens for signals at 915Mhz which is the open (free) range for LORA in the USA. When it receives a packet, it inspects the ID of the source and if it matches an ID I have in the database, it processes it based on that ID. This way, I could have several sensors that all get processed differently for different applications (weather, gate sensor, motion sensor, water sensor, temperature sensor)

It then processes the data either sending it to MQTT or writing it to a database. One thing I found is that the sensor sends the data three times to assure it was received at long distances. I really didn’t need it to be sent three times so I edited the LORA python module to only send it once. Here is the code for the server.

from pyLoraRFM9x import LoRa, ModemConfig
import time
import paho.mqtt.client as mqtt
import mariadb
import json
import sys

###### Make connection to the testing DB ######
try:
    conn = mariadb.connect(
        user="xxxxxxx", # your database user ID
        password="xxxxxxx", # your database password
        host="xxxxxxxxxxxxxx", # your database IP or FQDN
        port=3306,
        database="xxxxxx") # your database name
except mariadb.Error as e:
        print(f"Error connecting to MariaDB Platform: {e}")
        sys.exit(1)
###### Get Cursor for the testing DB ######
cur = conn.cursor()

###### MQTT Broker info ######
broker = 'xxx.xxx.xxx.xxx' # your MQTT server IP or FQDN
client = mqtt.Client(client_id="LORA-GATEWAY")

###### Connect to mqtt Server ######
client.connect(broker)

###### Pull MQTT Topics and sensor ID's from the DB ######
data = "SELECT * FROM LORA"
cur.execute(data)
result = cur.fetchall()
print(result)


###### Function that parses payloads based on received header info and processes according to sensor ID ######
def on_recv(payload):
    print("Got packet from:", payload.header_from)
    if payload.header_from == 4:
        ###### Pull the correct MQTT topic from the DB ######
        get_client_data = "SELECT mqtt_topic FROM LORA WHERE client_id=%s"
        header = payload.header_from,
        cur.execute(get_client_data, header)
        result = str(cur.fetchone())
        format_result = result.replace("'",'').replace("(",'').replace(")",'').replace(",",'')
        
        ###### Extract JSON data ######
        office_temp_json = json.loads(payload.message)
        office_temp = office_temp_json["Temperature"]

        ###### record temperature to the DB ######
        insert_temp = "INSERT INTO OfficeTemp (temperature) VALUES (?)"           
        cur.execute(insert_temp, [office_temp], None)
        conn.commit()

        ###### Publish temperature to MQTT ######
        client.publish(format_result, payload=payload.message)
        print("published to:", format_result, payload)
    elif payload.header_from != 4:     

        if payload.header_from == 3:
            ###### Convert bytes temp data to str and port to the DB ######
            message_data = str(payload.message, encoding='utf-8')
            insert_temp = "INSERT INTO Laser (temperature) VALUES (?)"           
            cur.execute(insert_temp, [message_data], None)
            conn.commit()
            get_client_data = "SELECT mqtt_topic FROM LORA WHERE client_id=%s"
            ###### Publish to MQTT topic ######
            header = payload.header_from,
            cur.execute(get_client_data, header)
            result = str(cur.fetchone())
            format_result = result.replace("'",'').replace("(",'').replace(")",'').replace(",",'')
            print(format_result)
            client.publish(format_result, payload=payload.message)
            print("published to:", format_result, payload)
        elif payload.header_from == 1:
            ###### Convert bytes temp data to str and port to the DB ######
            message_data = str(payload.message, encoding='utf-8')
            insert_state = "INSERT INTO Gate (state) VALUES (?)"           
            cur.execute(insert_state, [message_data], None)
            conn.commit()
            get_client_data = "SELECT mqtt_topic FROM LORA WHERE client_id=%s"
            ###### Publish to MQTT topic ######
            header = payload.header_from,
            cur.execute(get_client_data, header)
            result = str(cur.fetchone())
            format_result = result.replace("'",'').replace("(",'').replace(")",'').replace(",",'')
            print(format_result)
            client.publish(format_result, payload=payload.message)
            print("published to:", format_result, payload)
        elif payload.header_from == 5:
             ###### Write rain tip data to weather database ######
            print(payload.message)
            insert_state = "INSERT INTO Rain_Data (tips) VALUES (?)"           
            cur.execute(insert_state, [payload.message], None)
            conn.commit()
            print("published to DB: Rain_Data", payload)
        
def on_recv_main():
#    time.sleep(15)
    lora.on_recv=on_recv

#LORA Setup
lora = LoRa(0, 1, 5, 4, reset_pin=25, receive_all=True, freq=915, modem_config=ModemConfig.Bw125Cr45Sf128, tx_power=14, acks=False)

# set to listen continuously
lora.set_mode_rx()

# loop and wait for data
while True:
    on_recv_main()
    time.sleep(15)

I started the code by creating a table in the DB with each sensor ID and it’s corresponding MQTT topic. So, I could change those or add any sensors just by adding a few lines of code and one row in the DB. That table looks like this..

So the whole process looks like this… A packet is received from sensor with ID of 1.

I used this line to assign the MQTT topic:

if payload.header_from == SENSOR ID
       get_client_data = "SELECT mqtt_topic FROM LORA WHERE client_id=%s"

Then I process the message data in different ways.. I can either write it to the database or publish to an MQTT topic or both. BOOM!

The code on the sensor looks like this (just a simple temp probe taking the temp reading, converting from C to F and sending every ten seconds. Then it flashes the LEDs to let me know it sent it. (See, they have some functionality)

import time
from ulora import LoRa, ModemConfig, SPIConfig
import machine
from machine import Pin
import ds18x20
import onewire

# Lora Parameters
RFM95_RST = 9
RFM95_SPIBUS = SPIConfig.rp2_0
RFM95_CS = 8
RFM95_INT = 22
RF95_FREQ = 915.0
RF95_POW = 20
CLIENT_ADDRESS = 4
SERVER_ADDRESS = 2

ds_pin = machine.Pin(26)
ds_sensor = ds18x20.DS18X20(onewire.OneWire(ds_pin))
led1 = machine.Pin(12, Pin.OUT)
led2 = machine.Pin(13, Pin.OUT)
roms = ds_sensor.scan()
print('Found DS devices: ', roms)
    
# initialise LoRa radio
lora = LoRa(RFM95_SPIBUS, RFM95_INT, CLIENT_ADDRESS, RFM95_CS, reset_pin=RFM95_RST, freq=RF95_FREQ, tx_power=RF95_POW, acks=True)

# This is the send function that takes the temp from the probe, converts it to F and sends to the LoRa server
def send_temp():
    ds_sensor.convert_temp()
    time.sleep_ms(750)
    for rom in roms:
        print(rom)
        print(ds_sensor.read_temp(rom))
        temperature_c = ds_sensor.read_temp(rom)
        temperature_f = str(round(temperature_c * (9/5) + 32, 0))
        print(temperature_f)
    lora.send_to_wait(temperature_f, SERVER_ADDRESS)
    led1.on()
    time.sleep(.1)
    led2.on()
    time.sleep(.1)
    led1.off()
    time.sleep(.1)
    led2.off()
    print("sent")
    time.sleep(10)

# loop and wait for data
while True:
    send_temp()

I have converted my gate sensor, a couple temp probes and my rain gauge (more to come on that project) to LORA now and it’s all working flawlessly. I have solar panels connected to LIPO batteries in each enclosure and since it’s low power consumption, they never get below 80% charged.

The gate sensor code looks like this… basically just sends a binary number to the server via LORA when the gate is opened or closed.

import time
from ulora import LoRa, ModemConfig, SPIConfig
import machine
from machine import Pin

# Lora Parameters
RFM95_RST = 9
RFM95_SPIBUS = SPIConfig.rp2_0
RFM95_CS = 8
RFM95_INT = 22
RF95_FREQ = 915.0
RF95_POW = 20
CLIENT_ADDRESS = 1
SERVER_ADDRESS = 2

led1 = machine.Pin(12, Pin.OUT)
led2 = machine.Pin(13, Pin.OUT)
gate_pin = machine.Pin(14, Pin.OUT)
    
# initialise LoRa radio
lora = LoRa(RFM95_SPIBUS, RFM95_INT, CLIENT_ADDRESS, RFM95_CS, reset_pin=RFM95_RST, freq=RF95_FREQ, tx_power=RF95_POW, acks=True)

# This is the send function that will send a 1 or 0 to the server if the gate sensor is open or closed.
def send_gate_status():
    if gate_pin == 0:
        lora.send_to_wait("0", SERVER_ADDRESS)
    elif gate_pin ==1:
        lora.send_to_wait("1", SERVER_ADDRESS)

# loop and wait for data
while True:
    send_gate_status()
    time.sleep(10)

There are plenty of great applications for this technology on a local level like a farm or even a small factory. Like all technology, it can be used for good or evil and I choose good. I hope you do too.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.