Skip to content

Instantly share code, notes, and snippets.

@adlerweb
Created March 8, 2025 22:45
Show Gist options
  • Save adlerweb/601909e8172a52c650cda85fe23340f0 to your computer and use it in GitHub Desktop.
Save adlerweb/601909e8172a52c650cda85fe23340f0 to your computer and use it in GitHub Desktop.
LoRaWAN Sensor Node based on Heltec Wireless Stick Lite V3 + DS18B20
#include <Arduino.h>
#include <heltec_unofficial.h>
#include <LoRaWAN_ESP32.h>
#include <OneWire.h>
#define HELTEC_WIRELESS_STICK_LITE
#define MINIMUM_DELAY 300 //300 Sekunden = 5 Minuten
const char* band = "EU868";
const uint8_t subband = 0;
const uint64_t joinEUI = 0x0000000000000000;
const uint64_t devEUI = 0x1234567890abcdef;
const uint8_t appKey[] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
const uint8_t nwkKey[] = {0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00};
LoRaWANNode* node;
OneWire ds(1); // Sensor an Pin 1
float getTemperature() {
byte i;
byte present = 0;
byte type_s;
byte data[9];
byte addr[8];
float celsius;
ds.reset_search();
delay(250);
if ( !ds.search(addr)) {
Serial.println("No Sensor found.");
return -255;
}
if (OneWire::crc8(addr, 7) != addr[7]) {
Serial.println("CRC is not valid!");
return -254;
}
// the first ROM byte indicates which chip
switch (addr[0]) {
case 0x10:
//Serial.println(" Chip = DS18S20"); // or old DS1820
type_s = 1;
break;
case 0x28:
//Serial.println(" Chip = DS18B20");
type_s = 0;
break;
case 0x22:
//Serial.println(" Chip = DS1822");
type_s = 0;
break;
default:
Serial.println("Device is not a DS18x20 family device.");
return -253;
}
ds.reset();
ds.select(addr);
ds.write(0x44, 1); // start conversion, with parasite power on at the end
delay(1000); // maybe 750ms is enough, maybe not
// we might do a ds.depower() here, but the reset will take care of it.
present = ds.reset();
ds.select(addr);
ds.write(0xBE); // Read Scratchpad
for ( i = 0; i < 9; i++) { // we need 9 bytes
data[i] = ds.read();
}
// Convert the data to actual temperature
// because the result is a 16 bit signed integer, it should
// be stored to an "int16_t" type, which is always 16 bits
// even when compiled on a 32 bit processor.
int16_t raw = (data[1] << 8) | data[0];
if (type_s) {
raw = raw << 3; // 9 bit resolution default
if (data[7] == 0x10) {
// "count remain" gives full 12 bit resolution
raw = (raw & 0xFFF0) + 12 - data[6];
}
} else {
byte cfg = (data[4] & 0x60);
// at lower res, the low bits are undefined, so let's zero them
if (cfg == 0x00) raw = raw & ~7; // 9 bit resolution, 93.75 ms
else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms
else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms
//// default is 12 bit resolution, 750 ms conversion time
}
celsius = (float)raw / 16.0;
return celsius;
}
void goToSleep(bool useRadio=true, int16_t override=-1) {
Serial.println("[pw] Going to deep sleep now");
heltec_ve(false);
uint32_t delayMs = (uint32_t)MINIMUM_DELAY * 1000;
if(useRadio) {
persist.saveSession(node);
uint32_t interval = node->timeUntilUplink();
uint32_t delayMs = max(interval, (uint32_t)MINIMUM_DELAY * 1000);
}
if(override > 0) delayMs = (uint32_t)override * 1000;
Serial.printf("[pw] Next TX in %i s\n", delayMs/1000);
Serial.flush();
heltec_deep_sleep(delayMs/1000);
}
void setup() {
Serial.begin(115200);
heltec_setup();
heltec_ve(true); //Sensorstrom einschalten
pinMode(1, INPUT_PULLUP);
float temperature = getTemperature();
int16_t temperature_i = (int16_t)(temperature*100);
Serial.print("Temperature: ");
Serial.print(temperature_i);
Serial.print(" -> ");
Serial.print(temperature);
Serial.println("°C");
uint8_t uplinkData[2];
uplinkData[0] = (uint8_t)((temperature_i>>8) & 0xFF);
uplinkData[1] = (uint8_t)(temperature_i & 0xFF);
Serial.print("Uplink Data: ");
for(uint8_t i=0; i<(sizeof(uplinkData)/sizeof(uplinkData[0])); i++) {
if(i>0) Serial.print(", ");
Serial.print("0x");
Serial.print(uplinkData[i], HEX);
}
Serial.println();
int16_t state = radio.begin();
if (state != RADIOLIB_ERR_NONE) {
Serial.println("[RD] Radio did not initialize. Will try again later.");
Serial.println(state);
goToSleep();
}
persist.provision(band, subband, joinEUI, devEUI, appKey, nwkKey);
node = persist.manage(&radio);
if (!node->isActivated()) {
Serial.println("[RD] Could not join network. Maybe out of range. Will try again later.");
goToSleep();
}
node->setDutyCycle(true, 1250);
uint8_t downlinkData[256];
size_t lenDown = sizeof(downlinkData);
state = node->sendReceive(uplinkData, sizeof(uplinkData), 1, downlinkData, &lenDown);
if(state == RADIOLIB_ERR_NONE) {
Serial.println("[RD] Message sent, no downlink received.");
} else if (state > 0) {
Serial.println("[RD] Message sent, downlink received.");
Serial.print("Downlink data: ");
for(int16_t i=0; i<state; i++) {
Serial.printf("0x%02X ", downlinkData[i]);
}
Serial.println();
} else {
Serial.printf("[RD] sendReceive returned error %d, will try again later.\n", state);
}
goToSleep();
}
void loop() {
goToSleep();
}
[env:heltec_wireless_stick_lite]
platform = espressif32
board = heltec_wifi_lora_32_V3
framework = arduino
monitor_speed = 115200
lib_deps =
ropg/Heltec_ESP32_LoRa_v3@^0.9.2
ropg/LoRaWAN_ESP32@^1.2.0
paulstoffregen/OneWire @ ^2.3.8
function decodeUplink(input) {
let data = (input.bytes[0] << 8) | input.bytes[1];
if(data & 0x8000) data -= 0x10000;
let temperature = data / 100.0;
return {
data: {
bytes: input.bytes,
temperature: temperature
},
warnings: [],
errors: []
};
}
#!/usr/bin/env python3
import json
import pytz
from datetime import datetime, timezone
import mysql.connector
import paho.mqtt.client as mqtt
MQTT_SERVER = 'eu1.cloud.thethings.network'
MQTT_PORT = 1883
MQTT_USERNAME = 'meine-test-app@ttn'
MQTT_PASSWORD = 'NNSXS.'
MQTT_TOPIC = f'v3/{MQTT_USERNAME}/devices/#'
MYSQL_SERVER = 'localhost'
MYSQL_USER = 'user'
MYSQL_PASSWORD = 'pass'
MYSQL_DATABASE = 'data'
# MySQL connection
cnx = mysql.connector.connect(user=MYSQL_USER, password=MYSQL_PASSWORD, host=MYSQL_SERVER, database=MYSQL_DATABASE)
cursor = cnx.cursor()
def convert_utc_to_cet(utc_dt_str):
utc_dt = datetime.fromisoformat(utc_dt_str.replace('Z', '+00:00'))
cet_tz = pytz.timezone('Europe/Berlin')
cet_dt = utc_dt.astimezone(cet_tz)
return cet_dt
def parse_message(data):
fail = False
try:
received_at = convert_utc_to_cet(data["uplink_message"]["received_at"])
location = data["end_device_ids"]["device_id"]
temperature = data["uplink_message"]["decoded_payload"]["temperature"]
SF = data["uplink_message"]["settings"]["data_rate"]["lora"]["spreading_factor"]
RSSI = data["uplink_message"]["rx_metadata"][0]["rssi"]
SNR = data["uplink_message"]["rx_metadata"][0]["snr"]
query = """
INSERT INTO data (date, location, value, SF, RSSI, SNR)
VALUES (%s, %s, %s, %s, %s, %s)
"""
cursor.execute(query, (received_at, f"{location}", temperature, SF, RSSI, SNR))
cnx.commit()
print(f"Data inserted into MySQL ({type}, {data['end_device_ids']['device_id']})")
except Exception as e:
print(f"Failed {type} for sensor {data['end_device_ids']['device_id']}")
print(e)
return False
return True
# MQTT connection
def on_connect(client, userdata, flags, reasonCode, properties):
print(f"Connected with reason code {reasonCode}")
client.subscribe(MQTT_TOPIC)
def on_message(client, userdata, msg):
payload = msg.payload.decode()
try:
data = json.loads(payload)
if not parse_message(data):
print(payload)
except json.JSONDecodeError as e:
print(payload)
print("Failed to decode JSON:", e)
except Exception as e:
print(payload)
print("Failed", e)
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)
client.on_connect = on_connect
client.on_message = on_message
client.connect(MQTT_SERVER, MQTT_PORT, 60)
client.loop_forever()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment