From b5057347f6735c38d576886508c7897557eb9a3d Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 2 Jun 2023 20:36:23 +0200 Subject: [PATCH] initial work for rak wisnode trackit --- .gitignore | 3 +- rak2171.py | 170 ++++++++++++++++++++++++++++++++++++++++++ ttnscripts/rak2171.js | 76 +++++++++++++++++++ 3 files changed, 247 insertions(+), 2 deletions(-) create mode 100644 rak2171.py create mode 100644 ttnscripts/rak2171.js diff --git a/.gitignore b/.gitignore index 270cfe9..e6905a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ -.env -.env2 +.env* \ No newline at end of file diff --git a/rak2171.py b/rak2171.py new file mode 100644 index 0000000..d47e70f --- /dev/null +++ b/rak2171.py @@ -0,0 +1,170 @@ +import paho.mqtt.client as mqtt +import json +import os +import logging +import signal +import requests +from datetime import datetime +from dotenv import find_dotenv, load_dotenv +from pprint import pprint + +load_dotenv(find_dotenv(filename=".env_rak2171")) +SRC_MQTT_HOST = os.getenv("SRC_MQTT_HOST") +SRC_MQTT_USER = os.getenv("SRC_MQTT_USER") +SRC_MQTT_PASS = os.getenv("SRC_MQTT_PASS") +DST_MQTT_HOST = os.getenv("DST_MQTT_HOST") +DST_MQTT_USER = os.getenv("DST_MQTT_USER") +DST_MQTT_PASS = os.getenv("DST_MQTT_PASS") +DST_TRACCAR_URL = os.getenv("DST_TRACCAR_URL") +PUSHOVER_TOKEN = os.getenv("PUSHOVER_TOKEN") +PUSHOVER_USER_KEY = os.getenv("PUSHOVER_USER_KEY") +VERSION = "v1.0" + +OT_TOPIC_PREFIX = "owntracks/things/" + + +def on_connect_ttn(client, userdata, flags, rc): + logging.info("connected to ttn %s - %s", SRC_MQTT_HOST, str(rc)) + client.subscribe("v3/+/devices/+/up") + + +def on_connect_ot(client, userdata, flags, rc): + logging.info("connected to ot %s - %s", DST_MQTT_HOST, str(rc)) + + +def on_publish_ot(client, userdata, rc): + logging.info("published data to ot") + + +def on_log(client, userdata, level, buf): + logging_level = mqtt.LOGGING_LEVEL[level] + logging.log(logging_level, buf) + # logging.info("got a log message level %s: %s", level, str(buf)) + + +# The callback for when a PUBLISH message is received from the server. +def on_message_ttn(client, userdata, msg): + data = json.loads(msg.payload) + device_id = data["end_device_ids"]["device_id"] + logging.info( + "message from ttn received for %s via %s", + device_id, + data["uplink_message"]["network_ids"]["cluster_id"], + ) + + # retrieve info about gateway + # gtw_id = data["uplink_message"]["rx_metadata"][0]["gateway_ids"]["gateway_id"] + # for gtw in data["uplink_message"]["rx_metadata"]: + # pprint(gtw) + # gtw_id += data["uplink_message"]["rx_metadata"][gtw]["gateway_ids"]["gateway_id"] + # logging.info("received via gw %s", gtw_id) + + # the decoded data + fix = data["uplink_message"]["decoded_payload"].get("fix", 0) + sos = data["uplink_message"]["decoded_payload"].get("sos", 0) + alarm = data["uplink_message"]["decoded_payload"].get("alarm", 0) + if fix == 1: + logging.info("got payload with a fix") + ot_data = json.dumps( + { + "_type": "location", + "acc": data["uplink_message"]["decoded_payload"].get("acc", 0), + "lat": data["uplink_message"]["decoded_payload"].get("lat", 0.0), + "lon": data["uplink_message"]["decoded_payload"].get("lng", 0.0), + "batt": data["uplink_message"]["decoded_payload"].get("batt", 0), + "t": "p", + "tid": device_id, + "tst": data["uplink_message"]["decoded_payload"].get("time", 0), + "conn": "m", + } + ) + + # publish to owntracks via MQTT + logging.info( + "publishing data to owntracks via mqtt to topic %s", + OT_TOPIC_PREFIX + device_id, + ) + client_ot.publish( + OT_TOPIC_PREFIX + device_id, payload=ot_data, retain=True, qos=1 + ) + + # send to traccar + # logging.info("publishing data to traccar") + # traccar_url = f"{DST_TRACCAR_URL}/?id={device_id}&lat={latitude}&lon={longitude}×tamp={timestamp}&hdop={hdop}&altitude={altitude}&speed=0" + # requests.get(traccar_url) + elif sos == 1: + logging.info("got payload with a sos") + requests.post( + "https://api.pushover.net/1/messages.json", + data={ + "token": PUSHOVER_TOKEN, + "user": PUSHOVER_USER_KEY, + "message": f"SOS from {device_id}", + }, + ) + elif alarm == 1: + logging.info("got payload with an alarm") + else: + logging.info("no usable payload - skipping") + + +def shutdown(): + logging.info("disconnecting from mqtt") + client_ot.disconnect() + client_ot.loop_stop() + client_ttn.disconnect() + client_ttn.loop_stop() + + +def handleSIGTERM(signalNumber, frame): + logging.info("got SIGTERM") + shutdown() + return + + +if __name__ == "__main__": + signal.signal(signal.SIGTERM, handleSIGTERM) + + logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(message)s", + datefmt="%Y-%m-%d %H:%M:%S %Z", + ) + + logging.info("Starting ioteer rak2171. " + VERSION) + + # Prepare MQTT for The Things Network + client_ttn = mqtt.Client() + client_ttn.enable_logger() + client_ttn.on_connect = on_connect_ttn + client_ttn.on_message = on_message_ttn + client_ttn.on_log = on_log + client_ttn.username_pw_set(SRC_MQTT_USER, SRC_MQTT_PASS) + client_ttn.tls_set() + client_ttn.connect(SRC_MQTT_HOST, 8883, 60) + + # Prepare MQTT for OwnTracks + ot_lwt = json.dumps( + { + "_type": "lwt", + "tst": int(datetime.timestamp(datetime.now())), + } + ) + client_ot = mqtt.Client() + client_ot.enable_logger() + client_ot.on_connect = on_connect_ot + client_ot.on_publish = on_publish_ot + client_ot.on_log = on_log + client_ot.username_pw_set(DST_MQTT_USER, DST_MQTT_PASS) + client_ot.tls_set() + # todo: this is wrong + # client_ot.will_set(OT_TOPIC_PREFIX + device_id, payload=ot_lwt, qos=1, retain=True) + client_ot.connect(DST_MQTT_HOST, 8883, 60) + + try: + # Connect to MQTT and react to messages + client_ot.loop_start() + client_ttn.loop_forever() + except KeyboardInterrupt: + shutdown() + logging.info("tschuess") diff --git a/ttnscripts/rak2171.js b/ttnscripts/rak2171.js new file mode 100644 index 0000000..891ad60 --- /dev/null +++ b/ttnscripts/rak2171.js @@ -0,0 +1,76 @@ +// For TTN and Datacake +function Decoder(bytes, fPort) { + return Decode(fPort, bytes); +} + +// For Chirpstack +function Decode(fPort, bytes) { + var decoded = {}; + + // Adjust time zone only in older RAK2171 firmware versions! + // adjust time zone, here Asia/Manila = +8H + var my_time_zone = 0; + // (8 * 60 * 60); + + decoded.num = bytes[1]; + decoded.app_id = (bytes[2] << 24) | (bytes[3] << 16) | (bytes[4] << 8) | bytes[5]; + decoded.dev_id = (bytes[6] << 24) | (bytes[7] << 16) | (bytes[8] << 8) | bytes[9]; + switch (bytes[0]) { + case 0xCA: // No Location fix + decoded.acc = 0; + decoded.fix = 0; + decoded.batt = bytes[10]; + decoded.time = ((bytes[11] << 24) | (bytes[12] << 16) | (bytes[13] << 8) | bytes[14]); + // adjust time zone + decoded.time = decoded.time + my_time_zone; + var dev_date = new Date(decoded.time * 1000); + decoded.time_stamp = dev_date.getHours() + ":" + dev_date.getMinutes(); + decoded.date_stamp = dev_date.getDate() + "." + (dev_date.getMonth() + 1) + "." + dev_date.getFullYear(); + decoded.stat = bytes[15] & 0x03; + decoded.gps = bytes[15] & 0x0C; + break; + case 0xCB: // Location fix + decoded.fix = 1; + decoded.batt = bytes[20]; + decoded.time = ((bytes[21] << 24) | (bytes[22] << 16) | (bytes[23] << 8) | bytes[24]); + // adjust time zone + decoded.time = decoded.time + my_time_zone; + var dev_date = new Date(decoded.time * 1000); + decoded.time_stamp = dev_date.getHours() + ":" + dev_date.getMinutes(); + decoded.date_stamp = dev_date.getDate() + "." + (dev_date.getMonth() + 1) + "." + dev_date.getFullYear(); + decoded.stat = bytes[25] & 0x03; + decoded.gps = bytes[25] & 0x0C; + decoded.lng = (((bytes[10] << 24) | (bytes[11] << 16) | (bytes[12] << 8) | bytes[13]) * 0.000001).toFixed(6); + decoded.lat = (((bytes[14] << 24) | (bytes[15] << 16) | (bytes[16] << 8) | bytes[17]) * 0.000001).toFixed(6); + decoded.acc = bytes[18]; + decoded.gps_start = bytes[19]; + decoded.location = "(" + decoded.latitude + "," + decoded.longitude + ")"; + break; + case 0xCC: // SOS + decoded.sos = 1; + decoded.lng = (((bytes[10] << 24) | (bytes[11] << 16) | (bytes[12] << 8) | bytes[13]) * 0.000001).toFixed(6); + decoded.lat = (((bytes[14] << 24) | (bytes[15] << 16) | (bytes[16] << 8) | bytes[17]) * 0.000001).toFixed(6); + if (bytes.length > 18) { + var i; + for (i = 18; i < 28; i++) { + decoded.name += bytes[i].toString(); + } + for (i = 28; i < 40; i++) { + decoded.country += bytes[i].toString(); + } + for (i = 39; i < 50; i++) { + decoded.phone += bytes[i].toString(); + } + } + decoded.location = "(" + decoded.latitude + "," + decoded.longitude + ")"; + break; + case 0xCD: + decoded.sos = 0; + break; + case 0xCE: + decoded.alarm = 0x01; + decoded.alarm_lvl = bytes[10]; + break; + } + return decoded; +} \ No newline at end of file