Files
HenryPump-Drivers/tenflowmeterskid/tenflowmeterskid.py
Nico Melone b042d41fc1 changes
2022-07-21 13:57:30 -05:00

455 lines
22 KiB
Python

"""Driver for tenflowmeterskid"""
import threading
import json
import time
from random import randint
import os
from device_base import deviceBase
from Channel import PLCChannel, ModbusChannel,read_tag, write_tag, TAG_DATAERROR_SLEEPTIME
import persistence
from utilities import get_public_ip_address, get_private_ip_address
from file_logger import filelogger as log
PLC_IP_ADDRESS = "192.168.1.12"
from Tags import tags
from datetime import datetime as dt
_ = None
log.info("tenflowmeterskid startup")
# GLOBAL VARIABLES
WAIT_FOR_CONNECTION_SECONDS = 20
IP_CHECK_PERIOD = 60
CHANNELS = tags
# PERSISTENCE FILE
PERSIST = persistence.load()
if not PERSIST:
PERSIST = {
"ignore_list": []
}
persistence.store(PERSIST)
TOTALIZERS = persistence.load("totalizers.json")
if not TOTALIZERS:
TOTALIZERS = {}
for x in ['total_in', 'total_out', 'fm1', 'fm2', 'fm3', 'fm4', 'fm5', 'fm6', 'fm7', 'fm8', 'fm9', 'fm10']:
TOTALIZERS[x] = {
'Todays': 0,
'Yesterdays': 0,
'Current Months': 0,
'Previous Months': 0,
'Monthly Holding': 0,
'Daily Holding': 0,
'Lifetime': 0,
'Day': 0,
'Month': 0,
'Last Report': 0
}
persistence.store(TOTALIZERS, "totalizers.json")
CALIBRATION_TABLES = [[],[], [], ]
class start(threading.Thread, deviceBase):
"""Start class required by Meshify."""
def __init__(self, name=None, number=None, mac=None, Q=None, mcu=None,
companyId=None, offset=None, mqtt=None, Nodes=None):
"""Initialize the driver."""
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 = "5"
self.finished = threading.Event()
self.force_send = False
self.public_ip_address = ""
self.private_ip_address = ""
self.public_ip_address_last_checked = 0
self.ping_counter = 0
self.flowrates = [0] * 10
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):
"""Register the driver."""
# self.sendtodb("log", "BOOM! Booted.", 0)
pass
def run(self):
"""Actually run the driver."""
for i in range(0, WAIT_FOR_CONNECTION_SECONDS):
print("tenflowmeterskid driver will start in {} seconds".format(WAIT_FOR_CONNECTION_SECONDS - i))
time.sleep(1)
log.info("BOOM! Starting tenflowmeterskid driver...")
self._check_ip_address()
self.nodes["tenflowmeterskid_0199"] = self
send_loops = 0
while True:
now = time.time()
if self.force_send:
log.warning("FORCE SEND: TRUE")
for chan in CHANNELS:
try:
val = chan.read()
if chan.mesh_name == "total_in_flowrate":
val = sum(self.flowrates)
if chan.check(val, self.force_send) or self.check_new_day(chan.mesh_name[:-9]):
if chan.mesh_name in PERSIST["ignore_list"]:
if "lifetime" in chan.mesh_name and chan.mesh_name not in ['forward_out_lifetime', 'reverse_out_lifetime', 'net_out_lifetime']:
self.totalizer_null(chan.mesh_name[:-9])
else:
self.sendtodbDev(1, chan.mesh_name, None, 0, 'tenflowmeterskid')
elif "lifetime" in chan.mesh_name and chan.mesh_name not in ['forward_out_lifetime', 'reverse_out_lifetime', 'net_out_lifetime']:
self.totalize(val, chan.mesh_name[:-9])
else:
self.sendtodbDev(1, chan.mesh_name, chan.value, 0, 'tenflowmeterskid')
if "flowrate" in chan.mesh_name and chan.mesh_name not in ["total_in_flowrate","total_out_flowrate"]:
self.flowrates[int(chan.mesh_name.split("_")[0][2:]) - 1] = val
except Exception as e:
log.error("Error in reading {}".format(chan.mesh_name))
log.error(e)
for pond_index in range(1, 3):
self.read_pond_calibration(pond_index)
# print("tenflowmeterskid driver still alive...")
if self.force_send:
if send_loops > 2:
log.warning("Turning off force_send")
self.force_send = False
send_loops = 0
else:
send_loops += 1
if (now - self.public_ip_address_last_checked) > IP_CHECK_PERIOD:
self._check_ip_address()
time.sleep(5) # sleep to allow Micro800 to handle ENET requests
def check_new_day(self, totalizer):
right_now = dt.today()
day = right_now.day
if not(day == TOTALIZERS[totalizer]['Day']):
return True
return False
def _check_ip_address(self):
"""Check the public IP address and send to Meshify if changed."""
self.public_ip_address_last_checked = time.time()
test_public_ip = get_public_ip_address()
test_public_ip = test_public_ip
test_private_ip = get_private_ip_address()
if not test_public_ip == self.public_ip_address and not test_public_ip == "0.0.0.0":
self.sendtodbDev(1, 'public_ip_address', test_public_ip, 0, 'tenflowmeterskid')
self.public_ip_address = test_public_ip
if not test_private_ip == self.private_ip_address:
self.sendtodbDev(1, 'private_ip_address', test_private_ip, 0, 'tenflowmeterskid')
self.private_ip_address = test_private_ip
hostname = "google.com"
response = 1
try:
response = os.system("ping -c 1 " + hostname + " > /dev/null 2>&1")
except Exception as e:
print("Something went wrong in ping: {}".format(e))
#and then check the response...
if response == 0:
print hostname, 'is up!'
self.ping_counter = 0
else:
print hostname, 'is down!'
self.ping_counter += 1
if self.ping_counter >= 3:
log.info("Rebooting because no internet detected")
os.system('reboot')
def tenflowmeterskid_sync(self, name, value):
"""Sync all data from the driver."""
self.force_send = True
# self.sendtodb("log", "synced", 0)
return True
def read_pond_calibration(self, pond_number):
"""Read all calibration values for a specific pond."""
last_read_height = -1.0
cal_values = []
cal_index = 1
while last_read_height != 0 and cal_index <=10:
cal_tag_height = "pond{}CalibrationHeight[{}]".format(pond_number, cal_index)
cal_tag_volume = "pond{}CalibrationVolume[{}]".format(pond_number, cal_index)
read_height = read_tag(PLC_IP_ADDRESS, cal_tag_height, plc_type="Micro800")
#time.sleep(2)
read_volume = read_tag(PLC_IP_ADDRESS, cal_tag_volume, plc_type="Micro800")
#time.sleep(2)
if read_height and read_volume:
if read_height[0] > 0.0:
cal_values.append({"height": read_height[0], "volume": read_volume[0]})
last_read_height = read_height[0]
cal_index += 1
if cal_values != CALIBRATION_TABLES[pond_number] or self.force_send:
calibration_channel = "pond_{}_calibration".format(pond_number)
calibration_string = json.dumps(cal_values).replace('"', "'")
self.sendtodbDev(1, calibration_channel, calibration_string, 0, 'tenflowmeterskid')
CALIBRATION_TABLES[pond_number] = cal_values
def tenflowmeterskid_deletecalibrationpoint(self, name, value):
"""Delete a calibration point from a calibration table"""
# {"pond": int, "point": int}
value = value.replace("'", '"')
parsed = json.loads(value)
try:
pond_number = int(parsed['pond'])
point_number = int(parsed['point'])
write_pond = write_tag(PLC_IP_ADDRESS, "inpPondNumber", pond_number, plc_type="Micro800")
#time.sleep(2)
write_point = write_tag(PLC_IP_ADDRESS, "inpDeletePointIndex", point_number, plc_type="Micro800")
#time.sleep(2)
if write_pond and write_point:
write_cmd = write_tag(PLC_IP_ADDRESS, "cmdDeleteCalibrationPoint", 1, plc_type="Micro800")
#time.sleep(2)
if write_cmd:
read_val = read_tag(PLC_IP_ADDRESS, "deleteSuccess", plc_type="Micro800")
if read_val[0] == 1:
self.read_pond_calibration(pond_number)
return True
return "Wrote everything successfully, but delete didn't succeed (Check pond and point values)."
return "Didn't write delete command correctly."
return "Didn't write pond or point correctly."
except KeyError as e:
return "Couldn't parse input value: {} -- {}".format(value, e)
def tenflowmeterskid_addcalibrationpoint(self, name, value):
"""Add a calibration point to the calibration table"""
# value = {"pond": int, "height": float, "volume": float}
value = value.replace("'", '"')
parsed = json.loads(value)
try:
# parse json values, throw an error if one is missing
pond_number = int(parsed['pond'])
height = float(parsed['height'])
volume = float(parsed['volume'])
# write values to the tags
write_pond = write_tag(PLC_IP_ADDRESS, "inpPondNumber", pond_number, plc_type="Micro800")
#time.sleep(2)
write_height = write_tag(PLC_IP_ADDRESS, "inpPondHeight", height, plc_type="Micro800")
#time.sleep(2)
write_volume= write_tag(PLC_IP_ADDRESS, "inpPondVolume", volume, plc_type="Micro800")
#time.sleep(2)
if write_pond and write_height and write_volume:
write_cmd = write_tag(PLC_IP_ADDRESS, "cmdAddCalibrationPoint", 1, plc_type="Micro800")
#time.sleep(2)
if write_cmd:
read_val = read_tag(PLC_IP_ADDRESS, "addSuccess", plc_type="Micro800")
if read_val[0] == 1:
self.read_pond_calibration(pond_number)
return True
return "Wrote everything successfully, but delete didn't succeed (Check pond and point values)."
return "Didn't write delete command correctly."
return "Didn't write pond or point correctly."
except KeyError as e:
return "Couldn't parse input value: {} -- {}".format(value, e)
def tenflowmeterskid_setrawmin(self, name, value):
new_val = json.loads(str(value).replace("'", '"'))
tag_n = str(new_val['tag']) # "cmd_Start"
pond_num = tag_n[9]
tag_n = 'pond{}scaling.rawmin'.format(pond_num)
val_n = new_val['val']
print("Raw Min Value: {}".format(val_n))
try:
write_resp = write_tag(str(PLC_IP_ADDRESS), tag_n, val_n, plc_type="Micro800")
print("Result of tenflowmeterskid_writeplctag(self, {}, {}) = {}".format(name, value, write_resp))
self.sendtodbDev(1, 'pond_{}_raw_min'.format(pond_num), val_n, 0, 'tenflowmeterskid')
return True
except Exception as e:
print(e)
return False
def tenflowmeterskid_setrawmax(self, name, value):
new_val = json.loads(str(value).replace("'", '"'))
tag_n = str(new_val['tag']) # "cmd_Start"
pond_num = tag_n[9]
tag_n = 'pond{}scaling.rawmax'.format(pond_num)
val_n = new_val['val']
print("Raw Max Value: {}".format(val_n))
try:
write_resp = write_tag(str(PLC_IP_ADDRESS), tag_n, val_n, plc_type="Micro800")
print("Result of tenflowmeterskid_writeplctag(self, {}, {}) = {}".format(name, value, write_resp))
self.sendtodbDev(1, 'pond_{}_raw_max'.format(pond_num), val_n, 0, 'tenflowmeterskid')
return True
except Exception as e:
print(e)
return False
def tenflowmeterskid_seteumin(self, name, value):
new_val = json.loads(str(value).replace("'", '"'))
tag_n = str(new_val['tag']) # "cmd_Start"
pond_num = tag_n[9]
tag_n = 'pond{}scaling.eumin'.format(pond_num)
val_n = new_val['val']
print("EU Min Value: {}".format(val_n))
try:
write_resp = write_tag(str(PLC_IP_ADDRESS), tag_n, val_n, plc_type="Micro800")
print("Result of tenflowmeterskid_writeplctag(self, {}, {}) = {}".format(name, value, write_resp))
self.sendtodbDev(1, 'pond_{}_eu_min'.format(pond_num), val_n, 0, 'tenflowmeterskid')
return True
except Exception as e:
print(e)
return False
def tenflowmeterskid_seteumax(self, name, value):
new_val = json.loads(str(value).replace("'", '"'))
tag_n = str(new_val['tag']) # "cmd_Start"
pond_num = tag_n[9]
tag_n = 'pond{}scaling.eumax'.format(pond_num)
val_n = new_val['val']
print("EU Max Value: {}".format(val_n))
try:
write_resp = write_tag(str(PLC_IP_ADDRESS), tag_n, val_n, plc_type="Micro800")
print("Result of tenflowmeterskid_writeplctag(self, {}, {}) = {}".format(name, value, write_resp))
self.sendtodbDev(1, 'pond_{}_eu_max'.format(pond_num), val_n, 0, 'tenflowmeterskid')
return True
except Exception as e:
print(e)
return False
def tenflowmeterskid_setoffset(self, name, value):
new_val = json.loads(str(value).replace("'", '"'))
tag_n = str(new_val['tag']) # "cmd_Start"
pond_num = tag_n[9]
val_n = new_val['val']
print("Offset Value: {}".format(val_n))
try:
write_resp = write_tag(str(PLC_IP_ADDRESS), tag_n, val_n, plc_type="Micro800")
print("Result of tenflowmeterskid_writeplctag(self, {}, {}) = {}".format(name, value, write_resp))
self.sendtodbDev(1, 'pond_{}_offset'.format(pond_num), val_n, 0, 'tenflowmeterskid')
return True
except Exception as e:
print(e)
return False
def tenflowmeterskid_writeplctag(self, name, value):
"""Write a value to the PLC."""
new_val = json.loads(str(value).replace("'", '"'))
tag_n = str(new_val['tag']) # "cmd_Start"
val_n = new_val['val']
write_res = write_tag(str(PLC_IP_ADDRESS), tag_n, val_n, plc_type="Micro800")
print("Result of tenflowmeterskid_writeplctag(self, {}, {}) = {}".format(name, value, write_res))
if write_res is None:
write_res = "Error writing to PLC..."
return write_res
def tenflowmeterskid_ignorechannel(self, name, value):
if value not in PERSIST["ignore_list"]:
PERSIST["ignore_list"].append(value)
persistence.store(PERSIST)
return True
def tenflowmeterskid_observechannel(self, name, value):
if value in PERSIST["ignore_list"]:
PERSIST["ignore_list"].remove(value)
persistence.store(PERSIST)
return True
def totalize(self, val, totalizer):
right_now = dt.today()
month = right_now.month
day = right_now.day
#Totalize Today, Yesterday, Month, Last Month
#if the stored day is 0 then it's a fresh run of this should initalize values now
if TOTALIZERS[totalizer]['Day'] == 0:
TOTALIZERS[totalizer]['Day'] = day
TOTALIZERS[totalizer]['Month'] = month
TOTALIZERS[totalizer]['Daily Holding'] = val
TOTALIZERS[totalizer]['Monthly Holding'] = val
persistence.store(TOTALIZERS, 'totalizers.json')
#Communication error during initialization check if lifetime has reported properly and update holdings
if TOTALIZERS[totalizer]['Daily Holding'] == None and not(val == None):
TOTALIZERS[totalizer]['Daily Holding'] = val
TOTALIZERS[totalizer]['Monthly Holding'] = val
try:
if val - TOTALIZERS[totalizer]['Daily Holding'] - TOTALIZERS[totalizer]['Todays'] > 500 or time.time() - TOTALIZERS[totalizer]['Last Report'] > 3600 or self.force_send:
TOTALIZERS[totalizer]['Todays'] = val - TOTALIZERS[totalizer]['Daily Holding']
TOTALIZERS[totalizer]['Current Months'] = val - TOTALIZERS[totalizer]['Monthly Holding']
TOTALIZERS[totalizer]['Lifetime'] = val
self.sendtodbDev(1, '{}_todays'.format(totalizer), TOTALIZERS[totalizer]['Todays'], 0, 'tenflowmeterskid')
self.sendtodbDev(1, '{}_month'.format(totalizer), TOTALIZERS[totalizer]['Current Months'], 0, 'tenflowmeterskid')
self.sendtodbDev(1, '{}_yesterdays'.format(totalizer), TOTALIZERS[totalizer]['Yesterdays'], 0, 'tenflowmeterskid')
self.sendtodbDev(1, '{}_lifetime'.format(totalizer), TOTALIZERS[totalizer]['Lifetime'], 0, 'tenflowmeterskid')
if self.force_send:
self.sendtodbDev(1, '{}_lastmonth'.format(totalizer), TOTALIZERS[totalizer]['Previous Months'], 0, 'tenflowmeterskid')
TOTALIZERS[totalizer]['Last Report'] = time.time()
except:
if time.time() - TOTALIZERS[totalizer]['Last Report'] > 3600 or self.force_send:
self.sendtodbDev(1, '{}_todays'.format(totalizer), TOTALIZERS[totalizer]['Todays'], 0, 'tenflowmeterskid')
self.sendtodbDev(1, '{}_month'.format(totalizer), TOTALIZERS[totalizer]['Current Months'], 0, 'tenflowmeterskid')
self.sendtodbDev(1, '{}_yesterdays'.format(totalizer), TOTALIZERS[totalizer]['Yesterdays'], 0, 'tenflowmeterskid')
self.sendtodbDev(1, '{}_lifetime'.format(totalizer), TOTALIZERS[totalizer]['Lifetime'], 0, 'tenflowmeterskid')
if self.force_send:
self.sendtodbDev(1, '{}_lastmonth'.format(totalizer), TOTALIZERS[totalizer]['Previous Months'], 0, 'tenflowmeterskid')
TOTALIZERS[totalizer]['Last Report'] = time.time()
#If the current day doesn't equal the stored day roll the dailies over
if not(day == TOTALIZERS[totalizer]['Day']):
#if a comms error use the stored values else use the latested values
if val == None:
TOTALIZERS[totalizer]['Yesterdays'] = TOTALIZERS[totalizer]['Todays']
TOTALIZERS[totalizer]['Todays'] = 0
TOTALIZERS[totalizer]['Daily Holding'] = TOTALIZERS[totalizer]['Lifetime']
else:
TOTALIZERS[totalizer]['Yesterdays'] = val - TOTALIZERS[totalizer]['Daily Holding']
TOTALIZERS[totalizer]['Todays'] = 0
TOTALIZERS[totalizer]['Daily Holding'] = val
TOTALIZERS[totalizer]['Lifetime'] = val
TOTALIZERS[totalizer]['Day'] = day
self.sendtodbDev(1, '{}_todays'.format(totalizer), TOTALIZERS[totalizer]['Todays'], 0, 'tenflowmeterskid')
self.sendtodbDev(1, '{}_yesterdays'.format(totalizer), TOTALIZERS[totalizer]['Yesterdays'], 0, 'tenflowmeterskid')
self.sendtodbDev(1, '{}_lifetime'.format(totalizer), TOTALIZERS[totalizer]['Lifetime'], 0, 'tenflowmeterskid')
TOTALIZERS[totalizer]['Last Report'] = time.time()
#the day has rolled over if the month also rolls over
if not(month == TOTALIZERS[totalizer]['Month']):
#if a comms error use the stored values else use the latested values
if val == None:
TOTALIZERS[totalizer]['Previous Months'] = TOTALIZERS[totalizer]['Current Months']
TOTALIZERS[totalizer]['Current Months'] = 0
TOTALIZERS[totalizer]['Monthly Holding'] = TOTALIZERS[totalizer]['Lifetime']
else:
TOTALIZERS[totalizer]['Previous Months'] = val - TOTALIZERS[totalizer]['Monthly Holding']
TOTALIZERS[totalizer]['Current Months'] = 0
TOTALIZERS[totalizer]['Monthly Holding'] = val
TOTALIZERS[totalizer]['Month'] = month
self.sendtodbDev(1, '{}_month'.format(totalizer), TOTALIZERS[totalizer]['Current Months'], 0, 'tenflowmeterskid')
self.sendtodbDev(1, '{}_lastmonth'.format(totalizer), TOTALIZERS[totalizer]['Previous Months'], 0, 'tenflowmeterskid')
self.sendtodbDev(1, '{}_lifetime'.format(totalizer), TOTALIZERS[totalizer]['Lifetime'], 0, 'tenflowmeterskid')
TOTALIZERS[totalizer]['Last Report'] = time.time()
persistence.store(TOTALIZERS, 'totalizers.json')
def totalizer_null(self, totalizer):
if time.time() - TOTALIZERS[totalizer]['Last Report'] > 3600 or self.force_send:
self.sendtodbDev(1, '{}_todays'.format(totalizer), None, 0, 'tenflowmeterskid')
self.sendtodbDev(1, '{}_month'.format(totalizer), None, 0, 'tenflowmeterskid')
self.sendtodbDev(1, '{}_yesterdays'.format(totalizer), None, 0, 'tenflowmeterskid')
self.sendtodbDev(1, '{}_lastmonth'.format(totalizer), None, 0, 'tenflowmeterskid')
self.sendtodbDev(1, '{}_lifetime'.format(totalizer), None, 0, 'tenflowmeterskid')
TOTALIZERS[totalizer]['Last Report'] = time.time()
persistence.store(TOTALIZERS, 'totalizers.json')