Files
ThingsBoard/RPI Docker Test/drivers/lora-industrial.py
2024-10-04 18:53:54 -05:00

602 lines
21 KiB
Python

import types
import traceback
import binascii
import threading
import time
import thread
import os
import struct
import sys
import serial
import pickle
from device_base import deviceBase
import json
import requests
import binascii
import copy
nodeDefaults = {
"a1":{"uval":10,
"lval":0,
"uvolts":10,
"lvolts":0,
"low":0,
"high":10,
"last_value":[],
"last_send_time":0,
"type":None,
"diameter-ft":"",
"width-ft":"",
"length-ft":"",
"diameter-in":"",
"width-in":"",
"length-in":"",
"watertype":"",
"gallons":"",
"vn":"Analog Input 1",
"units":"Volts"
},
"cl1":{"uval":20,
"lval":0,
"uvolts":20,
"lvolts":0,
"low":0,
"high":20,
"last_value":[],
"last_send_time":0,
"type":None,
"diameter-ft":"",
"width-ft":"",
"length-ft":"",
"diameter-in":"",
"width-in":"",
"length-in":"",
"watertype":"",
"gallons":"",
"vn":"Current Loop 1",
"units":"ma"
},
"a2":{"uval":10,
"lval":0,
"uvolts":10,
"lvolts":0,
"low":0,
"high":10,
"last_value":[],
"last_send_time":0,
"type":None,
"diameter-ft":"",
"width-ft":"",
"length-ft":"",
"diameter-in":"",
"width-in":"",
"length-in":"",
"watertype":"",
"gallons":"",
"vn":"Analog Input 2",
"units":"Volts"
},
"cl2":{"uval":20,
"lval":0,
"uvolts":20,
"lvolts":0,
"low":0,
"high":20,
"last_value":[],
"last_send_time":0,
"type":None,
"diameter-ft":"",
"width-ft":"",
"length-ft":"",
"diameter-in":"",
"width-in":"",
"length-in":"",
"watertype":"",
"gallons":"",
"vn":"Current Loop 2",
"units":"ma"
}
}
class start(threading.Thread, deviceBase):
def __init__(self, name=None, number=None, mac=None, Q=None, mcu=None, companyId=None, offset=None, mqtt=None, Nodes=None):
threading.Thread.__init__(self)
deviceBase.__init__(self, name=name, number=number, mac=mac, Q=Q, mcu=mcu, companyId=companyId, offset=offset, mqtt=mqtt, Nodes=Nodes)
self.daemon = True
self.version = "4"
self.finished = threading.Event()
self.firstRun = True
#
self.idDIct = {}
#look for tank set up data
try:
with open('/root/python_firmware/drivers/tanksetup.p', 'rb') as handle:
self.tanksetup = pickle.load(handle)
print "found pickled tank set up data"
print self.tanksetup
except:
print "couldn't load tank set up from pickle"
self.tanksetup = {}
#load stored analog input data
self.analogLock = threading.Lock()
try:
with open('/root/python_firmware/drivers/LoRa_analogData.p', 'rb') as handle:
self.analogData = pickle.load(handle)
print "found analogData dictionary"
print self.analogData
except:
print "couldn't load analogData from pickle"
self.analogData = { }
self.analogDataSync(True)
threading.Thread.start(self)
#this is a required function for all drivers, its goal is to upload some piece of data
#about your device so it can be seen on the web
def register(self):
#send some peice of data to let meshify know you are there
pass
def analogDataSync(self, all=True):
with self.analogLock:
for id in self.analogData:
for item in self.analogData[id]:
self.analogData[id][item]["last_value"] = []
if all:
knownChannels = ["uval","lval","uvolts","lvolts","low","high","type","diameter-ft","width-ft","length-ft","diameter-in","width-in","length-in","watertype","gallons","units","vn"]
for channel in self.analogData[id][item]:
name = None
if item == "a1" and channel in knownChannels:
name = "a1-" + channel
elif item == "cl1" and channel in knownChannels:
name = "cl1-" + channel
elif item == "a2" and channel in knownChannels:
name = "a2-" + channel
elif item == "cl2" and channel in knownChannels:
name = "cl2-" + channel
if name is not None:
self.sendtodbLora(id, name, str(self.analogData[id][item][channel]), 0, "ilora")
def set(self, ser, cmd):
count = 0
resp = ""
while not "ok" in resp.lower():
count += 1
ser.write(cmd + '\r\n')
time.sleep(.005)
resp = ser.readline()
if count > 10:
print "FAILED", cmd
return False
print cmd, "success"
return True
def getsnr(self, ser):
ser.readline()
ser.write("radio get snr" + '\r\n')
time.sleep(.05)
print "here is the snr"
snr = ser.readline()
print snr
return snr.replace("\r\n","").strip()
def killOtherRadio(self, ser2):
while True:
ser2.write("sys sleep 1000000" + '\r\n')
print ser2.readline()
time.sleep(600)
def bin_to_float(self, b):
""" convert binary string to float """
return struct.unpack('<f', b)
def makeIntervalACK(self, id, ackData):
period = ackData.split(",")[0]
ackCount = ackData.split(",")[1]
warmup = ackData.split(",")[2]
inputs = ackData.split(",")[3]
try:
if int(period) > 16777215 or int(ackCount) > 255:
return None
if int(inputs,2) > 255:
return None
if int(warmup) > 100:
return None
except:
return None
val = hex(int(period)).replace('0x','').upper()
while len(val) < 6:
val = "0" + val
val2 = hex(int(ackCount)).replace('0x','').upper()
while len(val2) < 2:
val2 = "0" + val2
val3 = hex(int(warmup)).replace('0x','').upper()
while len(val3) < 2:
val3 = "0" + val3
inputs = hex(int(inputs,2)).replace("0x","").upper()
while len(inputs) < 2:
inputs = "0" + inputs
if len(id + val + val2 + val3 + inputs) == 16:
return ("53" + id + val + val2 + val3 + inputs)
else:
return None
def run(self):
lastMsgTime = time.time()
ser = serial.Serial(port='/dev/ttyUSB0', baudrate=57600, bytesize=8, timeout=5)
ser2 = serial.Serial(port='/dev/ttyUSB1', baudrate=57600, bytesize=8, timeout=5)
thread.start_new_thread(self.killOtherRadio, (ser2,))
ser.readline()
ser.write("mac pause" + '\r\n')
self.gatewayTimes = {}
self.gatewayNewTimes = {}
lst = ["radio set mod lora",
"radio set sf sf12",
"radio set afcbw 41.7",
"radio set rxbw 125",
"radio set fdev 5000",
"radio set prlen 8",
"radio set crc on",
"radio set cr 4/8",
"radio set wdt 0",
"radio set sync 12",
"radio set bw 125",
"radio set pwr 20"]
for i in lst:
self.set(ser, i)
while True:
ser.write("radio rx 0\r\n")
count = 0
try:
while True:
ser.write("radio rx 0\r\n")
time.sleep(1)
count +=1
a = ser.readline()
a = a.strip().replace("busy", "")
if len(a) == 0:
a = ser.readline()
if len(a) > 0:
print a
if "radio_err" in a:
break
if "radio_rx" in a:
lastMsgTime = time.time()
#snr = self.getsnr(ser)
last_time = time.time()
mainObj = {}
values = {}
try:
id = self.parseLoraData(a[10:-2], ser)
if id not in self.idDIct:
self.idDIct[id[0:2]] = id
if id in self.gatewayTimes:
lastTime = self.gatewayTimes[id]
else:
lastTime = time.time()
self.gatewayTimes[id] = time.time()
print "time since last ", time.time() - lastTime
except Exception,e:
print "buffer too short", e
continue
if count > 3000:
ser.write("mac pause" + '\r\n')
for i in lst:
self.set(ser, i)
time.sleep(.05)
if time.time() - lastMsgTime > 1860:
ser.close()
time.sleep(2)
ser = serial.Serial(port='/dev/ttyUSB0', baudrate=57600, bytesize=8, timeout=1)
lastMsgTime = time.time()
break
except Exception,e:
print e
def genericSet(self, name, value, id, longId=None):
print id
print longId
if name == "sinterval":
try:
if longId == None:
longId = self.idDIct[id]
val = self.makeIntervalACK(longId, value)
if val == None:
return "Node Update Data Invalid"
print "here is the new time and id"
print id, val
self.gatewayNewTimes[longId] = value
self.sendtodbLora(longId[0:2] + ":" + longId[2:4], "sinterval", value, 0, "ilora")
return True
except Exception,e:
print e
elif name.split("-")[0] == "a1" or name.split("-")[0] == "a2" or name.split("-")[0] == "cl1" or name.split("-")[0] == "cl2":
#try:
# value = float(value)
#except:
# return(" Value must be a float or integer")
return self.processAnalog(name, value, longId)
def processAnalog(self, name, value, id):
with self.analogLock:
stringTypeNames = ["units", "vn", "type", "watertype"]
try:
analogName = name.split("-")[0]
if len(name.split("-")) == 2:
chName = name.split("-")[1]
elif len(name.split("-")) == 3:
chName = name.split("-")[1] + "-" + name.split("-")[2]
if chName not in stringTypeNames:
self.analogData[id][analogName][chName] = float(value)
else:
self.analogData[id][analogName][chName] = str(value)
#update persistant dictionary
with open('/root/python_firmware/drivers/LoRa_analogData.p', 'wb') as handle:
pickle.dump(self.analogData, handle)
self.sendtodbLora(id, name, value, 0, "ilora")
except Exception, e:
return str(e)
return "device unknown or bad data"
return True
def sendData(self, obj):
s = obj["id"]
#make the short ID
id = s[0:2] + ":" + s[2:4]
chData = obj["values"]
for i in chData:
self.sendtodbLora(id, i, chData[i], 0, "ilora")
def ilora_sync(self, name, value):
self.sendtodb("connected", "true", 0)
#do anything here you want to synce your data (channel name is sync)
return True
def parseLoraData(self, data, ser):
if data[0:2] == "41":
id = data[2:6]
if id in self.gatewayNewTimes:
interval = self.gatewayNewTimes[id]
del self.gatewayNewTimes[id]
print ""
print ""
print "settings update success!!"
print ""
print ""
self.sendtodbLora(id, "interval", str(interval), 0, "ilora")
self.sendtodbLora(id, "sinterval", "No Pending Changes", 0, "ilora")
return id
scale = 16
id = data[4:8]
print "here is the id: ", id
if id not in self.analogData:
print "new device found!!!!!!!!!!"
self.analogData[id] = copy.deepcopy(nodeDefaults)
#update persistant dictionary
with open('/root/python_firmware/drivers/LoRa_analogData.p', 'wb') as handle:
pickle.dump(self.analogData, handle)
key = {
1: "a1",
2: "cl1",
3: "a2",
4: "cl2",
5: "temp",
6: "batt"
}
channels = {}
#first get the ack i/o
if data[0:2] == "04":
sendAck = True
else:
sendAck = False
if sendAck:
#check and see if there are pending changes
#this is where I delete the pendig change from the staging interval
if id in self.gatewayNewTimes:
interval = self.gatewayNewTimes[id]
ack = self.makeIntervalACK(id, interval)
#ack = self.makeIntervalACK(id, "30,5,30,1111")
print "sending... ", ack.upper()
#clear buffer:
ser.readline()
ser.write("radio tx " + ack.upper() + "\r\n")
loopCount = 0
a = ser.readline()
while "ok" not in a:
loopCount += 1
if loopCount > 60:
break
time.sleep(.05)
a = ser.readline()
ser.write("radio rx 0\r\n")
loopCount = 0
a = ser.readline()
while "ok" not in a:
loopCount += 1
if loopCount > 60:
break
time.sleep(.05)
a = ser.readline()
else:
print "sending small ack"
ser.write("radio tx " + "41" + id + "\r\n")
#clear buffer:
ser.readline()
loopCount = 0
a = ser.readline()
while "ok" not in a:
loopCount += 1
if loopCount > 60:
break
time.sleep(.05)
a = ser.readline()
ser.write("radio rx 0\r\n")
loopCount = 0
a = ser.readline()
while "ok" not in a:
loopCount += 1
if loopCount > 60:
break
time.sleep(.05)
a = ser.readline()
bits = bin(int(data[2:4], scale))[2:].zfill(8)
bits = bits[2:][::-1]
place = 1
bitCount = 1
print bits
for bit in bits:
print place
if bit == "1":
val = str(struct.unpack('<f', binascii.a2b_hex(data[(8 * bitCount):((8 * bitCount) + 8)]))[0])
if place == 2 or place == 4:
val = round(float(val) * 1000, 4)
else:
val = round(float(val), 4)
channels[key[place]] = val
bitCount += 1
place += 1
self.processData(id,channels)
for chan in channels:
self.sendtodbLora(id, chan + "-v", channels[chan], 0, "ilora")
print sendAck, id, channels
return id
def processData(self, id, channels):
#we send every time, no need to check timestamps
#lets for now keep a running list of last 10 sends in the last send list
#1. do trasnform to get real units value
#2. if there is a tank type selected, calculate gallons
ignore_data = ["batt","temp"]
for item in channels:
if item in ignore_data:
continue
if item in self.analogData[id]:
print channels
#get its value, its last value, its required change amount, and its last time it sent
val = float(channels[item])
units_high = float(self.analogData[id][item]["uval"])
units_low = float(self.analogData[id][item]["lval"])
volts_high = float(self.analogData[id][item]["uvolts"])
volts_low = float(self.analogData[id][item]["lvolts"])
val = round(((units_high - units_low) / (volts_high - volts_low) * (val - volts_low)) + units_low, 2)
self.sendtodbLora(id, (item), val, 0, "ilora")
try:
type = float(self.analogData[id][item]["type"])
diameter_ft = float(self.analogData[id][item]["diameter-ft"])
width_ft = float(self.analogData[id][item]["width-ft"])
length_ft = float(self.analogData[id][item]["length-ft"])
diameter_in = float(self.analogData[id][item]["diameter-in"])
width_in = float(self.analogData[id][item]["width-in"])
length_in = float(self.analogData[id][item]["length-in"])
gallons = float(self.analogData[id][item]["gallons"])
except:
print "no tank data"
continue
try:
if type == "rectangular":
length = (float(length_in) / 12) + float(length_ft)
width = (float(width_in) / 12) + float(width_ft)
height = float(val)
volumeCubicFeet = length * width * height
gallons = round(7.48052 * volumeCubicFeet, 2)
self.sendtodbLora(id, (item + "-gallons"), gallons, 0, "ilora")
elif type == "cylindrical":
diameter = (float(diameter_in) / 12) + float(diameter_ft)
height = float(val)
volumeCubicFeet = 3.14159265 * (diameter / 2)**2 * height
gallons = round(7.48052 * volumeCubicFeet, 2)
self.sendtodbLora(id, (item + "-gallons"), gallons, 0, "ilora")
elif type == "horizontal":
import math
r = ((float(diameter_in) / 12) + float(diameter_ft)) / 2
length = (float(length_in) / 12) + float(length_ft)
height = float(val)
volumeCubicFeet = length*(r**2 * math.acos( (r-height)/r )- (r-height)* math.sqrt(2*height*r-height**2) )
gallons = round(7.48052 * volumeCubicFeet, 2)
self.sendtodbLora(id, (item + "-gallons"), gallons, 0, "stnk")
except:
pass
last_val = self.analogData[id][item]["last_value"]
last_time = float(self.analogData[id][item]["last_send_time"])
self.analogData[id][item]["last_value"] = val
self.analogData[id][item]["last_send_time"] = time.time()