From 5bc8ae700773937e6c575c169270376f8a569dbc Mon Sep 17 00:00:00 2001 From: Patrick McDonagh Date: Tue, 9 Jan 2018 14:41:41 -0600 Subject: [PATCH] Initial commit --- .gitattributes | 2 + .gitignore | 99 ++ README.md | 1 + html-templates/Alerts.html | 1 + html-templates/Device.html | 42 + 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 | 282 +++++ python-driver/config.txt | 11 + python-driver/driverConfig.json | 11 + python-driver/modbusMap.p | 1414 ++++++++++++++++++++++++++ python-driver/persistence.py | 21 + python-driver/promagmbs.py | 75 ++ python-driver/utilities.py | 41 + 17 files changed, 2210 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 README.md create mode 100644 html-templates/Alerts.html create mode 100644 html-templates/Device.html create mode 100644 html-templates/NodeDetailHeader.html create mode 100644 html-templates/Nodelist.html create mode 100644 html-templates/Overview.html create mode 100644 html-templates/Sidebar.html create mode 100644 html-templates/Trends.html create mode 100644 python-driver/Channel.py create mode 100644 python-driver/config.txt create mode 100644 python-driver/driverConfig.json create mode 100644 python-driver/modbusMap.p create mode 100644 python-driver/persistence.py create mode 100644 python-driver/promagmbs.py create mode 100644 python-driver/utilities.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..113294a --- /dev/null +++ b/.gitignore @@ -0,0 +1,99 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..e501d62 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Promag400Modbus diff --git a/html-templates/Alerts.html b/html-templates/Alerts.html new file mode 100644 index 0000000..2971cab --- /dev/null +++ b/html-templates/Alerts.html @@ -0,0 +1 @@ +Alerts diff --git a/html-templates/Device.html b/html-templates/Device.html new file mode 100644 index 0000000..8f8d2df --- /dev/null +++ b/html-templates/Device.html @@ -0,0 +1,42 @@ +
+
+

Public IP Address

+

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

+

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

<%= node.vanityname %>

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

<%= node.vanityname %>

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

HEADER 1

+
+
+

CHANNEL 1

