Files
HenryPump-Drivers/plcpond/plcpond.py
2020-04-14 14:51:44 -05:00

263 lines
12 KiB
Python

"""Driver for plcpond"""
import threading
import sys
import json
import time
import logging
from random import randint
from device_base import deviceBase
from Channel import PLCChannel, read_tag, write_tag, TAG_DATAERROR_SLEEPTIME
from utilities import get_public_ip_address
import persistence
_ = None
# LOGGING SETUP
from logging.handlers import RotatingFileHandler
log_formatter = logging.Formatter('%(asctime)s %(levelname)s %(funcName)s(%(lineno)d) %(message)s')
logFile = './plcpond.log'
my_handler = RotatingFileHandler(logFile, mode='a', maxBytes=500*1024, backupCount=2, encoding=None, delay=0)
my_handler.setFormatter(log_formatter)
my_handler.setLevel(logging.INFO)
logger = logging.getLogger('plcpond')
logger.setLevel(logging.INFO)
logger.addHandler(my_handler)
console_out = logging.StreamHandler(sys.stdout)
console_out.setFormatter(log_formatter)
logger.addHandler(console_out)
logger.info("plcpond startup")
# GLOBAL VARIABLES
WATCHDOG_SEND_PERIOD = 3600 # Seconds, the longest amount of time before sending the watchdog status
PLC_IP_ADDRESS = "192.168.1.12"
PERSIST = persistence.load('persist.json')
if not PERSIST:
PERSIST = {
'flowmeter_enable': False
}
persistence.store(PERSIST, 'persist.json')
CHANNELS = [
PLCChannel(PLC_IP_ADDRESS, "cfgnumberofponds", "cfgNumberOfPonds", "REAL", 0.5, 3600, map_=False, write_enabled=False, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, "pond1height", "pond1Height", "REAL", 5.0, 3600, map_=False, write_enabled=False, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, "pond2height", "pond2Height", "REAL", 5.0, 3600, map_=False, write_enabled=False, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, "pond3height", "pond3Height", "REAL", 5.0, 3600, map_=False, write_enabled=False, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, "pond4height", "pond4Height", "REAL", 5.0, 3600, map_=False, write_enabled=False, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, "pond1volume", "pond1Volume", "REAL", 10000.0, 3600, map_=False, write_enabled=False, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, "pond2volume", "pond2Volume", "REAL", 10000.0, 3600, map_=False, write_enabled=False, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, "pond3volume", "pond3Volume", "REAL", 10000.0, 3600, map_=False, write_enabled=False, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, "pond4volume", "pond4Volume", "REAL", 10000.0, 3600, map_=False, write_enabled=False, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, "pondvolumetotal", "pondVolumeTotal", "REAL", 200000.0, 3600, map_=False, write_enabled=False, plc_type='Micro800')
]
if PERSIST['flowmeter_enable']:
CHANNELS.append(PLCChannel(PLC_IP_ADDRESS, 'volume_flow', 'Val_FlowMeterFR', 'REAL', 500, 3600, plc_type='Micro800'))
CHANNELS.append(PLCChannel(PLC_IP_ADDRESS, 'totalizer_1', 'Val_FlowMeterT1', 'REAL', 1000, 3600, plc_type='Micro800'))
CHANNELS.append(PLCChannel(PLC_IP_ADDRESS, 'totalizer_2', 'Val_FlowMeterT2', 'REAL', 1000, 3600, plc_type='Micro800'))
CHANNELS.append(PLCChannel(PLC_IP_ADDRESS, 'totalizer_3', 'Val_FlowMeterT3', 'REAL', 1000, 3600, plc_type='Micro800'))
CHANNELS.append(PLCChannel(PLC_IP_ADDRESS, 'today_flow', 'Val_FlowMeterToday', 'REAL', 1000, 3600, plc_type='Micro800'))
CHANNELS.append(PLCChannel(PLC_IP_ADDRESS, 'yesterday_flow', 'Val_FlowMeterYesterday', 'REAL', 1000, 3600, plc_type='Micro800'))
CHANNELS.append(PLCChannel(PLC_IP_ADDRESS, 'current_month_flow', 'Val_FlowMeterMonth', 'REAL', 1000, 3600, plc_type='Micro800'))
CHANNELS.append(PLCChannel(PLC_IP_ADDRESS, 'prev_month_flow', 'Val_FlowMeterLastMonth ', 'REAL', 1000, 3600, plc_type='Micro800'))
CALIBRATION_TABLES = [[],[], [], [], []] # position 0 is a dummy table
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
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."""
wait_sec = 30
for i in range(0, wait_sec):
print("plcpond driver will start in {} seconds".format(wait_sec - i))
time.sleep(1)
logger.info("BOOM! Starting plcpond driver...")
public_ip_address = get_public_ip_address()
self.sendtodbDev(1, 'public_ip_address', public_ip_address, 0, 'plcpond')
watchdog = self.plcpond_watchdog()
self.sendtodbDev(1, 'watchdog', watchdog, 0, 'plcpond')
watchdog_send_timestamp = time.time()
self.nodes["plcpond_0199"] = self
send_loops = 0
watchdog_loops = 0
watchdog_check_after = 5000
while True:
if self.force_send:
logger.warning("FORCE SEND: TRUE")
for c in CHANNELS:
v = c.read()
if v is not None: # read returns None if it fails
if c.check(v, self.force_send):
self.sendtodbDev(1, c.mesh_name, c.value, 0, 'plcpond')
#time.sleep(TAG_DATAERROR_SLEEPTIME) # sleep to allow Micro800 to handle ENET requests
for pond_index in range(1, 5):
self.read_pond_calibration(pond_index)
# print("plcpond driver still alive...")
if self.force_send:
if send_loops > 2:
logger.warning("Turning off force_send")
self.force_send = False
send_loops = 0
else:
send_loops += 1
watchdog_loops += 1
if watchdog_loops >= watchdog_check_after:
test_watchdog = self.plcpond_watchdog()
if not test_watchdog == watchdog or (time.time() - watchdog_send_timestamp) > WATCHDOG_SEND_PERIOD:
self.sendtodbDev(1, 'watchdog', test_watchdog, 0, 'plcpond')
watchdog = test_watchdog
test_public_ip = get_public_ip_address()
if not test_public_ip == public_ip_address:
self.sendtodbDev(1, 'public_ip_address', test_public_ip, 0, 'plcpond')
public_ip_address = test_public_ip
watchdog_loops = 0
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, 'plcpond')
CALIBRATION_TABLES[pond_number] = cal_values
def plcpond_watchdog(self):
"""Write a random integer to the PLC and then 1 seconds later check that it has been decremented by 1."""
randval = randint(0, 32767)
write_tag(str(PLC_IP_ADDRESS), 'watchdog_INT', randval, plc_type="Micro800")
time.sleep(1)
watchdog_val = read_tag(str(PLC_IP_ADDRESS), 'watchdog_INT', plc_type="Micro800")
try:
return (randval - 1) == watchdog_val[0]
except (KeyError, TypeError):
return False
def plcpond_sync(self, name, value):
"""Sync all data from the driver."""
self.force_send = True
# self.sendtodb("log", "synced", 0)
return True
def plcpond_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 plcpond_cfgnumberofponds(self, name, value):
"""Write the number of ponds to the plc."""
value = int(value)
return write_tag(PLC_IP_ADDRESS, "cfgNumberOfPonds", value, plc_type="Micro800")
def plcpond_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 plcpond_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']
w = write_tag(str(PLC_IP_ADDRESS), tag_n, val_n, plc_type="Micro800")
logger.info("Result of plcpond_writeplctag(self, {}, {}) = {}".format(name, value, w))
if w is None:
w = "Error writing to PLC..."
return w