Created
March 8, 2025 22:45
-
-
Save adlerweb/601909e8172a52c650cda85fe23340f0 to your computer and use it in GitHub Desktop.
LoRaWAN Sensor Node based on Heltec Wireless Stick Lite V3 + DS18B20
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: [] | |
}; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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