+
+
+
+
+ + + +
+ + <%= channels["promagmbs.channel_1"].timestamp %> + +
+
+
+ + + + + + + + diff --git a/html-templates/Sidebar.html b/html-templates/Sidebar.html new file mode 100644 index 0000000..b0eb787 --- /dev/null +++ b/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["promagmbs.sync"].techName %>" + data-name="<%= channels["promagmbs.sync"].name%>" + data-nodechannelcurrentId="<%= channels["promagmbs.sync"].nodechannelcurrentId %>" + id="<%= channels["promagmbs.sync"].channelId %>" + class="btn btn-large btn-block btn-theme animated setstatic mqtt"> + Sync All Data diff --git a/html-templates/Trends.html b/html-templates/Trends.html new file mode 100644 index 0000000..3b3c010 --- /dev/null +++ b/html-templates/Trends.html @@ -0,0 +1,37 @@ +
+
+ + to + + + Run + +
+
+
+
+
+ diff --git a/python-driver/Channel.py b/python-driver/Channel.py new file mode 100644 index 0000000..3814368 --- /dev/null +++ b/python-driver/Channel.py @@ -0,0 +1,282 @@ +"""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(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 not (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: + print("Cannot find a map value for {} in {} for {}".format(new_value, self.map_, self.mesh_name)) + self.value = new_value + else: + self.value = new_value + self.last_send_time = time.time() + print("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason)) + 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, transformFn=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.transformFn = transformFn + + def read(self, mbsvalue): + """Return the transformed read value.""" + return self.transformFn(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): + """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 + + 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) + 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.""" + 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: + 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/python-driver/config.txt b/python-driver/config.txt new file mode 100644 index 0000000..be8b7bd --- /dev/null +++ b/python-driver/config.txt @@ -0,0 +1,11 @@ +{ + "files": { + "file3": "modbusMap.p", + "file2": "utilities.py", + "file1": "promagmbs.py" + }, + "deviceName": "promagmbs", + "driverId": "0190", + "releaseVersion": "2", + "driverFileName": "promagmbs.py" +} \ No newline at end of file diff --git a/python-driver/driverConfig.json b/python-driver/driverConfig.json new file mode 100644 index 0000000..0948084 --- /dev/null +++ b/python-driver/driverConfig.json @@ -0,0 +1,11 @@ +{ + "name": "promagmbs", + "driverFilename": "promagmbs.py", + "driverId": "0190", + "additionalDriverFiles": [ + "utilities.py", + "modbusMap.p" + ], + "version": 2, + "s3BucketName": "promagmbs" +} \ No newline at end of file diff --git a/python-driver/modbusMap.p b/python-driver/modbusMap.p new file mode 100644 index 0000000..a4b605a --- /dev/null +++ b/python-driver/modbusMap.p @@ -0,0 +1,1414 @@ +(dp0 +S'1' +p1 +(dp2 +S'c' +p3 +VM1-485 +p4 +sS'b' +p5 +V9600 +p6 +sS'addresses' +p7 +(dp8 +V1 +p9 +(dp10 +V2-2 +p11 +(dp12 +Vah +p13 +V +p14 +sVbytary +p15 +NsVal +p16 +g14 +sVvn +p17 +VRaw Battery Terminal Voltage +p18 +sVct +p19 +Vnumber +p20 +sVle +p21 +V16 +p22 +sVgrp +p23 +V600 +p24 +sVla +p25 +I19225 +sVchn +p26 +Vadc_vbterm_raw +p27 +sVun +p28 +g9 +sVdn +p29 +Vprostarsolar +p30 +sVvm +p31 +NsVlrt +p32 +F1515515387.9155248 +sVda +p33 +g9 +sVa +p34 +V18 +p35 +sVc +p36 +V0 +p37 +sVmisc_u +p38 +g14 +sVf +p39 +V3 +p40 +sVmrt +p41 +V60 +p42 +sVm +p43 +Vnone +p44 +sVm1ch +p45 +g11 +sVmv +p46 +g37 +sVs +p47 +VOn +p48 +sVr +p49 +V0-65535 +p50 +sVt +p51 +Vint +p52 +ssV2-3 +p53 +(dp54 +Val +p55 +g14 +sVah +p56 +g14 +sVbytary +p57 +NsVvm +p58 +NsVvn +p59 +VRaw Array Voltage +p60 +sVct +p61 +Vnumber +p62 +sVle +p63 +V16 +p64 +sVgrp +p65 +V600 +p66 +sVla +p67 +I19467 +sVchn +p68 +Vadc_va_raw +p69 +sVun +p70 +g9 +sVdn +p71 +Vprostarsolar +p72 +sVda +p73 +g9 +sVlrt +p74 +F1515515388.4029889 +sg49 +V0-65535 +p75 +sg34 +V19 +p76 +sg36 +g37 +sVmisc_u +p77 +g14 +sg39 +g40 +sVmrt +p78 +V60 +p79 +sg43 +Vnone +p80 +sS'm1ch' +p81 +g53 +sg47 +VOn +p82 +sVmv +p83 +g37 +sg51 +Vint +p84 +ssV2-1 +p85 +(dp86 +Val +p87 +g14 +sVah +p88 +g14 +sVbytary +p89 +NsVvm +p90 +NsVvn +p91 +VRaw Array Current +p92 +sVct +p93 +Vnumber +p94 +sVle +p95 +V16 +p96 +sVgrp +p97 +V600 +p98 +sVla +p99 +I14789 +sVchn +p100 +Vadc_ia_raw +p101 +sVun +p102 +g9 +sVdn +p103 +Vprostarsolar +p104 +sVda +p105 +g9 +sVlrt +p106 +F1515515378.8921958 +sg49 +V0-65535 +p107 +sg34 +V17 +p108 +sg36 +g9 +sVmisc_u +p109 +g14 +sg39 +g40 +sVmrt +p110 +V60 +p111 +sg43 +Vnone +p112 +sg81 +g85 +sg47 +VOn +p113 +sVmv +p114 +g37 +sg51 +Vint +p115 +ssV2-6 +p116 +(dp117 +Val +p118 +g14 +sVah +p119 +g14 +sVbytary +p120 +NsVvm +p121 +NsVvn +p122 +VRaw Ambient Temp +p123 +sVct +p124 +Vnumber +p125 +sVle +p126 +V16 +p127 +sVgrp +p128 +V600 +p129 +sVla +p130 +I19515 +sVchn +p131 +Vt_amb_raw +p132 +sVun +p133 +g9 +sVdn +p134 +Vprostarsolar +p135 +sVda +p136 +g9 +sVlrt +p137 +F1515515379.419792 +sg49 +V0-65535 +p138 +sg34 +V28 +p139 +sg36 +g37 +sVmisc_u +p140 +g14 +sg39 +g40 +sVmrt +p141 +V60 +p142 +sg43 +Vnone +p143 +sg81 +g116 +sg47 +VOn +p144 +sVmv +p145 +g37 +sg51 +Vint +p146 +ssV2-7 +p147 +(dp148 +Val +p149 +g14 +sVah +p150 +g14 +sVbytary +p151 +NsVvm +p152 +NsVvn +p153 +VRaw Min Daily Battery Voltage +p154 +sVct +p155 +Vnumber +p156 +sVle +p157 +V16 +p158 +sVgrp +p159 +V600 +p160 +sVla +p161 +I19058 +sVchn +p162 +Vvb_min_daily_raw +p163 +sVun +p164 +g9 +sVdn +p165 +Vprostarsolar +p166 +sVda +p167 +g9 +sVlrt +p168 +F1515514883.7010118 +sg49 +V0-65535 +p169 +sg34 +V65 +p170 +sg36 +g37 +sVmisc_u +p171 +g14 +sg39 +g40 +sVmrt +p172 +V60 +p173 +sg43 +Vnone +p174 +sg81 +g147 +sg47 +VOn +p175 +sVmv +p176 +g37 +sg51 +Vint +p177 +ssV2-4 +p178 +(dp179 +Val +p180 +g14 +sVah +p181 +g14 +sVbytary +p182 +NsVvm +p183 +NsVvn +p184 +VRaw Load Voltage +p185 +sVct +p186 +Vnumber +p187 +sVle +p188 +V16 +p189 +sVgrp +p190 +V600 +p191 +sVla +p192 +I19232 +sVchn +p193 +Vadc_vl_raw +p194 +sVun +p195 +g9 +sVdn +p196 +Vprostarsolar +p197 +sVda +p198 +g9 +sVlrt +p199 +F1515515370.4007909 +sg49 +V0-65535 +p200 +sg34 +V20 +p201 +sg36 +g37 +sVmisc_u +p202 +g14 +sg39 +g40 +sVmrt +p203 +V60 +p204 +sg43 +Vnone +p205 +sg81 +g178 +sg47 +VOn +p206 +sVmv +p207 +g37 +sg51 +Vint +p208 +ssV2-5 +p209 +(dp210 +Val +p211 +g14 +sVah +p212 +g14 +sVbytary +p213 +NsVvm +p214 +NsVvn +p215 +VRaw Load Current +p216 +sVct +p217 +Vnumber +p218 +sVle +p219 +V16 +p220 +sVgrp +p221 +V600 +p222 +sVla +p223 +I12849 +sVchn +p224 +Vadc_il_raw +p225 +sVun +p226 +g9 +sVdn +p227 +Vprostarsolar +p228 +sVda +p229 +g9 +sVlrt +p230 +F1515515370.884296 +sg49 +V0-65535 +p231 +sg34 +V22 +p232 +sg36 +g37 +sVmisc_u +p233 +g14 +sg39 +g40 +sVmrt +p234 +V60 +p235 +sg43 +Vnone +p236 +sg81 +g209 +sg47 +VOn +p237 +sVmv +p238 +g37 +sg51 +Vint +p239 +ssV2-8 +p240 +(dp241 +Val +p242 +g14 +sVah +p243 +g14 +sVbytary +p244 +NsVvm +p245 +NsVvn +p246 +VRaw Max Daily Battery Voltage +p247 +sVct +p248 +Vnumber +p249 +sVle +p250 +V16 +p251 +sVgrp +p252 +V600 +p253 +sVla +p254 +I19280 +sVchn +p255 +Vvb_max_daily_raw +p256 +sVun +p257 +g9 +sVdn +p258 +Vprostarsolar +p259 +sVda +p260 +g9 +sVlrt +p261 +F1515514885.1708192 +sg49 +V0-65535 +p262 +sg34 +V66 +p263 +sg36 +g37 +sVmisc_u +p264 +g14 +sg39 +g40 +sVmrt +p265 +V60 +p266 +sg43 +Vnone +p267 +sg81 +g240 +sg47 +VOn +p268 +sVmv +p269 +g37 +sg51 +Vint +p270 +ssV2-9 +p271 +(dp272 +Val +p273 +g14 +sVah +p274 +g14 +sVbytary +p275 +NsVvm +p276 +(dp277 +g9 +VNight Check +p278 +sg37 +VStart +p279 +sg40 +VNight +p280 +sV2 +p281 +VDisconnect +p282 +sV5 +p283 +VBulk +p284 +sV4 +p285 +VFault +p286 +sV7 +p287 +VFloat +p288 +sV6 +p289 +VAbsorption +p290 +sV8 +p291 +VEqualize +p292 +ssVvn +p293 +VCharge State +p294 +sVct +p295 +Vnumber +p296 +sVle +p297 +V16 +p298 +sVgrp +p299 +V600 +p300 +sVla +p301 +I6 +sVchn +p302 +Vcharge_state +p303 +sVun +p304 +g9 +sVdn +p305 +Vprostarsolar +p306 +sVda +p307 +g9 +sVlrt +p308 +F1515514905.7810062 +sg49 +V0-65535 +p309 +sg34 +V33 +p310 +sg36 +g37 +sVmisc_u +p311 +g14 +sg39 +g40 +sVmrt +p312 +V60 +p313 +sg43 +Vnone +p314 +sg81 +g271 +sg47 +VOn +p315 +sVmv +p316 +g37 +sg51 +Vint +p317 +ssV2-10 +p318 +(dp319 +Val +p320 +g14 +sVah +p321 +g14 +sVbytary +p322 +NsVvm +p323 +(dp324 +V11 +p325 +VProcessor Supply Fault +p326 +sV10 +p327 +VDip Switch Changed (Excl. DIP 8) +p328 +sg9 +VFETs Shorted +p329 +sg37 +VOvercurrent Phase 1 +p330 +sg40 +VBattery HVD (High Voltage Disconnect) +p331 +sg281 +VSoftware Bug +p332 +sg283 +VEEPROM Setting Edit (Reset required) +p333 +sg285 +VArray HVD (High Voltage Disconnect) +p334 +sg287 +VRTS was valid now disconnected +p335 +sg289 +VRTS Shorted +p336 +sV9 +p337 +VBattery LVD (Low Voltage Disconnect) +p338 +sg291 +VLocal temp. sensor failed +p339 +ssVvn +p340 +VArray Fault +p341 +sVct +p342 +Vnumber +p343 +sVle +p344 +V16 +p345 +sVgrp +p346 +V600 +p347 +sVla +p348 +I0 +sVchn +p349 +Varray_fault +p350 +sVun +p351 +g9 +sVdn +p352 +Vprostarsolar +p353 +sVda +p354 +g9 +sVlrt +p355 +F1515514886.1563522 +sg49 +V0-65535 +p356 +sg34 +V34 +p357 +sg36 +g37 +sVmisc_u +p358 +g14 +sg39 +g40 +sVmrt +p359 +V60 +p360 +sg43 +Vnone +p361 +sg81 +g318 +sg47 +VOn +p362 +sVmv +p363 +g37 +sg51 +Vint +p364 +ssssS'f' +p365 +VOff +p366 +sS'p' +p367 +g14 +sS's' +p368 +g281 +ssS'2' +p369 +(dp370 +g3 +VM1-485 +p371 +sg5 +V9600 +p372 +sg7 +(dp373 +g281 +(dp374 +V4-1 +p375 +(dp376 +Val +p377 +g14 +sVah +p378 +g14 +sVbytary +p379 +NsVvm +p380 +NsVvn +p381 +VVolume Flow +p382 +sVct +p383 +Vnumber +p384 +sVle +p385 +V32 +p386 +sVgrp +p387 +V600 +p388 +sVla +p389 +NsVchn +p390 +Vvolume_flow +p391 +sVun +p392 +g9 +sVdn +p393 +Vpromagmbs +p394 +sVda +p395 +g281 +sVlrt +p396 +g37 +sg49 +V0-10000 +p397 +sg34 +V2008 +p398 +sg36 +g283 +sVmisc_u +p399 +g14 +sg39 +g40 +sVmrt +p400 +V60 +p401 +sg43 +Vnone +p402 +sg81 +g375 +sg47 +VOn +p403 +sVmv +p404 +g37 +sg51 +Vfloat +p405 +ssV4-2 +p406 +(dp407 +Val +p408 +g14 +sVah +p409 +g14 +sVbytary +p410 +NsVvm +p411 +NsVvn +p412 +VMass Flow +p413 +sVct +p414 +Vnumber +p415 +sVle +p416 +V32 +p417 +sVgrp +p418 +V600 +p419 +sVla +p420 +NsVchn +p421 +Vmass_flow +p422 +sVun +p423 +g9 +sVdn +p424 +Vpromagmbs +p425 +sVda +p426 +g281 +sVlrt +p427 +g37 +sg49 +V0-10000 +p428 +sg34 +V2010 +p429 +sg36 +g283 +sVmisc_u +p430 +g14 +sg39 +g40 +sVmrt +p431 +V60 +p432 +sg43 +Vnone +p433 +sg81 +g406 +sg47 +VOn +p434 +sVmv +p435 +g37 +sg51 +Vfloat +p436 +ssV4-3 +p437 +(dp438 +Val +p439 +g14 +sVah +p440 +g14 +sVbytary +p441 +NsVvm +p442 +NsVvn +p443 +VConductivity +p444 +sVct +p445 +Vnumber +p446 +sVle +p447 +V32 +p448 +sVgrp +p449 +V600 +p450 +sVla +p451 +NsVchn +p452 +Vconductivity +p453 +sVun +p454 +g9 +sVdn +p455 +Vpromagmbs +p456 +sVda +p457 +g281 +sVlrt +p458 +g37 +sg49 +V0-10000 +p459 +sg34 +V2014 +p460 +sg36 +V0.5 +p461 +sVmisc_u +p462 +g14 +sg39 +g40 +sVmrt +p463 +V60 +p464 +sg43 +Vnone +p465 +sg81 +g437 +sg47 +VOn +p466 +sVmv +p467 +g37 +sg51 +Vfloat +p468 +ssV4-4 +p469 +(dp470 +Val +p471 +g14 +sVah +p472 +g14 +sVbytary +p473 +NsVvm +p474 +NsVvn +p475 +VTotalizer 1 +p476 +sVct +p477 +Vnumber +p478 +sVle +p479 +V32 +p480 +sVgrp +p481 +V600 +p482 +sVla +p483 +NsVchn +p484 +Vtotalizer_1 +p485 +sVun +p486 +g9 +sVdn +p487 +Vpromagmbs +p488 +sVda +p489 +g281 +sVlrt +p490 +g37 +sg49 +V0-10000000 +p491 +sg34 +V2611 +p492 +sg36 +V50.0 +p493 +sVmisc_u +p494 +g14 +sg39 +g40 +sVmrt +p495 +V60 +p496 +sg43 +Vnone +p497 +sg81 +g469 +sg47 +VOn +p498 +sVmv +p499 +g37 +sg51 +Vfloat +p500 +ssV4-5 +p501 +(dp502 +Val +p503 +g14 +sVah +p504 +g14 +sVbytary +p505 +NsVvm +p506 +NsVvn +p507 +VTotalizer 2 +p508 +sVct +p509 +Vnumber +p510 +sVle +p511 +V32 +p512 +sVgrp +p513 +V600 +p514 +sVla +p515 +NsVchn +p516 +Vtotalizer_2 +p517 +sVun +p518 +g9 +sVdn +p519 +Vpromagmbs +p520 +sVda +p521 +g281 +sVlrt +p522 +g37 +sg49 +V0-10000000 +p523 +sg34 +V2811 +p524 +sg36 +V50 +p525 +sVmisc_u +p526 +g14 +sg39 +g40 +sVmrt +p527 +V60 +p528 +sg43 +Vnone +p529 +sg81 +g501 +sg47 +VOn +p530 +sVmv +p531 +g37 +sg51 +Vfloat +p532 +ssV4-6 +p533 +(dp534 +Val +p535 +g14 +sVah +p536 +g14 +sVbytary +p537 +NsVvm +p538 +NsVvn +p539 +VTotalizer 3 +p540 +sVct +p541 +Vnumber +p542 +sVle +p543 +V32 +p544 +sVgrp +p545 +V600 +p546 +sVla +p547 +NsVchn +p548 +Vtotalizer_3 +p549 +sVun +p550 +g9 +sVdn +p551 +Vpromagmbs +p552 +sVda +p553 +g281 +sVlrt +p554 +g37 +sg49 +V0-10000000 +p555 +sg34 +V3011 +p556 +sg36 +V50 +p557 +sVmisc_u +p558 +g14 +sg39 +g40 +sVmrt +p559 +V60 +p560 +sg43 +Vnone +p561 +sg81 +g533 +sg47 +VOn +p562 +sVmv +p563 +g37 +sg51 +Vfloat +p564 +ssssg365 +VOff +p565 +sg367 +VNone +p566 +sg368 +g9 +ss. \ No newline at end of file diff --git a/python-driver/persistence.py b/python-driver/persistence.py new file mode 100644 index 0000000..ed65271 --- /dev/null +++ b/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) + except Exception: + return False diff --git a/python-driver/promagmbs.py b/python-driver/promagmbs.py new file mode 100644 index 0000000..0ecf4ce --- /dev/null +++ b/python-driver/promagmbs.py @@ -0,0 +1,75 @@ +"""Driver for promagmbs.""" + +import threading +from device_base import deviceBase +from utilities import get_public_ip_address +import time + + +_ = None + + +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.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): + # pass + """Actually run the driver.""" + global persist + wait_sec = 60 + for i in range(0, wait_sec): + print("promagmbs driver will start in {} seconds".format(wait_sec - i)) + time.sleep(1) + print("BOOM! Starting promagmbs driver...") + + public_ip_address = get_public_ip_address() + self.sendtodbDev(1, 'public_ip_address', public_ip_address, 0, 'promagmbs') + + send_loops = 0 + watchdog_loops = 0 + watchdog_check_after = 5000 + while True: + if self.forceSend: + print "FORCE SEND: TRUE" + + print("promagmbs driver still alive...") + if self.forceSend: + if send_loops > 2: + print("Turning off forceSend") + self.forceSend = False + send_loops = 0 + else: + send_loops += 1 + + watchdog_loops += 1 + if (watchdog_loops >= watchdog_check_after): + test_public_ip = get_public_ip_address() + if not test_public_ip == public_ip_address: + self.sendtodbDev(1, 'public_ip_address', test_public_ip, 0, 'promagmbs') + public_ip_address = test_public_ip + watchdog_loops = 0 + time.sleep(10) + + def promagmbs_sync(self, name, value): + """Sync all data from the driver.""" + self.forceSend = True + self.sendtodb("log", "synced", 0) + return True diff --git a/python-driver/utilities.py b/python-driver/utilities.py new file mode 100644 index 0000000..2ddc31e --- /dev/null +++ b/python-driver/utilities.py @@ -0,0 +1,41 @@ +"""Utility functions for the driver.""" +import socket +import struct + + +def get_public_ip_address(): + """Find the public IP Address of the host device.""" + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + ip = s.getsockname()[0] + s.close() + return ip + + +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 ** int(bin_rep[0]) + exponent = int(bin_rep[1:6], 2) + fraction = int(bin_rep[7:17], 2) + + return sign * 2 ** (exponent - 15) * float("1.{}".format(fraction)) + + +def ints_to_float(intlist): + """Convert 2 registers into a floating point number.""" + mypack = struct.pack('>HH', intlist[0], intlist[1]) + f = struct.unpack('>f', mypack) + print("{} >> {}".format(intlist, f[0])) + return f[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