diff --git a/tenflowmeterskid/html-templates/Alerts.html b/tenflowmeterskid/html-templates/Alerts.html new file mode 100644 index 0000000..2971cab --- /dev/null +++ b/tenflowmeterskid/html-templates/Alerts.html @@ -0,0 +1 @@ +Alerts diff --git a/tenflowmeterskid/html-templates/Device.html b/tenflowmeterskid/html-templates/Device.html new file mode 100644 index 0000000..91d7188 --- /dev/null +++ b/tenflowmeterskid/html-templates/Device.html @@ -0,0 +1,42 @@ +
+
+

Public IP Address

+

<%= channels["tenflowmeterskid.public_ip_address"].value %>

+

+
+ + diff --git a/tenflowmeterskid/html-templates/NodeDetailHeader.html b/tenflowmeterskid/html-templates/NodeDetailHeader.html new file mode 100644 index 0000000..28262a3 --- /dev/null +++ b/tenflowmeterskid/html-templates/NodeDetailHeader.html @@ -0,0 +1,6 @@ +
+
+
+
+

<%= node.vanityname %>

+
diff --git a/tenflowmeterskid/html-templates/Nodelist.html b/tenflowmeterskid/html-templates/Nodelist.html new file mode 100644 index 0000000..756a869 --- /dev/null +++ b/tenflowmeterskid/html-templates/Nodelist.html @@ -0,0 +1,31 @@ + + +
+
+
+
+ +
+ +
+ +
+

<%= node.vanityname %>

+
+
diff --git a/tenflowmeterskid/html-templates/Overview.html b/tenflowmeterskid/html-templates/Overview.html new file mode 100644 index 0000000..eac5942 --- /dev/null +++ b/tenflowmeterskid/html-templates/Overview.html @@ -0,0 +1,121 @@ + +
+
+

HEADER 1

+
+
+

CHANNEL 1

