"""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