diff --git a/dual_flowmeter/Tags.py b/dual_flowmeter/Tags.py index 30ed23a..efe7d79 100644 --- a/dual_flowmeter/Tags.py +++ b/dual_flowmeter/Tags.py @@ -2,24 +2,24 @@ from Channel import PLCChannel, ModbusChannel from dual_flowmeter import PLC_IP_ADDRESS tags = [ - PLCChannel(PLC_IP_ADDRESS, "pump_1_daily_total","Pump_1_Daily_Flow_Rate_Total","REAL", 10, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pump_1_daily_total","Pump_1_Daily_Flow_Rate_Total","REAL", 100, 3600, plc_type="Micro800"), PLCChannel(PLC_IP_ADDRESS, "pump_1_run_status","Pump_1_Run_Status","BOOL", 0, 3600, plc_type="Micro800", map_={0: "Off", 1: "On"}), - PLCChannel(PLC_IP_ADDRESS, "pump_1_flowrate","Pump_1_SCL_Flow_Meter","REAL", 250, 600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pump_1_yesterdays_total","Pump_1_Yesterdays_Total","REAL", 10, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pump_1_power","Pump_1_Power","BOOL", 0, 3600, plc_type="Micro800", map_={0: "Off", 1: "On"}), - PLCChannel(PLC_IP_ADDRESS, "pump_1_prevmonth_total","Pump_1_PrevMonth_Total","REAL", 10, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pump_1_month_total","Pump_1_Current_Month_Total","REAL", 10, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pump_1_lifetime_total","Pump_1_Lifetime_Flow","REAL", 10, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pump_1_flowrate","Pump_1_SCL_Flow_Meter","REAL", 1000, 600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pump_1_yesterdays_total","Pump_1_Yesterdays_Total","REAL", 1000, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pump_1_prevmonth_total","Pump_1_PrevMonth_Total","REAL", 1000, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pump_1_month_total","Pump_1_Current_Month_Total","REAL", 1000, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pump_1_lifetime_total","Pump_1_Lifetime_Flow","REAL", 1000, 3600, plc_type="Micro800"), PLCChannel(PLC_IP_ADDRESS, "pump_1_suction","Suction_PSI_TP1_Scaled","REAL", 10, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pump_2_daily_total","Pump_2_Daily_Flow_Rate_Total","REAL", 10, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pump_2_daily_total","Pump_2_Daily_Flow_Rate_Total","REAL", 100, 3600, plc_type="Micro800"), PLCChannel(PLC_IP_ADDRESS, "pump_2_run_status","Pump_2_Run_Status","BOOL", 0, 3600, plc_type="Micro800", map_={0: "Off", 1: "On"}), - PLCChannel(PLC_IP_ADDRESS, "pump_2_flowrate","Pump_2_SCL_Flow_Meter","REAL", 250, 600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pump_2_yesterdays_total","Pump_2_Yesterdays_Total","REAL", 10, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pump_2_power","Pump_2_Power","BOOL", 0, 3600, plc_type="Micro800", map_={0: "Off", 1: "On"}), - PLCChannel(PLC_IP_ADDRESS, "pump_2_prevmonth_total","Pump_2_PrevMonth_Total","REAL", 100, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pump_2_month_total","Pump_2_Current_Month_Total","REAL", 50, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pump_2_lifetime_total","Pump_2_Lifetime_Flow","REAL", 10, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pump_2_flowrate","Pump_2_SCL_Flow_Meter","REAL", 1000, 600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pump_2_yesterdays_total","Pump_2_Yesterdays_Total","REAL", 1000, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pump_2_prevmonth_total","Pump_2_PrevMonth_Total","REAL", 1000, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pump_2_month_total","Pump_2_Current_Month_Total","REAL", 1000, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pump_2_lifetime_total","Pump_2_Lifetime_Flow","REAL", 1000, 3600, plc_type="Micro800"), PLCChannel(PLC_IP_ADDRESS, "pump_2_suction","Suction_PSI_TP2_Scaled","REAL", 10, 3600, plc_type="Micro800"), PLCChannel(PLC_IP_ADDRESS, "pump_charge_psi_tp1", "Charge_PSI_TP1_Scaled", "REAL", 10, 3600, plc_type="Micro800"), - PLCChannel(PLC_IP_ADDRESS, "pond_1_height", "Pond_level_tp1_scaled", "REAL", 2, 3600, plc_type="Micro800") + PLCChannel(PLC_IP_ADDRESS, "pond_1_height", "Pond_level_tp1_scaled", "REAL", 2, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "pond_1_volume", "pond1volume", "REAL", 100000, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "charge_pump_run_status","CHARGE_PUMP_Run_Status","BOOL", 0, 3600, plc_type="Micro800", map_={0: "Off", 1: "On"}) ] \ No newline at end of file diff --git a/dual_flowmeter/config.txt b/dual_flowmeter/config.txt index c4b56f5..ab26794 100644 --- a/dual_flowmeter/config.txt +++ b/dual_flowmeter/config.txt @@ -8,7 +8,7 @@ "file4": "Tags.py" }, "deviceName": "dual_flowmeter", - "releaseVersion": "5", + "releaseVersion": "7", "driverFileName": "dual_flowmeter.py", "driverId": "0100" } \ No newline at end of file diff --git a/dual_flowmeter/dual_flowmeter.py b/dual_flowmeter/dual_flowmeter.py index e3cba4d..53b942e 100644 --- a/dual_flowmeter/dual_flowmeter.py +++ b/dual_flowmeter/dual_flowmeter.py @@ -10,7 +10,7 @@ from Channel import PLCChannel, ModbusChannel,read_tag, write_tag, TAG_DATAERROR import persistence from utilities import get_public_ip_address from file_logger import filelogger as log -PLC_IP_ADDRESS = "192.168.1.10" +PLC_IP_ADDRESS = "192.168.1.12" from Tags import tags _ = None @@ -42,7 +42,7 @@ class start(threading.Thread, deviceBase): mqtt=mqtt, Nodes=Nodes) self.daemon = True - self.version = "5" + self.version = "7" self.finished = threading.Event() self.force_send = False self.public_ip_address = "" diff --git a/piflow/Channel.py b/piflow/Channel.py index 48961c8..6c6ccbb 100644 --- a/piflow/Channel.py +++ b/piflow/Channel.py @@ -321,38 +321,56 @@ def pad_to_x(n,x): def status_codes(n): status_array = int_to_bits(n,16) - - status = { - 0: "Stopped", - 1: "Operating Forward", - 2: "Operating Reverse", - 3: "Fault Trip", - 4: "Accelerating", - 5: "Decelerating", - 6: "Speed Reached", - 7: "DC Braking", - 8: "Drive Stopped", - 9: "Not Used", - 10: "Brake Release Signal", - 11: "Forward Cmd", - 12: "Reverse Cmd", - 13: "REM, R/S", - 14: "REM, Freq", - 15: "Reversed" + status_low = { + 0: "Stopped;", + 1: "Operating in Forward;", + 2: "Operating in Reverse;", + 3: "DC operating;" } - stats = [] - counter = 15 - for x in range(16): - if status_array[x] == 1: - stats = [status[counter]] + stats - counter = counter - 1 - return '; '.join(stats) + 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 = { + """ fault = { 0: "OCT", 1: "OVT", 2: "EXT-A", @@ -369,6 +387,24 @@ def fault_code_a(n): 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 = [] @@ -377,13 +413,13 @@ def fault_code_a(n): if fault_code_array[x] == 1: faults = [fault[counter]] + faults counter = counter - 1 - return '; '.join(faults) + return ' '.join(faults) def fault_code_b(n): fault_code_array = int_to_bits(n,8) - fault = { + """ fault = { 0: "COM", 1: "Reserved", 2: "NTC", @@ -392,6 +428,24 @@ def fault_code_b(n): 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 = [] @@ -400,7 +454,7 @@ def fault_code_b(n): if fault_code_array[x] == 1: faults = [fault[counter]] + faults counter = counter - 1 - return '; '.join(faults) + return ' '.join(faults) class ModbusChannel(Channel): """Modbus channel object.""" diff --git a/piflow/PiFlow.py b/piflow/PiFlow.py index faff57c..2fffab1 100644 --- a/piflow/PiFlow.py +++ b/piflow/PiFlow.py @@ -45,7 +45,7 @@ class start(threading.Thread, deviceBase): mqtt=mqtt, Nodes=Nodes) self.daemon = True - self.version = "19" + self.version = "20" self.finished = threading.Event() self.force_send = False self.public_ip_address = "" diff --git a/piflow/Tags.py b/piflow/Tags.py index aeb6d4d..46d00a0 100644 --- a/piflow/Tags.py +++ b/piflow/Tags.py @@ -18,13 +18,14 @@ if drive_enabled: ModbusChannel('totalizer_1_units', 4603, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units), ModbusChannel('totalizer_2_units', 4604, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units), ModbusChannel('totalizer_3_units', 4605, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units), - ModbusChannel('run_status', 13, 'STRING', 0, 3600, channel_size=1, unit_number=drive_unit_number, transform_fn=status_codes), - ModbusChannel('frequency', 9, 'INTEGER', 0.5, 600, channel_size=2, unit_number=drive_unit_number,scaling=2 ), - ModbusChannel('current', 8, 'INTEGER', 0.5, 600, channel_size=2, unit_number=drive_unit_number,scaling=1 ), - ModbusChannel('fault_a', 14, 'STRING', 1, 3600, channel_size=1, unit_number=drive_unit_number,transform_fn=fault_code_a), - ModbusChannel('fault_b', 28, 'STRING', 1, 3600, channel_size=1, unit_number=drive_unit_number,transform_fn=fault_code_b), - ModbusChannel('pid_ref', 4367, 'INTEGER', 1, 600, channel_size=1, unit_number=drive_unit_number,scaling=1), - ModbusChannel('pid_feedback', 4368, 'INTEGER', 1, 600, channel_size=1, unit_number=drive_unit_number,scaling=1), + + ModbusChannel('run_status', 772, 'STRING', 0, 3600, channel_size=1, unit_number=drive_unit_number, transform_fn=status_codes), + ModbusChannel('frequency', 784, 'INTEGER', 0.5, 600, channel_size=2, unit_number=drive_unit_number,scaling=2 ), + ModbusChannel('current', 783, 'INTEGER', 0.5, 600, 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', 1, 600, channel_size=1, unit_number=drive_unit_number,scaling=1), + ModbusChannel('pid_feedback', 792, 'INTEGER', 1, 600, channel_size=1, unit_number=drive_unit_number,scaling=1), ModbusChannel('motor_rated_current', 4896, 'INTEGER', 0, 3600, channel_size=1, unit_number=drive_unit_number,scaling=1), ModbusChannel('sleep_delay', 4924, 'INTEGER', 1, 600, channel_size=1, unit_number=drive_unit_number, scaling=1) ] diff --git a/piflow/config.txt b/piflow/config.txt index 9142d24..aaedc0d 100644 --- a/piflow/config.txt +++ b/piflow/config.txt @@ -3,7 +3,7 @@ "driverFileName":"PiFlow.py", "deviceName":"piflow", "driverId":"0280", -"releaseVersion":"19", +"releaseVersion":"20", "files": { "file1":"PiFlow.py", "file2":"Channel.py", diff --git a/piflow/v1/Channel.py b/piflow/v1/Channel.py new file mode 100644 index 0000000..48961c8 --- /dev/null +++ b/piflow/v1/Channel.py @@ -0,0 +1,557 @@ +"""Define Meshify channel class.""" +import time +from pycomm.ab_comm.clx import Driver as ClxDriver +from pycomm.cip.cip_base import CommError, DataError +from file_logger import filelogger as log +import minimalmodbus + +minimalmodbus.BAUDRATE = 9600 +minimalmodbus.STOPBITS = 1 + +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) + 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() + 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, 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: + return arr_vals + else: + log.error("No length for {}".format(addr)) + 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]) + 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): + """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' 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 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 + +def volume_units(vunit): + units = { + 0: "cm cubed/s", + 1: "cm cubed/min", + 2: "cm cubed/h", + 3: "cm cubed/d", + 4: "dm cubed/s", + 5: "dm cubed/min", + 6: "dm cubed/h", + 7: "dm cubed/d", + 8: "m cubed/s", + 9: "m cubed/min", + 10: "m cubed/h", + 11: "m cubed/d", + 12: "ml/s", + 13: "ml/min", + 14: "ml/h", + 15: "ml/d", + 16: "l/s", + 17: "l/min", + 18: "l/h (+)", + 19: "l/d", + 20: "hl/s", + 21: "hl/min", + 22: "hl/h", + 23: "hl/d", + 24: "Ml/s", + 25: "Ml/min", + 26: "Ml/h", + 27: "Ml/d", + 32: "af/s", + 33: "af/min", + 34: "af/h", + 35: "af/d", + 36: "ft cubed/s", + 37: "ft cubed/min", + 38: "ft cubed/h", + 39: "ft cubed/d", + 40: "fl oz/s (us)", + 41: "fl oz/min (us)", + 42: "fl oz/h (us)", + 43: "fl oz/d (us)", + 44: "gal/s (us)", + 45: "gal/min (us)", + 46: "gal/h (us)", + 47: "gal/d (us)", + 48: "Mgal/s (us)", + 49: "Mgal/min (us)", + 50: "Mgal/h (us)", + 51: "Mgal/d (us)", + 52: "bbl/s (us;liq.)", + 53: "bbl/min (us;liq.)", + 54: "bbl/h (us;liq.)", + 55: "bbl/d (us;liq.)", + 56: "bbl/s (us;beer)", + 57: "bbl/min (us;beer)", + 58: "bbl/h (us;beer)", + 59: "bbl/d (us;beer)", + 60: "bbl/s (us;oil)", + 61: "bbl/min (us;oil)", + 62: "bbl/h (us;oil)", + 63: "bbl/d (us;oil)", + 64: "bbl/s (us;tank)", + 65: "bbl/min (us;tank)", + 66: "bbl/h (us;tank)", + 67: "bbl/d (us;tank)", + 68: "gal/s (imp)", + 69: "gal/min (imp)", + 70: "gal/h (imp)", + 71: "gal/d (imp)", + 72: "Mgal/s (imp)", + 73: "Mgal/min (imp)", + 74: "Mgal/h (imp)", + 75: "Mgal/d (imp)", + 76: "bbl/s (imp;beer)", + 77: "bbl/min (imp;beer)", + 78: "bbl/h (imp;beer)", + 79: "bbl/d (imp;beer)", + 80: "bbl/s (imp;oil)", + 81: "bbl/min (imp;oil)", + 82: "bbl/h (imp;oil)", + 83: "bbl/d (imp;oil)", + 88: "kgal/s (us)", + 89: "kgal/min (us)", + 90: "kgal/h (us)", + 91: "kgal/d (us)", + 92: "MMft cubed/s", + 93: "MMft cubed/min", + 94: "MMft cubed/h", + 96: "Mft cubed/d" + } + return units[vunit] + +def totalizer_units(tunit): + + units = { + 0: "cm cubed", + 1: "dm cubed", + 2: "m cubed", + 3: "ml", + 4: "l", + 5: "hl", + 6: "Ml Mega", + 8: "af", + 9: "ft cubed", + 10: "fl oz (us)", + 11: "gal (us)", + 12: "Mgal (us)", + 13: "bbl (us;liq.)", + 14: "bbl (us;beer)", + 15: "bbl (us;oil)", + 16: "bbl (us;tank)", + 17: "gal (imp)", + 18: "Mgal (imp)", + 19: "bbl (imp;beer)", + 20: "bbl (imp;oil)", + 22: "kgal (us)", + 23: "Mft cubed", + 50: "g", + 51: "kg", + 52: "t", + 53: "oz", + 54: "lb", + 55: "STon", + 100: "Nl", + 101: "Nm cubed", + 102: "Sm cubed", + 103: "Sft cubed", + 104: "Sl", + 105: "Sgal (us)", + 106: "Sbbl (us;liq.)", + 107: "Sgal (imp)", + 108: "Sbbl (us;oil)", + 109: "MMSft cubed", + 110: "Nhl", + 251: "None" + } + 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 = { + 0: "Stopped", + 1: "Operating Forward", + 2: "Operating Reverse", + 3: "Fault Trip", + 4: "Accelerating", + 5: "Decelerating", + 6: "Speed Reached", + 7: "DC Braking", + 8: "Drive Stopped", + 9: "Not Used", + 10: "Brake Release Signal", + 11: "Forward Cmd", + 12: "Reverse Cmd", + 13: "REM, R/S", + 14: "REM, Freq", + 15: "Reversed" + } + stats = [] + counter = 15 + for x in range(16): + if status_array[x] == 1: + stats = [status[counter]] + stats + counter = counter - 1 + return '; '.join(stats) + +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" + } + + 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" + } + + 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): + """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.instrument = minimalmodbus.Instrument('/dev/ttyS0', self.unit_number) + self.scaling= scaling + + def read(self): + """Return the transformed read value.""" + if self.data_type == "FLOAT": + try: + read_value = self.instrument.read_float(self.register_number,4,self.channel_size) + except IOError as 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) + except IOError 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: + self.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 \ No newline at end of file diff --git a/piflow/v1/Tags.py b/piflow/v1/Tags.py new file mode 100644 index 0000000..aeb6d4d --- /dev/null +++ b/piflow/v1/Tags.py @@ -0,0 +1,50 @@ +from Channel import PLCChannel,Channel, ModbusChannel, status_codes, fault_code_a, fault_code_b, volume_units, totalizer_units +import persistence + +data = persistence.load('persist.json') +flowmeter_unit_number = data['flowmeter'] +drive_enabled = data['drive_enabled'] +if drive_enabled: + drive_unit_number = data['drive'] + + +if drive_enabled: + tags = [ + ModbusChannel('volume_flow', 3873, 'FLOAT', 1,600,channel_size=2, unit_number=flowmeter_unit_number), + ModbusChannel('totalizer_1', 2609, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number), + ModbusChannel('totalizer_2', 2809, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number), + ModbusChannel('totalizer_3', 3009, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number), + ModbusChannel('volume_flow_units', 2102, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=volume_units), + ModbusChannel('totalizer_1_units', 4603, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units), + ModbusChannel('totalizer_2_units', 4604, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units), + ModbusChannel('totalizer_3_units', 4605, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units), + ModbusChannel('run_status', 13, 'STRING', 0, 3600, channel_size=1, unit_number=drive_unit_number, transform_fn=status_codes), + ModbusChannel('frequency', 9, 'INTEGER', 0.5, 600, channel_size=2, unit_number=drive_unit_number,scaling=2 ), + ModbusChannel('current', 8, 'INTEGER', 0.5, 600, channel_size=2, unit_number=drive_unit_number,scaling=1 ), + ModbusChannel('fault_a', 14, 'STRING', 1, 3600, channel_size=1, unit_number=drive_unit_number,transform_fn=fault_code_a), + ModbusChannel('fault_b', 28, 'STRING', 1, 3600, channel_size=1, unit_number=drive_unit_number,transform_fn=fault_code_b), + ModbusChannel('pid_ref', 4367, 'INTEGER', 1, 600, channel_size=1, unit_number=drive_unit_number,scaling=1), + ModbusChannel('pid_feedback', 4368, 'INTEGER', 1, 600, channel_size=1, unit_number=drive_unit_number,scaling=1), + ModbusChannel('motor_rated_current', 4896, 'INTEGER', 0, 3600, channel_size=1, unit_number=drive_unit_number,scaling=1), + ModbusChannel('sleep_delay', 4924, 'INTEGER', 1, 600, channel_size=1, unit_number=drive_unit_number, scaling=1) + ] +else: + tags = [ + ModbusChannel('volume_flow', 3873, 'FLOAT', 1,600,channel_size=2, unit_number=flowmeter_unit_number), + ModbusChannel('totalizer_1', 2609, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number), + ModbusChannel('totalizer_2', 2809, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number), + ModbusChannel('totalizer_3', 3009, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number), + ModbusChannel('volume_flow_units', 2102, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=volume_units), + ModbusChannel('totalizer_1_units', 4603, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units), + ModbusChannel('totalizer_2_units', 4604, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units), + ModbusChannel('totalizer_3_units', 4605, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units) + ] + + + + + + + + + \ No newline at end of file diff --git a/piflow/v2/Channel.py b/piflow/v2/Channel.py new file mode 100644 index 0000000..43435fd --- /dev/null +++ b/piflow/v2/Channel.py @@ -0,0 +1,610 @@ +"""Define Meshify channel class.""" +import time +from pycomm.ab_comm.clx import Driver as ClxDriver +from pycomm.cip.cip_base import CommError, DataError +from file_logger import filelogger as log +import minimalmodbus + +minimalmodbus.BAUDRATE = 9600 +minimalmodbus.STOPBITS = 1 + +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) + 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() + 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, 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: + return arr_vals + else: + log.error("No length for {}".format(addr)) + 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]) + 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): + """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' 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 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 + +def volume_units(vunit): + units = { + 0: "cm cubed/s", + 1: "cm cubed/min", + 2: "cm cubed/h", + 3: "cm cubed/d", + 4: "dm cubed/s", + 5: "dm cubed/min", + 6: "dm cubed/h", + 7: "dm cubed/d", + 8: "m cubed/s", + 9: "m cubed/min", + 10: "m cubed/h", + 11: "m cubed/d", + 12: "ml/s", + 13: "ml/min", + 14: "ml/h", + 15: "ml/d", + 16: "l/s", + 17: "l/min", + 18: "l/h (+)", + 19: "l/d", + 20: "hl/s", + 21: "hl/min", + 22: "hl/h", + 23: "hl/d", + 24: "Ml/s", + 25: "Ml/min", + 26: "Ml/h", + 27: "Ml/d", + 32: "af/s", + 33: "af/min", + 34: "af/h", + 35: "af/d", + 36: "ft cubed/s", + 37: "ft cubed/min", + 38: "ft cubed/h", + 39: "ft cubed/d", + 40: "fl oz/s (us)", + 41: "fl oz/min (us)", + 42: "fl oz/h (us)", + 43: "fl oz/d (us)", + 44: "gal/s (us)", + 45: "gal/min (us)", + 46: "gal/h (us)", + 47: "gal/d (us)", + 48: "Mgal/s (us)", + 49: "Mgal/min (us)", + 50: "Mgal/h (us)", + 51: "Mgal/d (us)", + 52: "bbl/s (us;liq.)", + 53: "bbl/min (us;liq.)", + 54: "bbl/h (us;liq.)", + 55: "bbl/d (us;liq.)", + 56: "bbl/s (us;beer)", + 57: "bbl/min (us;beer)", + 58: "bbl/h (us;beer)", + 59: "bbl/d (us;beer)", + 60: "bbl/s (us;oil)", + 61: "bbl/min (us;oil)", + 62: "bbl/h (us;oil)", + 63: "bbl/d (us;oil)", + 64: "bbl/s (us;tank)", + 65: "bbl/min (us;tank)", + 66: "bbl/h (us;tank)", + 67: "bbl/d (us;tank)", + 68: "gal/s (imp)", + 69: "gal/min (imp)", + 70: "gal/h (imp)", + 71: "gal/d (imp)", + 72: "Mgal/s (imp)", + 73: "Mgal/min (imp)", + 74: "Mgal/h (imp)", + 75: "Mgal/d (imp)", + 76: "bbl/s (imp;beer)", + 77: "bbl/min (imp;beer)", + 78: "bbl/h (imp;beer)", + 79: "bbl/d (imp;beer)", + 80: "bbl/s (imp;oil)", + 81: "bbl/min (imp;oil)", + 82: "bbl/h (imp;oil)", + 83: "bbl/d (imp;oil)", + 88: "kgal/s (us)", + 89: "kgal/min (us)", + 90: "kgal/h (us)", + 91: "kgal/d (us)", + 92: "MMft cubed/s", + 93: "MMft cubed/min", + 94: "MMft cubed/h", + 96: "Mft cubed/d" + } + return units[vunit] + +def totalizer_units(tunit): + + units = { + 0: "cm cubed", + 1: "dm cubed", + 2: "m cubed", + 3: "ml", + 4: "l", + 5: "hl", + 6: "Ml Mega", + 8: "af", + 9: "ft cubed", + 10: "fl oz (us)", + 11: "gal (us)", + 12: "Mgal (us)", + 13: "bbl (us;liq.)", + 14: "bbl (us;beer)", + 15: "bbl (us;oil)", + 16: "bbl (us;tank)", + 17: "gal (imp)", + 18: "Mgal (imp)", + 19: "bbl (imp;beer)", + 20: "bbl (imp;oil)", + 22: "kgal (us)", + 23: "Mft cubed", + 50: "g", + 51: "kg", + 52: "t", + 53: "oz", + 54: "lb", + 55: "STon", + 100: "Nl", + 101: "Nm cubed", + 102: "Sm cubed", + 103: "Sft cubed", + 104: "Sl", + 105: "Sgal (us)", + 106: "Sbbl (us;liq.)", + 107: "Sgal (imp)", + 108: "Sbbl (us;oil)", + 109: "MMSft cubed", + 110: "Nhl", + 251: "None" + } + 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 = { + 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): + """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.instrument = minimalmodbus.Instrument('/dev/ttyS0', self.unit_number) + self.scaling= scaling + + def read(self): + """Return the transformed read value.""" + if self.data_type == "FLOAT": + try: + read_value = self.instrument.read_float(self.register_number,4,self.channel_size) + except IOError as 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) + except IOError 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: + self.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 \ No newline at end of file diff --git a/piflow/v2/Tags.py b/piflow/v2/Tags.py new file mode 100644 index 0000000..46d00a0 --- /dev/null +++ b/piflow/v2/Tags.py @@ -0,0 +1,51 @@ +from Channel import PLCChannel,Channel, ModbusChannel, status_codes, fault_code_a, fault_code_b, volume_units, totalizer_units +import persistence + +data = persistence.load('persist.json') +flowmeter_unit_number = data['flowmeter'] +drive_enabled = data['drive_enabled'] +if drive_enabled: + drive_unit_number = data['drive'] + + +if drive_enabled: + tags = [ + ModbusChannel('volume_flow', 3873, 'FLOAT', 1,600,channel_size=2, unit_number=flowmeter_unit_number), + ModbusChannel('totalizer_1', 2609, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number), + ModbusChannel('totalizer_2', 2809, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number), + ModbusChannel('totalizer_3', 3009, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number), + ModbusChannel('volume_flow_units', 2102, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=volume_units), + ModbusChannel('totalizer_1_units', 4603, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units), + ModbusChannel('totalizer_2_units', 4604, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units), + ModbusChannel('totalizer_3_units', 4605, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units), + + ModbusChannel('run_status', 772, 'STRING', 0, 3600, channel_size=1, unit_number=drive_unit_number, transform_fn=status_codes), + ModbusChannel('frequency', 784, 'INTEGER', 0.5, 600, channel_size=2, unit_number=drive_unit_number,scaling=2 ), + ModbusChannel('current', 783, 'INTEGER', 0.5, 600, 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', 1, 600, channel_size=1, unit_number=drive_unit_number,scaling=1), + ModbusChannel('pid_feedback', 792, 'INTEGER', 1, 600, channel_size=1, unit_number=drive_unit_number,scaling=1), + ModbusChannel('motor_rated_current', 4896, 'INTEGER', 0, 3600, channel_size=1, unit_number=drive_unit_number,scaling=1), + ModbusChannel('sleep_delay', 4924, 'INTEGER', 1, 600, channel_size=1, unit_number=drive_unit_number, scaling=1) + ] +else: + tags = [ + ModbusChannel('volume_flow', 3873, 'FLOAT', 1,600,channel_size=2, unit_number=flowmeter_unit_number), + ModbusChannel('totalizer_1', 2609, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number), + ModbusChannel('totalizer_2', 2809, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number), + ModbusChannel('totalizer_3', 3009, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number), + ModbusChannel('volume_flow_units', 2102, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=volume_units), + ModbusChannel('totalizer_1_units', 4603, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units), + ModbusChannel('totalizer_2_units', 4604, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units), + ModbusChannel('totalizer_3_units', 4605, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units) + ] + + + + + + + + + \ No newline at end of file diff --git a/submonitor/config.txt b/submonitor/config.txt index 30b05c2..40fc467 100644 --- a/submonitor/config.txt +++ b/submonitor/config.txt @@ -2,7 +2,7 @@ "driverFileName": "submonitor.py", "deviceName": "submonitor", "driverId": "0210", - "releaseVersion": "4", + "releaseVersion": "5", "files": { "file1": "submonitor.py", "file2": "modbusMap.p" diff --git a/submonitor/submonitor.py b/submonitor/submonitor.py index 0d22f0e..4fd313e 100644 --- a/submonitor/submonitor.py +++ b/submonitor/submonitor.py @@ -36,7 +36,7 @@ class start(threading.Thread, deviceBase): deviceBase.__init__(self, name=name, number=number, mac=mac, Q=Q, mcu=mcu, companyId=companyId, offset=offset, mqtt=mqtt, Nodes=Nodes) self.daemon = True - self.version = "4" + self.version = "5" self.finished = threading.Event() self.forceSend = False threading.Thread.start(self) @@ -45,7 +45,7 @@ class start(threading.Thread, deviceBase): # about your device so it can be seen on the web def register(self): """Register the driver.""" - self.sendtodb("log", "BOOM! Booted.", 0) + #self.sendtodb("log", "BOOM! Booted.", 0) pass def run(self): @@ -55,7 +55,7 @@ class start(threading.Thread, deviceBase): print("submonitor driver will start in {} seconds".format(wait_sec - i)) time.sleep(1) logger.info("BOOM! Starting submonitor driver...") - self.sendtodb("log", "BOOM! Starting submonitor driver...", 0) + #self.sendtodb("log", "BOOM! Starting submonitor driver...", 0) while True: time.sleep(15) diff --git a/transferlite/config.txt b/transferlite/config.txt index 1b7bc3a..5f5d34a 100644 --- a/transferlite/config.txt +++ b/transferlite/config.txt @@ -7,6 +7,6 @@ }, "deviceName": "transferlite", "driverId": "0230", - "releaseVersion": "3", + "releaseVersion": "4", "driverFileName": "transferlite.py" } \ No newline at end of file diff --git a/transferlite/transferlite.py b/transferlite/transferlite.py index 6ee882d..fce55ce 100644 --- a/transferlite/transferlite.py +++ b/transferlite/transferlite.py @@ -81,7 +81,7 @@ class start(threading.Thread, deviceBase): mqtt=mqtt, Nodes=Nodes) self.daemon = True - self.version = "3" + self.version = "4" self.finished = threading.Event() self.force_send = False self.public_ip_address = "" @@ -95,7 +95,8 @@ class start(threading.Thread, deviceBase): # about your device so it can be seen on the web def register(self): """Register the driver.""" - self.sendtodb("log", "BOOM! Booted.", 0) + #self.sendtodbDev("log", "BOOM! Booted.", 0) + pass def run(self): """Actually run the driver."""