+
+
+
+
+ + + +
+ + <%= channels["tenflowmeterskid.channel_1"].timestamp %> + +
+
+
+ + + + + + + + diff --git a/tenflowmeterskid/html-templates/Sidebar.html b/tenflowmeterskid/html-templates/Sidebar.html new file mode 100644 index 0000000..e80ac7c --- /dev/null +++ b/tenflowmeterskid/html-templates/Sidebar.html @@ -0,0 +1,15 @@ +" + class="data-table btn-block btn btn-theme animated" + title="Device Log"> Device Log + +" + data-techname="<%=channels["tenflowmeterskid.sync"].techName %>" + data-name="<%= channels["tenflowmeterskid.sync"].name%>" + data-nodechannelcurrentId="<%= channels["tenflowmeterskid.sync"].nodechannelcurrentId %>" + id="<%= channels["tenflowmeterskid.sync"].channelId %>" + class="btn btn-large btn-block btn-theme animated setstatic mqtt"> + Sync All Data diff --git a/tenflowmeterskid/html-templates/Trends.html b/tenflowmeterskid/html-templates/Trends.html new file mode 100644 index 0000000..8193486 --- /dev/null +++ b/tenflowmeterskid/html-templates/Trends.html @@ -0,0 +1,37 @@ +
+
+ + to + + + Run + +
+
+
+
+
+ diff --git a/tenflowmeterskid/python-driver/Channel.py b/tenflowmeterskid/python-driver/Channel.py new file mode 100644 index 0000000..353ead7 --- /dev/null +++ b/tenflowmeterskid/python-driver/Channel.py @@ -0,0 +1,299 @@ +"""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) + 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 + + +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): + """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 + + def read(self, mbsvalue): + """Return the transformed read value.""" + return self.transform_fn(mbsvalue) + + +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/tenflowmeterskid/python-driver/Tags.py b/tenflowmeterskid/python-driver/Tags.py new file mode 100644 index 0000000..c36eae6 --- /dev/null +++ b/tenflowmeterskid/python-driver/Tags.py @@ -0,0 +1,65 @@ +from Channel import PLCChannel, ModbusChannel +from tenflowmeterskid import PLC_IP_ADDRESS + +tags = [ + PLCChannel(PLC_IP_ADDRESS, "fm1_flowrate","Val_FM1_FR","REAL", 250, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm2_flowrate","Val_FM2_FR","REAL", 250, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm3_flowrate","Val_FM3_FR","REAL", 250, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm4_flowrate","Val_FM4_FR","REAL", 250, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm5_flowrate","Val_FM5_FR","REAL", 250, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm6_flowrate","Val_FM6_FR","REAL", 250, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm7_flowrate","Val_FM7_FR","REAL", 250, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm8_flowrate","Val_FM8_FR","REAL", 250, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm9_flowrate","Val_FM9_FR","REAL", 250, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm10_flowrate","Val_FM10_FR","REAL", 250, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm1_lifetime","Val_FM1_T1","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm2_lifetime","Val_FM2_T1","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm3_lifetime","Val_FM3_T1","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm4_lifetime","Val_FM4_T1","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm5_lifetime","Val_FM5_T1","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm6_lifetime","Val_FM6_T1","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm7_lifetime","Val_FM7_T1","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm8_lifetime","Val_FM8_T1","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm9_lifetime","Val_FM9_T1","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm10_lifetime","Val_FM10_T1","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm1_month","Val_Flowmeter1MonthTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm2_month","Val_Flowmeter2MonthTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm3_month","Val_Flowmeter3MonthTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm4_month","Val_Flowmeter4MonthTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm5_month","Val_Flowmeter5MonthTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm6_month","Val_Flowmeter6MonthTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm7_month","Val_Flowmeter7MonthTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm8_month","Val_Flowmeter8MonthTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm9_month","Val_Flowmeter9MonthTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm10_month","Val_Flowmeter10MonthTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm1_lastmonth","Val_Flowmeter1LastMonthTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm2_lastmonth","Val_Flowmeter2LastMonthTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm3_lastmonth","Val_Flowmeter3LastMonthTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm4_lastmonth","Val_Flowmeter4LastMonthTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm5_lastmonth","Val_Flowmeter5LastMonthTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm6_lastmonth","Val_Flowmeter6LastMonthTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm7_lastmonth","Val_Flowmeter7LastMonthTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm8_lastmonth","Val_Flowmeter8LastMonthTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm9_lastmonth","Val_Flowmeter9LastMonthTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm10_lastmonth","Val_Flowmeter10LastMonthTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm1_yesterdays","Val_Flowmeter1YesterdaysTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm2_yesterdays","Val_Flowmeter2YesterdaysTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm3_yesterdays","Val_Flowmeter3YesterdaysTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm4_yesterdays","Val_Flowmeter4YesterdaysTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm5_yesterdays","Val_Flowmeter5YesterdaysTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm6_yesterdays","Val_Flowmeter6YesterdaysTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm7_yesterdays","Val_Flowmeter7YesterdaysTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm8_yesterdays","Val_Flowmeter8YesterdaysTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm9_yesterdays","Val_Flowmeter9YesterdaysTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm10_yesterdays","Val_Flowmeter10YesterdaysTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm1_todays","Val_Flowmeter1TodaysTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm2_todays","Val_Flowmeter2TodaysTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm3_todays","Val_Flowmeter3TodaysTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm4_todays","Val_Flowmeter4TodaysTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm5_todays","Val_Flowmeter5TodaysTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm6_todays","Val_Flowmeter6TodaysTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm7_todays","Val_Flowmeter7TodaysTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm8_todays","Val_Flowmeter8TodaysTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm9_todays","Val_Flowmeter9TodaysTotal","REAL", 100, 3600, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, "fm10_todays","Val_Flowmeter10TodaysTotal","REAL", 100, 3600, plc_type="Micro800") +] \ No newline at end of file diff --git a/tenflowmeterskid/python-driver/config.txt b/tenflowmeterskid/python-driver/config.txt new file mode 100644 index 0000000..127f415 --- /dev/null +++ b/tenflowmeterskid/python-driver/config.txt @@ -0,0 +1,14 @@ +{ + "files": { + "file3": "file_logger.py", + "file2": "Channel.py", + "file1": "tenflowmeterskid.py", + "file6": "persistence.py", + "file5": "utilities.py", + "file4": "Tags.py" + }, + "deviceName": "tenflowmeterskid", + "releaseVersion": "1", + "driverFileName": "tenflowmeterskid.py", + "driverId": "0100" +} \ No newline at end of file diff --git a/tenflowmeterskid/python-driver/device_base.py b/tenflowmeterskid/python-driver/device_base.py new file mode 100644 index 0000000..6e4a3e4 --- /dev/null +++ b/tenflowmeterskid/python-driver/device_base.py @@ -0,0 +1,360 @@ +import types +import traceback +import binascii +import threading +import time +import thread +import os +import struct +import sys +import textwrap +import Queue +import json + + +class deviceBase(): + + def __init__(self, name=None, number=None, mac=None, Q=None, mcu=None, companyId=None, offset=None, mqtt=None, Nodes=None): + self.offset = offset + self.company = companyId + self.name = name + self.number = number + self.q = Q + self.deviceName = name + '_[' + mac + ':' + number[0:2] + ':' + number[2:] + ']!' + self.chName = "M1" + '_[' + mac + ':' + self.chName2 = '_[' + mac + ':' + print 'device name is:' + print self.deviceName + mac2 = mac.replace(":", "") + self.mac = mac2.upper() + self.address = 1 + self.debug = True + self.mcu = mcu + self.firstRun = True + self.mqtt = mqtt + self.nodes = Nodes + #local dictionary of derived nodes ex: localNodes[tank_0199] = self + self.localNodes = {} + os.system("chmod 777 /root/reboot") + os.system("echo nameserver 8.8.8.8 > /etc/resolv.conf") + #Queue for imcoming sets + self.loraQ = Queue.Queue() + + self.knownIDs = [] + thread.start_new_thread(self.getSetsThread, ()) + + def getSetsThread(self): + + while True: + try: + item = self.loraQ.get(block=True, timeout=600) + try: + print "here is the item from the sets q" + print item + if len(item) == 2: + techname = str(json.loads(item[1])[0]['payload']['name'].split(".")[0]) + channel = str(json.loads(item[1])[0]['payload']['name'].split(".")[1]) + name = techname.split("_")[0] + id = techname.split("_")[1][1:-2].replace(":","").upper() + value = json.loads(item[1])[0]['payload']['value'] + msgId = json.loads(item[1])[0]['msgId'] + + print channel, value, id, name, msgId + success = self.specificSets(channel, value, id, name) + + if success == True: + print "SUCCESS ON SET" + if int(msgId) == 0: + return + lc = self.getTime() + + value = str(self.mac) + " Success Setting: " + channel + " To: " + value + msg = """[ { "value":"%s", "timestamp":"%s", "msgId":"%s" } ]""" % (value, str(lc), msgId) + print value + print msg + topic = "meshify/responses/" + str(msgId) + print topic + self.q.put([topic, str(msg), 2]) + + + else: + + lc = self.getTime() + if success == False: + reason = "(Internal Gateway/Device Error)" + else: + reason = success + value = str(self.mac) + " Failed Setting: " + channel + " To: " + value + " " + reason + msg = """[ { "value":"%s", "timestamp":"%s", "msgId":"%s" } ]""" % (value, str(lc), msgId) + topic = "meshify/responses/" + msgId + self.q.put([topic, str(msg), 2]) + + except: + if int(msgId) == 0: + return + lc = self.getTime() + value = str(self.mac) + " Failed Setting: " + channel + " To: " + value + " (No Callback Found)" + msg = """[ { "value":"%s", "timestamp":"%s", "msgId":"%s" } ]""" % (value, str(lc), msgId) + topic = "meshify/responses/" + msgId + self.q.put([topic, str(msg), 2]) + print 'no Set callback found for channel: ' + funcName + + except: + print "sets queue timeout, restarting..." + + + def sendtodbDevLora(self, id, channel, value, timestamp, deviceName): + + + + mac = self.mac + + if deviceName == "mainMeshify": + zigmac = "_[01:00:00:00:00:" + id[0:2] + ":" + id[2:4] + ":" + id[4:6] + "]!" + else: + zigmac = "_[00:00:00:00:00:" + id[0:2] + ":" + id[2:4] + ":" + id[4:6] + "]!" + dname = deviceName + zigmac + + #define dname, make id into techname and mac + if id not in self.knownIDs: + self.knownIDs.append(id) + self.mcu.xbees[dname] = self.loraQ + + #meshify/db/330/C493000354FB/ilora/c493000354fb2A6E/a1-v + #[ { "value":"0.5635", "timestamp":"1486039316" } ] + + if int(timestamp) == 0: + timestamp = self.getTime() + + topic = 'meshify/db/%s/%s/%s/%s' % (self.company, mac, dname, channel) + print topic + msg = """[ { "value":"%s", "timestamp":"%s" } ]""" % (str(value), str(timestamp)) + print msg + self.q.put([topic, msg, 0]) + + def sendtodbLocLora(self, id, channel, value, timestamp, deviceName): + + + + mac = id + while len(mac) < 12: + mac = "0" + mac + if deviceName == "mainMeshify": + zigmac = "_[01:00:00:00:00:" + id[0:2] + ":" + id[2:4] + ":" + id[4:6] + "]!" + else: + zigmac = "_[00:00:00:00:00:" + id[0:2] + ":" + id[2:4] + ":" + id[4:6] + "]!" + dname = deviceName + zigmac + + #define dname, make id into techname and mac + if id not in self.knownIDs: + self.knownIDs.append(id) + topic = str(("meshify/sets/" + str(self.company) + "/" + mac + "/#")) + self.mqtt.subscribe(topic, 0) + topic = str(("meshify/sets/" + "1" + "/" + mac + "/#")) + self.mqtt.subscribe(topic, 0) + self.mcu.xbees[dname] = self.loraQ + + #meshify/db/330/C493000354FB/ilora/c493000354fb2A6E/a1-v + #[ { "value":"0.5635", "timestamp":"1486039316" } ] + + if int(timestamp) == 0: + timestamp = self.getTime() + + topic = 'meshify/db/%s/%s/%s/%s' % (self.company, mac, dname, channel) + print topic + msg = """[ { "value":"%s", "timestamp":"%s" } ]""" % (str(value), str(timestamp)) + print msg + self.q.put([topic, msg, 0]) + + def sendtodbLocLoraCom(self, id, channel, value, timestamp, deviceName): + + + + mac = "1" + id + while len(mac) < 12: + mac = "0" + mac + + if deviceName == "mainMeshify": + zigmac = "_[01:00:00:00:00:" + id[0:2] + ":" + id[2:4] + ":" + id[4:6] + "]!" + else: + zigmac = "_[00:00:00:00:01:" + id[0:2] + ":" + id[2:4] + ":" + id[4:6] + "]!" + dname = deviceName + zigmac + + #define dname, make id into techname and mac + if id not in self.knownIDs: + self.knownIDs.append(id) + topic = str(("meshify/sets/" + str(self.company) + "/" + mac + "/#")) + self.mqtt.subscribe(topic, 0) + topic = str(("meshify/sets/" + "1" + "/" + mac + "/#")) + self.mqtt.subscribe(topic, 0) + self.mcu.xbees[dname] = self.loraQ + + #meshify/db/330/C493000354FB/ilora/c493000354fb2A6E/a1-v + #[ { "value":"0.5635", "timestamp":"1486039316" } ] + + if int(timestamp) == 0: + timestamp = self.getTime() + + topic = 'meshify/db/%s/%s/%s/%s' % (self.company, mac, dname, channel) + print topic + msg = """[ { "value":"%s", "timestamp":"%s" } ]""" % (str(value), str(timestamp)) + print msg + self.q.put([topic, msg, 0]) + + def sendtodbLoc(self, ch, channel, value, timestamp, deviceName, mac): + + + #this will add your derived nodes the master nodes list, allowing them to receive sets!! + localNodesName = deviceName + "_" + str(ch) + "99" + + if not self.localNodes.has_key(localNodesName): + self.localNodes[localNodesName] = True + self.nodes[localNodesName] = self + + #make the techname + lst = textwrap.wrap(str(mac), width=2) + tech = "" + for i in range(len(lst)): + tech += lst[i].lower() + ":" + + + chName2 = '_[' + tech + + if int(ch) < 10: + ch = "0" + str(int(ch)) + + if len(ch) > 2: + ch = ch[:-2] + + dname = deviceName + chName2 + str(ch) + ":98]!" + + if int(timestamp) == 0: + timestamp = self.getTime() + + topic = 'meshify/db/%s/%s/%s/%s' % (self.company, mac, dname, channel) + print topic + msg = """[ { "value":"%s", "timestamp":"%s" } ]""" % (str(value), str(timestamp)) + print msg + self.q.put([topic, msg, 0]) + + def sendtodbDevJSON(self, ch, channel, value, timestamp, deviceName): + + if int(ch) < 10: + ch = "0" + str(int(ch)) + dname = deviceName + self.chName2 + str(ch) + ":99]!" + if int(timestamp) == 0: + timestamp = self.getTime() + + topic = 'meshify/db/%s/%s/%s/%s' % (self.company, self.mac, dname, channel) + print topic + msg = """[ { "value":%s, "timestamp":"%s" } ]""" % (str(value), str(timestamp)) + print msg + self.q.put([topic, msg, 0]) + + def sendtodbLora(self, ch, channel, value, timestamp, deviceName): + + if ":" not in ch: + ch = ch[0:2] + ":" + ch[2:4] + + #this will add your derived nodes the master nodes list, allowing them to receive sets!! + localNodesName = deviceName + "_" + str(ch).replace(':', "") + + if not self.localNodes.has_key(localNodesName): + self.localNodes[localNodesName] = True + self.nodes[localNodesName] = self + + + + dname = deviceName + self.chName2 + str(ch) + "]!" + + + + if int(timestamp) == 0: + timestamp = self.getTime() + + topic = 'meshify/db/%s/%s/%s/%s' % (self.company, self.mac, dname, channel) + print topic + msg = """[ { "value":"%s", "timestamp":"%s" } ]""" % (str(value), str(timestamp)) + print msg + self.q.put([topic, msg, 0]) + + def sendtodbDev(self, ch, channel, value, timestamp, deviceName): + + + #this will add your derived nodes the master nodes list, allowing them to receive sets!! + localNodesName = deviceName + "_" + str(ch) + "99" + + if not self.localNodes.has_key(localNodesName): + self.localNodes[localNodesName] = True + self.nodes[localNodesName] = self + + if int(ch) < 10: + ch = "0" + str(int(ch)) + + dname = deviceName + self.chName2 + str(ch) + ":99]!" + + + + if int(timestamp) == 0: + timestamp = self.getTime() + + topic = 'meshify/db/%s/%s/%s/%s' % (self.company, self.mac, dname, channel) + print topic + msg = """[ { "value":"%s", "timestamp":"%s" } ]""" % (str(value), str(timestamp)) + print msg + self.q.put([topic, msg, 0]) + + def sendtodbCH(self, ch, channel, value, timestamp): + + + if int(ch) < 10: + ch = "0" + str(ch) + + dname = self.chName + str(ch) + ":99]!" + + + + if int(timestamp) == 0: + timestamp = self.getTime() + + topic = 'meshify/db/%s/%s/%s/%s' % (self.company, self.mac, dname, channel) + print topic + msg = """[ { "value":"%s", "timestamp":"%s" } ]""" % (str(value), str(timestamp)) + print msg + self.q.put([topic, msg, 0]) + + def sendtodb(self, channel, value, timestamp): + + if int(timestamp) == 0: + timestamp = self.getTime() + if timestamp < 1400499858: + return + else: + timestamp = str(int(timestamp) + int(self.offset)) + + topic = 'meshify/db/%s/%s/%s/%s' % (self.company, self.mac, self.deviceName, channel) + print topic + msg = """[ { "value":"%s", "timestamp":"%s" } ]""" % (str(value), str(timestamp)) + print msg + self.q.put([topic, msg, 0]) + + def sendtodbJSON(self, channel, value, timestamp): + + if int(timestamp) == 0: + timestamp = self.getTime() + if timestamp < 1400499858: + return + else: + timestamp = str(int(timestamp) + int(self.offset)) + + topic = 'meshify/db/%s/%s/%s/%s' % (self.company, self.mac, self.deviceName, channel) + print topic + msg = """[ { "value":%s, "timestamp":"%s" } ]""" % (str(value), str(timestamp)) + print msg + self.q.put([topic, msg, 0]) + def getTime(self): + return str(int(time.time() + int(self.offset))) + + + + diff --git a/tenflowmeterskid/python-driver/file_logger.py b/tenflowmeterskid/python-driver/file_logger.py new file mode 100644 index 0000000..f1957bd --- /dev/null +++ b/tenflowmeterskid/python-driver/file_logger.py @@ -0,0 +1,18 @@ +"""Logging setup for tenflowmeterskid""" +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 = './tenflowmeterskid.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('tenflowmeterskid') +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/tenflowmeterskid/python-driver/persistence.py b/tenflowmeterskid/python-driver/persistence.py new file mode 100644 index 0000000..8c8703f --- /dev/null +++ b/tenflowmeterskid/python-driver/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/tenflowmeterskid/python-driver/tenflowmeterskid.py b/tenflowmeterskid/python-driver/tenflowmeterskid.py new file mode 100644 index 0000000..484ad09 --- /dev/null +++ b/tenflowmeterskid/python-driver/tenflowmeterskid.py @@ -0,0 +1,133 @@ +"""Driver for tenflowmeterskid""" + +import threading +import json +import time +from random import randint +import os +from device_base import deviceBase +from Channel import PLCChannel, ModbusChannel,read_tag, write_tag, TAG_DATAERROR_SLEEPTIME +import persistence +from utilities import get_public_ip_address +from file_logger import filelogger as log +PLC_IP_ADDRESS = "192.168.1.12" +from Tags import tags + +_ = None + +log.info("tenflowmeterskid startup") + +# GLOBAL VARIABLES +WAIT_FOR_CONNECTION_SECONDS = 20 +IP_CHECK_PERIOD = 60 + + +CHANNELS = tags + +# PERSISTENCE FILE +PERSIST = persistence.load() + + +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 = "1" + self.finished = threading.Event() + self.force_send = False + self.public_ip_address = "" + self.public_ip_address_last_checked = 0 + threading.Thread.start(self) + + # this is a required function for all drivers, its goal is to upload some piece of data + # about your device so it can be seen on the web + def register(self): + """Register the driver.""" + # self.sendtodb("log", "BOOM! Booted.", 0) + pass + + def run(self): + """Actually run the driver.""" + for i in range(0, WAIT_FOR_CONNECTION_SECONDS): + print("tenflowmeterskid driver will start in {} seconds".format(WAIT_FOR_CONNECTION_SECONDS - i)) + time.sleep(1) + log.info("BOOM! Starting tenflowmeterskid driver...") + + self._check_ip_address() + + self.nodes["tenflowmeterskid_0199"] = self + + send_loops = 0 + + while True: + now = time.time() + if self.force_send: + log.warning("FORCE SEND: TRUE") + + for chan in CHANNELS: + val = chan.read() + if chan.check(val, self.force_send): + self.sendtodbDev(1, chan.mesh_name, chan.value, 0, 'tenflowmeterskid') + #time.sleep(TAG_DATAERROR_SLEEPTIME) # sleep to allow Micro800 to handle ENET requests + + # print("tenflowmeterskid driver still alive...") + if self.force_send: + if send_loops > 2: + log.warning("Turning off force_send") + self.force_send = False + send_loops = 0 + else: + send_loops += 1 + + + if (now - self.public_ip_address_last_checked) > IP_CHECK_PERIOD: + self._check_ip_address() + + + 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() + if not test_public_ip == self.public_ip_address: + self.sendtodbDev(1, 'public_ip_address', test_public_ip, 0, 'tenflowmeterskid') + self.public_ip_address = test_public_ip + hostname = "google.com" + response = os.system("ping -c 1 " + hostname) + + #and then check the response... + if response == 0: + print hostname, 'is up!' + self.ping_counter = 0 + else: + print hostname, 'is down!' + self.ping_counter += 1 + + if self.ping_counter >= 3: + log.info("Rebooting because no internet detected") + os.system('reboot') + + + def tenflowmeterskid_sync(self, name, value): + """Sync all data from the driver.""" + self.force_send = True + # self.sendtodb("log", "synced", 0) + return True + + def tenflowmeterskid_writeplctag(self, name, value): + """Write a value to the PLC.""" + new_val = json.loads(str(value).replace("'", '"')) + tag_n = str(new_val['tag']) # "cmd_Start" + val_n = new_val['val'] + write_res = write_tag(str(PLC_IP_ADDRESS), tag_n, val_n, plc_type="Micro800") + print("Result of tenflowmeterskid_writeplctag(self, {}, {}) = {}".format(name, value, write_res)) + if write_res is None: + write_res = "Error writing to PLC..." + return write_res diff --git a/tenflowmeterskid/python-driver/utilities.py b/tenflowmeterskid/python-driver/utilities.py new file mode 100644 index 0000000..7e88d62 --- /dev/null +++ b/tenflowmeterskid/python-driver/utilities.py @@ -0,0 +1,52 @@ +"""Utility functions for the driver.""" +import socket +import struct + + +def get_public_ip_address(): + """Find the public IP Address of the host device.""" + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.connect(("8.8.8.8", 80)) + ip_address = sock.getsockname()[0] + sock.close() + except Exception as e: + return e + return ip_address + + +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