From 529399e2f36b6a26e95698f7503a2c215dfb3ea9 Mon Sep 17 00:00:00 2001 From: Patrick McDonagh Date: Tue, 3 Jul 2018 11:24:53 -0500 Subject: [PATCH] Initial python driver converted parts from Multi-Pond --- POCloud/channels_multisensor.csv | 42 +++ POCloud/python-driver/Channel.py | 289 +++++++++++++++++++ POCloud/python-driver/Channel.pyc | Bin 0 -> 8572 bytes POCloud/python-driver/config.txt | 11 + POCloud/python-driver/device_base.py | 360 ++++++++++++++++++++++++ POCloud/python-driver/driverConfig.json | 11 + POCloud/python-driver/logger.py | 18 ++ POCloud/python-driver/multisensor.py | 194 +++++++++++++ POCloud/python-driver/plcpond.py | 247 ++++++++++++++++ POCloud/python-driver/test.py | 26 ++ POCloud/python-driver/utilities.py | 51 ++++ 11 files changed, 1249 insertions(+) create mode 100644 POCloud/channels_multisensor.csv create mode 100644 POCloud/python-driver/Channel.py create mode 100644 POCloud/python-driver/Channel.pyc create mode 100644 POCloud/python-driver/config.txt create mode 100644 POCloud/python-driver/device_base.py create mode 100644 POCloud/python-driver/driverConfig.json create mode 100644 POCloud/python-driver/logger.py create mode 100644 POCloud/python-driver/multisensor.py create mode 100644 POCloud/python-driver/plcpond.py create mode 100644 POCloud/python-driver/test.py create mode 100644 POCloud/python-driver/utilities.py diff --git a/POCloud/channels_multisensor.csv b/POCloud/channels_multisensor.csv new file mode 100644 index 0000000..52e212c --- /dev/null +++ b/POCloud/channels_multisensor.csv @@ -0,0 +1,42 @@ +id,name,deviceTypeId,fromMe,io,subTitle,helpExplanation,channelType,dataType,defaultValue,regex,regexErrMsg,units,min,max,change,guaranteedReportPeriod,minReportTime +13902,log,465,FALSE,readwrite,Log,Device Log,device,string,Initialized,,,,,,,, +,an0val,465,FALSE,readonly,Analog 0 Value,Scaled Value,device,float,0,,,,,,,, +,an1val,465,FALSE,readonly,Analog 1 Value,Scaled Value,device,float,0,,,,,,,, +,an2val,465,FALSE,readonly,Analog 2 Value,Scaled Value,device,float,0,,,,,,,, +,an3val,465,FALSE,readonly,Analog 3 Value,Scaled Value,device,float,0,,,,,,,, +,an4val,465,FALSE,readonly,Analog 4 Value,Scaled Value,device,float,0,,,,,,,, +,an5val,465,FALSE,readonly,Analog 5 Value,Scaled Value,device,float,0,,,,,,,, +,an6val,465,FALSE,readonly,Analog 6 Value,Scaled Value,device,float,0,,,,,,,, +,an7val,465,FALSE,readonly,Analog 7 Value,Scaled Value,device,float,0,,,,,,,, +,pond0volume,465,FALSE,readonly,Pond 0 Volume,BBL,device,float,0,,,,,,,, +,pond1volume,465,FALSE,readonly,Pond 1 Volume,BBL,device,float,0,,,,,,,, +,pond2volume,465,FALSE,readonly,Pond 2 Volume,BBL,device,float,0,,,,,,,, +,pond3volume,465,FALSE,readonly,Pond 3 Volume,BBL,device,float,0,,,,,,,, +,pond4volume,465,FALSE,readonly,Pond 4 Volume,BBL,device,float,0,,,,,,,, +,pond5volume,465,FALSE,readonly,Pond 5 Volume,BBL,device,float,0,,,,,,,, +,pond6volume,465,FALSE,readonly,Pond 6 Volume,BBL,device,float,0,,,,,,,, +,pond7volume,465,FALSE,readonly,Pond 7 Volume,BBL,device,float,0,,,,,,,, +,an0active,465,FALSE,readonly,Analog 0 Active,Signal Above 3.0 mA,device,boolean,FALSE,,,,,,,, +,an1active,465,FALSE,readonly,Analog 1 Active,Signal Above 3.0 mA,device,boolean,FALSE,,,,,,,, +,an2active,465,FALSE,readonly,Analog 2 Active,Signal Above 3.0 mA,device,boolean,FALSE,,,,,,,, +,an3active,465,FALSE,readonly,Analog 3 Active,Signal Above 3.0 mA,device,boolean,FALSE,,,,,,,, +,an4active,465,FALSE,readonly,Analog 4 Active,Signal Above 3.0 mA,device,boolean,FALSE,,,,,,,, +,an5active,465,FALSE,readonly,Analog 5 Active,Signal Above 3.0 mA,device,boolean,FALSE,,,,,,,, +,an6active,465,FALSE,readonly,Analog 6 Active,Signal Above 3.0 mA,device,boolean,FALSE,,,,,,,, +,an7active,465,FALSE,readonly,Analog 7 Active,Signal Above 3.0 mA,device,boolean,FALSE,,,,,,,, +,an0units,465,FALSE,readwrite,Analog 0 Units,Channel Units,user input,string,,,,,,,,, +,an1units,465,FALSE,readwrite,Analog 1 Units,Channel Units,user input,string,,,,,,,,, +,an2units,465,FALSE,readwrite,Analog 2 Units,Channel Units,user input,string,,,,,,,,, +,an3units,465,FALSE,readwrite,Analog 3 Units,Channel Units,user input,string,,,,,,,,, +,an4units,465,FALSE,readwrite,Analog 4 Units,Channel Units,user input,string,,,,,,,,, +,an5units,465,FALSE,readwrite,Analog 5 Units,Channel Units,user input,string,,,,,,,,, +,an6units,465,FALSE,readwrite,Analog 6 Units,Channel Units,user input,string,,,,,,,,, +,an7units,465,FALSE,readwrite,Analog 7 Units,Channel Units,user input,string,,,,,,,,, +,an0ispond,465,FALSE,readonly,Analog 0 Is Pond Level,Use the input for pond level monitoring,device,boolean,FALSE,,,,,,,, +,an1ispond,465,FALSE,readonly,Analog 1 Is Pond Level,Use the input for pond level monitoring,device,boolean,FALSE,,,,,,,, +,an2ispond,465,FALSE,readonly,Analog 2 Is Pond Level,Use the input for pond level monitoring,device,boolean,FALSE,,,,,,,, +,an3ispond,465,FALSE,readonly,Analog 3 Is Pond Level,Use the input for pond level monitoring,device,boolean,FALSE,,,,,,,, +,an4ispond,465,FALSE,readonly,Analog 4 Is Pond Level,Use the input for pond level monitoring,device,boolean,FALSE,,,,,,,, +,an5ispond,465,FALSE,readonly,Analog 5 Is Pond Level,Use the input for pond level monitoring,device,boolean,FALSE,,,,,,,, +,an6ispond,465,FALSE,readonly,Analog 6 Is Pond Level,Use the input for pond level monitoring,device,boolean,FALSE,,,,,,,, +,an7ispond,465,FALSE,readonly,Analog 7 Is Pond Level,Use the input for pond level monitoring,device,boolean,FALSE,,,,,,,, \ No newline at end of file diff --git a/POCloud/python-driver/Channel.py b/POCloud/python-driver/Channel.py new file mode 100644 index 0000000..4e131bc --- /dev/null +++ b/POCloud/python-driver/Channel.py @@ -0,0 +1,289 @@ +"""Define Meshify channel class.""" +from pycomm.ab_comm.clx import Driver as ClxDriver +from pycomm.cip.cip_base import CommError, DataError +import time + +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" + c = ClxDriver() + try: + if c.open(addr, direct_connection=direct): + try: + v = c.read_tag(tag) + return v + except DataError as e: + c.close() + time.sleep(TAG_DATAERROR_SLEEPTIME) + print("Data Error during readTag({}, {}): {}".format(addr, tag, e)) + 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, plc_type="CLX"): + """Read an array from the PLC.""" + direct = plc_type == "Micro800" + c = ClxDriver() + if c.open(addr, direct_connection=direct): + arr_vals = [] + try: + for i in range(start, end): + tag_w_index = tag + "[{}]".format(i) + v = c.read_tag(tag_w_index) + # print('{} - {}'.format(tag_w_index, v)) + arr_vals.append(round(v[0], 4)) + # print(v) + if len(arr_vals) > 0: + 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, plc_type="CLX"): + """Write a tag value to the PLC.""" + direct = plc_type == "Micro800" + c = ClxDriver() + if c.open(addr, direct_connection=direct): + try: + cv = c.read_tag(tag) + print(cv) + 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, 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.""" + 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/POCloud/python-driver/Channel.pyc b/POCloud/python-driver/Channel.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c41f58d8a09a0d1c331caa7a5f637397431d6e1f GIT binary patch literal 8572 zcmb_hTWlOx89uYSUVELjovRb~I=uwk(r#1eO(lgUPMVY^P8kQ;q_m^)%-Eh}J!{X5 z6Pwr)Qt|>%NQmc32!TL6paO5aAo0W-5>LE^QN^?!a}5RjubOtnELY97nsNAC zGwlJhTsPP1W_i$D8$|7Zxr?DgCaRlg(A>q)hRQ=K53Afz`GARrP24mKnCJjU-7)5? z*G5d#)D)v88Zq&Rnn!!hV@j{Zu?&UaN zN)|TUa4AUBxb24RAkSOLfADX5(m}tX&dynKGtMR}d~P6{>MSpx&$3RYr*lCO=qcoY z8WK-$3MqdH8P4XqF`L+uW40W#h_YkOz1uWf6|-3}?^Vp|xY??j&8oTXaNiC}GkRJ6 zD8|pMw3EUOQa4G9crniS+i{a3cju9{@`N3ooqQM3bti2)}F_c zT$px)@i&8ZF^uQ=B=z$mOVY)HYfm(Ql|hzGqQWT4*>%dEP-|tQ7{Vhup--^EH{`4^ zl`&_+8FmV7{?c5?U$zb)=`cP-0UO47+^BHFg>HGPW;PbhW{sQ2A>o#={X346-8LJ? zRgW5+3l49s9Uy(XyuD=Kz(99%nAwPl4i-X7w z+#*TP(%y+n1(VI>yUMm~~z`b*eaoo>7v;Vc~}zKrt+mPMV)YE#c(~Uv9L< zt8z2Ucr{p@yuI~|dwc7tSCQqssHx6cJ95)b;g(bEGa!E&ozE1w_xZIVzW3C_x3?w< zd5in@x)p_jVCt;IsW!p&_}HH(0pWdz?M@zhM1UtD8bxX63k!C8BWMGd1c@dLq9_x5 zFqbA?X@`EXu@Vcm_H=~PQ29;eu^_5!7f!t~Ow5!&iOf`*PQ$4>$B@RHA*5mFQD>ww z>>LGhhn*8hAs5v|;;%6KZxnE`C1wY@n5`S!d>^m_0_;C2+gpQXa?Sgt_Y z&zh|v!kGAks1Fg($F~}qre-!9!uPP*93-}|-A#~V%}jtnF&urJ)hRQJ*6cg%GXp~9 zG~$#C7lG87+4LMBJ0!^dndk=L+Aer2q>c-z0ohzP-iC!9(94G6=MN&YKu%rB{Z1@<2iVS&%ZZG4Zg0Jt;MXL4$`2x&?zrt3G_&M_nh4lWq4Fcp zJ3~q-aZPLs-;g%t4efqpr6K*1k(cbm{SK($DJe2SmlvQ-KjaF0PTo#+-U-+1Na+pLY^-7 z*MsZN@qn8y;acm$Rm9ncE2t9u`ct#IFmxT>9chyXQM*(aiZ#F#x zoYjQ0?+ItyxDz-N<8lU&FF*#+qdaIqHbjtt8|V6U1R2?qJCI(U&5|N^1JNZ8crA9J z?7EcMhgm{e$sa?u14Fksx=)Q3K0G0>2OqJ5P@&G^EgA%ttsY^Cs1SI>eA}Q=MFSSq z2ZSy;Y_CI5j9G!ErD7$X?A%$LueA&B2~_OQQYX=R7Abd;buXVBH2{6eD?hsJ%JrgB z6^HE4e_WPk_&f?REH3a2@`{P8{0}tTtsgZMlB7vmS|^slCq^sUWS3^r@?Zp zrwF>#Y7~mjbSI6)2r&8zit}15>9-|vjT&-rfL|n-f*=Z)7X4xgH@k#0QqAI806Hq- z*w3N1Iq`C^;;YHRAWnn%b{zE;T^_d=yw9Mc_YAY=nSGYo=a{{KY+tyDeSGjtQuzKH zdgf#~Q)xiCZT|}?AWU43?&Kj90EO$g5L4GwoM=LL&Yv3iiFgsbePC{6Tl{pay?nR{y@U=6wf(Y-y2it(X)1f%dMM z?2p~9L)|xHu^3<%+bAp3AMV`Ec(;RglYX;YSGk)Px~p&fI;TpuaP1fw{1wA3<|U~qSbDsw3|)P3H*VjuV2)C?GC?`f1IBRyqT zH#135&nbmdOL2IE8YhoqH(3y~r134w4Q?98WW|m+c$>OL5>;Zt3v9njMT#^|w zbJcru`hs{Urz%J}TN=4!5P3?dkmRUUC9&6HoReRAY=TjtCWOHh;>FZTWVuBWUdcZ> zC2I<vnX~AGVw?x)rJ8h)5;teV1J_kt6OY+6Wy^|FQYnz}@m~gbG#JW83 zc-u3GDet8F(*}7V z8|;DpRVd^U=K<#+ICso>*y%AX*;b@PIDn2yA6~~wACMHP>uyck9dN+M@F1t6jrcx( zOA?epUaA2qHs9Cr`TlYzT5I#sl4sNjec#?E!w}xf%+4`8kF0-&D(!Zi=pj;xs~%@l z8EZCb&6oGyD`av;q@F*)>EmK8OWk-q2#fZHduu5u+*?6TAt)$3Nzv+WUqE6@e?7aF zqMarYx+KNM_qimi3#4B!ccS^Ve78MB6r5#GYS(z1J%mfKCQ)pSp&E@L*td7lr%nTi z$DLC~?8K5whJ`xw*CC88S)Czm3nm`3?)$qafC%7@;w0d&S&Nz`4G0Hj42&5#e2i0A zhC)Nv04j}}Z3a+j8zE?B`@rt8qu?Eizeu3gHfm~W&?fj1b`lDpHW9mz(AX!imPjqu z?^#=kGw&@tdcq9pr67Q_crnR~IP=rB<@q?1ja!;rehwSVQik|}@^9&a46>JTBl#|1 zGvy3Zy^+dVlOR~5=rYIcvW%u#*E5g2D|p-&GFtmj&?A2UnW>!IVHx$Uq>xDJCfy_n zm>LGTLQW;DA4hcwYYK61>2ai3OexjYuF=XkspYvLl$Pgn$^HHdrbPY{sVm}ymiGp; z3(PJuqn+n{iJ7kEK7^R}_#>p8u_y}hv4f%tM4xtx41s@^U7td>U1WML1n_iM!QN6{ z272;B*BMi~<(%#nD=Z#mB|EsWg zm#{bQ%glHgQqv4pB`ZR(7+Aq81jPj8`;-iMmVI$R3<)t5JEu3px9yUOzCav9>;CxS zB7cqaaRNb&R7Dy5aN6^@a61d`JeCpTV}fE%UHz7~!Uo1O5plZtMHEVXg@Tncpumct6l}PBFDGUWj$NDuM?zwk8dY~Im>ZB28vqFYLp11+8=_Lk{ z(HU|xyE=lfoBinVp2BYMw9X2rbF-*T>Y1BE4+TRzHVSM!Db9; z6x!EUQg4TcgwLH3$Z4lDzK8qmy@DH+#>gK>X&P=YLu~{&qWl)%J8&t&|6e?Ek3=h> zpV`NaK7Y>9xUvBXSZ)RL z_-u+_#Nv6@Hr`(x>30Z|l@^jeAK=3z%^ZEK^mHe>q9<8*5}AxS>+pF`vr;xUZE0)p racTEny-=*)sj2;|x#;c8?=85Km%_HMi8Uy1e#vl1F_9`Z( literal 0 HcmV?d00001 diff --git a/POCloud/python-driver/config.txt b/POCloud/python-driver/config.txt new file mode 100644 index 0000000..e57b6be --- /dev/null +++ b/POCloud/python-driver/config.txt @@ -0,0 +1,11 @@ +{ + "driverFileName": "multisensor.py", + "deviceName": "multisensor", + "driverId": "0240", + "releaseVersion": "1", + "files": { + "file1": "multisensor.py", + "file2": "utilities.py", + "file3": "Channel.py" + } +} \ No newline at end of file diff --git a/POCloud/python-driver/device_base.py b/POCloud/python-driver/device_base.py new file mode 100644 index 0000000..6e4a3e4 --- /dev/null +++ b/POCloud/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/POCloud/python-driver/driverConfig.json b/POCloud/python-driver/driverConfig.json new file mode 100644 index 0000000..b509425 --- /dev/null +++ b/POCloud/python-driver/driverConfig.json @@ -0,0 +1,11 @@ +{ + "name": "multisensor", + "driverFilename": "multisensor.py", + "driverId": "0000", + "additionalDriverFiles": [ + "utilities.py", + "Channel.py" + ], + "version": 1, + "s3BucketName": "multisensor" +} \ No newline at end of file diff --git a/POCloud/python-driver/logger.py b/POCloud/python-driver/logger.py new file mode 100644 index 0000000..1071659 --- /dev/null +++ b/POCloud/python-driver/logger.py @@ -0,0 +1,18 @@ +# LOGGING SETUP +import logging +import sys +from logging.handlers import RotatingFileHandler + +log_formatter = logging.Formatter('%(asctime)s %(levelname)s %(funcName)s(%(lineno)d) %(message)s') +logFile = './multisensor.log' +my_handler = RotatingFileHandler(logFile, mode='a', maxBytes=500*1024, + backupCount=2, encoding=None, delay=0) +my_handler.setFormatter(log_formatter) +my_handler.setLevel(logging.INFO) +filelogger = logging.getLogger('multisensor') +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/POCloud/python-driver/multisensor.py b/POCloud/python-driver/multisensor.py new file mode 100644 index 0000000..16046ab --- /dev/null +++ b/POCloud/python-driver/multisensor.py @@ -0,0 +1,194 @@ +"""Driver for multisensor""" + +import threading +import json +import time +from random import randint + +from device_base import deviceBase +from Channel import PLCChannel, read_tag, write_tag +from utilities import get_public_ip_address +from logger import filelogger + +_ = None + +filelogger.info("multisensor startup") + +# GLOBAL VARIABLES +WAIT_FOR_CONNECTION_SECONDS = 60 +WATCHDOG_ENABLE = True +WATCHDOG_SEND_PERIOD = 3600 # Seconds, the longest amount of time before sending the watchdog status +PLC_IP_ADDRESS = "192.168.1.12" + +CALIBRATION_TABLES = [[] for x in xrange(8)] + +CHANNELS = [ + PLCChannel(PLC_IP_ADDRESS, 'an0val', 'input0.scaledValue', 'REAL', 1.0, 600, map_=False, write_enabled=False, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, 'an1val', 'input1.scaledValue', 'REAL', 1.0, 600, map_=False, write_enabled=False, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, 'an2val', 'input2.scaledValue', 'REAL', 1.0, 600, map_=False, write_enabled=False, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, 'an3val', 'input3.scaledValue', 'REAL', 1.0, 600, map_=False, write_enabled=False, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, 'an4val', 'input4.scaledValue', 'REAL', 1.0, 600, map_=False, write_enabled=False, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, 'an5val', 'input5.scaledValue', 'REAL', 1.0, 600, map_=False, write_enabled=False, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, 'an6val', 'input6.scaledValue', 'REAL', 1.0, 600, map_=False, write_enabled=False, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, 'an7val', 'input7.scaledValue', 'REAL', 1.0, 600, map_=False, write_enabled=False, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, 'pond0volume', 'input0.pondVolume', 'REAL', 1000.0, 600, map_=False, write_enabled=False, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, 'pond1volume', 'input1.pondVolume', 'REAL', 1000.0, 600, map_=False, write_enabled=False, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, 'pond2volume', 'input2.pondVolume', 'REAL', 1000.0, 600, map_=False, write_enabled=False, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, 'pond3volume', 'input3.pondVolume', 'REAL', 1000.0, 600, map_=False, write_enabled=False, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, 'pond4volume', 'input4.pondVolume', 'REAL', 1000.0, 600, map_=False, write_enabled=False, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, 'pond5volume', 'input5.pondVolume', 'REAL', 1000.0, 600, map_=False, write_enabled=False, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, 'pond6volume', 'input6.pondVolume', 'REAL', 1000.0, 600, map_=False, write_enabled=False, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, 'pond7volume', 'input7.pondVolume', 'REAL', 1000.0, 600, map_=False, write_enabled=False, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, 'an0active', 'input0.active', 'BOOL', 1.0, 600, map_=False, write_enabled=False, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, 'an1active', 'input1.active', 'BOOL', 1.0, 600, map_=False, write_enabled=False, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, 'an2active', 'input2.active', 'BOOL', 1.0, 600, map_=False, write_enabled=False, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, 'an3active', 'input3.active', 'BOOL', 1.0, 600, map_=False, write_enabled=False, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, 'an4active', 'input4.active', 'BOOL', 1.0, 600, map_=False, write_enabled=False, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, 'an5active', 'input5.active', 'BOOL', 1.0, 600, map_=False, write_enabled=False, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, 'an6active', 'input6.active', 'BOOL', 1.0, 600, map_=False, write_enabled=False, plc_type="Micro800"), + PLCChannel(PLC_IP_ADDRESS, 'an7active', 'input7.active', 'BOOL', 1.0, 600, map_=False, write_enabled=False, plc_type="Micro800"), +] + + +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 + self.watchdog = False + self.watchdog_last_checked = 0 + self.watchdog_last_sent = 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("multisensor driver will start in {} seconds".format(WAIT_FOR_CONNECTION_SECONDS - i)) + time.sleep(1) + filelogger.info("BOOM! Starting multisensor driver...") + + self._check_watchdog() + self._check_ip_address() + + self.nodes["multisensor_0199"] = self + + send_loops = 0 + watchdog_check_after = 60 + ip_check_after = 60 + + while True: + now = time.time() + if self.force_send: + filelogger.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, 'multisensor') + + + # print("multisensor driver still alive...") + if self.force_send: + if send_loops > 2: + filelogger.warning("Turning off force_send") + self.force_send = False + send_loops = 0 + else: + send_loops += 1 + + if WATCHDOG_ENABLE: + if (now - self.watchdog_last_checked) > watchdog_check_after: + self._check_watchdog() + + if (now - self.public_ip_address_last_checked) > ip_check_after: + self._check_ip_address() + + def read_pond_calibration(self, input_number): + """Read all calibration values for a specific pond.""" + last_read_height = -1.0 + cal_values = [] + cal_index = 1 + while last_read_height != 0 and cal_index <=10: + cal_tag_height = "pond{}CalibrationHeight[{}]".format(input_number, cal_index) + cal_tag_volume = "pond{}CalibrationVolume[{}]".format(input_number, cal_index) + read_height = read_tag(PLC_IP_ADDRESS, cal_tag_height, plc_type="Micro800") + time.sleep(2) + read_volume = read_tag(PLC_IP_ADDRESS, cal_tag_volume, plc_type="Micro800") + time.sleep(2) + if read_height and read_volume: + if read_height[0] > 0.0: + cal_values.append({"height": read_height[0], "volume": read_volume[0]}) + last_read_height = read_height[0] + cal_index += 1 + + if cal_values != CALIBRATION_TABLES[input_number] or self.force_send: + calibration_channel = "pond{}calibration".format(input_number) + calibration_string = json.dumps(cal_values).replace('"', "'") + self.sendtodbDev(1, calibration_channel, calibration_string, 0, 'plcpond') + CALIBRATION_TABLES[input_number] = cal_values + + def _check_watchdog(self): + """Check the watchdog and send to Meshify if changed or stale.""" + test_watchdog = self.multisensor_watchdog() + now = time.time() + self.watchdog_last_checked = now + if test_watchdog != self.watchdog or (now - self.watchdog_last_sent) > WATCHDOG_SEND_PERIOD: + self.sendtodbDev(1, 'watchdog', test_watchdog, 0, 'multisensor') + self.watchdog = test_watchdog + self.watchdog_last_sent = now + + + 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, 'multisensor') + self.public_ip_address = test_public_ip + + def multisensor_watchdog(self): + """Write a random integer to the PLC and then 1 seconds later check that it has been decremented by 1.""" + randval = randint(0, 32767) + write_tag(str(PLC_IP_ADDRESS), 'watchdog_INT', randval) + time.sleep(1) + watchdog_val = read_tag(str(PLC_IP_ADDRESS), 'watchdog_INT') + try: + return (randval - 1) == watchdog_val[0] + except (KeyError, TypeError): + return False + + def multisensor_sync(self, name, value): + """Sync all data from the driver.""" + self.force_send = True + self.sendtodb("log", "synced", 0) + return True + + def multisensor_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) + print("Result of multisensor_writeplctag(self, {}, {}) = {}".format(name, value, write_res)) + if write_res is None: + write_res = "Error writing to PLC..." + return write_res diff --git a/POCloud/python-driver/plcpond.py b/POCloud/python-driver/plcpond.py new file mode 100644 index 0000000..45ae354 --- /dev/null +++ b/POCloud/python-driver/plcpond.py @@ -0,0 +1,247 @@ +"""Driver for plcpond""" + +import threading +import sys +import json +import time +import logging +from random import randint +from device_base import deviceBase +from Channel import PLCChannel, read_tag, write_tag, TAG_DATAERROR_SLEEPTIME +from utilities import get_public_ip_address + + +_ = None + +# LOGGING SETUP +from logging.handlers import RotatingFileHandler + +log_formatter = logging.Formatter('%(asctime)s %(levelname)s %(funcName)s(%(lineno)d) %(message)s') +logFile = './plcpond.log' +my_handler = RotatingFileHandler(logFile, mode='a', maxBytes=500*1024, backupCount=2, encoding=None, delay=0) +my_handler.setFormatter(log_formatter) +my_handler.setLevel(logging.INFO) +logger = logging.getLogger('plcpond') +logger.setLevel(logging.INFO) +logger.addHandler(my_handler) + +console_out = logging.StreamHandler(sys.stdout) +console_out.setFormatter(log_formatter) +logger.addHandler(console_out) + +logger.info("plcpond startup") + +# GLOBAL VARIABLES +WATCHDOG_SEND_PERIOD = 3600 # Seconds, the longest amount of time before sending the watchdog status +PLC_IP_ADDRESS = "192.168.1.12" +CHANNELS = [ + PLCChannel(PLC_IP_ADDRESS, "cfgnumberofponds", "cfgNumberOfPonds", "REAL", 0.5, 600, map_=False, write_enabled=False, plc_type='Micro800'), + + PLCChannel(PLC_IP_ADDRESS, "pond1height", "pond1Height", "REAL", 0.5, 600, map_=False, write_enabled=False, plc_type='Micro800'), + PLCChannel(PLC_IP_ADDRESS, "pond2height", "pond2Height", "REAL", 0.5, 600, map_=False, write_enabled=False, plc_type='Micro800'), + PLCChannel(PLC_IP_ADDRESS, "pond3height", "pond3Height", "REAL", 0.5, 600, map_=False, write_enabled=False, plc_type='Micro800'), + PLCChannel(PLC_IP_ADDRESS, "pond4height", "pond4Height", "REAL", 0.5, 600, map_=False, write_enabled=False, plc_type='Micro800'), + + PLCChannel(PLC_IP_ADDRESS, "pond1volume", "pond1Volume", "REAL", 500.0, 600, map_=False, write_enabled=False, plc_type='Micro800'), + PLCChannel(PLC_IP_ADDRESS, "pond2volume", "pond2Volume", "REAL", 500.0, 600, map_=False, write_enabled=False, plc_type='Micro800'), + PLCChannel(PLC_IP_ADDRESS, "pond3volume", "pond3Volume", "REAL", 500.0, 600, map_=False, write_enabled=False, plc_type='Micro800'), + PLCChannel(PLC_IP_ADDRESS, "pond4volume", "pond4Volume", "REAL", 500.0, 600, map_=False, write_enabled=False, plc_type='Micro800'), + + PLCChannel(PLC_IP_ADDRESS, "pondvolumetotal", "pondVolumeTotal", "REAL", 500.0, 600, map_=False, write_enabled=False, plc_type='Micro800') +] + +CALIBRATION_TABLES = [[],[], [], [], []] # position 0 is a dummy table + + + + +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 = "2" + self.finished = threading.Event() + self.force_send = 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.""" + wait_sec = 30 + for i in range(0, wait_sec): + print("plcpond driver will start in {} seconds".format(wait_sec - i)) + time.sleep(1) + logger.info("BOOM! Starting plcpond driver...") + + public_ip_address = get_public_ip_address() + self.sendtodbDev(1, 'public_ip_address', public_ip_address, 0, 'plcpond') + watchdog = self.plcpond_watchdog() + self.sendtodbDev(1, 'watchdog', watchdog, 0, 'plcpond') + watchdog_send_timestamp = time.time() + + self.nodes["plcpond_0199"] = self + + send_loops = 0 + watchdog_loops = 0 + watchdog_check_after = 5000 + while True: + if self.force_send: + logger.warning("FORCE SEND: TRUE") + + for c in CHANNELS: + v = c.read() + if v is not None: # read returns None if it fails + if c.check(v, self.force_send): + self.sendtodbDev(1, c.mesh_name, c.value, 0, 'plcpond') + time.sleep(TAG_DATAERROR_SLEEPTIME) # sleep to allow Micro800 to handle ENET requests + + for pond_index in range(1, 5): + self.read_pond_calibration(pond_index) + + # print("plcpond driver still alive...") + if self.force_send: + if send_loops > 2: + logger.warning("Turning off force_send") + self.force_send = False + send_loops = 0 + else: + send_loops += 1 + + watchdog_loops += 1 + if watchdog_loops >= watchdog_check_after: + test_watchdog = self.plcpond_watchdog() + if not test_watchdog == watchdog or (time.time() - watchdog_send_timestamp) > WATCHDOG_SEND_PERIOD: + self.sendtodbDev(1, 'watchdog', test_watchdog, 0, 'plcpond') + watchdog = test_watchdog + + 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, 'plcpond') + public_ip_address = test_public_ip + watchdog_loops = 0 + + + def read_pond_calibration(self, pond_number): + """Read all calibration values for a specific pond.""" + last_read_height = -1.0 + cal_values = [] + cal_index = 1 + while last_read_height != 0 and cal_index <=10: + cal_tag_height = "pond{}CalibrationHeight[{}]".format(pond_number, cal_index) + cal_tag_volume = "pond{}CalibrationVolume[{}]".format(pond_number, cal_index) + read_height = read_tag(PLC_IP_ADDRESS, cal_tag_height, plc_type="Micro800") + time.sleep(2) + read_volume = read_tag(PLC_IP_ADDRESS, cal_tag_volume, plc_type="Micro800") + time.sleep(2) + if read_height and read_volume: + if read_height[0] > 0.0: + cal_values.append({"height": read_height[0], "volume": read_volume[0]}) + last_read_height = read_height[0] + cal_index += 1 + + if cal_values != CALIBRATION_TABLES[pond_number] or self.force_send: + calibration_channel = "pond{}calibration".format(pond_number) + calibration_string = json.dumps(cal_values).replace('"', "'") + self.sendtodbDev(1, calibration_channel, calibration_string, 0, 'plcpond') + CALIBRATION_TABLES[pond_number] = cal_values + + def plcpond_watchdog(self): + """Write a random integer to the PLC and then 1 seconds later check that it has been decremented by 1.""" + randval = randint(0, 32767) + write_tag(str(PLC_IP_ADDRESS), 'watchdog_INT', randval, plc_type="Micro800") + time.sleep(1) + watchdog_val = read_tag(str(PLC_IP_ADDRESS), 'watchdog_INT', plc_type="Micro800") + try: + return (randval - 1) == watchdog_val[0] + except (KeyError, TypeError): + return False + + def plcpond_sync(self, name, value): + """Sync all data from the driver.""" + self.force_send = True + # self.sendtodb("log", "synced", 0) + return True + + def plcpond_deletecalibrationpoint(self, name, value): + """Delete a calibration point from a calibration table""" + # {"pond": int, "point": int} + value = value.replace("'", '"') + parsed = json.loads(value) + try: + pond_number = int(parsed['pond']) + point_number = int(parsed['point']) + write_pond = write_tag(PLC_IP_ADDRESS, "inpPondNumber", pond_number, plc_type="Micro800") + time.sleep(2) + write_point = write_tag(PLC_IP_ADDRESS, "inpDeletePointIndex", point_number, plc_type="Micro800") + time.sleep(2) + if write_pond and write_point: + write_cmd = write_tag(PLC_IP_ADDRESS, "cmdDeleteCalibrationPoint", 1, plc_type="Micro800") + time.sleep(2) + if write_cmd: + read_val = read_tag(PLC_IP_ADDRESS, "deleteSuccess", plc_type="Micro800") + if read_val[0] == 1: + self.read_pond_calibration(pond_number) + return True + return "Wrote everything successfully, but delete didn't succeed (Check pond and point values)." + return "Didn't write delete command correctly." + return "Didn't write pond or point correctly." + except KeyError as e: + return "Couldn't parse input value: {} -- {}".format(value, e) + + def plcpond_cfgnumberofponds(self, name, value): + """Write the number of ponds to the plc.""" + value = int(value) + return write_tag(PLC_IP_ADDRESS, "cfgNumberOfPonds", value, plc_type="Micro800") + + def plcpond_addcalibrationpoint(self, name, value): + """Add a calibration point to the calibration table""" + # value = {"pond": int, "height": float, "volume": float} + value = value.replace("'", '"') + parsed = json.loads(value) + try: + # parse json values, throw an error if one is missing + pond_number = int(parsed['pond']) + height = float(parsed['height']) + volume = float(parsed['volume']) + # write values to the tags + write_pond = write_tag(PLC_IP_ADDRESS, "inpPondNumber", pond_number, plc_type="Micro800") + time.sleep(2) + write_height = write_tag(PLC_IP_ADDRESS, "inpPondHeight", height, plc_type="Micro800") + time.sleep(2) + write_volume= write_tag(PLC_IP_ADDRESS, "inpPondVolume", volume, plc_type="Micro800") + time.sleep(2) + if write_pond and write_height and write_volume: + write_cmd = write_tag(PLC_IP_ADDRESS, "cmdAddCalibrationPoint", 1, plc_type="Micro800") + time.sleep(2) + if write_cmd: + read_val = read_tag(PLC_IP_ADDRESS, "addSuccess", plc_type="Micro800") + if read_val[0] == 1: + self.read_pond_calibration(pond_number) + return True + return "Wrote everything successfully, but delete didn't succeed (Check pond and point values)." + return "Didn't write delete command correctly." + return "Didn't write pond or point correctly." + except KeyError as e: + return "Couldn't parse input value: {} -- {}".format(value, e) + + def plcpond_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) + logger.info("Result of plcpond_writeplctag(self, {}, {}) = {}".format(name, value, w)) + if w is None: + w = "Error writing to PLC..." + return w diff --git a/POCloud/python-driver/test.py b/POCloud/python-driver/test.py new file mode 100644 index 0000000..935f17a --- /dev/null +++ b/POCloud/python-driver/test.py @@ -0,0 +1,26 @@ +import json +import time +from Channel import read_tag, write_tag + +PLC_IP_ADDRESS = "192.168.1.12" + +def read_pond_calibration(pond_number): + """Read all calibration values for a specific pond.""" + last_read_height = -1.0 + cal_values = [] + cal_index = 1 + while last_read_height != 0 and cal_index <=10: + cal_tag_height = "pond{}CalibrationHeight[{}]".format(pond_number, cal_index) + cal_tag_volume = "pond{}CalibrationVolume[{}]".format(pond_number, cal_index) + print(cal_tag_height, cal_tag_volume) + read_height = read_tag(PLC_IP_ADDRESS, cal_tag_height, plc_type="Micro800") + time.sleep(2) + read_volume = read_tag(PLC_IP_ADDRESS, cal_tag_volume, plc_type="Micro800") + time.sleep(2) + print(read_height, read_volume) + if read_height and read_volume: + if read_height[0] > 0.0: + cal_values.append({"height": read_height[0], "volume": read_volume[0]}) + last_read_height = read_height[0] + cal_index += 1 + return cal_values diff --git a/POCloud/python-driver/utilities.py b/POCloud/python-driver/utilities.py new file mode 100644 index 0000000..58c7ab0 --- /dev/null +++ b/POCloud/python-driver/utilities.py @@ -0,0 +1,51 @@ +"""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.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") + else: + return float("NaN") + else: + 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 = struct.unpack('>f', mypack) + print("[{}, {}] >> {}".format(int1, int2, 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