From fa946ee55d0cd6692569fbc8a0ae31aee3e773e5 Mon Sep 17 00:00:00 2001 From: Patrick McDonagh Date: Tue, 29 Aug 2017 17:43:23 -0500 Subject: [PATCH] Initial commit with all the cookiecutter files working --- cookiecutter.json | 4 + .../html-templates/Alerts.html | 1 + .../html-templates/NodeDetailHeader.html | 6 + .../html-templates/Nodelist.html | 31 +++ .../html-templates/Overview.html | 121 ++++++++++ .../html-templates/Sidebar.html | 15 ++ .../html-templates/Trends.html | 37 +++ .../python-driver/Channel.py | 211 ++++++++++++++++++ .../python-driver/Maps.py | 3 + .../python-driver/config.txt | 14 ++ .../{{cookiecutter.driver_name}}.py | 99 ++++++++ 11 files changed, 542 insertions(+) create mode 100644 cookiecutter.json create mode 100644 {{cookiecutter.driver_name}}/html-templates/Alerts.html create mode 100644 {{cookiecutter.driver_name}}/html-templates/NodeDetailHeader.html create mode 100644 {{cookiecutter.driver_name}}/html-templates/Nodelist.html create mode 100644 {{cookiecutter.driver_name}}/html-templates/Overview.html create mode 100644 {{cookiecutter.driver_name}}/html-templates/Sidebar.html create mode 100644 {{cookiecutter.driver_name}}/html-templates/Trends.html create mode 100644 {{cookiecutter.driver_name}}/python-driver/Channel.py create mode 100644 {{cookiecutter.driver_name}}/python-driver/Maps.py create mode 100644 {{cookiecutter.driver_name}}/python-driver/config.txt create mode 100644 {{cookiecutter.driver_name}}/python-driver/{{cookiecutter.driver_name}}.py diff --git a/cookiecutter.json b/cookiecutter.json new file mode 100644 index 0000000..c74cf3e --- /dev/null +++ b/cookiecutter.json @@ -0,0 +1,4 @@ +{ + "driver_name": "pocloud_driver", + "driver_id": "0000" +} diff --git a/{{cookiecutter.driver_name}}/html-templates/Alerts.html b/{{cookiecutter.driver_name}}/html-templates/Alerts.html new file mode 100644 index 0000000..2971cab --- /dev/null +++ b/{{cookiecutter.driver_name}}/html-templates/Alerts.html @@ -0,0 +1 @@ +Alerts diff --git a/{{cookiecutter.driver_name}}/html-templates/NodeDetailHeader.html b/{{cookiecutter.driver_name}}/html-templates/NodeDetailHeader.html new file mode 100644 index 0000000..28262a3 --- /dev/null +++ b/{{cookiecutter.driver_name}}/html-templates/NodeDetailHeader.html @@ -0,0 +1,6 @@ +
+
+
+
+

<%= node.vanityname %>

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

<%= node.vanityname %>

+
+
diff --git a/{{cookiecutter.driver_name}}/html-templates/Overview.html b/{{cookiecutter.driver_name}}/html-templates/Overview.html new file mode 100644 index 0000000..51e744f --- /dev/null +++ b/{{cookiecutter.driver_name}}/html-templates/Overview.html @@ -0,0 +1,121 @@ + +
+
+

HEADER 1

+
+
+

CHANNEL 1

