From 1bddf775916801776c97e1ce345aa49b081e26c1 Mon Sep 17 00:00:00 2001 From: Nico Melone Date: Fri, 11 Sep 2020 14:08:38 -0500 Subject: [PATCH] Updated totalizers --- advvfdipppond/Tags.py | 79 ++-- advvfdipppond/advvfdipppond.py | 107 ++++- advvfdipppond/config.txt | 2 +- flow-monitor/flow-monitorv2/Channel.py | 349 ++++++++++++++ flow-monitor/flow-monitorv2/Tags.py | 12 + flow-monitor/flow-monitorv2/config.txt | 14 + flow-monitor/flow-monitorv2/file_logger.py | 18 + flow-monitor/flow-monitorv2/flowmonitor.py | 522 +++++++++++++++++++++ flow-monitor/flow-monitorv2/persistence.py | 21 + flow-monitor/flow-monitorv2/utilities.py | 64 +++ flowmeterskid/Tags.py | 16 +- flowmeterskid/config.txt | 2 +- flowmeterskid/flowmeterskid.py | 115 ++++- plcfreshwater/config.txt | 2 +- plcfreshwater/plcfreshwater.py | 8 +- promagmbs/rework/Channel.py | 344 ++++++++++---- promagmbs/rework/PiFlow.py | 519 ++++++++++++++++++++ promagmbs/rework/Tags.py | 92 ++++ tenflowmeterskid/Tags.py | 45 +- tenflowmeterskid/config.txt | 2 +- tenflowmeterskid/tenflowmeterskid.py | 116 ++++- 21 files changed, 2270 insertions(+), 179 deletions(-) create mode 100644 flow-monitor/flow-monitorv2/Channel.py create mode 100644 flow-monitor/flow-monitorv2/Tags.py create mode 100644 flow-monitor/flow-monitorv2/config.txt create mode 100644 flow-monitor/flow-monitorv2/file_logger.py create mode 100644 flow-monitor/flow-monitorv2/flowmonitor.py create mode 100644 flow-monitor/flow-monitorv2/persistence.py create mode 100644 flow-monitor/flow-monitorv2/utilities.py create mode 100644 promagmbs/rework/PiFlow.py create mode 100644 promagmbs/rework/Tags.py diff --git a/advvfdipppond/Tags.py b/advvfdipppond/Tags.py index d428db3..f260674 100644 --- a/advvfdipppond/Tags.py +++ b/advvfdipppond/Tags.py @@ -10,40 +10,15 @@ tags = [ PLCChannel(PLC_IP_ADDRESS, "fm6_flowrate", "Val_FM6_FR", "REAL", 1000000, 3600, plc_type='CLX'), PLCChannel(PLC_IP_ADDRESS, "total_out_flowrate", "Val_FRTotalOut", "REAL", 1000000, 3600, plc_type='CLX'), PLCChannel(PLC_IP_ADDRESS, "total_in_flowrate", "Val_FRTotalIn", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "fm1_todays", "Flowmeter_Totals.Todays_Totalflow", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "fm2_todays", "Val_FM2_Todays", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "fm3_todays", "Val_FM3_Todays", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "fm4_todays", "Val_FM4_Todays", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "fm5_todays", "Val_FM5_Todays", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "fm6_todays", "Val_FM6_Todays", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "total_out_todays", "Val_TodaysTotalOut", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "total_in_todays", "Val_TodaysTotalIn", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "fm1_yesterdays", "Flowmeter_Totals.Yesterdays_Totalflow", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "fm2_yesterdays", "Val_FM2_Yesterdays", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "fm3_yesterdays", "Val_FM3_Yesterdays", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "fm4_yesterdays", "Val_FM4_Yesterdays", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "fm5_yesterdays", "Val_FM5_Yesterdays", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "fm6_yesterdays", "Val_FM6_Yesterdays", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "total_out_yesterdays", "Val_YesterdaysTotalOut", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "total_in_yesterdays", "Val_YesterdaysTotalIn", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "fm1_month", "Flowmeter_Totals.CurrentMonth_Totalflow", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "fm2_month", "Val_FM2_Months", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "fm3_month", "Val_FM3_Months", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "fm4_month", "Val_FM4_Months", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "fm5_month", "Val_FM5_Months", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "fm6_month", "Val_FM6_Months", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "total_out_months", "Val_MonthsTotalOut", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "total_in_months", "Val_MonthsTotalIn", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "fm1_lastmonths", "Flowmeter_Totals.LastMonth_Totalflow", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "fm2_lastmonths", "Val_FM2_LastMonths", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "fm3_lastmonths", "Val_FM3_LastMonths", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "fm4_lastmonths", "Val_FM4_LastMonths", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "fm5_lastmonths", "Val_FM5_LastMonths", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "fm6_lastmonths", "Val_FM6_LastMonths", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "total_out_lastmonths", "Val_LastMonthsTotalOut", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "total_in_lastmonths", "Val_LastMonthsTotalIn", "REAL", 1000000, 3600, plc_type='CLX'), - PLCChannel(PLC_IP_ADDRESS, "pond_level","val_FluidLevel","REAL", 200, 3600, plc_type="CLX"), - PLCChannel(PLC_IP_ADDRESS, "pond_volume","val_PondVolume","REAL", 1000000, 3600, plc_type="CLX"), + PLCChannel(PLC_IP_ADDRESS, "fm1_lifetime", "Flowmeter_Totals.Flow_Totalizer", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm2_lifetime", "Val_FM2_T1", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm3_lifetime", "Val_FM3_T1", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm4_lifetime", "Val_FM4_T1", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm5_lifetime", "Val_FM5_T1", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm6_lifetime", "Val_FM6_T1", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "total_in_lifetime", "Total_In.Flow_Totalizer", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "pond_level","val_FluidLevel","REAL", 200, 3600, plc_type="CLX"), + #PLCChannel(PLC_IP_ADDRESS, "pond_volume","val_PondVolume","REAL", 1000000, 3600, plc_type="CLX"), PLCChannel(PLC_IP_ADDRESS, "intake_pressure","val_IntakePressure","REAL", 100000, 3600, plc_type="CLX"), PLCChannel(PLC_IP_ADDRESS, "intake_temperature","val_IntakeTemperature","REAL", 10000, 3600, plc_type="CLX"), PLCChannel(PLC_IP_ADDRESS, "discharge_pressure","val_TubingPressure","REAL", 10000, 3600, plc_type="CLX"), @@ -86,4 +61,38 @@ tags = [ PLCChannel(PLC_IP_ADDRESS, "total_out_net","in_HART_Flowmeter_Net","REAL", 1000000, 3600, plc_type="CLX"), PLCChannel(PLC_IP_ADDRESS, "total_out_forward","in_HART_Flowmeter_Fwd","REAL", 1000000, 3600, plc_type="CLX"), PLCChannel(PLC_IP_ADDRESS, "total_out_reverse","in_HART_Flowmeter_Rev","REAL", 1000000, 3600, plc_type="CLX") -] \ No newline at end of file +] + + +""" PLCChannel(PLC_IP_ADDRESS, "fm1_todays", "Flowmeter_Totals.Today_Totalflow", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm2_todays", "Val_FM2_Todays", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm3_todays", "Val_FM3_Todays", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm4_todays", "Val_FM4_Todays", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm5_todays", "Val_FM5_Todays", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm6_todays", "Val_FM6_Todays", "REAL", 1000000, 3600, plc_type='CLX'), + #PLCChannel(PLC_IP_ADDRESS, "total_out_todays", "Val_TodaysTotalOut", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "total_in_todays", "Val_TodaysTotalIn", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm1_yesterdays", "Flowmeter_Totals.Yesterdays_Totalflow", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm2_yesterdays", "Val_FM2_Yesterdays", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm3_yesterdays", "Val_FM3_Yesterdays", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm4_yesterdays", "Val_FM4_Yesterdays", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm5_yesterdays", "Val_FM5_Yesterdays", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm6_yesterdays", "Val_FM6_Yesterdays", "REAL", 1000000, 3600, plc_type='CLX'), + #PLCChannel(PLC_IP_ADDRESS, "total_out_yesterdays", "Val_YesterdaysTotalOut", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "total_in_yesterdays", "Val_YesterdaysTotalIn", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm1_month", "Flowmeter_Totals.Monthlys_Totalflow", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm2_month", "Val_FM2_Months", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm3_month", "Val_FM3_Months", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm4_month", "Val_FM4_Months", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm5_month", "Val_FM5_Months", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm6_month", "Val_FM6_Months", "REAL", 1000000, 3600, plc_type='CLX'), + #PLCChannel(PLC_IP_ADDRESS, "total_out_months", "Val_MonthsTotalOut", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "total_in_months", "Val_MonthsTotalIn", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm1_lastmonths", "Flowmeter_Totals.PrevMonthlys_Totalflow", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm2_lastmonths", "Val_FM2_LastMonths", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm3_lastmonths", "Val_FM3_LastMonths", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm4_lastmonths", "Val_FM4_LastMonths", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm5_lastmonths", "Val_FM5_LastMonths", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "fm6_lastmonths", "Val_FM6_LastMonths", "REAL", 1000000, 3600, plc_type='CLX'), + #PLCChannel(PLC_IP_ADDRESS, "total_out_lastmonths", "Val_LastMonthsTotalOut", "REAL", 1000000, 3600, plc_type='CLX'), + PLCChannel(PLC_IP_ADDRESS, "total_in_lastmonths", "Val_LastMonthsTotalIn", "REAL", 1000000, 3600, plc_type='CLX'), """ \ No newline at end of file diff --git a/advvfdipppond/advvfdipppond.py b/advvfdipppond/advvfdipppond.py index 0b51d73..2f518bf 100644 --- a/advvfdipppond/advvfdipppond.py +++ b/advvfdipppond/advvfdipppond.py @@ -28,6 +28,7 @@ from Channel import PLCChannel, ModbusChannel,read_tag, write_tag, TAG_DATAERROR from utilities import get_public_ip_address, get_private_ip_address, get_additional_tags, convert_int from file_logger import filelogger as log from Tags import tags +from datetime import datetime as dt path = "/root/python_firmware/drivers/additional_tags.py" @@ -53,6 +54,23 @@ IP_CHECK_PERIOD = 60 CHANNELS = tags + additional_tags +TOTALIZERS = persistence.load("totalizers.json") +if not TOTALIZERS: + TOTALIZERS = {} + for x in ['total_in','fm1', 'fm2', 'fm3', 'fm4', 'fm5', 'fm6']: + 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") class start(threading.Thread, deviceBase): """Start class required by Meshify.""" @@ -66,7 +84,7 @@ class start(threading.Thread, deviceBase): mqtt=mqtt, Nodes=Nodes) self.daemon = True - self.version = "1" + self.version = "2" self.finished = threading.Event() self.force_send = False self.public_ip_address = "" @@ -123,6 +141,8 @@ class start(threading.Thread, deviceBase): if chan.plc_tag in convert_list: converted_value = convert_int(chan.plc_tag, val) self.sendtodbDev(1, chan.mesh_name, converted_value, 0, 'advvfdipppond') + elif "lifetime" in chan.mesh_name: + self.totalize(val,chan.mesh_name[:-9]) else: self.sendtodbDev(1, chan.mesh_name, chan.value, 0, 'advvfdipppond') #time.sleep(TAG_DATAERROR_SLEEPTIME) # sleep to allow Micro800 to handle ENET requests @@ -211,4 +231,87 @@ class start(threading.Thread, deviceBase): new_val = json.loads(str(value).replace("'", '"')) PERSIST['flowmeter_units'] = new_val persistence.store(PERSIST, "extra_data.json") - self.sendtodbDev(1, 'flowunits', PERSIST['flowmeter_units'], 0, 'advvfdipppond') \ No newline at end of file + self.sendtodbDev(1, 'flowunits', PERSIST['flowmeter_units'], 0, 'advvfdipppond') + + 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, 'advvfdipppond') + if totalizer == "total_in": + self.sendtodbDev(1, '{}_months'.format(totalizer), TOTALIZERS[totalizer]['Current Months'], 0, 'advvfdipppond') + else: + self.sendtodbDev(1, '{}_month'.format(totalizer), TOTALIZERS[totalizer]['Current Months'], 0, 'advvfdipppond') + self.sendtodbDev(1, '{}_yesterdays'.format(totalizer), TOTALIZERS[totalizer]['Yesterdays'], 0, 'advvfdipppond') + self.sendtodbDev(1, '{}_lifetime'.format(totalizer), TOTALIZERS[totalizer]['Lifetime'], 0, 'advvfdipppond') + if self.force_send: + self.sendtodbDev(1, '{}_lastmonths'.format(totalizer), TOTALIZERS[totalizer]['Previous Months'], 0, 'advvfdipppond') + 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, 'advvfdipppond') + if totalizer == "total_in": + self.sendtodbDev(1, '{}_months'.format(totalizer), TOTALIZERS[totalizer]['Current Months'], 0, 'advvfdipppond') + else: + self.sendtodbDev(1, '{}_month'.format(totalizer), TOTALIZERS[totalizer]['Current Months'], 0, 'advvfdipppond') + self.sendtodbDev(1, '{}_yesterdays'.format(totalizer), TOTALIZERS[totalizer]['Yesterdays'], 0, 'advvfdipppond') + self.sendtodbDev(1, '{}_lifetime'.format(totalizer), TOTALIZERS[totalizer]['Lifetime'], 0, 'advvfdipppond') + if self.force_send: + self.sendtodbDev(1, '{}_lastmonths'.format(totalizer), TOTALIZERS[totalizer]['Previous Months'], 0, 'advvfdipppond') + 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, 'advvfdipppond') + self.sendtodbDev(1, '{}_yesterdays'.format(totalizer), TOTALIZERS[totalizer]['Yesterdays'], 0, 'advvfdipppond') + self.sendtodbDev(1, '{}_lifetime'.format(totalizer), TOTALIZERS[totalizer]['Lifetime'], 0, 'advvfdipppond') + 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 + if totalizer == "total_in": + self.sendtodbDev(1, '{}_months'.format(totalizer), TOTALIZERS[totalizer]['Current Months'], 0, 'advvfdipppond') + else: + self.sendtodbDev(1, '{}_month'.format(totalizer), TOTALIZERS[totalizer]['Current Months'], 0, 'advvfdipppond') + self.sendtodbDev(1, '{}_lastmonths'.format(totalizer), TOTALIZERS[totalizer]['Previous Months'], 0, 'advvfdipppond') + self.sendtodbDev(1, '{}_lifetime'.format(totalizer), TOTALIZERS[totalizer]['Lifetime'], 0, 'advvfdipppond') + TOTALIZERS[totalizer]['Last Report'] = time.time() + persistence.store(TOTALIZERS, 'totalizers.json') \ No newline at end of file diff --git a/advvfdipppond/config.txt b/advvfdipppond/config.txt index 1c03c6c..cc5dc47 100644 --- a/advvfdipppond/config.txt +++ b/advvfdipppond/config.txt @@ -8,7 +8,7 @@ "file4": "Tags.py" }, "deviceName": "advvfdipppond", - "releaseVersion": "1", + "releaseVersion": "2", "driverFileName": "advvfdipppond.py", "driverId": "0100" } \ No newline at end of file diff --git a/flow-monitor/flow-monitorv2/Channel.py b/flow-monitor/flow-monitorv2/Channel.py new file mode 100644 index 0000000..e95cd9b --- /dev/null +++ b/flow-monitor/flow-monitorv2/Channel.py @@ -0,0 +1,349 @@ +"""Define Meshify channel class.""" +import time +from pycomm.ab_comm.clx import Driver as ClxDriver +from pycomm.cip.cip_base import CommError, DataError +import struct +#from file_logger import filelogger as log + +from flowmonitor import INSTRUMENT, log, lock + +instrument = INSTRUMENT + + +TAG_DATAERROR_SLEEPTIME = 5 + +def binarray(intval): + """Split an integer into its bits.""" + bin_string = '{0:08b}'.format(intval) + bin_arr = [i for i in bin_string] + bin_arr.reverse() + return bin_arr + + +def read_tag(addr, tag, plc_type="CLX"): + """Read a tag from the PLC.""" + direct = plc_type == "Micro800" + clx = ClxDriver() + try: + if clx.open(addr, direct_connection=direct): + try: + val = clx.read_tag(tag) + clx.close() + return val + except DataError as err: + clx.close() + time.sleep(TAG_DATAERROR_SLEEPTIME) + log.error("Data Error during readTag({}, {}): {}".format(addr, tag, err)) + except CommError: + # err = c.get_status() + + log.error("Could not connect during readTag({}, {})".format(addr, tag)) + except AttributeError as err: + clx.close() + log.error("AttributeError during readTag({}, {}): \n{}".format(addr, tag, err)) + clx.close() + return False + + +def read_array(addr, tag, start, end, plc_type="CLX"): + """Read an array from the PLC.""" + direct = plc_type == "Micro800" + clx = ClxDriver() + if clx.open(addr, direct_connection=direct): + arr_vals = [] + try: + for i in range(start, end): + tag_w_index = tag + "[{}]".format(i) + val = clx.read_tag(tag_w_index) + arr_vals.append(round(val[0], 4)) + if arr_vals: + clx.close() + return arr_vals + else: + log.error("No length for {}".format(addr)) + clx.close() + return False + except Exception: + log.error("Error during readArray({}, {}, {}, {})".format(addr, tag, start, end)) + err = clx.get_status() + clx.close() + log.error(err) + clx.close() + + +def write_tag(addr, tag, val, plc_type="CLX"): + """Write a tag value to the PLC.""" + direct = plc_type == "Micro800" + clx = ClxDriver() + try: + if clx.open(addr, direct_connection=direct): + try: + initial_val = clx.read_tag(tag) + write_status = clx.write_tag(tag, val, initial_val[1]) + clx.close() + return write_status + except DataError as err: + clx_err = clx.get_status() + clx.close() + log.error("--\nDataError during writeTag({}, {}, {}, plc_type={}) -- {}\n{}\n".format(addr, tag, val, plc_type, err, clx_err)) + + except CommError as err: + clx_err = clx.get_status() + log.error("--\nCommError during write_tag({}, {}, {}, plc_type={})\n{}\n--".format(addr, tag, val, plc_type, err)) + clx.close() + return False + +def byteSwap32(array): + #array is a list of 2 dec numbers + newVal = "" + for i in array: + i = hex(i).replace('0x', '') + while len(i) < 4: + i = "0" + i + print i + newVal = i + newVal + print newVal + return struct.unpack('!f', newVal.decode('hex'))[0] + +class Channel(object): + """Holds the configuration for a Meshify channel.""" + + def __init__(self, mesh_name, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False): + """Initialize the channel.""" + self.mesh_name = mesh_name + self.data_type = data_type + self.last_value = None + self.value = None + self.last_send_time = 0 + self.chg_threshold = chg_threshold + self.guarantee_sec = guarantee_sec + self.map_ = map_ + self.write_enabled = write_enabled + + def __str__(self): + """Create a string for the channel.""" + return "{}\nvalue: {}, last_send_time: {}".format(self.mesh_name, self.value, self.last_send_time) + + def check(self, new_value, force_send=False): + """Check to see if the new_value needs to be stored.""" + send_needed = False + send_reason = "" + if self.data_type == 'BOOL' or self.data_type == 'STRING': + if self.last_send_time == 0: + send_needed = True + send_reason = "no send time" + elif self.value is None: + send_needed = True + send_reason = "no value" + elif self.value != new_value: + if self.map_: + if not self.value == self.map_[new_value]: + send_needed = True + send_reason = "value change" + else: + send_needed = True + send_reason = "value change" + elif (time.time() - self.last_send_time) > self.guarantee_sec: + send_needed = True + send_reason = "guarantee sec" + elif force_send: + send_needed = True + send_reason = "forced" + else: + if self.last_send_time == 0: + send_needed = True + send_reason = "no send time" + elif self.value is None: + send_needed = True + send_reason = "no value" + elif abs(self.value - new_value) > self.chg_threshold: + send_needed = True + send_reason = "change threshold" + elif (time.time() - self.last_send_time) > self.guarantee_sec: + send_needed = True + send_reason = "guarantee sec" + elif force_send: + send_needed = True + send_reason = "forced" + if send_needed: + self.last_value = self.value + if self.map_: + try: + self.value = self.map_[new_value] + except KeyError: + log.error("Cannot find a map value for {} in {} for {}".format(new_value, self.map_, self.mesh_name)) + self.value = new_value + else: + self.value = new_value + self.last_send_time = time.time() + log.info("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason)) + return send_needed + + def read(self): + """Read the value.""" + pass + +def identity(sent): + """Return exactly what was sent to it.""" + return sent + + +class ModbusChannel(Channel): + """Modbus channel object.""" + + def __init__(self, mesh_name, register_number, data_type, chg_threshold, guarantee_sec, channel_size=1, map_=False, write_enabled=False, transform_fn=identity, unit_number=1, scaling=0): + """Initialize the channel.""" + super(ModbusChannel, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled) + self.mesh_name = mesh_name + self.register_number = register_number + self.channel_size = channel_size + self.data_type = data_type + self.last_value = None + self.value = None + self.last_send_time = 0 + self.chg_threshold = chg_threshold + self.guarantee_sec = guarantee_sec + self.map_ = map_ + self.write_enabled = write_enabled + self.transform_fn = transform_fn + self.unit_number = unit_number + self.scaling= scaling + + def read(self): + """Return the transformed read value.""" + with lock: + print("ATTEMPTING TO READ ON {}".format(self.mesh_name)) + if self.data_type == "FLOAT": + try: + read_value = instrument.read_float(self.register_number,4,self.channel_size) + except Exception as e: + log.info(e) + return None + elif self.data_type == "FLOATBS": + try: + read_value = byteSwap32(instrument.read_registers(self.register_number,2, 4)) + except Exception as e: + log.info(e) + return None + elif self.data_type == "INTEGER" or self.data_type == "STRING": + try: + read_value = instrument.read_register(self.register_number, self.scaling, 4) + except Exception as e: + log.info(e) + return None + read_value = self.transform_fn(read_value) + return read_value + + + def write(self, value): + """Write a value to a register""" + if self.data_type == "FLOAT": + value = float(value) + elif self.data_type == "INTEGER": + value = int(value) + else: + value = str(value) + try: + instrument.write_register(self.register_number,value, self.scaling, 16 if self.channel_size > 1 else 6 ) + return True + except Exception as e: + log.info("Failed to write value: {}".format(e)) + return False + +class PLCChannel(Channel): + """PLC Channel Object.""" + + def __init__(self, ip, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False, plc_type='CLX'): + """Initialize the channel.""" + super(PLCChannel, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled) + self.plc_ip = ip + self.mesh_name = mesh_name + self.plc_tag = plc_tag + self.data_type = data_type + self.last_value = None + self.value = None + self.last_send_time = 0 + self.chg_threshold = chg_threshold + self.guarantee_sec = guarantee_sec + self.map_ = map_ + self.write_enabled = write_enabled + self.plc_type = plc_type + + def read(self): + """Read the value.""" + plc_value = None + if self.plc_tag and self.plc_ip: + read_value = read_tag(self.plc_ip, self.plc_tag, plc_type=self.plc_type) + if read_value: + plc_value = read_value[0] + + return plc_value + + +class BoolArrayChannels(Channel): + """Hold the configuration for a set of boolean array channels.""" + + def __init__(self, ip, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False): + """Initialize the channel.""" + super(BoolArrayChannels, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled) + self.plc_ip = ip + self.mesh_name = mesh_name + self.plc_tag = plc_tag + self.data_type = data_type + self.last_value = None + self.value = None + self.last_send_time = 0 + self.chg_threshold = chg_threshold + self.guarantee_sec = guarantee_sec + self.map_ = map_ + self.write_enabled = write_enabled + + def compare_values(self, new_val_dict): + """Compare new values to old values to see if the values need storing.""" + send = False + for idx in new_val_dict: + try: + if new_val_dict[idx] != self.last_value[idx]: + send = True + except KeyError: + log.error("Key Error in self.compare_values for index {}".format(idx)) + send = True + return send + + def read(self, force_send=False): + """Read the value and check to see if needs to be stored.""" + send_needed = False + send_reason = "" + if self.plc_tag: + val = read_tag(self.plc_ip, self.plc_tag) + if val: + bool_arr = binarray(val[0]) + new_val = {} + for idx in self.map_: + try: + new_val[self.map_[idx]] = bool_arr[idx] + except KeyError: + log.error("Not able to get value for index {}".format(idx)) + + if self.last_send_time == 0: + send_needed = True + send_reason = "no send time" + elif self.value is None: + send_needed = True + send_reason = "no value" + elif self.compare_values(new_val): + send_needed = True + send_reason = "value change" + elif (time.time() - self.last_send_time) > self.guarantee_sec: + send_needed = True + send_reason = "guarantee sec" + elif force_send: + send_needed = True + send_reason = "forced" + + if send_needed: + self.value = new_val + self.last_value = self.value + self.last_send_time = time.time() + log.info("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason)) + return send_needed diff --git a/flow-monitor/flow-monitorv2/Tags.py b/flow-monitor/flow-monitorv2/Tags.py new file mode 100644 index 0000000..87d2043 --- /dev/null +++ b/flow-monitor/flow-monitorv2/Tags.py @@ -0,0 +1,12 @@ +from Channel import PLCChannel, ModbusChannel +from flowmonitor import PLC_IP_ADDRESS + +tags = [ + ModbusChannel('gpm_flow', 3873, 'FLOATBS', 10, 3600,channel_size=2, unit_number=2), + ModbusChannel('psi_pressure', 0, 'FLOAT', 100, 3600,channel_size=2, unit_number=2), + ModbusChannel('run_status', 0, 'STRING', 1, 3600,channel_size=2, unit_number=2), + ModbusChannel('gal_total', 2609, 'FLOATBS', 100, 3600,channel_size=2, unit_number=2), + #ModbusChannel('gal_total_thismonth', 2609, 'FLOAT', 100, 3600,channel_size=2, unit_number=flowmeter_unit_number), + #ModbusChannel('gal_total_yesterday', 2609, 'FLOAT', 100, 3600,channel_size=2, unit_number=flowmeter_unit_number), + #ModbusChannel('gal_total_lastmonth', 2609, 'FLOAT', 100, 3600,channel_size=2, unit_number=flowmeter_unit_number) +] \ No newline at end of file diff --git a/flow-monitor/flow-monitorv2/config.txt b/flow-monitor/flow-monitorv2/config.txt new file mode 100644 index 0000000..ba9f2ba --- /dev/null +++ b/flow-monitor/flow-monitorv2/config.txt @@ -0,0 +1,14 @@ +{ + "files": { + "file3": "file_logger.py", + "file2": "Channel.py", + "file1": "flowmonitor.py", + "file6": "persistence.py", + "file5": "utilities.py", + "file4": "Tags.py" + }, + "deviceName": "flowmonitor", + "releaseVersion": "21", + "driverFileName": "flowmonitor.py", + "driverId": "0140" +} \ No newline at end of file diff --git a/flow-monitor/flow-monitorv2/file_logger.py b/flow-monitor/flow-monitorv2/file_logger.py new file mode 100644 index 0000000..3aec459 --- /dev/null +++ b/flow-monitor/flow-monitorv2/file_logger.py @@ -0,0 +1,18 @@ +"""Logging setup for flow-monitor""" +import logging +from logging.handlers import RotatingFileHandler +import sys + +log_formatter = logging.Formatter('%(asctime)s %(levelname)s %(funcName)s(%(lineno)d) %(message)s') +log_file = './flowmonitor.log' +my_handler = RotatingFileHandler(log_file, mode='a', maxBytes=500*1024, + backupCount=2, encoding=None, delay=0) +my_handler.setFormatter(log_formatter) +my_handler.setLevel(logging.INFO) +filelogger = logging.getLogger('flowmonitor') +filelogger.setLevel(logging.INFO) +filelogger.addHandler(my_handler) + +console_out = logging.StreamHandler(sys.stdout) +console_out.setFormatter(log_formatter) +filelogger.addHandler(console_out) diff --git a/flow-monitor/flow-monitorv2/flowmonitor.py b/flow-monitor/flow-monitorv2/flowmonitor.py new file mode 100644 index 0000000..14e9a53 --- /dev/null +++ b/flow-monitor/flow-monitorv2/flowmonitor.py @@ -0,0 +1,522 @@ +"""Driver for flow-monitor""" + +import threading +import json +import time +import os +from device_base import deviceBase +import persistence +from utilities import get_public_ip_address, get_private_ip_address +from file_logger import filelogger as log +from datetime import datetime as dt +import minimalmodbusM1 + +try: + os.system("/usr/sbin/ntpdate pool.ntp.org") +except: + dtz = dt.fromtimestamp(last_measured_timestamp) + os.system('date -s "{}-{}-{} {}:{}:{}"'.format(dtz.year,dtz.month,dtz.day,dtz.hour,dtz.minute,dtz.second)) + +PLC_IP_ADDRESS = "192.168.1.12" +#from Tags import tags + +_ = None + +log.info("flow-monitor startup") + +# GLOBAL VARIABLES +WAIT_FOR_CONNECTION_SECONDS = 20 +IP_CHECK_PERIOD = 60 + + +#CHANNELS = tags + +# PERSISTENCE FILE +PERSIST = persistence.load() +if not PERSIST: + PERSIST = { + "pressure_raw_min": 0.0, + "pressure_raw_max": 10.0, + "flow_raw_min": 3.89, + "pressure_psi_min": 0.0, + "gpm_or_bpd": "gpm", + "gpm_ignore_limit": 1.0, + "flow_gpm_max": 100.0, + "pressure_psi_max": 600.0, + "flow_raw_max": 19.54, + "flow_gpm_min": 0.0, + "lowflow": 10.0, + "modbus": False + } + persistence.store(PERSIST, 'persist.json') +try: + PERSIST['modbus'] +except: + PERSIST['modbus'] = False + persistence.store(PERSIST, 'persist.json') + +TOTALIZER = persistence.load('totalizers.json') +if not TOTALIZER: + TOTALIZER = { + '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(TOTALIZER, 'totalizers.json') + +def scale(raw_val, raw_min, raw_max, eu_min, eu_max): + """Scale a raw value.""" + if raw_val < raw_min: + raw_val = raw_min + if raw_val > raw_max: + raw_val = raw_max + slope = (eu_max - eu_min) / (raw_max - raw_min) + intercept = eu_max - (slope * raw_max) + return slope * raw_val + intercept + +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 = "21" + self.lock = threading.Lock() + self.force_send = False + self.public_ip_address = "" + self.private_ip_address = "" + self.public_ip_address_last_checked = 0 + self.ping_counter = 0 + self.finished = threading.Event() + 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.""" + self.instrument = self.startRS485() + global CHANNELS, INSTRUMENT, lock + INSTRUMENT = self.instrument + lock = self.lock + #from Channel import PLCChannel, ModbusChannel,read_tag, write_tag, TAG_DATAERROR_SLEEPTIME + from Tags import tags + CHANNELS = tags + + for i in range(0, WAIT_FOR_CONNECTION_SECONDS): + print("flow-monitor driver will start in {} seconds".format(WAIT_FOR_CONNECTION_SECONDS - i)) + time.sleep(1) + log.info("BOOM! Starting flow-monitor driver...") + + self._check_ip_address() + + self.nodes["flowmonitor_0140"] = self + + send_loops = 0 + self.sendtodb("setrawmin", PERSIST["flow_raw_min"], 0) + self.sendtodb("setrawmax", PERSIST["flow_raw_max"], 0) + self.sendtodb("setgpmmin", PERSIST["flow_gpm_min"], 0) + self.sendtodb("setgpmmax", PERSIST["flow_gpm_max"], 0) + self.sendtodb("setpressurerawmin", PERSIST["pressure_raw_min"], 0) + self.sendtodb("setpressurerawmax", PERSIST["pressure_raw_max"], 0) + self.sendtodb("setpressurepsimin", PERSIST["pressure_psi_min"], 0) + self.sendtodb("setpressurepsimax", PERSIST["pressure_psi_max"], 0) + gal_totalizer_value = TOTALIZER['Lifetime'] + last_measured_timestamp = time.time() + while True: + try: + # Gets a dictionary of the IO states + # { + # 'bat': u'23.10', + # 'ver': u'Mar 16 2016 21:29:31', + # 'dout3': 'Off', + # 'temp': u'40.37', + # 'vin': u'24.6', + # 'pulse': u'0', + # 'dout4': 'Off', + # 'dout1': 'Off', + # 'din2': 'Off', + # 'din1': 'Off', + # 'dout2': 'On', + # 'cloop': u'0.0', + # 'analog4': u'0.0', + # 'analog3': u'0.0', + # 'analog2': u'0.0', + # 'analog1': u'0.0', + # 'relay1': 'Off' + # } + mcu_status = self.mcu.getDict() + except Exception as e: + log.error("Error getting MCU State: {}".format(e)) + + cloop_val = float(mcu_status['cloop']) + analog1_val = float(mcu_status['analog1']) + din1_val = 1 if mcu_status['din1'] == 'On' else 0 # Check DIGITAL INPUT 1 for run status + scaled_cloop = scale(cloop_val, PERSIST["flow_raw_min"], PERSIST["flow_raw_max"], PERSIST["flow_gpm_min"], PERSIST["flow_gpm_max"]) + psi_val = scale(analog1_val, PERSIST["pressure_raw_min"], PERSIST["pressure_raw_max"], PERSIST["pressure_psi_min"], PERSIST["pressure_psi_max"]) + + if PERSIST["gpm_or_bpd"] == "gpm": + gpm_val = scaled_cloop + bpd_val = (gpm_val / 42.0) * 60.0 * 24.0 # Computes BPD from GPM + else: + bpd_val = scaled_cloop + gpm_val = (((bpd_val * 42.0) / 24.0) / 60.0) # Computes GPM from BPD + + if gpm_val < PERSIST["gpm_ignore_limit"]: + gpm_val = 0 + bpd_val = 0 + + #Determine run status + runstatus = "undefined" + if din1_val == 0 and gpm_val == 0: + runstatus = "Stopped" #Stopped + elif din1_val == 0 and gpm_val > 10: + runstatus = "Running" #Assumed running might not have run indication + elif din1_val == 0 and gpm_val > 0: + runstatus = "Running: Low Flow" + elif din1_val == 1 and gpm_val == 0: + runstatus = "Running: No Flow" #no flow warning + elif din1_val == 1 and gpm_val < PERSIST["lowflow"]: + runstatus = "Running: Low Flow" #low flow warning + elif din1_val == 1 and gpm_val >= PERSIST["lowflow"]: + runstatus = "Running" #running normally + + now = time.time() + time_diff = now - last_measured_timestamp + if 0 < time_diff < 180: + # Volume flowed since last measuring + gal_flow_delta = (time_diff / 60.0) * gpm_val + + # Increment totalizers + gal_totalizer_value += gal_flow_delta + last_measured_timestamp = now + elif time_diff < 0: + #negative time difference means clock got reset or somehow went the wrong way + try: + os.system("/usr/sbin/ntpdate pool.ntp.org") + except: + dtz = dt.fromtimestamp(last_measured_timestamp) + os.system('date -s "{}-{}-{} {}:{}:{}"'.format(dtz.year,dtz.month,dtz.day,dtz.hour,dtz.minute,dtz.second)) + now = time.time() + elif time_diff > 180: + last_measured_timestamp = now + + if self.force_send: + log.warning("FORCE SEND: TRUE") + + for chan in CHANNELS: + if chan.mesh_name == "psi_pressure": + val = psi_val + elif chan.mesh_name == "run_status": + val = runstatus + elif PERSIST["modbus"]: + val = chan.read() + if chan.mesh_name == "gpm_flow": + if val == 0: + runstatus = "Stopped" + elif val > PERSIST["lowflow"]: + runstatus = "Running" + else: + runstatus = "Running: Low Flow" + elif chan.mesh_name == "gpm_flow": + val = gpm_val + else: + val = gal_totalizer_value + + if chan.mesh_name == 'gal_total': + self.totalize(val) + elif chan.check(val, self.force_send): + self.sendtodb(chan.mesh_name, chan.value, 0) + if chan.mesh_name == 'gpm_flow': + if val == None: + self.sendtodb('bpd_flow', chan.value,0) + else: + self.sendtodb('bpd_flow', (chan.value / 42.0) * 60 * 24, 0) + + #time.sleep(TAG_DATAERROR_SLEEPTIME) # sleep to allow Micro800 to handle ENET requests + if PERSIST["modbus"]: + time.sleep(5) + # print("flow-monitor 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() + + + 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.sendtodb('public_ip_address', test_public_ip, 0) + self.public_ip_address = test_public_ip + if not test_private_ip == self.private_ip_address: + self.sendtodb('private_ip_address', test_private_ip, 0) + self.private_ip_address = test_private_ip + + + def flowmonitor_sync(self, name, value): + """Sync all data from the driver.""" + self.force_send = True + # self.sendtodb("log", "synced", 0) + return True + + def flowmonitor_startcmd(self, name, value): + """Start the well.""" + self.mcu.relay1(str(1)) + return True + + def flowmonitor_stopcmd(self, name, value): + """Stop the well.""" + self.mcu.relay1(str(0)) + return True + + def flowmonitor_modbus(self, name, value): + if PERSIST["modbus"] == False: + PERSIST["modbus"] = True + else: + PERSIST["modbus"] = False + persistence.store(PERSIST, "persist.json") + return True + + def flowmonitor_setrawmin(self, name, value): + """Set the raw min scaling value.""" + try: + PERSIST['flow_raw_min'] = float(value) + self.sendtodb("setrawmin", PERSIST['flow_raw_min'], 0) + persistence.store(PERSIST) + except Exception as e: + log.error("Could not set self.flow_raw_min: {}".format(e)) + return(True) + + def flowmonitor_setrawmax(self, name, value): + """Set the raw max scaling value.""" + try: + PERSIST['flow_raw_max'] = float(value) + self.sendtodb("setrawmax", PERSIST['flow_raw_max'], 0) + persistence.store(PERSIST) + except Exception as e: + log.error("Could not set self.flow_raw_max: {}".format(e)) + return(True) + + def flowmonitor_setgpmmin(self, name, value): + """Set the gpm min scaling value.""" + try: + PERSIST['flow_gpm_min'] = float(value) + self.sendtodb("setgpmmin", PERSIST['flow_gpm_min'], 0) + persistence.store(PERSIST) + except Exception as e: + log.error("Could not set self.flow_gpm_min: {}".format(e)) + return(True) + + def flowmonitor_setgpmmax(self, name, value): + """Set the gpm max scaling value.""" + try: + PERSIST['flow_gpm_max'] = float(value) + self.sendtodb("setgpmmax", PERSIST['flow_gpm_max'], 0) + persistence.store(PERSIST) + except Exception as e: + log.error("Could not set self.flow_gpm_max: {}".format(e)) + return(True) + + def flowmonitor_setpressurerawmin(self, name, value): + """Set the pressure raw min scaling value.""" + try: + PERSIST['pressure_raw_min'] = float(value) + self.sendtodb("setpressurerawmin", PERSIST['pressure_raw_min'], 0) + persistence.store(PERSIST) + except Exception as e: + log.error("Could not set self.pressure_raw_min: {}".format(e)) + return(True) + + def flowmonitor_setpressurerawmax(self, name, value): + """Set the pressure raw max scaling value.""" + try: + PERSIST['pressure_raw_max'] = float(value) + self.sendtodb("setpressurerawmax", PERSIST['pressure_raw_max'], 0) + persistence.store(PERSIST) + except Exception as e: + log.error("Could not set self.pressure_raw_max: {}".format(e)) + return(True) + + def flowmonitor_setpressurepsimin(self, name, value): + """Set the pressure psi min scaling value.""" + try: + PERSIST['pressure_psi_min'] = float(value) + self.sendtodb("setpressurepsimin", PERSIST['pressure_psi_min'], 0) + persistence.store(PERSIST) + except Exception as e: + log.error("Could not set self.pressure_psi_min: {}".format(e)) + return(True) + + def flowmonitor_setpressurepsimax(self, name, value): + """Set the pressure psi max scaling value.""" + try: + PERSIST['pressure_psi_max'] = float(value) + self.sendtodb("setpressurepsimax", PERSIST['pressure_psi_max'], 0) + persistence.store(PERSIST) + except Exception as e: + log.error("Could not set self.pressure_psi_max: {}".format(e)) + return(True) + + def flowmonitor_setgpmignorelimit(self, name, value): + """Set the GPM Ignore Limit.""" + try: + PERSIST['gpm_ignore_limit'] = float(value) + self.sendtodb("setgpmignorelimit", PERSIST['gpm_ignore_limit'], 0) + persistence.store(PERSIST) + return True + except Exception as e: + log.error("Error during flowmonitor_setgpmignorelimit: {}".format(e)) + return False + + def flowmonitor_gpmorbpd(self, name, value): + """Set the read in value to GPM or BPD""" + try: + PERSIST["gpm_or_bpd"] = str(value) + self.sendtodb("gpmorbpd", PERSIST["gpm_or_bpd"], 0) + return True + except Exception as e: + log.error("Error during flowmonitor_setgpmorbpd: {}".format(e)) + return False + + def flowmonitor_setlowflow(self, name, value): + """Set the low flow limit""" + try: + PERSIST["lowflow"] = float(value) + self.sendtodb("setlowflow", PERSIST["lowflow"],0) + persistence.store(PERSIST) + except Exception as e: + log.error("Error during flomonitor_setlowflow: {}".format(e)) + return False + return True + + + + + def totalize(self, val): + 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 TOTALIZER['Day'] == 0: + TOTALIZER['Day'] = day + TOTALIZER['Month'] = month + TOTALIZER['Daily Holding'] = val + TOTALIZER['Monthly Holding'] = val + persistence.store(TOTALIZER, 'totalizers.json') + #Communication error during initialization check if lifetime has reported properly and update holdings + if TOTALIZER['Daily Holding'] == None and not(val == None): + TOTALIZER['Daily Holding'] = val + TOTALIZER['Monthly Holding'] = val + + try: + if val - TOTALIZER['Daily Holding'] - TOTALIZER['Todays'] > 500 or time.time() - TOTALIZER['Last Report'] > 3600 or self.force_send: + TOTALIZER['Todays'] = val - TOTALIZER['Daily Holding'] + TOTALIZER['Current Months'] = val - TOTALIZER['Monthly Holding'] + TOTALIZER['Lifetime'] = val + self.sendtodb('gal_total', TOTALIZER['Todays'], 0) + self.sendtodb('bbl_total', TOTALIZER['Todays']/42, 0) + self.sendtodb('gal_total_thismonth', TOTALIZER['Current Months'], 0) + self.sendtodb('bbl_total_thismonth', TOTALIZER['Current Months']/42, 0) + self.sendtodb('gal_total_yesterday', TOTALIZER['Yesterdays'], 0) + self.sendtodb('bbl_total_yesterday', TOTALIZER['Yesterdays']/42, 0) + if self.force_send: + self.sendtodb('gal_total_lastmonth', TOTALIZER['Previous Months'], 0) + self.sendtodb('bbl_total_lastmonth', TOTALIZER['Previous Months']/42, 0) + TOTALIZER['Last Report'] = time.time() + except: + if time.time() - TOTALIZER['Last Report'] > 3600 or self.force_send: + self.sendtodb('gal_total', TOTALIZER['Todays'], 0) + self.sendtodb('bbl_total', TOTALIZER['Todays']/42, 0) + self.sendtodb('gal_total_thismonth', TOTALIZER['Current Months'], 0) + self.sendtodb('bbl_total_thismonth', TOTALIZER['Current Months']/42, 0) + self.sendtodb('gal_total_yesterday', TOTALIZER['Yesterdays'], 0) + self.sendtodb('bbl_total_yesterday', TOTALIZER['Yesterdays']/42, 0) + if self.force_send: + self.sendtodb('gal_total_lastmonth', TOTALIZER['Previous Months'], 0) + self.sendtodb('bbl_total_lastmonth', TOTALIZER['Previous Months']/42, 0) + TOTALIZER['Last Report'] = time.time() + + #If the current day doesn't equal the stored day roll the dailies over + if not(day == TOTALIZER['Day']): + #if a comms error use the stored values else use the latested values + if val == None: + TOTALIZER['Yesterdays'] = TOTALIZER['Todays'] + TOTALIZER['Todays'] = 0 + TOTALIZER['Daily Holding'] = TOTALIZER['Lifetime'] + else: + TOTALIZER['Yesterdays'] = val - TOTALIZER['Daily Holding'] + TOTALIZER['Todays'] = 0 + TOTALIZER['Daily Holding'] = val + TOTALIZER['Lifetime'] = val + TOTALIZER['Day'] = day + self.sendtodb('gal_total', TOTALIZER['Todays'], 0) + self.sendtodb('bbl_total', TOTALIZER['Todays']/42, 0) + self.sendtodb('total_fm_yesterday_gal', TOTALIZER['Yesterdays'], 0) + self.sendtodb('total_fm_yesterday_bbls', TOTALIZER['Yesterdays']/42, 0) + self.sendtodb('lifetime_flow_meter_gal', TOTALIZER['Lifetime'], 0) + self.sendtodb('lifetime_flow_meter_bbls', TOTALIZER['Lifetime']/42, 0) + TOTALIZER['Last Report'] = time.time() + #the day has rolled over if the month also rolls over + if not(month == TOTALIZER['Month']): + #if a comms error use the stored values else use the latested values + if val == None: + TOTALIZER['Previous Months'] = TOTALIZER['Current Months'] + TOTALIZER['Current Months'] = 0 + TOTALIZER['Monthly Holding'] = TOTALIZER['Lifetime'] + else: + TOTALIZER['Previous Months'] = val - TOTALIZER['Monthly Holding'] + TOTALIZER['Current Months'] = 0 + TOTALIZER['Monthly Holding'] = val + TOTALIZER['Month'] = month + self.sendtodb('gal_total_thismonth', TOTALIZER['Current Months'], 0) + self.sendtodb('bbl_total_thismonth', TOTALIZER['Current Months']/42, 0) + self.sendtodb('gal_total_lastmonth', TOTALIZER['Previous Months'], 0) + self.sendtodb('bbl_total_lastmonth', TOTALIZER['Previous Months']/42, 0) + TOTALIZER['Last Report'] = time.time() + persistence.store(TOTALIZER, 'totalizers.json') + + def startRS485(self): + instrument = "" + with self.lock: + #minimalmodbus.BAUDRATE = 9600 + #minimalmodbus.STOPBITS = 1 + connected = False + while connected == False: + log.info("Attempting to setup RS485") + connected = self.mcu.set485Baud(9600)#switch to configurable + time.sleep(1) + log.info("RS485 SETUP SUCCESSFUL!!!!!") + serial = self.mcu.rs485 + instrument = minimalmodbusM1.Instrument(1,serial) + instrument.address = 2 #switch to configurable + return instrument + diff --git a/flow-monitor/flow-monitorv2/persistence.py b/flow-monitor/flow-monitorv2/persistence.py new file mode 100644 index 0000000..8c8703f --- /dev/null +++ b/flow-monitor/flow-monitorv2/persistence.py @@ -0,0 +1,21 @@ +"""Data persistance functions.""" +# if more advanced persistence is needed, use a sqlite database +import json + + +def load(filename="persist.json"): + """Load persisted settings from the specified file.""" + try: + with open(filename, 'r') as persist_file: + return json.load(persist_file) + except Exception: + return False + + +def store(persist_obj, filename="persist.json"): + """Store the persisting settings into the specified file.""" + try: + with open(filename, 'w') as persist_file: + return json.dump(persist_obj, persist_file, indent=4) + except Exception: + return False diff --git a/flow-monitor/flow-monitorv2/utilities.py b/flow-monitor/flow-monitorv2/utilities.py new file mode 100644 index 0000000..4f16549 --- /dev/null +++ b/flow-monitor/flow-monitorv2/utilities.py @@ -0,0 +1,64 @@ +"""Utility functions for the driver.""" +import socket +import struct +import urllib +import contextlib + + +def get_private_ip_address(): + """Find the private IP Address of the host device.""" + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + sock.connect(("8.8.8.8", 80)) + except Exception as e: + return e + ip_address = sock.getsockname()[0] + sock.close() + return ip_address + +def get_public_ip_address(): + ip_address = "0.0.0.0" + try: + with contextlib.closing(urllib.urlopen("http://checkip.amazonaws.com")) as url: + ip_address = url.read() + except Exception as e: + print("could not resolve check IP: {}".format(e)) + return ip_address + return ip_address[:-1] + + +def int_to_float16(int_to_convert): + """Convert integer into float16 representation.""" + bin_rep = ('0' * 16 + '{0:b}'.format(int_to_convert))[-16:] + sign = 1.0 + if int(bin_rep[0]) == 1: + sign = -1.0 + exponent = float(int(bin_rep[1:6], 2)) + fraction = float(int(bin_rep[6:17], 2)) + + if exponent == float(0b00000): + return sign * 2 ** -14 * fraction / (2.0 ** 10.0) + elif exponent == float(0b11111): + if fraction == 0: + return sign * float("inf") + return float("NaN") + frac_part = 1.0 + fraction / (2.0 ** 10.0) + return sign * (2 ** (exponent - 15)) * frac_part + + +def ints_to_float(int1, int2): + """Convert 2 registers into a floating point number.""" + mypack = struct.pack('>HH', int1, int2) + f_unpacked = struct.unpack('>f', mypack) + print("[{}, {}] >> {}".format(int1, int2, f_unpacked[0])) + return f_unpacked[0] + + +def degf_to_degc(temp_f): + """Convert deg F to deg C.""" + return (temp_f - 32.0) * (5.0/9.0) + + +def degc_to_degf(temp_c): + """Convert deg C to deg F.""" + return temp_c * 1.8 + 32.0 diff --git a/flowmeterskid/Tags.py b/flowmeterskid/Tags.py index 4cc4469..800e826 100644 --- a/flowmeterskid/Tags.py +++ b/flowmeterskid/Tags.py @@ -8,16 +8,16 @@ tags = [ PLCChannel(PLC_IP_ADDRESS, "val_fm1_t1","val_fm1_t1","REAL", 50, 3600, plc_type="Micro800"), PLCChannel(PLC_IP_ADDRESS, "val_fm1_t2","val_fm1_t2","REAL", 50, 3600, plc_type="Micro800"), PLCChannel(PLC_IP_ADDRESS, "val_fm1_t3","val_fm1_t3","REAL", 50, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "val_fm1_yesterday","val_fm1_yesterday","REAL", 100, 86400, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "val_fm1_today","val_fm1_today","REAL", 25, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "val_fm1_lastmonth","val_fm1_lastmonth","REAL", 100, 86400, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "val_fm1_month","val_fm1_month","REAL", 100, 3600, plc_type="Micro800"), + #PLCChannel(PLC_IP_ADDRESS, "val_fm1_yesterday","val_fm1_yesterday","REAL", 100, 86400, plc_type="Micro800"), + #PLCChannel(PLC_IP_ADDRESS, "val_fm1_today","val_fm1_today","REAL", 25, 3600, plc_type="Micro800"), + #PLCChannel(PLC_IP_ADDRESS, "val_fm1_lastmonth","val_fm1_lastmonth","REAL", 100, 86400, plc_type="Micro800"), + #PLCChannel(PLC_IP_ADDRESS, "val_fm1_month","val_fm1_month","REAL", 100, 3600, plc_type="Micro800"), PLCChannel(PLC_IP_ADDRESS, "val_fm2_fr","val_fm2_fr","REAL", 5, 3600, plc_type="Micro800"), PLCChannel(PLC_IP_ADDRESS, "val_fm2_t1","val_fm2_t1","REAL", 50, 3600, plc_type="Micro800"), PLCChannel(PLC_IP_ADDRESS, "val_fm2_t2","val_fm2_t2","REAL", 50, 3600, plc_type="Micro800"), PLCChannel(PLC_IP_ADDRESS, "val_fm2_t3","val_fm3_t3","REAL", 50, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "val_fm2_yesterday","val_fm2_yesterday","REAL", 100, 86400, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "val_fm2_today","val_fm2_today","REAL", 25, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "val_fm2_lastmonth","val_fm2_lastmonth","REAL", 100, 86400, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "val_fm2_month","val_fm2_month","REAL", 100, 3600, plc_type="Micro800") + #PLCChannel(PLC_IP_ADDRESS, "val_fm2_yesterday","val_fm2_yesterday","REAL", 100, 86400, plc_type="Micro800"), + #PLCChannel(PLC_IP_ADDRESS, "val_fm2_today","val_fm2_today","REAL", 25, 3600, plc_type="Micro800"), + #PLCChannel(PLC_IP_ADDRESS, "val_fm2_lastmonth","val_fm2_lastmonth","REAL", 100, 86400, plc_type="Micro800"), + #PLCChannel(PLC_IP_ADDRESS, "val_fm2_month","val_fm2_month","REAL", 100, 3600, plc_type="Micro800") ] \ No newline at end of file diff --git a/flowmeterskid/config.txt b/flowmeterskid/config.txt index 00d9e71..65614dc 100644 --- a/flowmeterskid/config.txt +++ b/flowmeterskid/config.txt @@ -8,7 +8,7 @@ "file4": "Tags.py" }, "deviceName": "flowmeterskid", - "releaseVersion": "2", + "releaseVersion": "3", "driverFileName": "flowmeterskid.py", "driverId": "0100" } \ No newline at end of file diff --git a/flowmeterskid/flowmeterskid.py b/flowmeterskid/flowmeterskid.py index 3540a19..19bda6c 100644 --- a/flowmeterskid/flowmeterskid.py +++ b/flowmeterskid/flowmeterskid.py @@ -12,13 +12,13 @@ from utilities import get_public_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("flowmeterskid startup") # GLOBAL VARIABLES -WAIT_FOR_CONNECTION_SECONDS = 60 +WAIT_FOR_CONNECTION_SECONDS = 20 IP_CHECK_PERIOD = 60 @@ -32,6 +32,24 @@ if not PERSIST: } persistence.store(PERSIST) +TOTALIZERS = persistence.load("totalizers.json") +if not TOTALIZERS: + TOTALIZERS = {} + for x in ['fm1', 'fm2']: + 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") + class start(threading.Thread, deviceBase): """Start class required by Meshify.""" @@ -44,7 +62,7 @@ class start(threading.Thread, deviceBase): mqtt=mqtt, Nodes=Nodes) self.daemon = True - self.version = "2" + self.version = "3" self.finished = threading.Event() self.force_send = False self.public_ip_address = "" @@ -80,7 +98,12 @@ class start(threading.Thread, deviceBase): val = chan.read() if chan.check(val, self.force_send): if chan.mesh_name in PERSIST["ignore_list"]: - self.sendtodbDev(1, chan.mesh_name, None, 0, 'flowmeterskid') + if "_t1" in chan.mesh_name: + self.totalizer_null(chan.mesh_name.split("_")[1]) + else: + self.sendtodbDev(1, chan.mesh_name, None, 0, 'flowmeterskid') + elif "_t1" in chan.mesh_name: + self.totalize(val,chan.mesh_name.split("_")[1]) else: self.sendtodbDev(1, chan.mesh_name, chan.value, 0, 'flowmeterskid') #time.sleep(TAG_DATAERROR_SLEEPTIME) # sleep to allow Micro800 to handle ENET requests @@ -150,3 +173,87 @@ class start(threading.Thread, deviceBase): 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, 'val_{}_today'.format(totalizer), TOTALIZERS[totalizer]['Todays'], 0, 'flowmeterskid') + self.sendtodbDev(1, 'val_{}_month'.format(totalizer), TOTALIZERS[totalizer]['Current Months'], 0, 'flowmeterskid') + self.sendtodbDev(1, 'val_{}_yesterday'.format(totalizer), TOTALIZERS[totalizer]['Yesterdays'], 0, 'flowmeterskid') + self.sendtodbDev(1, 'val_{}_t1'.format(totalizer), TOTALIZERS[totalizer]['Lifetime'], 0, 'flowmeterskid') + if self.force_send: + self.sendtodbDev(1, 'val_{}_lastmonth'.format(totalizer), TOTALIZERS[totalizer]['Previous Months'], 0, 'flowmeterskid') + TOTALIZERS[totalizer]['Last Report'] = time.time() + except: + if time.time() - TOTALIZERS[totalizer]['Last Report'] > 3600 or self.force_send: + self.sendtodbDev(1, 'val_{}_today'.format(totalizer), TOTALIZERS[totalizer]['Todays'], 0, 'flowmeterskid') + self.sendtodbDev(1, 'val_{}_month'.format(totalizer), TOTALIZERS[totalizer]['Current Months'], 0, 'flowmeterskid') + self.sendtodbDev(1, 'val_{}_yesterday'.format(totalizer), TOTALIZERS[totalizer]['Yesterdays'], 0, 'flowmeterskid') + self.sendtodbDev(1, 'val_{}_t1'.format(totalizer), TOTALIZERS[totalizer]['Lifetime'], 0, 'flowmeterskid') + if self.force_send: + self.sendtodbDev(1, 'val_{}_lastmonth'.format(totalizer), TOTALIZERS[totalizer]['Previous Months'], 0, 'flowmeterskid') + 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, 'val_{}_today'.format(totalizer), TOTALIZERS[totalizer]['Todays'], 0, 'flowmeterskid') + self.sendtodbDev(1, 'val_{}_yesterday'.format(totalizer), TOTALIZERS[totalizer]['Yesterdays'], 0, 'flowmeterskid') + self.sendtodbDev(1, 'val_{}_t1'.format(totalizer), TOTALIZERS[totalizer]['Lifetime'], 0, 'flowmeterskid') + 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, 'val_{}_month'.format(totalizer), TOTALIZERS[totalizer]['Current Months'], 0, 'flowmeterskid') + self.sendtodbDev(1, 'val_{}_lastmonth'.format(totalizer), TOTALIZERS[totalizer]['Previous Months'], 0, 'flowmeterskid') + self.sendtodbDev(1, 'val_{}_t1'.format(totalizer), TOTALIZERS[totalizer]['Lifetime'], 0, 'flowmeterskid') + 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, 'val_{}_today'.format(totalizer), None, 0, 'flowmeterskid') + self.sendtodbDev(1, 'val_{}_month'.format(totalizer), None, 0, 'flowmeterskid') + self.sendtodbDev(1, 'val_{}_yesterday'.format(totalizer), None, 0, 'flowmeterskid') + self.sendtodbDev(1, 'val_{}_lastmonth'.format(totalizer), None, 0, 'flowmeterskid') + self.sendtodbDev(1, 'val_{}_t1'.format(totalizer), None, 0, 'flowmeterskid') + TOTALIZERS[totalizer]['Last Report'] = time.time() + persistence.store(TOTALIZERS, 'totalizers.json') diff --git a/plcfreshwater/config.txt b/plcfreshwater/config.txt index 1e8d413..367b3d4 100644 --- a/plcfreshwater/config.txt +++ b/plcfreshwater/config.txt @@ -8,7 +8,7 @@ "file4": "Tags.py" }, "deviceName": "plcfreshwater", - "releaseVersion": "12", + "releaseVersion": "13", "driverFileName": "plcfreshwater.py", "driverId": "0100" } \ No newline at end of file diff --git a/plcfreshwater/plcfreshwater.py b/plcfreshwater/plcfreshwater.py index 0d55e03..c04d261 100644 --- a/plcfreshwater/plcfreshwater.py +++ b/plcfreshwater/plcfreshwater.py @@ -53,7 +53,7 @@ class start(threading.Thread, deviceBase): mqtt=mqtt, Nodes=Nodes) self.daemon = True - self.version = "12" + self.version = "13" self.finished = threading.Event() self.force_send = False self.public_ip_address = "" @@ -238,6 +238,9 @@ class start(threading.Thread, deviceBase): self.sendtodbDev(1, 'total_fm_yesterday_bbls', PERSIST['Yesterdays']/42, 0, 'plcfreshwater') self.sendtodbDev(1, 'lifetime_flow_meter_gal', PERSIST['Lifetime'], 0, 'plcfreshwater') self.sendtodbDev(1, 'lifetime_flow_meter_bbls', PERSIST['Lifetime']/42, 0, 'plcfreshwater') + if self.force_send: + self.sendtodbDev(1, 'total_fm_last_month_gal', PERSIST['Previous Months'], 0, 'plcfreshwater') + self.sendtodbDev(1, 'total_fm_last_month_bbls', PERSIST['Previous Months']/42, 0, 'plcfreshwater') PERSIST['Last Report'] = time.time() except: if time.time() - PERSIST['Last Report'] > 3600 or self.force_send: @@ -249,6 +252,9 @@ class start(threading.Thread, deviceBase): self.sendtodbDev(1, 'total_fm_yesterday_bbls', PERSIST['Yesterdays']/42, 0, 'plcfreshwater') self.sendtodbDev(1, 'lifetime_flow_meter_gal', PERSIST['Lifetime'], 0, 'plcfreshwater') self.sendtodbDev(1, 'lifetime_flow_meter_bbls', PERSIST['Lifetime']/42, 0, 'plcfreshwater') + if self.force_send: + self.sendtodbDev(1, 'total_fm_last_month_gal', PERSIST['Previous Months'], 0, 'plcfreshwater') + self.sendtodbDev(1, 'total_fm_last_month_bbls', PERSIST['Previous Months']/42, 0, 'plcfreshwater') PERSIST['Last Report'] = time.time() #If the current day doesn't equal the stored day roll the dailies over diff --git a/promagmbs/rework/Channel.py b/promagmbs/rework/Channel.py index f3d1e07..61372f3 100644 --- a/promagmbs/rework/Channel.py +++ b/promagmbs/rework/Channel.py @@ -1,10 +1,20 @@ """Define Meshify channel class.""" +import time from pycomm.ab_comm.clx import Driver as ClxDriver from pycomm.cip.cip_base import CommError, DataError -import time -import minimalmodbusM1 from file_logger import filelogger as log -import struct +import minimalmodbusM1 +from PiFlow import mcug +#minimalmodbus.BAUDRATE = 9600 +#minimalmodbus.STOPBITS = 1 +connected = False +while connected == False: + connected = mcug.set485Baud(9600) + time.sleep(1) +serial = mcug.rs485 +instrument = minimalmodbusM1.Instrument(1,serial) +instrument.address = 2 +TAG_DATAERROR_SLEEPTIME = 5 def binarray(intval): """Split an integer into its bits.""" @@ -14,69 +24,78 @@ def binarray(intval): return bin_arr -def read_tag(addr, tag): +def read_tag(addr, tag, plc_type="CLX"): """Read a tag from the PLC.""" - c = ClxDriver() + direct = plc_type == "Micro800" + clx = ClxDriver() try: - if c.open(addr): + if clx.open(addr, direct_connection=direct): try: - v = c.read_tag(tag) - return v - except DataError: - c.close() - print("Data Error during readTag({}, {})".format(addr, tag)) + val = clx.read_tag(tag) + clx.close() + return val + except DataError as err: + clx.close() + time.sleep(TAG_DATAERROR_SLEEPTIME) + log.error("Data Error during readTag({}, {}): {}".format(addr, tag, err)) except CommError: # err = c.get_status() - c.close() - print("Could not connect during readTag({}, {})".format(addr, tag)) - # print err - except AttributeError as e: - c.close() - print("AttributeError during readTag({}, {}): \n{}".format(addr, tag, e)) - c.close() + clx.close() + log.error("Could not connect during readTag({}, {})".format(addr, tag)) + except AttributeError as err: + clx.close() + log.error("AttributeError during readTag({}, {}): \n{}".format(addr, tag, err)) + clx.close() return False -def read_array(addr, tag, start, end): +def read_array(addr, tag, start, end, plc_type="CLX"): """Read an array from the PLC.""" - c = ClxDriver() - if c.open(addr): + direct = plc_type == "Micro800" + clx = ClxDriver() + if clx.open(addr, direct_connection=direct): arr_vals = [] try: for i in range(start, end): tag_w_index = tag + "[{}]".format(i) - v = c.read_tag(tag_w_index) - # print('{} - {}'.format(tag_w_index, v)) - arr_vals.append(round(v[0], 4)) - # print(v) - if len(arr_vals) > 0: + val = clx.read_tag(tag_w_index) + arr_vals.append(round(val[0], 4)) + if arr_vals: + clx.close() return arr_vals else: - print("No length for {}".format(addr)) + log.error("No length for {}".format(addr)) + clx.close() return False except Exception: - print("Error during readArray({}, {}, {}, {})".format(addr, tag, start, end)) - err = c.get_status() - c.close() - print err - pass - c.close() + log.error("Error during readArray({}, {}, {}, {})".format(addr, tag, start, end)) + err = clx.get_status() + clx.close() + log.error(err) + clx.close() -def write_tag(addr, tag, val): +def write_tag(addr, tag, val, plc_type="CLX"): """Write a tag value to the PLC.""" - c = ClxDriver() - if c.open(addr): - try: - cv = c.read_tag(tag) - wt = c.write_tag(tag, val, cv[1]) - return wt - except Exception: - print("Error during writeTag({}, {}, {})".format(addr, tag, val)) - err = c.get_status() - c.close() - print err - c.close() + direct = plc_type == "Micro800" + clx = ClxDriver() + try: + if clx.open(addr, direct_connection=direct): + try: + initial_val = clx.read_tag(tag) + write_status = clx.write_tag(tag, val, initial_val[1]) + clx.close() + return write_status + except DataError as err: + clx_err = clx.get_status() + clx.close() + log.error("--\nDataError during writeTag({}, {}, {}, plc_type={}) -- {}\n{}\n".format(addr, tag, val, plc_type, err, clx_err)) + + except CommError as err: + clx_err = clx.get_status() + log.error("--\nCommError during write_tag({}, {}, {}, plc_type={})\n{}\n--".format(addr, tag, val, plc_type, err)) + clx.close() + return False class Channel(object): @@ -102,14 +121,14 @@ class Channel(object): """Check to see if the new_value needs to be stored.""" send_needed = False send_reason = "" - if self.data_type == 'BOOL' or self.data_type == 'STRING': + if self.data_type == 'BOOL' or self.data_type == 'STRING' or type(new_value) == str: if self.last_send_time == 0: send_needed = True send_reason = "no send time" elif self.value is None: send_needed = True send_reason = "no value" - elif not (self.value == new_value): + elif self.value != new_value: if self.map_: if not self.value == self.map_[new_value]: send_needed = True @@ -145,12 +164,12 @@ class Channel(object): try: self.value = self.map_[new_value] except KeyError: - print("Cannot find a map value for {} in {} for {}".format(new_value, self.map_, self.mesh_name)) + log.error("Cannot find a map value for {} in {} for {}".format(new_value, self.map_, self.mesh_name)) self.value = new_value else: self.value = new_value self.last_send_time = time.time() - print("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason)) + log.info("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason)) return send_needed def read(self): @@ -162,7 +181,6 @@ def identity(sent): """Return exactly what was sent to it.""" return sent - def volume_units(vunit): units = { 0: "cm cubed/s", @@ -302,12 +320,156 @@ def totalizer_units(tunit): } return units[tunit] +def int_to_bits(n,x): + return pad_to_x([int(digit) for digit in bin(n)[2:]],x) # [2:] to chop off the "0b" part +def pad_to_x(n,x): + while len(n) < x: + n = [0] + n + return n + +def status_codes(n): + + status_array = int_to_bits(n,16) + status_low = { + 0: "Stopped;", + 1: "Operating in Forward;", + 2: "Operating in Reverse;", + 3: "DC operating;" + } + status_mid = { + 0: "", + 1: "Speed searching;", + 2: "Accelerating;", + 3: "At constant speed;", + 4: "Decelerating;", + 5: "Decelerating to stop;", + 6: "H/W OCS;", + 7: "S/W OCS;", + 8: "Dwell operating;" + } + status_high = { + 0: "Normal state", + 4: "Warning occurred", + 8: "Fault occurred" + } + values = { + 0: 8, + 1: 4, + 2: 2, + 3: 1 + } + + stats_low = status_array[12:] + stats_mid = status_array[8:12] + stats_high = status_array[:4] + low = 0 + mid = 0 + high = 0 + for x in range(4): + if stats_low[x] == 1: + low = low + values[x] + if stats_mid[x] == 1: + mid = mid + values[x] + if stats_high[x] == 1: + high = high + values[x] + + return status_low[low] + " " + status_mid[mid] + ' ' + status_high[high] + +def fault_code_a(n): + + fault_code_array = int_to_bits(n,16) + + """ fault = { + 0: "OCT", + 1: "OVT", + 2: "EXT-A", + 3: "EST", + 4: "COL", + 5: "GFT", + 6: "OHT", + 7: "ETH", + 8: "OLT", + 9: "Reserved", + 10: "EXT-B", + 11: "EEP", + 12: "FAN", + 13: "POT", + 14: "IOLT", + 15: "LVT" + } """ + fault = { + 0: "Overload Trip", + 1: "Underload Trip", + 2: "Inverter Overload Trip", + 3: "E-Thermal Trip", + 4: "Ground Fault Trip", + 5: "Output Image Trip", + 6: "Inmput Imaging Trip", + 7: "Reserved", + 8: "Reserved", + 9: "NTC Trip", + 10: "Overcurrent Trip", + 11: "Overvoltage Trip", + 12: "External Trip", + 13: "Arm Short", + 14: "Over Heat Trip", + 15: "Fuse Open Trip" + } + + faults = [] + counter = 15 + for x in range(16): + if fault_code_array[x] == 1: + faults = [fault[counter]] + faults + counter = counter - 1 + return ' '.join(faults) + +def fault_code_b(n): + + fault_code_array = int_to_bits(n,8) + + """ fault = { + 0: "COM", + 1: "Reserved", + 2: "NTC", + 3: "REEP", + 4: "OC2", + 5: "NBR", + 6: "SAFA", + 7: "SAFB" + } """ + fault = { + 0: "Reserved", + 1: "Reserved", + 2: "Reserved", + 3: "FAN Trip", + 4: "Reserved", + 5: "Reserved", + 6: "Pre PID Fail", + 7: "Bad contact at basic I/O board", + 8: "External Brake Trip", + 9: "No Motor Trip", + 10: "Bad Option Card", + 11: "Reserved", + 12: "Reserved", + 13: "Reserved", + 14: "Pre Over Heat Trip", + 15: "Reserved" + } + + faults = [] + counter = 7 + for x in range(8): + if fault_code_array[x] == 1: + faults = [fault[counter]] + faults + counter = counter - 1 + return ' '.join(faults) class ModbusChannel(Channel): """Modbus channel object.""" - def __init__(self, mesh_name, register_number, data_type, chg_threshold, guarantee_sec, channel_size=1, map_=False, write_enabled=False, transform_fn=identity, unit_number=1, scaling=0, mcu=None): + def __init__(self, mesh_name, register_number, data_type, chg_threshold, guarantee_sec, channel_size=1, map_=False, write_enabled=False, transform_fn=identity, unit_number=1, scaling=0): """Initialize the channel.""" super(ModbusChannel, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled) self.mesh_name = mesh_name @@ -324,63 +486,45 @@ class ModbusChannel(Channel): self.transform_fn = transform_fn self.unit_number = unit_number self.scaling= scaling - self.mcu = mcu - - baud = 9600 - connected = False - while connected == False: - try: - #print("CONNECTING!!!!!!!!!!!!!!!!!!!!!!!!!") - connected = self.mcu.set485Baud(baud) - time.sleep(1) - except Exception as e: - print("Error on connect: ", e) - serial4 = self.mcu.rs485 - - self.instrument = minimalmodbusM1.Instrument(1, serial4) - self.instrument.address = 1 - def read(self): """Return the transformed read value.""" + print("ATTEMPTING TO READ ON {}".format(self.mesh_name)) if self.data_type == "FLOAT": try: - read_value = self.instrument.read_float(self.register_number,4,self.channel_size) + read_value = instrument.read_float(self.register_number,4,self.channel_size) except IOError as e: - log.error(e) + log.info(e) return None elif self.data_type == "INTEGER" or self.data_type == "STRING": try: - read_value = self.instrument.read_register(self.register_number, self.scaling, 4) + read_value = instrument.read_register(self.register_number, self.scaling, 4) except IOError as e: - log.error(e) - return None - elif self.data_type == "FLOATBS": - try: - t = self.instrument.read_registers(self.register_number, 2, 3) - read_value = round(self.byteSwap32(t), 2) - except IOError as e: - log.error(e) + log.info(e) return None read_value = self.transform_fn(read_value) return read_value - - def byteSwap32(self, array): - newVal = "" - for i in array: - i = hex(i).replace('0x', '') - while len(i) < 4: - i = "0" + i - print i - newVal = i + newVal - print newVal - return struct.unpack('!f', newVal.decode('hex'))[0] + + def write(self, value): + """Write a value to a register""" + if self.data_type == "FLOAT": + value = float(value) + elif self.data_type == "INTEGER": + value = int(value) + else: + value = str(value) + try: + instrument.write_register(self.register_number,value, self.scaling, 16 if self.channel_size > 1 else 6 ) + return True + except Exception as e: + log.info("Failed to write value: {}".format(e)) + return False class PLCChannel(Channel): """PLC Channel Object.""" - def __init__(self, ip, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False): + def __init__(self, ip, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False, plc_type='CLX'): """Initialize the channel.""" super(PLCChannel, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled) self.plc_ip = ip @@ -394,12 +538,13 @@ class PLCChannel(Channel): self.guarantee_sec = guarantee_sec self.map_ = map_ self.write_enabled = write_enabled + self.plc_type = plc_type def read(self): """Read the value.""" plc_value = None if self.plc_tag and self.plc_ip: - read_value = read_tag(self.plc_ip, self.plc_tag) + read_value = read_tag(self.plc_ip, self.plc_tag, plc_type=self.plc_type) if read_value: plc_value = read_value[0] @@ -411,6 +556,7 @@ class BoolArrayChannels(Channel): def __init__(self, ip, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False): """Initialize the channel.""" + super(BoolArrayChannels, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled) self.plc_ip = ip self.mesh_name = mesh_name self.plc_tag = plc_tag @@ -431,7 +577,7 @@ class BoolArrayChannels(Channel): if new_val_dict[idx] != self.last_value[idx]: send = True except KeyError: - print("Key Error in self.compare_values for index {}".format(idx)) + log.error("Key Error in self.compare_values for index {}".format(idx)) send = True return send @@ -440,15 +586,15 @@ class BoolArrayChannels(Channel): send_needed = False send_reason = "" if self.plc_tag: - v = read_tag(self.plc_ip, self.plc_tag) - if v: - bool_arr = binarray(v[0]) + val = read_tag(self.plc_ip, self.plc_tag) + if val: + bool_arr = binarray(val[0]) new_val = {} for idx in self.map_: try: new_val[self.map_[idx]] = bool_arr[idx] except KeyError: - print("Not able to get value for index {}".format(idx)) + log.error("Not able to get value for index {}".format(idx)) if self.last_send_time == 0: send_needed = True @@ -470,5 +616,5 @@ class BoolArrayChannels(Channel): self.value = new_val self.last_value = self.value self.last_send_time = time.time() - print("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason)) - return send_needed + log.info("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason)) + return send_needed \ No newline at end of file diff --git a/promagmbs/rework/PiFlow.py b/promagmbs/rework/PiFlow.py new file mode 100644 index 0000000..f4f83de --- /dev/null +++ b/promagmbs/rework/PiFlow.py @@ -0,0 +1,519 @@ +"""Driver for PiFlow""" +import os +import threading +import json +import time +from random import randint +from datetime import datetime as dt +from device_base import deviceBase +import persistence +from utilities import get_public_ip_address, get_private_ip_address +from file_logger import filelogger as log +"""import RPi.GPIO as GPIO + +Relay_Ch1 = 26 +Relay_Ch2 = 20 +Relay_Ch3 = 21 + +GPIO.setwarnings(False) +GPIO.setmode(GPIO.BCM) + +GPIO.setup(Relay_Ch1,GPIO.OUT) +GPIO.output(Relay_Ch1, GPIO.HIGH) +GPIO.setup(Relay_Ch2,GPIO.OUT) +GPIO.output(Relay_Ch2, GPIO.HIGH) +GPIO.setup(Relay_Ch3,GPIO.OUT) +GPIO.output(Relay_Ch3, GPIO.HIGH) +""" +_ = None +os.system('sudo timedatectl set-timezone America/Chicago') +log.info("PiFlow startup") + +# GLOBAL VARIABLES +WAIT_FOR_CONNECTION_SECONDS = 5 +IP_CHECK_PERIOD = 60 + + +# PERSISTENCE FILE +PERSIST = persistence.load('persist.json') +if not PERSIST: + PERSIST = { + 'flowmeter': 247, + 'drive': 1, + 'isVFD': False, + 'drive_enabled': True, + 'state': False, + 'state_timer': 0, + 'plc_ip': '192.168.1.12', + 'yesterday_totalizer_1': dt.today().day, + 'yesterday_totalizer_2': dt.today().day, + 'yesterday_totalizer_3': dt.today().day, + 'yesterday_total_totalizer_1': 0, + 'yesterday_total_midnight_totalizer_1': 0, + 'yesterday_total_totalizer_2': 0, + 'yesterday_total_midnight_totalizer_2': 0, + 'yesterday_total_totalizer_3': 0, + 'yesterday_total_midnight_totalizer_3': 0 + } + persistence.store(PERSIST, 'persist.json') +try: + if time.time() - PERSIST['state_timer'] >= 60: + GPIO.output(Relay_Ch1, GPIO.HIGH) + PERSIST['state'] = False + persistence.store(PERSIST, "persist.json") + elif PERSIST['state']: + GPIO.output(Relay_Ch1, GPIO.LOW) + else: + GPIO.output(Relay_Ch1, GPIO.HIGH) +except: + PERSIST['state'] = False + PERSIST['state_time'] = time.time() + persistence.store(PERSIST, "persist.json") + +drive_enabled = PERSIST['drive_enabled'] +try: + isVFD = PERSIST['isVFD'] +except: + PERSIST['isVFD'] = False + isVFD = PERSIST['isVFD'] + persistence.store(PERSIST) + +try: + plc_ip = PERSIST['plc_ip'] +except: + PERSIST['plc_ip'] = '192.168.1.12' + plc_ip = PERSIST['plc_ip'] + persistence.store(PERSIST) + + +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 = "25" + self.finished = threading.Event() + self.force_send = False + self.public_ip_address = "" + self.private_ip_address = "" + self.public_ip_address_last_checked = 0 + self.status = "" + self.alarm = "" + global mcug, CHANNELS + mcug = mcu + from Tags import tags + + CHANNELS = tags + + 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("PiFlow driver will start in {} seconds".format(WAIT_FOR_CONNECTION_SECONDS - i)) + time.sleep(1) + log.info("BOOM! Starting PiFlow driver...") + + #self._check_watchdog() + self._check_ip_address() + + self.nodes["PiFlow_0199"] = self + + send_loops = 0 + + while True: + now = time.time() + if self.force_send: + log.warning("FORCE SEND: TRUE") + if isVFD: + status = {} + for chan in CHANNELS[:24]: #build status/alarm strings + try: + val = chan.read() + chan.check(val, self.force_send) + status[chan.mesh_name] = chan.value + except Exception as e: + log.warning("An error occured in status check: {}".format(e)) + try: + self.sendStatus(status) + except Exception as e: + log.warning("An error occured in send status: {}".format(e)) + for chan in CHANNELS[24:]: + try: + val = chan.read() + if chan.mesh_name in ['totalizer_1','totalizer_2','totalizer_3']: + right_now = dt.today() + today_total, yesterday_total = self.totalize(val, PERSIST['yesterday_'+chan.mesh_name], right_now.day, right_now.hour, right_now.minute, PERSIST['yesterday_total_midnight_'+chan.mesh_name], PERSIST['yesterday_total_'+chan.mesh_name], chan.mesh_name) + if chan.check(val, self.force_send): + self.sendtodbDev(1, chan.mesh_name, chan.value, 0, 'PiFlow') + self.sendtodbDev(1,"today_"+chan.mesh_name, today_total,0,'PiFlow') + self.sendtodbDev(1,"yesterday_"+chan.mesh_name, yesterday_total,0,'PiFlow') + self.sendtodbDev(1, chan.mesh_name + "_units", "BBL",0,'PiFlow') + else: + if chan.check(val, self.force_send): + self.sendtodbDev(1, chan.mesh_name, chan.value, 0, 'PiFlow') + except Exception as e: + log.warning("An error occured in data collection: {}".format(e)) + else: + for chan in CHANNELS: + try: + if chan.mesh_name == "remote_start": + val = PERSIST["state"] + else: + val = None + for _ in range(3): + temp = chan.read() + if not temp == None: + val = temp + if val == None: + log.info("No modbus data sending previous value") + val = chan.value + if chan.mesh_name in ['totalizer_1','totalizer_2','totalizer_3']: + right_now = dt.today() + today_total, yesterday_total = self.totalize(val, PERSIST['yesterday_'+chan.mesh_name], right_now.day, right_now.hour, right_now.minute, PERSIST['yesterday_total_midnight_'+chan.mesh_name], PERSIST['yesterday_total_'+chan.mesh_name], chan.mesh_name) + if chan.check(val, self.force_send): + self.sendtodbDev(1, chan.mesh_name, chan.value, 0, 'PiFlow') + self.sendtodbDev(1,"today_"+chan.mesh_name, today_total,0,'PiFlow') + self.sendtodbDev(1,"yesterday_"+chan.mesh_name, yesterday_total,0,'PiFlow') + elif chan.mesh_name == "volume_flow" and not PERSIST['drive_enabled']: + if chan.check(val, self.force_send): + self.sendtodbDev(1, chan.mesh_name, chan.value, 0, 'PiFlow') + if chan.value > 0: + self.sendtodbDev(1, "run_status", "Running", 0, 'PiFlow') + else: + self.sendtodbDev(1,"run_status", "Stopped", 0, 'PiFlow') + elif chan.mesh_name == "remote_start": + if chan.check(val, self.force_send): + self.sendtodbDev(1, chan.mesh_name, chan.value, 0, 'PiFlow') + PERSIST["state_timer"] = time.time() + persistence.store(PERSIST, "persist.json") + else: + if chan.check(val, self.force_send): + self.sendtodbDev(1, chan.mesh_name, chan.value, 0, 'PiFlow') + + except Exception as e: + log.warning("An error occured: {}".format(e)) + time.sleep(3) + + + # print("PiFlow 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(10) + + 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[:-1] + 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, 'PiFlow') + 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, 'PiFlow') + self.private_ip_address = test_private_ip + + def PiFlow_sync(self, name, value): + """Sync all data from the driver.""" + self.force_send = True + # self.sendtodb("log", "synced", 0) + return True + + def PiFlow_flowmeternumber(self, name, unit_number): + """Change the unit number for the PiFlow flow meter""" + unit_number = int(unit_number) + if drive_enabled: + for chan in CHANNELS[0:8]: + chan.unit_number = unit_number + PERSIST['flowmeter'] = unit_number + persistence.store(PERSIST, 'persist.json') + return True + else: + for chan in CHANNELS: + chan.unit_number = unit_number + PERSIST['flowmeter'] = unit_number + persistence.store(PERSIST, 'persist.json') + self.sendtodbDev(1, 'flowmeternumber', unit_number, 0,'PiFlow') + return True + return False + + def PiFlow_drivenumber(self, name, unit_number): + """Change the unit number for the PiFlow drive""" + unit_number = int(unit_number) + for chan in CHANNELS[8:]: + chan.unit_number = unit_number + + PERSIST['drive'] = unit_number + persistence.store(PERSIST, 'persist.json') + self.sendtodbDev(1, 'drivenumber', unit_number, 0,'PiFlow') + return True + + def PiFlow_reboot(self, name, value): + os.system('reboot') + return True + + def PiFlow_drive_enabled(self, name, value): + value = int(value) + if value == 1: + PERSIST['drive_enabled'] = True + else: + PERSIST['drive_enabled'] = False + + persistence.store(PERSIST, 'persist.json') + self.sendtodbDev(1, 'drive_enabled', value, 0,'PiFlow') + return True + + def PiFlow_write(self, name, value): + """Write a value to the device via modbus""" + new_val = json.loads(str(value).replace("'", '"')) + addr_n = int(new_val['addr']) + reg_n = int(new_val['reg']) + val_n = new_val['val'] + for chan in CHANNELS: + if chan.unit_number == addr_n and chan.register_number == reg_n: + write_res = chan.write(val_n) + + log.info("Result of PiFlow_write(self, {}, {}) = {}".format(name, value, write_res)) + return write_res + + """def PiFlow_start(self, name, value): + if isVFD: + #do something with the plc + log.info("Sending START signal to PLC") + else: + log.info("Sending START signal to Drive via relay {}".format(Relay_Ch1)) + GPIO.output(Relay_Ch1,GPIO.LOW) + PERSIST["state"] = True + PERSIST["state_timer"] = time.time() + persistence.store(PERSIST,"persist.json") + + return True""" + + """def PiFlow_stop(self, name, value): + if isVFD: + log.info("Sending STOP signal to PLC") + #do something with the plc + else: + log.info("Sending STOP signal to Drive") + GPIO.output(Relay_Ch1,GPIO.HIGH) + PERSIST["state"] = False + PERSIST["state_timer"] = time.time() + persistence.store(PERSIST, "persist.json") + return True""" + + def totalize(self,val, yesterday, day, hour, minute, yesterday_total_midnight, yesterday_total,channel): + if (yesterday_total == 0 and yesterday_total_midnight == 0) or (yesterday_total == None or yesterday_total_midnight == None): + yesterday_total_midnight = val + PERSIST['yesterday_total_midnight_'+channel] = yesterday_total_midnight + persistence.store(PERSIST, 'persist.json') + today_total = val - yesterday_total_midnight + if hour == 0 and minute == 0 and not(day == yesterday): + yesterday_total = today_total + yesterday_total_midnight = val + today_total = val - yesterday_total_midnight + yesterday = day + PERSIST['yesterday_'+channel] = yesterday + PERSIST['yesterday_total_'+channel] = yesterday_total + PERSIST['yesterday_total_midnight_'+channel] = yesterday_total_midnight + persistence.store(PERSIST,'persist.json') + + return today_total,yesterday_total + + def sendStatus(self,status): + status_string = "" + + fault_codes = { + 0: "", + 2: "Auxiliary Input", + 3: "Power Loss", + 4: "UnderVoltage", + 5: "OverVoltage", + 7: "Motor Overload", + 8: "Heatsink OvrTemp", + 9: "Thermister OvrTemp", + 10: "DynBrake OverTemp", + 12: "HW OverCurrent", + 13: "Ground Fault", + 14: "Ground Warning", + 15: "Load Loss", + 17: "Input Phase Loss", + 18: "Motor PTC Trip", + 19: "Task Overrun", + 20: "TorqPrv Spd Band", + 21: "Output PhaseLoss", + 24: "Decel Inhibit", + 25: "OverSpeed Limit", + 26: "Brake Slipped", + 27: "Torq Prove Cflct", + 28: "TP Encls Config", + 29: "Analog In Loss", + 33: "AuRsts Exhausted", + 35: "IPM OverCurrent", + 36: "SW OverCurrent", + 38: "Phase U to Grnd", + 39: "Phase V to Grnd", + 40: "Phase W to Grnd", + 41: "Phase UV Short", + 42: "Phase VW Short", + 43: "Phase WU Short", + 44: "Phase UNegToGrnd", + 45: "Phase VNegToGrnd", + 46: "Phase WNegToGrnd", + 48: "System Defaulted", + 49: "Drive Powerup", + 51: "Clr Fault Queue", + 55: "Ctrl Bd Overtemp", + 59: "Invalid Code", + 61: "Shear Pin 1", + 62: "Shear Pin 2", + 64: "Drive Overload", + 67: "Pump Off", + 71: "Port 1 Adapter", + 72: "Port 2 Adapter", + 73: "Port 3 Adapter", + 74: "Port 4 Adapter", + 75: "Port 5 Adapter", + 76: "Port 6 Adapter", + 77: "IR Volts Range", + 78: "FluxAmpsRef Rang", + 79: "Excessive Load", + 80: "AutoTune Aborted", + 81: "Port 1 DPI Loss", + 82: "Port 2 DPI Loss", + 83: "Port 3 DPI Loss", + 84: "Port 4 DPI Loss", + 85: "Port 5 DPI Loss", + 86: "Port 6 DPI Loss", + 87: "Ixo VoltageRange", + 91: "Pri VelFdbk Loss", + 93: "Hw Enable Check", + 94: "Alt VelFdbk Loss", + 95: "Aux VelFdbk Loss", + 96: "PositionFdbkLoss", + 97: "Auto Tach Switch", + 100: "Parameter Chksum", + 101: "PwrDn NVS Blank", + 102: "NVS Not Blank", + 103: "PwrDn Nvs Incomp", + 104: "Pwr Brd Checksum", + 106: "Incompat MCB-PB", + 107: "Replaced MCB-PB", + 108: "Anlg Cal Chksum", + 110: "Ivld Pwr Bd Data", + 111: "PwrBd Invalid ID", + 112: "PwrBd App MinVer", + 113: "Tracking DataErr", + 115: "PwrDn Table Full", + 116: "PwrDnEntry2Large", + 117: "PwrDn Data Chksm", + 118: "PwrBd PwrDn Chks", + 124: "App ID Changed", + 125: "Using Backup App", + 134: "Start on PowerUp", + 137: "Ext Prechrg Err", + 138: "Precharge Open", + 141: "Autn Enc Angle", + 142: "Autn Spd Rstrct", + 143: "AutoTune CurReg", + 144: "AutoTune Inertia", + 145: "AutoTune Travel", + 13037: "Net IO Timeout" + } + + if status['vfd_active'] == "Stopped": + status_string = status_string + status['vfd_active'] + "; " + status['vfd_ready'] + else: + status_string = status_string + status['vfd_active'] + if status['vfd_rev']: + status_string = status_string + '; ' + status['vfd_rev'] + if status['vfd_fwd']: + status_string = status_string + '; ' + status['vfd_fwd'] + if status['vfd_atreference']: + status_string = status_string + '; ' + status['vfd_atreference'] + alarm_string = "" + if status['vfd_faulted'] == "Drive Faulted": + status_string = status_string + '; ' + status['vfd_faulted'] + if status['vfd_commloss']: + alarm_string = alarm_string + '; ' + status['vfd_commloss'] + if status['vfd_fbkalarm']: + alarm_string = alarm_string + '; ' + status['vfd_fbkalarm'] + if status['vfd_faultcode']: + alarm_string = alarm_string + '; ' + "Fault: {} Fault code: {}".format(fault_codes[status['vfd_faultcode']],str(status['vfd_faultcode'])) + if status['minspeedalarm']: + alarm_string = alarm_string + '; ' + status['minspeedalarm'] + if status['pumpedoff']: + alarm_string = alarm_string + '; ' + status['pumpedoff'] + if status['lockedout']: + alarm_string = alarm_string + '; ' + status['lockedout'] + if status['tubingpressurehi']: + alarm_string = alarm_string + '; ' + status['tubingpressurehi'] + if status['tubingpressurehihi']: + alarm_string = alarm_string + '; ' + status['tubingpressurehihi'] + if status['tubingpressurelo']: + alarm_string = alarm_string + '; ' + status['tubingpressurelo'] + if status['tubingpressurelolo']: + alarm_string = alarm_string + '; ' + status['tubingpressurelolo'] + if status['flowmeterhihi']: + alarm_string = alarm_string + '; ' + status['flowmeterhihi'] + if status['flowmeterhi']: + alarm_string = alarm_string + '; ' + status['flowmeterhi'] + if status['flowmeterlolo']: + alarm_string = alarm_string + '; ' + status['flowmeterlolo'] + if status['flowmeterlo']: + alarm_string = alarm_string + '; ' + status['flowmeterlo'] + if status['fluidlevellolo']: + alarm_string = alarm_string + '; ' + status['fluidlevellolo'] + if status['fluidlevello']: + alarm_string = alarm_string + '; ' + status['fluidlevello'] + if status['fluidlevelhi']: + alarm_string = alarm_string + '; ' + status['fluidlevelhi'] + if status['fluidlevelhihi']: + alarm_string = alarm_string + '; ' + status['fluidlevelhihi'] + try: + if status_string and status_string[0] == '; ': + status_string = status_string[1:] + if status_string and status_string[-1] == '; ': + status_string = status_string[:-1] + if alarm_string and alarm_string[0] == '; ': + alarm_string = alarm_string[1:] + if alarm_string and alarm_string[-1] == '; ': + alarm_string = alarm_string[:-1] + except Exception as e: + log.warning("Error in send status semicolon: {}".format(e)) + + if self.status != status_string: + self.status = status_string + log.info("Sending {} for {}".format(status_string, 'run_status')) + self.sendtodbDev(1, 'run_status', status_string, 0, 'PiFlow') + if self.alarm != alarm_string: + self.alarm = alarm_string + log.info("Sending {} for {}".format(alarm_string, 'fault_a')) + self.sendtodbDev(1, 'fault_a', alarm_string, 0 , 'PiFlow') + + + + diff --git a/promagmbs/rework/Tags.py b/promagmbs/rework/Tags.py new file mode 100644 index 0000000..7a85fa5 --- /dev/null +++ b/promagmbs/rework/Tags.py @@ -0,0 +1,92 @@ +from Channel import PLCChannel,Channel, ModbusChannel, status_codes, fault_code_a, fault_code_b, volume_units, totalizer_units +import persistence + +PERSIST = persistence.load('persist.json') +flowmeter_unit_number = PERSIST['flowmeter'] +drive_enabled = PERSIST['drive_enabled'] +isVFD = PERSIST['isVFD'] +if drive_enabled: + drive_unit_number = PERSIST['drive'] +try: + plc_ip = PERSIST['plc_ip'] +except: + PERSIST['plc_ip'] = '192.168.1.12' + persistence.store(PERSIST) +if isVFD: + tags = [ + PLCChannel(plc_ip,'vfd_atreference','sts_VFD_AtReference','BOOL',0,3600,map_={0: "", 1: "At speed"},plc_type='Micro800'), + PLCChannel(plc_ip,'vfd_rev','sts_VFD_REV','BOOL',0,3600,map_={0: "", 1: "Operating in Reverse"},plc_type='Micro800'), + PLCChannel(plc_ip,'vfd_fwd','sts_VFD_FWD','BOOL',0,3600,map_={0: "", 1: "Operating in Forward"},plc_type='Micro800'), + PLCChannel(plc_ip,'vfd_active','sts_VFD_Active','BOOL',0,3600,map_={0: "Stopped", 1: "Running"},plc_type='Micro800'), + PLCChannel(plc_ip,'vfd_ready','sts_VFD_Ready','BOOL',0,3600,map_={0: "Drive Not Ready", 1: "Drive Ready"},plc_type='Micro800'), + PLCChannel(plc_ip,'vfd_faultcode','sts_VFD_FaultCode','REAL',0,3600, plc_type='Micro800'), + PLCChannel(plc_ip,'vfd_faulted','AL0_VFD','BOOL',0,3600,map_={0: "", 1: "Drive Faulted"},plc_type='Micro800'), + PLCChannel(plc_ip,'vfd_commloss','AL0_VFDComLoss','BOOL',0,3600,map_={0: "", 1: "Drive Comms Loss"},plc_type='Micro800'), + PLCChannel(plc_ip,'vfd_fbkalarm','AL0_VFD_FBAlarm','BOOL',0,3600,map_={0: "", 1: "Drive Lost Feedback"},plc_type='Micro800'), + PLCChannel(plc_ip,'tubingpressurehi','AL0_TubingPressureHi','BOOL',0,3600,map_={0: "", 1: "High Tubing Pressure"},plc_type='Micro800'), + PLCChannel(plc_ip,'tubingpressurehihi','AL0_TubingPressureHiHi','BOOL',0,3600,map_={0: "", 1: "High High Tubing Pressure"},plc_type='Micro800'), + PLCChannel(plc_ip,'tubingpressurelo','AL0_TubingPressureLo','BOOL',0,3600,map_={0: "", 1: "Low Tubing Pressure"},plc_type='Micro800'), + PLCChannel(plc_ip,'tubingpressurelolo','AL0_TubingPressureLoLo','BOOL',0,3600,map_={0: "", 1: "Low Low Tubing Pressure"},plc_type='Micro800'), + PLCChannel(plc_ip,'flowmeterhihi','AL0_FlowMeterHiHi','BOOL',0,3600,map_={0: "", 1: "High High FM Flow Rate"},plc_type='Micro800'), + PLCChannel(plc_ip,'flowmeterhi','AL0_FlowMeterHi','BOOL',0,3600,map_={0: "", 1: "High FM Flow Rate"},plc_type='Micro800'), + PLCChannel(plc_ip,'flowmeterlolo','AL0_FlowMeterLoLo','BOOL',0,3600,map_={0: "", 1: "Low Low FM Flow Rate"},plc_type='Micro800'), + PLCChannel(plc_ip,'flowmeterlo','AL0_FlowMeterLo','BOOL',0,3600,map_={0: "", 1: "Low FM Flow Rate"},plc_type='Micro800'), + PLCChannel(plc_ip,'minspeedalarm','AL0_MinSpeedAlarm','BOOL',0,3600,map_={0: "", 1: "Drive not able to maintain min speed"},plc_type='Micro800'), + PLCChannel(plc_ip,'pumpedoff','AL0_PumpedOff','BOOL',0,3600,map_={0: "", 1: "Pumped Off"},plc_type='Micro800'), + PLCChannel(plc_ip,'fluidlevellolo','AL0_FluidLevelLoLo','BOOL',0,3600,map_={0: "", 1: "Low Low Fluid Level"},plc_type='Micro800'), + PLCChannel(plc_ip,'fluidlevello','AL0_FluidLevelLo','BOOL',0,3600,map_={0: "", 1: "Low Fluid Level"},plc_type='Micro800'), + PLCChannel(plc_ip,'fluidlevelhi','AL0_FluidLevelHi','BOOL',0,3600,map_={0: "", 1: "High Fluid Level"},plc_type='Micro800'), + PLCChannel(plc_ip,'fluidlevelhihi','AL0_FluidLevelHiHi','BOOL',0,3600,map_={0: "", 1: "High High Fluid Level"},plc_type='Micro800'), + PLCChannel(plc_ip,'lockedout','AlarmLockOut','BOOL',0,3600,map_={0: "", 1: "Locked Out Repeated Alarms"},plc_type='Micro800'), + PLCChannel(plc_ip,'volume_flow','Val_FlowmeterFR','REAL',5,3600,plc_type='Micro800'), + PLCChannel(plc_ip,'current','val_VFD_OutputCurrent','REAL',5,3600,plc_type='Micro800'), + PLCChannel(plc_ip,'frequency','val_VFD_ActualSpeed','REAL',5,3600,plc_type='Micro800'), + PLCChannel(plc_ip,'pid_feedback','val_FluidLevel','REAL',5,3600,plc_type='Micro800'), + PLCChannel(plc_ip,'totalizer_1','Val_FlowMeterT1','REAL',5,3600,plc_type='Micro800'), + PLCChannel(plc_ip,'totalizer_2','Val_FlowMeterT2','REAL',5,3600,plc_type='Micro800'), + PLCChannel(plc_ip,'totalizer_3','Val_FlowMeterT3','REAL',5,3600,plc_type='Micro800'), + PLCChannel(plc_ip,'volume_flow_units','CMD_FlowMeterUnit','BOOL',1,3600,map_={0: "GPM", 1: "BPD"},plc_type='Micro800') + ] +else: + if drive_enabled: + tags = [ + ModbusChannel('volume_flow', 3873, 'FLOAT', 10, 3600,channel_size=2, unit_number=flowmeter_unit_number), + ModbusChannel('totalizer_1', 2609, 'FLOAT', 100, 3600,channel_size=2, unit_number=flowmeter_unit_number), + ModbusChannel('totalizer_2', 2809, 'FLOAT', 100, 3600,channel_size=2, unit_number=flowmeter_unit_number), + ModbusChannel('totalizer_3', 3009, 'FLOAT', 100, 3600,channel_size=2, unit_number=flowmeter_unit_number), + ModbusChannel('volume_flow_units', 2102, 'INTEGER', 1,86400,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=volume_units), + ModbusChannel('totalizer_1_units', 4603, 'INTEGER', 1,86400,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units), + ModbusChannel('totalizer_2_units', 4604, 'INTEGER', 1,86400,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units), + ModbusChannel('totalizer_3_units', 4605, 'INTEGER', 1,86400,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units), + ModbusChannel('remote_start', 0000, 'INTEGER', 1, 86400, channel_size=1, unit_number=flowmeter_unit_number), + ModbusChannel('run_status', 772, 'STRING', 0, 3600, channel_size=1, unit_number=drive_unit_number, transform_fn=status_codes), + ModbusChannel('frequency', 784, 'INTEGER', 2, 3600, channel_size=2, unit_number=drive_unit_number,scaling=2 ), + ModbusChannel('current', 783, 'INTEGER', 2, 3600, channel_size=2, unit_number=drive_unit_number,scaling=1 ), + ModbusChannel('fault_a', 815, 'STRING', 1, 3600, channel_size=1, unit_number=drive_unit_number,transform_fn=fault_code_a), + ModbusChannel('fault_b', 816, 'STRING', 1, 3600, channel_size=1, unit_number=drive_unit_number,transform_fn=fault_code_b), + ModbusChannel('pid_ref', 791, 'INTEGER', 5, 3600, channel_size=1, unit_number=drive_unit_number,scaling=1), + ModbusChannel('pid_feedback', 792, 'INTEGER', 5, 3600, channel_size=1, unit_number=drive_unit_number,scaling=1), + ModbusChannel('motor_rated_current', 4896, 'INTEGER', 300, 86400, channel_size=1, unit_number=drive_unit_number,scaling=1), + ModbusChannel('sleep_delay', 4924, 'INTEGER', 5, 86400, channel_size=1, unit_number=drive_unit_number, scaling=1) + ] + else: + tags = [ + ModbusChannel('volume_flow', 3873, 'FLOAT', 10, 3600,channel_size=2, unit_number=flowmeter_unit_number), + ModbusChannel('totalizer_1', 2609, 'FLOAT', 100, 3600,channel_size=2, unit_number=flowmeter_unit_number), + ModbusChannel('totalizer_2', 2809, 'FLOAT', 100, 3600,channel_size=2, unit_number=flowmeter_unit_number), + ModbusChannel('totalizer_3', 3009, 'FLOAT', 100, 3600,channel_size=2, unit_number=flowmeter_unit_number), + ModbusChannel('volume_flow_units', 2102, 'INTEGER', 1, 86400,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=volume_units), + ModbusChannel('totalizer_1_units', 4603, 'INTEGER', 1, 86400,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units), + ModbusChannel('totalizer_2_units', 4604, 'INTEGER', 1, 86400,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units), + ModbusChannel('totalizer_3_units', 4605, 'INTEGER', 1, 86400,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units), + ModbusChannel('remote_start', 0000, 'BOOL', 1, 86400, channel_size=1, unit_number=flowmeter_unit_number) + ] + + + + + + + + + \ No newline at end of file diff --git a/tenflowmeterskid/Tags.py b/tenflowmeterskid/Tags.py index 73a5a0d..9d56f3d 100644 --- a/tenflowmeterskid/Tags.py +++ b/tenflowmeterskid/Tags.py @@ -29,7 +29,28 @@ tags = [ PLCChannel(PLC_IP_ADDRESS, "reverse_out_lifetime","Val_Out_T3","REAL", 1000000000, 3600, plc_type="Micro800"), PLCChannel(PLC_IP_ADDRESS, "total_in_lifetime", "Val_T1TotalIn", "REAL",1000000000, 3600, plc_type="Micro800"), PLCChannel(PLC_IP_ADDRESS, "total_out_lifetime", "Val_T1TotalOut", "REAL", 1000000000, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "fm1_month","Val_Flowmeter1MonthTotal","REAL", 1000000000, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pond_1_raw_min","pond1scaling.rawmin","REAL", 5, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pond_1_raw_max","pond1scaling.rawmax","REAL", 5, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pond_1_eu_min","pond1scaling.eumin","REAL", 5, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pond_1_eu_max","pond1scaling.eumax","REAL", 5, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pond_1_offset","pond1offset","REAL", 5, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pond_2_raw_min","pond2scaling.rawmin","REAL", 5, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pond_2_raw_max","pond2scaling.rawmax","REAL", 5, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pond_2_eu_min","pond2scaling.eumin","REAL", 5, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pond_2_eu_max","pond2scaling.eumax","REAL", 5, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pond_2_offset","pond2offset","REAL", 5, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pond_1_height","pond1Height","REAL", 5, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pond_2_height","pond2height","REAL", 5, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pond_1_volume","pond1volume","REAL", 1000000000, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pond_2_volume","pond2volume","REAL", 1000000000, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pond_sum_volume","pondVolumeTotal","REAL", 3000000000, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pond_1_high_spt","SPT_Pond_1_High_Level","REAL", 1, 86400, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pond_2_high_spt","SPT_Pond_2_High_Level","REAL", 1, 86400, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pond_1_alarm","Pond1HiAlarm","BOOL", 0, 3600, plc_type="Micro800", map_={0: "Good", 1: "High", None: "Error"}), + PLCChannel(PLC_IP_ADDRESS, "pond_2_alarm","Pond2HiAlarm","BOOL", 0, 3600, plc_type="Micro800", map_={0: "Good", 1: "High", None: "Error"}) +] + +""" PLCChannel(PLC_IP_ADDRESS, "fm1_month","Val_Flowmeter1MonthTotal","REAL", 1000000000, 3600, plc_type="Micro800"), PLCChannel(PLC_IP_ADDRESS, "fm2_month","Val_Flowmeter2MonthTotal","REAL", 1000000000, 3600, plc_type="Micro800"), PLCChannel(PLC_IP_ADDRESS, "fm3_month","Val_Flowmeter3MonthTotal","REAL", 1000000000, 3600, plc_type="Micro800"), PLCChannel(PLC_IP_ADDRESS, "fm4_month","Val_Flowmeter4MonthTotal","REAL", 1000000000, 3600, plc_type="Micro800"), @@ -76,24 +97,4 @@ tags = [ PLCChannel(PLC_IP_ADDRESS, "fm9_todays","Val_Flowmeter9TodaysTotal","REAL", 1000000000, 3600, plc_type="Micro800"), PLCChannel(PLC_IP_ADDRESS, "fm10_todays","Val_Flowmeter10TodaysTotal","REAL", 1000000000, 3600, plc_type="Micro800"), PLCChannel(PLC_IP_ADDRESS, "total_in_today", "Val_TodaysTotalIn", "REAL",1000000000, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "total_out_today", "Val_TodaysTotalOut", "REAL", 1000000000, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pond_1_raw_min","pond1scaling.rawmin","REAL", 5, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pond_1_raw_max","pond1scaling.rawmax","REAL", 5, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pond_1_eu_min","pond1scaling.eumin","REAL", 5, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pond_1_eu_max","pond1scaling.eumax","REAL", 5, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pond_1_offset","pond1offset","REAL", 5, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pond_2_raw_min","pond2scaling.rawmin","REAL", 5, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pond_2_raw_max","pond2scaling.rawmax","REAL", 5, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pond_2_eu_min","pond2scaling.eumin","REAL", 5, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pond_2_eu_max","pond2scaling.eumax","REAL", 5, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pond_2_offset","pond2offset","REAL", 5, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pond_1_height","pond1Height","REAL", 5, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pond_2_height","pond2height","REAL", 5, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pond_1_volume","pond1volume","REAL", 1000000000, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pond_2_volume","pond2volume","REAL", 1000000000, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pond_sum_volume","pondVolumeTotal","REAL", 3000000000, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pond_1_high_spt","SPT_Pond_1_High_Level","REAL", 1, 86400, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pond_2_high_spt","SPT_Pond_2_High_Level","REAL", 1, 86400, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pond_1_alarm","Pond1HiAlarm","BOOL", 0, 3600, plc_type="Micro800", map_={0: "Good", 1: "High", None: "Error"}), - PLCChannel(PLC_IP_ADDRESS, "pond_2_alarm","Pond2HiAlarm","BOOL", 0, 3600, plc_type="Micro800", map_={0: "Good", 1: "High", None: "Error"}) -] + PLCChannel(PLC_IP_ADDRESS, "total_out_today", "Val_TodaysTotalOut", "REAL", 1000000000, 3600, plc_type="Micro800"), """ \ No newline at end of file diff --git a/tenflowmeterskid/config.txt b/tenflowmeterskid/config.txt index 127f415..5d37c53 100644 --- a/tenflowmeterskid/config.txt +++ b/tenflowmeterskid/config.txt @@ -8,7 +8,7 @@ "file4": "Tags.py" }, "deviceName": "tenflowmeterskid", - "releaseVersion": "1", + "releaseVersion": "4", "driverFileName": "tenflowmeterskid.py", "driverId": "0100" } \ No newline at end of file diff --git a/tenflowmeterskid/tenflowmeterskid.py b/tenflowmeterskid/tenflowmeterskid.py index 22fbfd3..0f43db8 100644 --- a/tenflowmeterskid/tenflowmeterskid.py +++ b/tenflowmeterskid/tenflowmeterskid.py @@ -12,7 +12,7 @@ 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") @@ -32,6 +32,24 @@ if not PERSIST: } 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): @@ -46,12 +64,13 @@ class start(threading.Thread, deviceBase): mqtt=mqtt, Nodes=Nodes) self.daemon = True - self.version = "1" + self.version = "4" 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 threading.Thread.start(self) # this is a required function for all drivers, its goal is to upload some piece of data @@ -83,7 +102,12 @@ class start(threading.Thread, deviceBase): val = chan.read() if chan.check(val, self.force_send): if chan.mesh_name in PERSIST["ignore_list"]: - self.sendtodbDev(1, chan.mesh_name, None, 0, 'tenflowmeterskid') + 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') #time.sleep(TAG_DATAERROR_SLEEPTIME) # sleep to allow Micro800 to handle ENET requests @@ -327,4 +351,88 @@ class start(threading.Thread, deviceBase): if value in PERSIST["ignore_list"]: PERSIST["ignore_list"].remove(value) persistence.store(PERSIST) - return True \ No newline at end of file + 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') \ No newline at end of file