+
+
+
+
+ + + +
+ + <%= channels["{{cookiecutter.driver_name}}.channel_1"].timestamp %> + +
+
+
+ + + + + + + + diff --git a/{{cookiecutter.driver_name}}/html-templates/Sidebar.html b/{{cookiecutter.driver_name}}/html-templates/Sidebar.html new file mode 100644 index 0000000..7c78ef3 --- /dev/null +++ b/{{cookiecutter.driver_name}}/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["{{cookiecutter.driver_name}}.sync"].techName %>" + data-name="<%= channels["{{cookiecutter.driver_name}}.sync"].name%>" + data-nodechannelcurrentId="<%= channels["{{cookiecutter.driver_name}}.sync"].nodechannelcurrentId %>" + id="<%= channels["{{cookiecutter.driver_name}}.sync"].channelId %>" + class="btn btn-large btn-block btn-theme animated setstatic mqtt"> + Sync All Data diff --git a/{{cookiecutter.driver_name}}/html-templates/Trends.html b/{{cookiecutter.driver_name}}/html-templates/Trends.html new file mode 100644 index 0000000..b07d9ec --- /dev/null +++ b/{{cookiecutter.driver_name}}/html-templates/Trends.html @@ -0,0 +1,37 @@ +
+
+ + to + + + Run + +
+
+
+
+
+ diff --git a/{{cookiecutter.driver_name}}/python-driver/Channel.py b/{{cookiecutter.driver_name}}/python-driver/Channel.py new file mode 100644 index 0000000..199c08e --- /dev/null +++ b/{{cookiecutter.driver_name}}/python-driver/Channel.py @@ -0,0 +1,211 @@ +"""Define Meshify channel class.""" +from pycomm.ab_comm.clx import Driver as ClxDriver +from pycomm.cip.cip_base import CommError, DataError +import time + + +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): + """Read a tag from the PLC.""" + c = ClxDriver() + try: + if c.open(addr): + try: + v = c.read_tag(tag) + return v + except DataError: + c.close() + print("Data Error during readTag({}, {})".format(addr, tag)) + 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() + return False + + +def read_array(addr, tag, start, end): + """Read an array from the PLC.""" + c = ClxDriver() + if c.open(addr): + 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: + return arr_vals + else: + print("No length for {}".format(addr)) + return False + except Exception: + print("Error during readArray({}, {}, {}, {})".format(addr, tag, start, end)) + err = c.get_status() + c.close() + print err + pass + c.close() + + +def write_tag(addr, tag, val): + """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() + + +class Channel: + """Holds the configuration for a Meshify channel.""" + + def __init__(self, ip, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False): + """Initialize the channel.""" + 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 __str__(self): + """Create a string for the channel.""" + return "{}: {}\nvalue: {}, last_send_time: {}".format(self.mesh_name, self.plc_tag, self.value, self.last_send_time) + + 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: + v = read_tag(self.plc_ip, self.plc_tag) + if v: + 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 not (self.value == v[0]): + if self.map_: + if not self.value == self.map_[v[0]]: + 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 - v[0]) > 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_[v[0]] + except KeyError: + print("Cannot find a map value for {} in {} for {}".format(v[0], self.map_, self.mesh_name)) + self.value = v[0] + else: + self.value = v[0] + self.last_send_time = time.time() + print("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason)) + return send_needed + + +class BoolArrayChannels(Channel): + """Hold the configuration for a set of boolean array channels.""" + + 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: + print("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: + v = read_tag(self.plc_ip, self.plc_tag) + if v: + bool_arr = binarray(v[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)) + + 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() + print("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason)) + return send_needed diff --git a/{{cookiecutter.driver_name}}/python-driver/Maps.py b/{{cookiecutter.driver_name}}/python-driver/Maps.py new file mode 100644 index 0000000..96328eb --- /dev/null +++ b/{{cookiecutter.driver_name}}/python-driver/Maps.py @@ -0,0 +1,3 @@ +"""Holds map values for {{cookiecutter.driver_name}}.""" + +{{cookiecutter.driver_name}}_map = {} diff --git a/{{cookiecutter.driver_name}}/python-driver/config.txt b/{{cookiecutter.driver_name}}/python-driver/config.txt new file mode 100644 index 0000000..cc80d47 --- /dev/null +++ b/{{cookiecutter.driver_name}}/python-driver/config.txt @@ -0,0 +1,14 @@ +{ + +"driverFileName":"{{cookiecutter.driver_name}}.py", +"deviceName":"{{cookiecutter.driver_name}}", +"driverId":"{{cookiecutter.driver_id}}", +"releaseVersion":"1", +"files": { + "file1":"{{cookiecutter.driver_name}}.py", + "file2":"Channel.py", + "file3":"Maps.py", + "file4":"Scheduler.py" + } + +} diff --git a/{{cookiecutter.driver_name}}/python-driver/{{cookiecutter.driver_name}}.py b/{{cookiecutter.driver_name}}/python-driver/{{cookiecutter.driver_name}}.py new file mode 100644 index 0000000..5a6e6ed --- /dev/null +++ b/{{cookiecutter.driver_name}}/python-driver/{{cookiecutter.driver_name}}.py @@ -0,0 +1,99 @@ +"""Driver for {{cookiecutter.driver_name}}""" + +import threading +from device_base import deviceBase +from Channel import Channel, write_tag, BoolArrayChannels +from Maps import {{cookiecutter.driver_name}}_map as maps +import json +import time + +_ = None + +try: + with open("persist.json", 'r') as persist_file: + persist = json.load(persist_file) +except Exception: + persist = {} + +plc_ip_address = "192.168.1.10" + + +def reverse_map(value, map_): + """Perform the opposite of mapping to an object.""" + for x in map_: + if map_[x] == value: + return x + return None + + +channels = [] + + +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 = "3" + self.finished = threading.Event() + self.forceSend = False + threading.Thread.start(self) + + # this is a required function for all drivers, its goal is to upload some piece of data + # about your device so it can be seen on the web + def register(self): + """Register the driver.""" + # self.sendtodb("log", "BOOM! Booted.", 0) + pass + + def run(self): + """Actually run the driver.""" + global persist + wait_sec = 60 + for i in range(0, wait_sec): + print("{{cookiecutter.driver_name}} driver will start in {} seconds".format(wait_sec - i)) + time.sleep(1) + print("BOOM! Starting {{cookiecutter.driver_name}} driver...") + # after its booted up assuming that M1 is now reading modbus data + # we can replace the reference made to this device name to the M1 driver with this + # driver. The 01 in the 0199 below is the device number you referenced in the modbus wizard + self.nodes["{{cookiecutter.driver_name}}_0199"] = self + send_loops = 0 + while True: + if self.forceSend: + print "FORCE SEND: TRUE" + + for c in channels: + if c.read(self.forceSend): + self.sendtodb(c.mesh_name, c.value, 0) + + + # print("{{cookiecutter.driver_name}} driver still alive...") + if self.forceSend: + if send_loops > 2: + print("Turning off forceSend") + self.forceSend = False + send_loops = 0 + else: + send_loops += 1 + + def {{cookiecutter.driver_name}}_sync(self, name, value): + """Sync all data from the driver.""" + self.forceSend = True + # self.sendtodb("log", "synced", 0) + return True + + def {{cookiecutter.driver_name}}_writeplctag(self, name, value): + """Write a value to the PLC.""" + new_val = json.loads(str(value).replace("'", '"')) + tag_n = str(new_val['tag']) # "cmd_Start" + val_n = new_val['val'] + w = write_tag(str(plc_ip_address), tag_n, val_n) + print("Result of {{cookiecutter.driver_name}}_writeplctag(self, {}, {}) = {}".format(name, value, w)) + if w is None: + w = "Error writing to PLC..." + return w