Added Folders

Add all the driver folders
This commit is contained in:
2019-12-13 12:15:30 -06:00
parent 7ea92c19f6
commit 632dcdb3e8
226 changed files with 54771 additions and 0 deletions

53
abbflow/abbflow.py Normal file
View File

@@ -0,0 +1,53 @@
"""Driver for connecting ABB Flowmeter to Meshify."""
import threading
import time
from device_base import deviceBase
from utilities import get_public_ip_address
WAIT_FOR_CONNECTION_SECONDS = 60
IP_CHECK_PERIOD = 60
class start(threading.Thread, deviceBase):
"""Start class required for driver."""
def __init__(self, name=None, number=None, mac=None, Q=None, mcu=None,
companyId=None, offset=None, mqtt=None, Nodes=None):
"""Initalize 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 = "8"
self.finished = threading.Event()
self.public_ip_address = ""
self.public_ip_address_last_checked = 0
threading.Thread.start(self)
# this is a required function for all drivers
# its goal is to upload some piece of data
# about your device so it can be seen on the web
def register(self):
"""Register the driver."""
self.channels["status"]["last_value"] = ""
def run(self):
"""Run the driver."""
for i in range(0, WAIT_FOR_CONNECTION_SECONDS):
print("abbflow driver will start in {} seconds".format(WAIT_FOR_CONNECTION_SECONDS - i))
time.sleep(1)
while True:
if (time.time() - self.public_ip_address_last_checked) > IP_CHECK_PERIOD:
self._check_ip_address()
def _check_ip_address(self):
"""Check the public IP address and send to Meshify if changed."""
print("Checking IP Address...")
self.public_ip_address_last_checked = time.time()
test_public_ip = get_public_ip_address()
print("Got {} for IP Address".format(test_public_ip))
if not test_public_ip == self.public_ip_address:
self.sendtodbDev(1, 'public_ip_address', test_public_ip, 0, 'abbflow')
self.public_ip_address = test_public_ip

11
abbflow/config.txt Normal file
View File

@@ -0,0 +1,11 @@
{
"driverFileName":"abbflow.py",
"deviceName":"abbflow",
"driverId":"0110",
"releaseVersion":"8",
"files": {
"file1":"abbflow.py",
"file2":"modbusMap.p" }
}

893
abbflow/modbusMap.p Normal file
View File

@@ -0,0 +1,893 @@
(dp0
S'1'
p1
(dp2
S'c'
p3
VM1-232
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
VVolume Flow
p18
sVct
p19
Vnumber
p20
sVle
p21
V32
p22
sVgrp
p23
V3600
p24
sVla
p25
F1301.77
sVchn
p26
Vvolume_flow
p27
sVun
p28
V1
p29
sVdn
p30
Vabbflow
p31
sVda
p32
g29
sVlrt
p33
F1502726350.0681692
sVa
p34
V4003
p35
sVc
p36
V20.0
p37
sVmisc_u
p38
VMCF/Day
p39
sVf
p40
V4
p41
sVmrt
p42
V60
p43
sVm
p44
Vnone
p45
sVm1ch
p46
g11
sVmv
p47
V0
p48
sVs
p49
VOn
p50
sVr
p51
V0-5000
p52
sVt
p53
Vfloat
p54
sVvm
p55
NssV2-3
p56
(dp57
Vah
p58
g14
sVbytary
p59
NsVal
p60
g48
sVvn
p61
VToday Volume
p62
sVct
p63
Vnumber
p64
sVle
p65
V32
p66
sVgrp
p67
V3600
p68
sVla
p69
F214.742
sVchn
p70
Vtoday_volume
p71
sVun
p72
g29
sVdn
p73
Vabbflow
p74
sVda
p75
g29
sVlrt
p76
F1502726379.1855019
sg51
V0-1000
p77
sg34
V4005
p78
sg36
V10.0
p79
sVmisc_u
p80
VMCF
p81
sg40
g41
sVmrt
p82
V60
p83
sg44
Vnone
p84
sVm1ch
p85
g56
sg49
VOn
p86
sVmv
p87
g48
sg53
Vfloat
p88
sVvm
p89
NssV2-1
p90
(dp91
Vah
p92
g14
sVbytary
p93
NsVal
p94
g14
sVvn
p95
VBattery Voltage
p96
sVct
p97
Vnumber
p98
sVle
p99
V32
p100
sVgrp
p101
V3600
p102
sVla
p103
F12.477
sVchn
p104
Vbattery_voltage
p105
sg51
V0-50
p106
sVdn
p107
Vabbflow
p108
sVda
p109
g29
sVlrt
p110
F1502725891.0710198
sg34
V4001
p111
sg36
V5
p112
sVmisc_u
p113
VV
p114
sg40
g41
sVmrt
p115
V60
p116
sg44
Vnone
p117
sVm1ch
p118
g90
sVmv
p119
g48
sg49
VOn
p120
sVun
p121
g29
sg53
Vfloat
p122
sVvm
p123
NssV2-6
p124
(dp125
Vah
p126
g14
sVbytary
p127
NsVal
p128
g48
sVvn
p129
VLast Calc. Period Volume
p130
sVct
p131
Vnumber
p132
sVle
p133
V32
p134
sVgrp
p135
V86400
p136
sVla
p137
F14.691
sVchn
p138
Vlast_calculation_period_volume
p139
sVun
p140
g29
sVdn
p141
Vabbflow
p142
sVda
p143
g29
sVlrt
p144
F1502682560.7307109
sg34
V4011
p145
sg36
V10
p146
sVmisc_u
p147
VSCF
p148
sg40
g41
sVmrt
p149
V60
p150
sg44
Vnone
p151
sVm1ch
p152
g124
sVmv
p153
g48
sg49
VOn
p154
sg51
V0-1000000
p155
sg53
Vfloat
p156
sVvm
p157
NssV2-7
p158
(dp159
Vah
p160
g14
sVbytary
p161
NsVal
p162
g48
sVvn
p163
VDifferential Pressure
p164
sVct
p165
Vnumber
p166
sVle
p167
V32
p168
sVgrp
p169
V3600
p170
sVla
p171
F3.095
sVchn
p172
Vdifferential_pressure
p173
sVun
p174
g29
sVdn
p175
Vabbflow
p176
sVda
p177
g29
sVlrt
p178
F1502725891.811299
sg51
V0-500
p179
sg34
V4013
p180
sg36
g112
sVmisc_u
p181
VInH20
p182
sg40
g41
sVmrt
p183
V60
p184
sg44
Vnone
p185
sVm1ch
p186
g158
sg49
VOn
p187
sVmv
p188
g48
sg53
Vfloat
p189
sVvm
p190
NssV2-4
p191
(dp192
Vah
p193
g14
sVbytary
p194
NsVal
p195
V0
p196
sVvn
p197
VYesterday Volume
p198
sVct
p199
Vnumber
p200
sVle
p201
V32
p202
sVgrp
p203
V21600
p204
sVla
p205
F640.448
sVchn
p206
Vyesterday_volume
p207
sVun
p208
V1
p209
sVdn
p210
Vabbflow
p211
sVda
p212
g209
sVlrt
p213
F1502517692.3270172
sVa
p214
V4007
p215
sVc
p216
V10.0
p217
sVmisc_u
p218
VMCF
p219
sVf
p220
V4
p221
sVmrt
p222
V60
p223
sVm
p224
Vnone
p225
sVm1ch
p226
g191
sVmv
p227
g196
sVs
p228
VOn
p229
sVr
p230
V0-5000
p231
sVt
p232
Vfloat
p233
sVvm
p234
NssV2-5
p235
(dp236
Vah
p237
g14
sVbytary
p238
NsVal
p239
g48
sVvn
p240
VAccumulated Volume
p241
sVct
p242
Vnumber
p243
sVle
p244
V32
p245
sVgrp
p246
V3600
p247
sVla
p248
F23666.424
sVchn
p249
Vaccumulated_volume
p250
sVun
p251
g29
sVdn
p252
Vabbflow
p253
sVda
p254
g29
sVlrt
p255
F1502726134.0120882
sg34
V4009
p256
sg36
V10
p257
sVmisc_u
p258
VMCF
p259
sg40
g41
sVmrt
p260
V60
p261
sg44
Vnone
p262
sVm1ch
p263
g235
sVmv
p264
g48
sg49
VOn
p265
sg51
V0-1000000
p266
sg53
Vfloat
p267
sVvm
p268
NssV2-8
p269
(dp270
Vah
p271
g14
sVbytary
p272
NsVal
p273
g48
sVvn
p274
VStatic Pressure
p275
sVct
p276
Vnumber
p277
sVle
p278
V32
p279
sVgrp
p280
V3600
p281
sVla
p282
F27.048
sVchn
p283
Vstatic_pressure
p284
sg51
V0-250
p285
sVun
p286
g29
sVdn
p287
Vabbflow
p288
sVda
p289
g29
sVlrt
p290
F1502725884.2453662
sg34
V4015
p291
sg36
g112
sVmisc_u
p292
VPSIA
p293
sg40
g41
sVmrt
p294
V60
p295
sg44
Vnone
p296
sVm1ch
p297
g269
sg49
VOn
p298
sVmv
p299
g48
sg53
Vfloat
p300
sVvm
p301
NssV2-9
p302
(dp303
Vah
p304
g14
sVbytary
p305
NsVal
p306
g48
sVvn
p307
VTemperature
p308
sVct
p309
Vnumber
p310
sVle
p311
V32
p312
sVgrp
p313
V3600
p314
sVla
p315
F85.208
sVchn
p316
Vtemperature
p317
sg51
V0-300
p318
sVun
p319
g29
sVdn
p320
Vabbflow
p321
sVda
p322
g29
sVlrt
p323
F1502724763.458232
sg34
V4017
p324
sg36
g112
sVmisc_u
p325
Vdeg F
p326
sg40
g41
sVmrt
p327
V60
p328
sg44
Vnone
p329
sVm1ch
p330
g302
sg49
VOn
p331
sVmv
p332
g48
sg53
Vfloat
p333
sVvm
p334
NssV2-10
p335
(dp336
Vah
p337
g14
sVbytary
p338
NsVal
p339
g14
sVvn
p340
VCharger Voltage
p341
sVct
p342
Vnumber
p343
sVle
p344
V32
p345
sVgrp
p346
V3600
p347
sVla
p348
F13.356
sVchn
p349
Vcharger_voltage
p350
sg51
V0-50
p351
sVdn
p352
Vabbflow
p353
sVda
p354
g29
sVlrt
p355
F1502725893.760519
sg34
V4019
p356
sg36
g112
sVmisc_u
p357
g114
sg40
g41
sVmrt
p358
V60
p359
sg44
Vnone
p360
sVm1ch
p361
g335
sVmv
p362
g48
sg49
VOn
p363
sVun
p364
g29
sg53
Vfloat
p365
sVvm
p366
NssssS'f'
p367
VOff
p368
sS'p'
p369
g14
sS's'
p370
g209
ssS'2'
p371
(dp372
g3
VM1-232
p373
sg5
V9600
p374
sg7
(dp375
sg367
VOff
p376
sg369
g14
sg370
g209
ss.

49
abbflow/utilities.py Normal file
View File

@@ -0,0 +1,49 @@
"""Utility functions for the driver."""
import socket
import struct
def get_public_ip_address():
"""Find the public IP Address of the host device."""
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(("8.8.8.8", 80))
ip_address = sock.getsockname()[0]
sock.close()
return ip_address
def int_to_float16(int_to_convert):
"""Convert integer into float16 representation."""
bin_rep = ('0' * 16 + '{0:b}'.format(int_to_convert))[-16:]
sign = 1.0
if int(bin_rep[0]) == 1:
sign = -1.0
exponent = float(int(bin_rep[1:6], 2))
fraction = float(int(bin_rep[6:17], 2))
if exponent == float(0b00000):
return sign * 2 ** -14 * fraction / (2.0 ** 10.0)
elif exponent == float(0b11111):
if fraction == 0:
return sign * float("inf")
return float("NaN")
frac_part = 1.0 + fraction / (2.0 ** 10.0)
return sign * (2 ** (exponent - 15)) * frac_part
def ints_to_float(int1, int2):
"""Convert 2 registers into a floating point number."""
mypack = struct.pack('>HH', int1, int2)
f_unpacked = struct.unpack('>f', mypack)
print("[{}, {}] >> {}".format(int1, int2, f_unpacked[0]))
return f_unpacked[0]
def degf_to_degc(temp_f):
"""Convert deg F to deg C."""
return (temp_f - 32.0) * (5.0/9.0)
def degc_to_degf(temp_c):
"""Convert deg C to deg F."""
return temp_c * 1.8 + 32.0

149
advvfdipp/Channel.py Normal file
View File

@@ -0,0 +1,149 @@
"""Define Meshify channel class."""
from pycomm.ab_comm.clx import Driver as ClxDriver
from pycomm.cip.cip_base import CommError, DataError
import time
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

116
advvfdipp/advvfdipp.py Normal file
View File

@@ -0,0 +1,116 @@
"""Max Water System driver code."""
import threading
import sys
import logging
import time
from random import randint
from Channel import write_tag, read_tag
from device_base import deviceBase
from utilities import get_public_ip_address
import json
# LOGGING SETUP
from logging.handlers import RotatingFileHandler
log_formatter = logging.Formatter('%(asctime)s %(levelname)s %(funcName)s(%(lineno)d) %(message)s')
logFile = './advvfdipp.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('advvfdipp')
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("advvfdipp startup")
PLC_IP_ADDRESS = "192.168.1.10"
WATCHDOG_SEND_PERIOD = 3600 # seconds
class start(threading.Thread, deviceBase):
"""Driver class."""
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 = "9"
self.finished = threading.Event()
threading.Thread.start(self)
def register(self):
"""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."""
self.channels["status"]["last_value"] = ""
def run(self):
"""Run the driver."""
wait_sec = 30
for i in range(0, wait_sec):
print("advvfdipp driver will start in {} seconds".format(wait_sec - i))
time.sleep(1)
logger.info("BOOM! Starting advvfdipp driver...")
self.nodes["advvfdipp_0199"] = self
public_ip_address = get_public_ip_address()
self.sendtodbDev(1, 'public_ip_address', public_ip_address, 0, 'advvfdipp')
watchdog = self.advvfdipp_watchdog()
self.sendtodbDev(1, 'watchdog', watchdog, 0, 'advvfdipp')
watchdog_send_timestamp = time.time()
watchdog_loops = 0
watchdog_check_after = 5000
while True:
self.nodes["advvfdipp_0199"] = self
watchdog_loops += 1
if (watchdog_loops >= watchdog_check_after):
test_watchdog = self.advvfdipp_watchdog()
if not test_watchdog == watchdog or (time.time() - watchdog_send_timestamp) > WATCHDOG_SEND_PERIOD:
self.sendtodbDev(1, 'watchdog', test_watchdog, 0, 'advvfdipp')
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, 'advvfdipp')
public_ip_address = test_public_ip
watchdog_loops = 0
time.sleep(15)
def advvfdipp_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 advvfdipp_sync(self, name, value):
"""Sync all data from the driver."""
self.forceSend = True
# self.sendtodb("log", "synced", 0)
return True
def advvfdipp_writeplctag(self, name, value):
"""Write a value to the PLC."""
try:
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.warning("Result of advvfdipp_writeplctag(self, {}, {}) = {}".format(name, value, w))
if w is None:
w = "Error writing to PLC..."
return w
except Exception as e:
logger.warning("GOT EXCEPTION in advvfdipp_writeplctag(self, {}, {}) => {}".format(name, value, e))
return e

View File

@@ -0,0 +1,298 @@
"""Define Meshify channel class."""
import time
import urllib
from pycomm.ab_comm.clx import Driver as ClxDriver
from pycomm.cip.cip_base import CommError, DataError
from file_logger import filelogger as log
TAG_DATAERROR_SLEEPTIME = 5
def binarray(intval):
"""Split an integer into its bits."""
bin_string = '{0:08b}'.format(intval)
bin_arr = [i for i in bin_string]
bin_arr.reverse()
return bin_arr
def read_tag(addr, tag, plc_type="CLX"):
"""Read a tag from the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
try:
if clx.open(addr, direct_connection=direct):
try:
val = clx.read_tag(tag)
clx.close()
return val
except DataError as err:
clx.close()
time.sleep(TAG_DATAERROR_SLEEPTIME)
log.error("Data Error during readTag({}, {}): {}".format(addr, tag, err))
except CommError:
# err = c.get_status()
clx.close()
log.error("Could not connect during readTag({}, {})".format(addr, tag))
except AttributeError as err:
clx.close()
log.error("AttributeError during readTag({}, {}): \n{}".format(addr, tag, err))
clx.close()
return False
def read_array(addr, tag, start, end, plc_type="CLX"):
"""Read an array from the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
if clx.open(addr, direct_connection=direct):
arr_vals = []
try:
for i in range(start, end):
tag_w_index = tag + "[{}]".format(i)
val = clx.read_tag(tag_w_index)
arr_vals.append(round(val[0], 4))
if arr_vals:
clx.close()
return arr_vals
else:
log.error("No length for {}".format(addr))
clx.close()
return False
except Exception:
log.error("Error during readArray({}, {}, {}, {})".format(addr, tag, start, end))
err = clx.get_status()
clx.close()
log.error(err)
clx.close()
def write_tag(addr, tag, val, plc_type="CLX"):
"""Write a tag value to the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
try:
if clx.open(addr, direct_connection=direct):
try:
initial_val = clx.read_tag(tag)
write_status = clx.write_tag(tag, val, initial_val[1])
clx.close()
return write_status
except DataError as err:
clx_err = clx.get_status()
clx.close()
log.error("--\nDataError during writeTag({}, {}, {}, plc_type={}) -- {}\n{}\n".format(addr, tag, val, plc_type, err, clx_err))
except CommError as err:
clx_err = clx.get_status()
log.error("--\nCommError during write_tag({}, {}, {}, plc_type={})\n{}\n--".format(addr, tag, val, plc_type, err))
clx.close()
return False
class Channel(object):
"""Holds the configuration for a Meshify channel."""
def __init__(self, mesh_name, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False):
"""Initialize the channel."""
self.mesh_name = mesh_name
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
def __str__(self):
"""Create a string for the channel."""
return "{}\nvalue: {}, last_send_time: {}".format(self.mesh_name, self.value, self.last_send_time)
def check(self, new_value, force_send=False):
"""Check to see if the new_value needs to be stored."""
send_needed = False
send_reason = ""
if self.data_type == 'BOOL' or self.data_type == 'STRING':
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif self.value != new_value:
if self.map_:
if not self.value == self.map_[new_value]:
send_needed = True
send_reason = "value change"
else:
send_needed = True
send_reason = "value change"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
else:
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif abs(self.value - new_value) > self.chg_threshold:
send_needed = True
send_reason = "change threshold"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
if send_needed:
self.last_value = self.value
if self.map_:
try:
self.value = self.map_[new_value]
except KeyError:
log.error("Cannot find a map value for {} in {} for {}".format(new_value, self.map_, self.mesh_name))
self.value = new_value
else:
self.value = new_value
self.last_send_time = time.time()
log.info("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason))
return send_needed
def read(self):
"""Read the value."""
pass
def identity(sent):
"""Return exactly what was sent to it."""
return sent
class ModbusChannel(Channel):
"""Modbus channel object."""
def __init__(self, mesh_name, register_number, data_type, chg_threshold, guarantee_sec, channel_size=1, map_=False, write_enabled=False, transform_fn=identity):
"""Initialize the channel."""
super(ModbusChannel, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.mesh_name = mesh_name
self.register_number = register_number
self.channel_size = channel_size
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
self.transform_fn = transform_fn
def read(self, mbsvalue):
"""Return the transformed read value."""
return self.transform_fn(mbsvalue)
class PLCChannel(Channel):
"""PLC Channel Object."""
def __init__(self, ip, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False, plc_type='CLX'):
"""Initialize the channel."""
super(PLCChannel, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.plc_ip = ip
self.mesh_name = mesh_name
self.plc_tag = plc_tag
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
self.plc_type = plc_type
def read(self):
"""Read the value."""
plc_value = None
if self.plc_tag and self.plc_ip:
read_value = read_tag(self.plc_ip, self.plc_tag, plc_type=self.plc_type)
if read_value:
plc_value = read_value[0]
return plc_value
class BoolArrayChannels(Channel):
"""Hold the configuration for a set of boolean array channels."""
def __init__(self, ip, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False):
"""Initialize the channel."""
super(BoolArrayChannels, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.plc_ip = ip
self.mesh_name = mesh_name
self.plc_tag = plc_tag
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
def compare_values(self, new_val_dict):
"""Compare new values to old values to see if the values need storing."""
send = False
for idx in new_val_dict:
try:
if new_val_dict[idx] != self.last_value[idx]:
send = True
except KeyError:
log.error("Key Error in self.compare_values for index {}".format(idx))
send = True
return send
def read(self, force_send=False):
"""Read the value and check to see if needs to be stored."""
send_needed = False
send_reason = ""
if self.plc_tag:
val = read_tag(self.plc_ip, self.plc_tag)
if val:
bool_arr = binarray(val[0])
new_val = {}
for idx in self.map_:
try:
new_val[self.map_[idx]] = bool_arr[idx]
except KeyError:
log.error("Not able to get value for index {}".format(idx))
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif self.compare_values(new_val):
send_needed = True
send_reason = "value change"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
if send_needed:
self.value = new_val
self.last_value = self.value
self.last_send_time = time.time()
log.info("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason))
return send_needed

View File

@@ -0,0 +1,49 @@
from Channel import PLCChannel, ModbusChannel
from advvfdipp import PLC_IP_ADDRESS
tags = [
PLCChannel(PLC_IP_ADDRESS, "flowrate","val_Flowmeter","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "fluidlevel","val_FluidLevel","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "intakepressure","val_IntakePressure","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "intaketemperature","val_IntakeTemperature","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "tubingpressure","val_TubingPressure","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "pidcontrolmode","sts_PID_Control","BOOL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "wellstatus","Device_Status_INT","INT", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "vfdfrequency","VFD_SpeedFdbk","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "flowtotal","Flow_Total[0]","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "energytotal","Energy_Total[0]","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "vfdcurrent","VFD_OutCurrent","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "downholesensorstatus","Downhole_Sensor_Status_INT","INT", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "fluidspecificgravity","cfg_FluidSpecificGravity","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "flowtotalyesterday","Flow_Total[1]","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "energytotalyesterday","Energy_Total[1]","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "alarmflowrate","alarm_Flowmeter","BOOL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "alarmintakepressure","alarm_IntakePressure","BOOL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "alarmintaketemperature","alarm_IntakeTemperature","BOOL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "alarmtubingpressure","alarm_TubingPressure","BOOL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "alarmvfd","alarm_VFD","BOOL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "alarmlockout","alarm_Lockout","BOOL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "runpermissive","Run_Permissive_INT","INT", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "startpermissive","Start_Permissive_INT","INT", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "startcommand","cmd_Start","BOOL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "stopcommand","cmd_Stop","BOOL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "flowsetpoint","cfg_PID_FlowSP","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "fluidlevelsetpoint","cfg_PID_FluidLevelSP","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "manualfrequencysetpoint","cfg_PID_ManualSP","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "tubingpressuresetpoint","cfg_PID_TubingPressureSP","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "alarmfluidlevel","alarm_FluidLevel","BOOL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "pressureshutdownlimit","AIn_IntakePressure.Val_LoLim","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "pressurestartuplimit","AIn_IntakePressure.Val_HiLim","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "temperatureshutdownlimit","AIn_IntakeTemperature.Val_HiLim","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "temperaturestartuplimit","AIn_IntakeTemperature.Val_LoLim","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "sensorheight","cfg_DHSensorDistToIntake","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "last_vfd_fault_code","PowerFlex755.Val_LastFaultCode","INT", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "vfd_fault","sts_CurrentVFDFaultCode","INT", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "controllerfault_io","ControllerFault_IO","BOOL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "controllerfault_program","ControllerFault_Program","BOOL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "minvfdfrequency","PowerFlex755.Cfg_MinSpdRef","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "maxvfdfrequency","PowerFlex755.Cfg_MaxSpdRef","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "hartnettotal","in_HART_Flowmeter_Net","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "hartfwdtotal","in_HART_Flowmeter_Fwd","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "hartrevtotal","in_HART_Flowmeter_Rev","REAL", 1, 3600, plc_type="CLX")
]

View File

@@ -0,0 +1,200 @@
"""Driver for advvfdipp"""
import threading
import json
import time
from random import randint
# PERSISTENCE FILE
import persistence
PERSIST = persistence.load("extra_data.json")
if not PERSIST:
PERSIST = {'ip_address': '192.168.1.10', 'download_pycomm': True}
persistence.store(PERSIST, "extra_data.json")
PLC_IP_ADDRESS = PERSIST['ip_address']
from device_base import deviceBase
from Channel import PLCChannel, ModbusChannel,read_tag, write_tag, TAG_DATAERROR_SLEEPTIME
from utilities import get_public_ip_address, get_additional_tags, convert_int
from file_logger import filelogger as log
from Tags import tags
import os
import urllib
path = "/root/python_firmware/drivers/additional_tags.py"
f = open(path, "a+")
f.seek(0)
if os.stat(path).st_size == 0:
f.write("from Channel import PLCChannel, ModbusChannel\n")
f.write("from advvfdipp import PLC_IP_ADDRESS\n")
f.write("additional_tags = []")
f.close()
from additional_tags import additional_tags
_ = None
log.info("advvfdipp startup")
# GLOBAL VARIABLES
WAIT_FOR_CONNECTION_SECONDS = 60
IP_CHECK_PERIOD = 60
WATCHDOG_ENABLE = False
WATCHDOG_CHECK_PERIOD = 60
WATCHDOG_SEND_PERIOD = 3600 # Seconds, the longest amount of time before sending the watchdog status
CHANNELS = tags + additional_tags
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 = "13"
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
self.ping_counter = 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("advvfdipp driver will start in {} seconds".format(WAIT_FOR_CONNECTION_SECONDS - i))
time.sleep(1)
log.info("BOOM! Starting advvfdipp driver...")
if PERSIST['download_pycomm']:
try:
urllib.urlretrieve('http://s3.amazonaws.com/pocloud-drivers/pycomm/clx.py', '/root/python_firmware/pycomm/ab_comm/clx.py')
urllib.urlretrieve('http://s3.amazonaws.com/pocloud-drivers/pycomm/cip_base.py', '/root/python_firmware/pycomm/cip/cip_base.py')
PERSIST['download_pycomm'] = False
persistence.store(PERSIST, "extra_data.json")
except Exception as e:
print("Could not download latest pycomm update: {}".format(e))
#self._check_watchdog()
self._check_ip_address()
self.nodes["advvfdipp_0199"] = self
send_loops = 0
convert_list = ["Device_Status_INT","sts_PID_Control","Downhole_Sensor_Status_INT","alarm_Flowmeter","alarm_IntakePressure",
"alarm_IntakeTemperature","alarm_TubingPressure","alarm_VFD","alarm_Lockout","alarm_FluidLevel","Run_Permissive_INT",
"Start_Permissive_INT","PowerFlex755.Val_LastFaultCode","sts_CurrentVFDFaultCode"]
while True:
now = time.time()
if self.force_send:
log.warning("FORCE SEND: TRUE")
for chan in CHANNELS:
try:
val = chan.read()
if "hart" in chan.mesh_name and val == None:
val = 0.0
if chan.check(val, self.force_send):
if chan.plc_tag in convert_list:
converted_value = convert_int(chan.plc_tag, val)
self.sendtodbDev(1, chan.mesh_name, converted_value, 0, 'advvfdipp')
else:
self.sendtodbDev(1, chan.mesh_name, chan.value, 0, 'advvfdipp')
#time.sleep(TAG_DATAERROR_SLEEPTIME) # sleep to allow Micro800 to handle ENET requests
except Exception as e:
log.info("Error: {}".format(e))
time.sleep(30) #sleep for 30 seconds after a full poll
# print("advvfdipp driver still alive...")
if self.force_send:
if send_loops > 2:
log.warning("Turning off force_send")
self.force_send = False
send_loops = 0
else:
send_loops += 1
if WATCHDOG_ENABLE:
if (now - self.watchdog_last_checked) > WATCHDOG_CHECK_PERIOD:
self._check_watchdog()
if (now - self.public_ip_address_last_checked) > IP_CHECK_PERIOD:
self._check_ip_address()
def _check_watchdog(self):
"""Check the watchdog and send to Meshify if changed or stale."""
test_watchdog = self.advvfdipp_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, 'advvfdipp')
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, 'advvfdipp')
self.public_ip_address = test_public_ip
hostname = "google.com"
response = os.system("ping -c 1 " + hostname)
#and then check the response...
if response == 0:
print hostname, 'is up!'
self.ping_counter = 0
else:
print hostname, 'is down!'
self.ping_counter += 1
if self.ping_counter >= 3:
log.info("Rebooting because no internet detected")
os.system('reboot')
def advvfdipp_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="CLX")
time.sleep(1)
watchdog_val = read_tag(str(PLC_IP_ADDRESS), 'watchdog_INT', plc_type="CLX")
try:
return (randval - 1) == watchdog_val[0]
except (KeyError, TypeError):
return False
def advvfdipp_sync(self, name, value):
"""Sync all data from the driver."""
self.force_send = True
# self.sendtodb("log", "synced", 0)
return True
def advvfdipp_writeplctag(self, name, value):
"""Write a value to the PLC."""
new_val = json.loads(str(value).replace("'", '"'))
tag_n = str(new_val['tag']) # "cmd_Start"
val_n = new_val['val']
write_res = write_tag(str(PLC_IP_ADDRESS), tag_n, val_n, plc_type="CLX")
print("Result of advvfdipp_writeplctag(self, {}, {}) = {}".format(name, value, write_res))
if write_res is None:
write_res = "Error writing to PLC..."
return write_res

View File

@@ -0,0 +1,14 @@
{
"files": {
"file3": "file_logger.py",
"file2": "Channel.py",
"file1": "advvfdipp.py",
"file6": "persistence.py",
"file5": "utilities.py",
"file4": "Tags.py"
},
"deviceName": "advvfdipp",
"releaseVersion": "13",
"driverFileName": "advvfdipp.py",
"driverId": "0100"
}

View File

@@ -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)))

View File

@@ -0,0 +1,14 @@
{
"name": "advvfdipp",
"driverFilename": "advvfdipp.py",
"driverId": "0000",
"additionalDriverFiles": [
"utilities.py",
"persistence.py",
"Channel.py",
"logger.py",
"Tags.py"
],
"version": 1,
"s3BucketName": "advvfdipp"
}

View File

@@ -0,0 +1,18 @@
"""Logging setup for advvfdipp"""
import logging
from logging.handlers import RotatingFileHandler
import sys
log_formatter = logging.Formatter('%(asctime)s %(levelname)s %(funcName)s(%(lineno)d) %(message)s')
log_file = './advvfdipp.log'
my_handler = RotatingFileHandler(log_file, mode='a', maxBytes=500*1024,
backupCount=2, encoding=None, delay=0)
my_handler.setFormatter(log_formatter)
my_handler.setLevel(logging.INFO)
filelogger = logging.getLogger('advvfdipp')
filelogger.setLevel(logging.INFO)
filelogger.addHandler(my_handler)
console_out = logging.StreamHandler(sys.stdout)
console_out.setFormatter(log_formatter)
filelogger.addHandler(console_out)

View File

@@ -0,0 +1,21 @@
"""Data persistance functions."""
# if more advanced persistence is needed, use a sqlite database
import json
def load(filename="persist.json"):
"""Load persisted settings from the specified file."""
try:
with open(filename, 'r') as persist_file:
return json.load(persist_file)
except Exception:
return False
def store(persist_obj, filename="persist.json"):
"""Store the persisting settings into the specified file."""
try:
with open(filename, 'w') as persist_file:
return json.dump(persist_obj, persist_file, indent=4)
except Exception:
return False

View File

@@ -0,0 +1,226 @@
"""Utility functions for the driver."""
import socket
import struct
from Channel import PLCChannel
def get_public_ip_address():
"""Find the public IP Address of the host device."""
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(("8.8.8.8", 80))
ip_address = sock.getsockname()[0]
sock.close()
return ip_address
def int_to_float16(int_to_convert):
"""Convert integer into float16 representation."""
bin_rep = ('0' * 16 + '{0:b}'.format(int_to_convert))[-16:]
sign = 1.0
if int(bin_rep[0]) == 1:
sign = -1.0
exponent = float(int(bin_rep[1:6], 2))
fraction = float(int(bin_rep[6:17], 2))
if exponent == float(0b00000):
return sign * 2 ** -14 * fraction / (2.0 ** 10.0)
elif exponent == float(0b11111):
if fraction == 0:
return sign * float("inf")
return float("NaN")
frac_part = 1.0 + fraction / (2.0 ** 10.0)
return sign * (2 ** (exponent - 15)) * frac_part
def ints_to_float(int1, int2):
"""Convert 2 registers into a floating point number."""
mypack = struct.pack('>HH', int1, int2)
f_unpacked = struct.unpack('>f', mypack)
print("[{}, {}] >> {}".format(int1, int2, f_unpacked[0]))
return f_unpacked[0]
def degf_to_degc(temp_f):
"""Convert deg F to deg C."""
return (temp_f - 32.0) * (5.0/9.0)
def degc_to_degf(temp_c):
"""Convert deg C to deg F."""
return temp_c * 1.8 + 32.0
def get_additional_tags(tag_dict):
tags_array = tag_dict['additional_tags']
channel_array = []
for x in tags_array:
try:
print "Making channel {}".format(x)
channel_array.append(PLCChannel(tag_dict['ip_address'], x['mesh_name'], x['plc_tag'], x['data_type'],x['chg_threshold'],x['guarantee_sec'],plc_type='CLX'))
except Exception:
print "Nothing to write or bad key"
return channel_array
def convert_int(plc_tag, value):
well_status_codes = {
0: "Running",
1: "Pumped Off",
2: "Alarmed",
3: "Locked Out",
4: "Stopped"
}
pid_control_codes = {
0: "Flow",
1: "Fluid Level",
2: "Tubing Pressure",
3: "Manual"
}
downhole_codes = {
0: "OK",
1: "Connecting",
2: "Open Circuit",
3: "Shorted",
4: "Cannot Decode"
}
permissive_codes = {
0: "OK",
1: "Flow",
2: "Intake Pressure",
3: "Intake Temperature",
4: "Tubing Pressure",
5: "VFD",
6: "Fluid Level",
7: "Min. Downtime"
}
alarm_codes = {
0: "OK",
1: "Alarm"
}
alarm_vfd_codes = {
0: "OK",
1: "Locked Out"
}
vfd_fault_codes = {
0: "No Fault",
2: "Auxiliary Input",
3: "Power Loss",
4: "UnderVoltage",
5: "OverVoltage",
7: "Motor Overload",
8: "Heatsink OverTemp",
9: "Thermister OverTemp",
10: "Dynamic Brake OverTemp",
12: "Hardware OverCurrent",
13: "Ground Fault",
14: "Ground Warning",
15: "Load Loss",
17: "Input Phase Loss",
18: "Motor PTC Trip",
19: "Task Overrun",
20: "Torque Prove Speed Band",
21: "Output Phase Loss",
24: "Decel Inhibit",
25: "OverSpeed Limit",
26: "Brake Slipped",
27: "Torque Prove Conflict",
28: "TP Encls Confict",
29: "Analog In Loss",
33: "Auto Restarts Exhausted",
35: "IPM OverCurrent",
36: "SW OverCurrent",
38: "Phase U to Ground",
39: "Phase V to Ground",
40: "Phase W to Ground",
41: "Phase UV Short",
42: "Phase VW Short",
43: "Phase WU Short",
44: "Phase UNeg to Ground",
45: "Phase VNeg to Ground",
46: "Phase WNeg to Ground",
48: "System Defaulted",
49: "Drive Powerup",
51: "Clear Fault Queue",
55: "Control Board Overtemp",
59: "Invalid Code",
61: "Shear Pin 1",
62: "Shear Pin 2",
64: "Drive Overload",
66: "OW Torque Level",
67: "Pump Off",
71: "Port 1 Adapter",
72: "Port 2 Adapter",
73: "Port 3 Adapter",
74: "Port 4 Adapter",
75: "Port 5 Adapter",
76: "Port 6 Adapter",
77: "IR Volts Range",
78: "FluxAmps Ref Range",
79: "Excessive Load",
80: "AutoTune Aborted",
81: "Port 1 DPI Loss",
82: "Port 2 DPI Loss",
83: "Port 3 DPI Loss",
84: "Port 4 DPI Loss",
85: "Port 5 DPI Loss",
86: "Port 6 DPI Loss",
87: "IXo Voltage Range",
91: "Primary Velocity Feedback Loss",
93: "Hardware Enable Check",
94: "Alternate Velocity Feedback Loss",
95: "Auxiliary Velocity Feedback Loss",
96: "Position Feedback Loss",
97: "Auto Tach Switch",
100: "Parameter Checksum",
101: "Power Down NVS Blank",
102: "NVS Not Blank",
103: "Power Down NVS Incompatible",
104: "Power Board Checksum",
106: "Incompat MCB-PB",
107: "Replaced MCB-PB",
108: "Analog Calibration Checksum",
110: "Invalid Power Board Data",
111: "Power Board Invalid ID",
112: "Power Board App Min Version",
113: "Tracking DataError",
115: "Power Down Table Full",
116: "Power Down Entry Too Large",
117: "Power Down Data Checksum",
118: "Power Board Power Down Checksum",
124: "App ID Changed",
125: "Using Backup App",
134: "Start on Power Up",
137: "External Precharge Error",
138: "Precharge Open",
141: "Autotune Enc Angle",
142: "Autotune Speed Restricted",
143: "Autotune Current Regulator",
144: "Autotune Inertia",
145: "Autotune Travel",
13035: "Net IO Timeout",
13037: "Net IO Timeout"
}
plc_tags = {
"Device_Status_INT": well_status_codes.get(value, "Invalid Code"),
"sts_PID_Control": pid_control_codes.get(value, "Invalid Code"),
"Downhole_Sensor_Status_INT": downhole_codes.get(value, "Invalid Code"),
"alarm_Flowmeter": alarm_codes.get(value, "Invalid Code"),
"alarm_IntakePressure": alarm_codes.get(value, "Invalid Code"),
"alarm_IntakeTemperature": alarm_codes.get(value, "Invalid Code"),
"alarm_TubingPressure": alarm_codes.get(value, "Invalid Code"),
"alarm_VFD": alarm_codes.get(value, "Invalid Code"),
"alarm_Lockout": alarm_vfd_codes.get(value, "Invalid Code"),
"alarm_FluidLevel": alarm_codes.get(value, "Invalid Code"),
"Run_Permissive_INT": permissive_codes.get(value, "Invalid Code"),
"Start_Permissive_INT": permissive_codes.get(value, "Invalid Code"),
"PowerFlex755.Val_LastFaultCode": vfd_fault_codes.get(value, "Invalid Code"),
"sts_CurrentVFDFaultCode": vfd_fault_codes.get(value, "Invalid Code")
}
return plc_tags.get(plc_tag, "Invalid Tag")

12
advvfdipp/config.txt Normal file
View File

@@ -0,0 +1,12 @@
{
"files": {
"file3": "Channel.py",
"file2": "utilities.py",
"file1": "advvfdipp.py",
"file4": "modbusMap.p"
},
"deviceName": "advvfdipp",
"driverId": "0100",
"releaseVersion": "9",
"driverFileName": "advvfdipp.py"
}

4675
advvfdipp/modbusMap.p Normal file

File diff suppressed because it is too large Load Diff

79
advvfdipp/utilities.py Normal file
View File

@@ -0,0 +1,79 @@
"""Utility functions for the driver."""
import socket
import struct
import os
import json
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))
if exponent == 30:
fraction = float(int("1" + bin_rep[7:17], 2))
else:
fraction = float(int(bin_rep[7: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
def get_plc_ip():
"""Get PLC IP from persistent json file"""
path = "./persistent.json"
#open the file for appending to not delete anything
#use + to create the file as needed
f = open(path, "a+")
#go to the beginning of the file
f.seek(0)
# if persistent is empty add default IP
if os.stat(path).st_size == 0:
data = {}
data["ip"] = "192.168.1.10"
json.dump(data, f)
f.close()
# load whatever is in persistent
with open(path) as json_file:
data = json.load(json_file)
plc_ip = data["ip"]
return plc_ip

27
deviceIds.csv Normal file
View File

@@ -0,0 +1,27 @@
driver,id
dh_monitor,10
flow-monitor,20
easttexas,30
ipp,40
poc,50
solarh2o,60
testtrailer,70
vfdipp,80
henryhyd,90
advvfdipp,100
abbflow,110
henrypump,120
pondlevel,130
transferstation,140
rigpump,150
solarpondlevel,160
prostarsolar,170
flowmonitor,180
promagmbs,190
fracskid,200
submonitor,210
plcpond,220
transferlite,230
multisensor,240
tanktransfer,250
rigpump_w_valve,260
1 driver id
2 dh_monitor 10
3 flow-monitor 20
4 easttexas 30
5 ipp 40
6 poc 50
7 solarh2o 60
8 testtrailer 70
9 vfdipp 80
10 henryhyd 90
11 advvfdipp 100
12 abbflow 110
13 henrypump 120
14 pondlevel 130
15 transferstation 140
16 rigpump 150
17 solarpondlevel 160
18 prostarsolar 170
19 flowmonitor 180
20 promagmbs 190
21 fracskid 200
22 submonitor 210
23 plcpond 220
24 transferlite 230
25 multisensor 240
26 tanktransfer 250
27 rigpump_w_valve 260

345
dh_monitor/dhMonitor.py Normal file
View File

@@ -0,0 +1,345 @@
#!/usr/bin/python
import types
import traceback
import binascii
import threading
import time
import thread
import os
import struct
import sys
import serial
import minimalmodbus
import pickle
from device_base import deviceBase
from datetime import datetime
import requests
try:
import json
except:
import simplejson as json
import calendar
from math import ceil, floor
data_channels = {
"IntakeTemperature":{"modbus_register":0, "value":"", "last_value":"", "last_send_time":0, "min_change_limit":0, "change_threshold":0, "min_upload_time":180},
"IntakePressure": {"modbus_register":1, "value":"", "last_value":"", "last_send_time":0, "min_change_limit":0, "change_threshold":0, "min_upload_time":180},
"WindingTemperature": {"modbus_register":2, "value":"", "last_value":"", "last_send_time":0, "min_change_limit":0, "change_threshold":0, "min_upload_time":180},
"DischargeTemperature": {"modbus_register":5, "value":"", "last_value":"", "last_send_time":0, "min_change_limit":0, "change_threshold":0, "min_upload_time":180},
"DischargePressure": {"modbus_register":6, "value":"", "last_value":"", "last_send_time":0, "min_change_limit":0, "change_threshold":0, "min_upload_time":180},
"VibrationX": {"modbus_register":3, "value":"", "last_value":"", "last_send_time":0, "min_change_limit":0, "change_threshold":0, "min_upload_time":180},
"VibrationY": {"modbus_register":4, "value":"", "last_value":"", "last_send_time":0, "min_change_limit":0, "change_threshold":0, "min_upload_time":180}
}
status_channels = {
"DownholeStatus": {"modbus_register":97, "map":"mainStatus" "value":"", "last_value":"", "last_send_time":0, "min_change_limit":0, "change_threshold":0, "min_upload_time":180}
}
map = {
"mainStatus": {
0: "OK",
1: "Connecting",
2: "Open circuit",
3: "Shorted",
4: "Cannot decode"
},
}
class start(threading.Thread, deviceBase):
def __init__(self, name=None, number=None, mac=None, Q=None, mcu=None, companyId=None, offset=None, mqtt=None, Nodes=None):
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.forceSend = True
self.version = "2"
self.cardLoopTimer = 600
self.finished = threading.Event()
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):
channels["status"]["last_value"] = ""
def run(self):
self.runLoopStatus = ""
while True:
try:
runLoopStatus = "readStatusRegisters"
self.readStatusRegisters()
runLoopStatus = "readDataRegisters"
self.readDataRegisters()
runLoopStatus = "Complete"
time.sleep(3)
except Exception, e:
sleep_timer = 20
print "Error during {0} of run loop: {1}\nWill try again in {2} seconds...".format(runLoopStatus, e, sleep_timer)
time.sleep(sleep_timer)
def readStatusRegisters(self):
for entry in day:
if go_channels.has_key(entry):
#"percent_run":{"meshifyName":"go_percent_run","last_value":"","last_send_time":0,"data_type":"float","change_amount":0},
if go_channels[entry]["last_value"] != day[entry]:
print entry, day[entry]
print go_channels[entry]["meshifyName"], day[entry], timestamp
self.sendtodb(go_channels[entry]["meshifyName"], day[entry], timestamp)
go_channels[entry]["last_value"] = day[entry]
def checkBackup(self):
backupList = json.loads(requests.get(self.device_address + "/json/backups").text)
file = backupList["backups"][0]
data = json.loads(requests.get(self.device_address + "/json/backups/" + file).text)
timestamp = time.time()
if data != self.wellSetup or self.forceSend:
self.sendtodbJSON("well_setup", json.dumps(data), timestamp)
self.wellSetup = data;
with open('wellSetup.p', 'wb') as handle:
pickle.dump(self.wellSetup, handle)
def checkEvents(self):
data = json.loads(requests.get(self.device_address + "/json/event_list").text)
events = data["events"]
for event in events:
if int(event["id"]) not in self.eventIds:
timestamp = calendar.timegm(time.strptime(event["datetime"], '%Y-%m-%dT%H:%M:%S.%fZ'))
#we have a new event
self.sendtodbJSON("events", json.dumps(event), timestamp)
self.eventIds.append(int(event["id"]))
if len(self.eventIds) > 50:
del self.eventIds[0]
with open('eventIds.p', 'wb') as handle:
pickle.dump(self.eventIds, handle)
def checkStatus(self):
statusMap = {
0:'Stopped',
1:'Running',
2:'Pumped Off',
3:'Faulted',
4:'Starting',
5:'Recovering',
100:'Read Error',
1000:'PLC Error',
9999:'No Response'
}
st_response = requests.get(self.device_address + "/json/status")
if st_response.status_code == 200:
data = json.loads(st_response.text)
date = data["ISOdate"]
status = statusMap[int(data["status"])]
if channels["status"]["last_value"] != status:
self.statusChanged = True
print "Status has changed from {0} to {1} @ {2}".format(channels["status"]["last_value"], status, time.time())
else:
self.statusChanged = False
if self.statusChanged or self.forceSend:
self.status = status
timestamp = int(time.mktime(time.strptime(date, '%Y-%m-%dT%H:%M:%S.%fZ')))
self.sendtodb("status", status, timestamp)
channels["status"]["last_value"] = status
self.checkLatestCard()
def checkDailyTotals(self):
data = json.loads(requests.get(self.device_address + "/json/totals").text)
total = data["totals"]
if total['status'] == "success":
timestamp = 0
for val in total['values']:
if dt_channels.has_key(val['name']):
if ((time.time() - int(dt_channels[val['name']]['last_time_uploaded'])) > int(dt_channels[val['name']]['min_time_between_uploads'])):
if (float(val['value']) >= (float(dt_channels[val['name']]["last_value"]) + float(dt_channels[val['name']]["change_amount"]))) or (float(val['value']) <= (float(dt_channels[val['name']]["last_value"]) - float(dt_channels[val['name']]["change_amount"]))):
print("[dailyTotal] {0}: {1}".format(val['name'], val['value']))
self.sendtodb(dt_channels[val['name']]["meshify_channel"], float(val['value']), timestamp)
dt_channels[val['name']]["last_value"] = float(val['value'])
dt_channels[val['name']]["last_time_uploaded"] = time.time()
else:
print("checkDailyTotalsError: {0}".format(total.message))
def checkGaugeOffData(self):
data = json.loads(requests.get(self.device_address + "/json/history").text)
day = data["hist"]
# print day["gauge_date"]
#timestamp = calendar.timegm(time.strptime(day["gauge_date"], '%Y-%m-%dT%H:%M:%S.%fZ'))
timestamp = time.mktime(time.strptime(day["gauge_date"], '%Y-%m-%dT%H:%M:%S.%Z'))
for entry in day:
if go_channels.has_key(entry):
#"percent_run":{"meshifyName":"go_percent_run","last_value":"","last_send_time":0,"data_type":"float","change_amount":0},
if go_channels[entry]["last_value"] != day[entry]:
print entry, day[entry]
print go_channels[entry]["meshifyName"], day[entry], timestamp
self.sendtodb(go_channels[entry]["meshifyName"], day[entry], timestamp)
go_channels[entry]["last_value"] = day[entry]
def checkLatestCard(self):
latest = requests.get(self.device_address + "/json/latest")
latest = json.loads(latest.text)
folder = str(latest["folder"])
file = latest["file"].replace(".csv", "")
#check the card to see if its new
# 1. if its new send the folder/file_name to the card_history channel
# 2. if its new and its been 10 minutes since you last sent an entire card, then send up all of the data
if channels["card_history"]["last_value"] != (folder + "/" + file):
#we have a new card
#get the data for this event
data = json.loads(requests.get(self.device_address + "/json/" + folder + "/" + file).text)
dateTime = str(data["contents"]["utctime"])
#timestamp = time.mktime(time.strptime(dateTime, '%Y-%m-%d %H:%M:%S.%f'))
#timestamp = calendar.timegm(time.strptime(dateTime, '%Y-%m-%d %H:%M:%S.%f'))
timestamp = time.mktime(time.strptime(dateTime, '%Y-%m-%d %H:%M:%S.%f'))
print "New card detected @ {0}".format(datetime.strftime(datetime.fromtimestamp(timestamp),"%Y-%m-%d %H:%M:%S.%f"))
#set the last value = to current value and upload your data
channels["card_history"]["last_value"] = (folder + "/" + file)
self.sendtodb("card_history", (folder + "/" + file), timestamp)
#check the last time the card was updated
if (time.time() - int(channels["card_history"]["last_time_uploaded"])) > self.cardLoopTimer or self.statusChanged or self.forceSend:
#its been 10 minutes, send the full upload
print "Either status has changed or last stored card is too old."
channels["card_history"]["last_time_uploaded"] = time.time()
self.process_card(data, timestamp, sendCards=True)
return
self.process_card(data, timestamp, sendCards=False)
def process_card(self, data, timestamp, sendCards=False):
#if sendCards = True then we upload all data no matter what, including cards
#check what type of data it is
#check if its changed, if it has, how long has it been since it changed
#NOTE: the initial vaue of "" is given to all channels in the channels object,
# so to avoid comparing a string to a float, and to make sure on startup we send all of the values, the first time through we send everything that has a "" as its last value
# We don't want to store any data on starting, just the cards
if self.status != 'Starting':
for channel in data["contents"]:
if channels.has_key(channel):
if channels[channel]["data_type"] == "str":
if (data["contents"][channel] != channels[channel]["last_value"] and ((time.time() - int(channels[channel]["last_time_uploaded"])) > int(channels[channel]["min_time_between_uploads"]))) or sendCards == True:
print "new value for: ", channel
print data["contents"][channel]
self.sendtodb(channel, str(data["contents"][channel]), int(timestamp))
channels[channel]["last_value"] = data["contents"][channel]
channels[channel]["last_time_uploaded"] = time.time()
if channels[channel]["data_type"] == "float" or channels[channel]["data_type"] == "int":
if channels[channel]["last_value"] == "":
#print "first time getting data"
print "new value for: ", channel
print data["contents"][channel]
self.sendtodb(channel, str(data["contents"][channel]), int(timestamp))
channels[channel]["last_value"] = data["contents"][channel]
channels[channel]["last_time_uploaded"] = time.time()
if (abs(float(data["contents"][channel]) - float(channels[channel]["last_value"])) > channels[channel]["change_amount"] and ((time.time() - int(channels[channel]["last_time_uploaded"])) > int(channels[channel]["min_time_between_uploads"]))) or sendCards == True:
# print "first time getting data"
print "new value for: ", channel
print data["contents"][channel]
self.sendtodb(channel, str(data["contents"][channel]), int(timestamp))
channels[channel]["last_value"] = data["contents"][channel]
channels[channel]["last_time_uploaded"] = time.time()
if sendCards:
sc = data["s"]
dc = data["d"]
for i in range(len(data["d"])):
try:
for x in range(len(data["d"][i])):
data["d"][i][x] = float('%.3f' % data["d"][i][x])
except Exception, e:
print e
for i in range(len(data["s"])):
try:
for x in range(len(data["s"][i])):
data["s"][i][x] = float('%.3f' % data["s"][i][x])
except Exception, e:
print e
sc = data["s"]
dc = data["d"]
newSc = "["
for i in sc:
try:
if i[0] is None:
continue
if i[0] != 0.0 and i[1]!= 0.0:
newSc += "[" + str(i[0]) + "," + str(i[1]) + "],"
except:
pass
newSc += "[" + str(sc[0][0]) + "," + str(sc[0][1]) + "]"
newSc += "]"
newDc = "["
for i in dc:
try:
if i[0] is None:
continue
if i[0] != 0.0 and i[1]!= 0.0:
newDc += "[" + str(i[0]) + "," + str(i[1]) + "],"
except:
pass
newDc += "[" + str(dc[0][0]) + "," + str(dc[0][1]) + "]"
newDc += "]"
self.sendtodb("sc", newSc, timestamp)
self.sendtodb("dc", newDc, timestamp)
def getLatestXCards(self, numCards):
data = json.loads(requests.get(self.device_address + "/json/latest/"+ str(int(numCards))).text)
for card in data['cards']:
card_data = json.loads(requests.get(self.device_address + "/json/" + data['folder'] + "/" + card).text)
dateTime = str(card_data["contents"]["utctime"])
timestamp = time.mktime(time.strptime(dateTime, '%Y-%m-%d %H:%M:%S.%f'))
self.process_card(card_data, timestamp, sendCards=True)
def poc_get_card(self, name, value):
self.getcard(value)
def poc_sync(self, name, value):
self.sendtodb("connected", "true", 0)
return True
def poc_set_address(self, name, value):
self.device_address = value
return True
def poc_refresh_data(self, name, value):
self.forceSend = True
return True
def poc_read_tag(self, name, value):
print "Reading Tag {0}".format(str(value))
tagObj = readTag.readTag(str(value))
print "tagObj: {0}".format(tagObj)
if tagObj['status'] == 'success':
result = "{{'tag':{0},'value':{1}}}".format(str(value),str(tagObj['value']))
self.sendtodb("read_tag_value", result, 0)
return True
else:
return tagObj['message']

282
dhsensor/Channel.py Normal file
View File

@@ -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

47
dhsensor/Maps.py Normal file
View File

@@ -0,0 +1,47 @@
"""Holds map values for prostarsolar."""
def charge_state(inp_state):
"""Map function for charge state."""
states = {
0: "Start",
1: "Night Check",
2: "Disconnect",
3: "Night",
4: "Fault",
5: "Bulk",
6: "Absorption",
7: "Float",
8: "Equalize"
}
if inp_state in range(0,9):
return states[inp_state]
else:
return inp_state
def array_faults(inp_array_faults):
"""Form a string for the array_faults."""
fault_string = ""
faults = {
0: "Overcurrent Phase 1",
1: "FETs Shorted",
2: "Software Bug",
3: "Battery HVD (High Voltage Disconnect)",
4: "Array HVD (High Voltage Disconnect)",
5: "EEPROM Setting Edit (reset required)",
6: "RTS Shorted",
7: "RTS was valid now disconnected",
8: "Local temp. sensor failed",
9: "Battery LVD (Low Voltage Disconect)",
10: "DIP Switch Changed (excl. DIP 8)",
11: "Processor Supply Fault"
}
bit_string = ("0" * 16 + "{0:b}".format(inp_array_faults))[-16:]
for i in range(0, 12):
if int(bit_string[i]) == 1:
fault_string += faults[i] + ", "
if fault_string:
return fault_string[:-2]
else:
return "None"

12
dhsensor/config.txt Normal file
View File

@@ -0,0 +1,12 @@
{
"files": {
"file3": "persistence.py",
"file2": "utilities.py",
"file1": "dhsensor.py",
"file4": "modbusMap.p"
},
"deviceName": "dhsensor",
"driverId": "0170",
"releaseVersion": "1",
"driverFileName": "dhsensor.py"
}

84
dhsensor/dhsensor.py Normal file
View File

@@ -0,0 +1,84 @@
"""Driver for dhsensor."""
import threading
from device_base import deviceBase
import persistence
from utilities import get_public_ip_address
import time
_ = None
# GLOBAL VARIABLES
WATCHDOG_SEND_PERIOD = 3600 # Seconds, the longest amount of time before sending the watchdog status
PLC_IP_ADDRESS = "192.168.1.10"
# PERSISTENCE FILE
persist = persistence.load()
class start(threading.Thread, deviceBase):
"""Start class required by Meshify."""
def __init__(self, name=None, number=None, mac=None, Q=None, mcu=None, companyId=None, offset=None, mqtt=None, Nodes=None):
"""Initialize the driver."""
threading.Thread.__init__(self)
deviceBase.__init__(self, name=name, number=number, mac=mac, Q=Q, mcu=mcu, companyId=companyId, offset=offset, mqtt=mqtt, Nodes=Nodes)
self.daemon = True
self.version = "1"
self.finished = threading.Event()
self.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("dhsensor driver will start in {} seconds".format(wait_sec - i))
time.sleep(1)
print("BOOM! Starting dhsensor driver...")
self.nodes["dhsensor_0199"] = self
public_ip_address = get_public_ip_address()
self.sendtodbDev(1, 'public_ip_address', public_ip_address, 0, 'dhsensor')
send_loops = 0
watchdog_loops = 0
watchdog_check_after = 5000
while True:
if self.forceSend:
print "FORCE SEND: TRUE"
print("dhsensor 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, 'dhsensor')
public_ip_address = test_public_ip
watchdog_loops = 0
time.sleep(10)
def dhsensor_sync(self, name, value):
"""Sync all data from the driver."""
self.forceSend = True
# self.sendtodb("log", "synced", 0)
return True

578
dhsensor/modbusMap.p Normal file
View File

@@ -0,0 +1,578 @@
(dp0
S'1'
p1
(dp2
S'c'
p3
VM1-485
p4
sS'b'
p5
V38400
p6
sS'addresses'
p7
(dp8
V1
p9
(dp10
V2-2
p11
(dp12
Vah
p13
V90
p14
sVbytary
p15
NsVal
p16
V4
p17
sVvn
p18
VIntake Pressure
p19
sVct
p20
Vnumber
p21
sVle
p22
V16
p23
sVgrp
p24
V3600
p25
sVla
p26
I0
sVchn
p27
Vintake_pressure
p28
sVun
p29
V01
p30
sVdn
p31
Vdhsensor
p32
sVda
p33
g9
sVlrt
p34
F1568755102.8790879
sVr
p35
V0-100
p36
sVa
p37
g9
sVc
p38
g9
sVmisc_u
p39
VPSI
p40
sVf
p41
V3
p42
sVmrt
p43
V20
p44
sVm
p45
Vnone
p46
sS'm1ch'
p47
g11
sVs
p48
VOn
p49
sVmv
p50
V0
p51
sVt
p52
Vint
p53
sVvm
p54
NssV2-3
p55
(dp56
Vah
p57
V90
p58
sVbytary
p59
NsVal
p60
V5
p61
sVvn
p62
VDischarge Pressure
p63
sVct
p64
Vnumber
p65
sVle
p66
V16
p67
sVgrp
p68
V3600
p69
sVla
p70
I0
sVchn
p71
Vdischarge_pressure
p72
sVun
p73
V01
p74
sVdn
p75
Vdhsensor
p76
sVda
p77
g9
sVlrt
p78
F1568755103.3660561
sg35
V0-100
p79
sg37
g61
sg38
g9
sVmisc_u
p80
VPSI
p81
sg41
g42
sVmrt
p82
V20
p83
sg45
Vnone
p84
sg47
g55
sg48
VOn
p85
sVmv
p86
g51
sg52
Vint
p87
sVvm
p88
NssV2-1
p89
(dp90
Vah
p91
V
p92
sVbytary
p93
NsVal
p94
g92
sVvn
p95
VIntake Temperature
p96
sVct
p97
Vnumber
p98
sVle
p99
V16
p100
sVgrp
p101
V3600
p102
sVla
p103
g92
sVchn
p104
Vintake_temperature
p105
sVun
p106
V01
p107
sVdn
p108
Vdhsensor
p109
sVda
p110
V1
p111
sVlrt
p112
F1568753044.747895
sVr
p113
V0-100
p114
sVa
p115
V0
p116
sVc
p117
g111
sVmisc_u
p118
VF
p119
sVf
p120
V3
p121
sVmrt
p122
V20
p123
sVm
p124
Vnone
p125
sVm1ch
p126
g89
sVs
p127
VOn
p128
sVmv
p129
g116
sVt
p130
Vint
p131
sVvm
p132
NssV2-6
p133
(dp134
Vah
p135
g92
sVbytary
p136
NsVal
p137
g92
sVvn
p138
VDown Hole Level
p139
sVct
p140
Vnumber
p141
sVle
p142
V16
p143
sVgrp
p144
V3600
p145
sVla
p146
g92
sVchn
p147
Vdh_level
p148
sg113
V0-1023
p149
sVdn
p150
Vdhsensor
p151
sVda
p152
g111
sVlrt
p153
F1568753045.237281
sg115
g111
sg117
g111
sVmisc_u
p154
VFt
p155
sg120
g121
sVmrt
p156
V20
p157
sg124
Vfunction
p158
sVm1ch
p159
g133
sVmv
p160
Vx * (60 / 1023) + (60 - 1023 * (60/1023))
p161
sg127
VOn
p162
sVun
p163
V01
p164
sg130
Vint
p165
sVvm
p166
NssV2-4
p167
(dp168
Vah
p169
g92
sVbytary
p170
NsVal
p171
g92
sVvn
p172
VDischarge Temperature
p173
sVct
p174
Vnumber
p175
sVle
p176
V16
p177
sVgrp
p178
V3600
p179
sVla
p180
I0
sVchn
p181
Vdischarge_temperature
p182
sVun
p183
V01
p184
sVdn
p185
Vdhsensor
p186
sVda
p187
g9
sVlrt
p188
F1568755096.304796
sg35
V0-100
p189
sg37
V6
p190
sg38
g9
sVmisc_u
p191
VF
p192
sg41
g42
sVmrt
p193
V20
p194
sg45
Vnone
p195
sg47
g167
sg48
VOn
p196
sVmv
p197
g51
sg52
Vint
p198
sVvm
p199
NssV2-5
p200
(dp201
Vah
p202
g92
sVbytary
p203
NsVal
p204
g92
sVvn
p205
VD/H Status
p206
sVct
p207
Vnumber
p208
sVle
p209
V16
p210
sVgrp
p211
V3600
p212
sVla
p213
I2
sVchn
p214
Vdh_status
p215
sVun
p216
V01
p217
sVdn
p218
Vdhsensor
p219
sVda
p220
g9
sVlrt
p221
F1568755097.231012
sg35
g92
sg37
V97
p222
sg38
g9
sVmisc_u
p223
g92
sg41
g42
sVmrt
p224
g61
sg45
Vnone
p225
sg47
g200
sg48
VOn
p226
sVmv
p227
g51
sg52
Vint
p228
sVvm
p229
(dp230
g9
VConnecting
p231
sg51
VOK
p232
sg42
VShorted
p233
sV2
p234
VOpen Circuit
p235
sg17
VCannot Decode
p236
sssssS'f'
p237
VOff
p238
sS'p'
p239
g92
sS's'
p240
g111
ssS'2'
p241
(dp242
g3
VM1-485
p243
sg5
V9600
p244
sg7
(dp245
sg237
VOff
p246
sg239
VNone
p247
sg240
g111
ss.

21
dhsensor/persistence.py Normal file
View File

@@ -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

53
dhsensor/utilities.py Normal file
View File

@@ -0,0 +1,53 @@
"""Utility functions for the driver."""
import socket
def get_public_ip_address():
"""Find the public IP Address of the host device."""
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
ip = s.getsockname()[0]
s.close()
except:
return "Could not connect to the Internet"
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 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
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

298
dual_flowmeter/Channel.py Normal file
View File

@@ -0,0 +1,298 @@
"""Define Meshify channel class."""
import time
import urllib
from pycomm.ab_comm.clx import Driver as ClxDriver
from pycomm.cip.cip_base import CommError, DataError
from file_logger import filelogger as log
TAG_DATAERROR_SLEEPTIME = 5
def binarray(intval):
"""Split an integer into its bits."""
bin_string = '{0:08b}'.format(intval)
bin_arr = [i for i in bin_string]
bin_arr.reverse()
return bin_arr
def read_tag(addr, tag, plc_type="CLX"):
"""Read a tag from the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
try:
if clx.open(addr, direct_connection=direct):
try:
val = clx.read_tag(tag)
clx.close()
return val
except DataError as err:
clx.close()
time.sleep(TAG_DATAERROR_SLEEPTIME)
log.error("Data Error during readTag({}, {}): {}".format(addr, tag, err))
except CommError:
# err = c.get_status()
clx.close()
log.error("Could not connect during readTag({}, {})".format(addr, tag))
except AttributeError as err:
clx.close()
log.error("AttributeError during readTag({}, {}): \n{}".format(addr, tag, err))
clx.close()
return False
def read_array(addr, tag, start, end, plc_type="CLX"):
"""Read an array from the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
if clx.open(addr, direct_connection=direct):
arr_vals = []
try:
for i in range(start, end):
tag_w_index = tag + "[{}]".format(i)
val = clx.read_tag(tag_w_index)
arr_vals.append(round(val[0], 4))
if arr_vals:
clx.close()
return arr_vals
else:
log.error("No length for {}".format(addr))
clx.close()
return False
except Exception:
log.error("Error during readArray({}, {}, {}, {})".format(addr, tag, start, end))
err = clx.get_status()
clx.close()
log.error(err)
clx.close()
def write_tag(addr, tag, val, plc_type="CLX"):
"""Write a tag value to the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
try:
if clx.open(addr, direct_connection=direct):
try:
initial_val = clx.read_tag(tag)
write_status = clx.write_tag(tag, val, initial_val[1])
clx.close()
return write_status
except DataError as err:
clx_err = clx.get_status()
clx.close()
log.error("--\nDataError during writeTag({}, {}, {}, plc_type={}) -- {}\n{}\n".format(addr, tag, val, plc_type, err, clx_err))
except CommError as err:
clx_err = clx.get_status()
log.error("--\nCommError during write_tag({}, {}, {}, plc_type={})\n{}\n--".format(addr, tag, val, plc_type, err))
clx.close()
return False
class Channel(object):
"""Holds the configuration for a Meshify channel."""
def __init__(self, mesh_name, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False):
"""Initialize the channel."""
self.mesh_name = mesh_name
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
def __str__(self):
"""Create a string for the channel."""
return "{}\nvalue: {}, last_send_time: {}".format(self.mesh_name, self.value, self.last_send_time)
def check(self, new_value, force_send=False):
"""Check to see if the new_value needs to be stored."""
send_needed = False
send_reason = ""
if self.data_type == 'BOOL' or self.data_type == 'STRING':
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif self.value != new_value:
if self.map_:
if not self.value == self.map_[new_value]:
send_needed = True
send_reason = "value change"
else:
send_needed = True
send_reason = "value change"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
else:
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif abs(self.value - new_value) > self.chg_threshold:
send_needed = True
send_reason = "change threshold"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
if send_needed:
self.last_value = self.value
if self.map_:
try:
self.value = self.map_[new_value]
except KeyError:
log.error("Cannot find a map value for {} in {} for {}".format(new_value, self.map_, self.mesh_name))
self.value = new_value
else:
self.value = new_value
self.last_send_time = time.time()
log.info("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason))
return send_needed
def read(self):
"""Read the value."""
pass
def identity(sent):
"""Return exactly what was sent to it."""
return sent
class ModbusChannel(Channel):
"""Modbus channel object."""
def __init__(self, mesh_name, register_number, data_type, chg_threshold, guarantee_sec, channel_size=1, map_=False, write_enabled=False, transform_fn=identity):
"""Initialize the channel."""
super(ModbusChannel, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.mesh_name = mesh_name
self.register_number = register_number
self.channel_size = channel_size
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
self.transform_fn = transform_fn
def read(self, mbsvalue):
"""Return the transformed read value."""
return self.transform_fn(mbsvalue)
class PLCChannel(Channel):
"""PLC Channel Object."""
def __init__(self, ip, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False, plc_type='CLX'):
"""Initialize the channel."""
super(PLCChannel, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.plc_ip = ip
self.mesh_name = mesh_name
self.plc_tag = plc_tag
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
self.plc_type = plc_type
def read(self):
"""Read the value."""
plc_value = None
if self.plc_tag and self.plc_ip:
read_value = read_tag(self.plc_ip, self.plc_tag, plc_type=self.plc_type)
if read_value:
plc_value = read_value[0]
return plc_value
class BoolArrayChannels(Channel):
"""Hold the configuration for a set of boolean array channels."""
def __init__(self, ip, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False):
"""Initialize the channel."""
super(BoolArrayChannels, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.plc_ip = ip
self.mesh_name = mesh_name
self.plc_tag = plc_tag
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
def compare_values(self, new_val_dict):
"""Compare new values to old values to see if the values need storing."""
send = False
for idx in new_val_dict:
try:
if new_val_dict[idx] != self.last_value[idx]:
send = True
except KeyError:
log.error("Key Error in self.compare_values for index {}".format(idx))
send = True
return send
def read(self, force_send=False):
"""Read the value and check to see if needs to be stored."""
send_needed = False
send_reason = ""
if self.plc_tag:
val = read_tag(self.plc_ip, self.plc_tag)
if val:
bool_arr = binarray(val[0])
new_val = {}
for idx in self.map_:
try:
new_val[self.map_[idx]] = bool_arr[idx]
except KeyError:
log.error("Not able to get value for index {}".format(idx))
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif self.compare_values(new_val):
send_needed = True
send_reason = "value change"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
if send_needed:
self.value = new_val
self.last_value = self.value
self.last_send_time = time.time()
log.info("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason))
return send_needed

21
dual_flowmeter/Tags.py Normal file
View File

@@ -0,0 +1,21 @@
from Channel import PLCChannel, ModbusChannel
from dual_flowmeter import PLC_IP_ADDRESS
tags = [
PLCChannel(PLC_IP_ADDRESS, "pump_1_daily_total","Pump_1_Daily_Flow_Rate_Total","REAL", 10, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pump_1_run_status","Pump_1_Run_Status","BOOL", 1, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pump_1_flowrate","Pump_1_SCL_Flow_Meter","REAL", 2, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pump_1_yesterdays_total","Pump_1_Yesterdays_Total","REAL", 10, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pump_1_power","Pump_1_Power","BOOL", 1, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pump_1_prevmonth_total","Pump_1_PrevMonth_Total","REAL", 10, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pump_1_month_total","Pump_1_Current_Month_Total","REAL", 10, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pump_1_lifetime_total","Pump_1_Lifetime_Flow","REAL", 10, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pump_2_daily_total","Pump_2_Daily_Flow_Rate_Total","REAL", 10, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pump_2_run_status","Pump_2_Run_Status","BOOL", 1, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pump_2_flowrate","Pump_2_SCL_Flow_Meter","REAL", 2, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pump_2_yesterdays_total","Pump_2_Yesterdays_Total","REAL", 10, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pump_2_power","Pump_2_Power","BOOL", 10, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pump_2_prevmonth_total","Pump_2_PrevMonth_Total","REAL", 10, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pump_2_month_total","Pump_2_Current_Month_Total","REAL", 10, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pump_2_lifetime_total","Pump_2_Lifetime_Flow","REAL", 10, 3600, plc_type="Micro800")
]

14
dual_flowmeter/config.txt Normal file
View File

@@ -0,0 +1,14 @@
{
"files": {
"file3": "file_logger.py",
"file2": "Channel.py",
"file1": "dual_flowmeter.py",
"file6": "persistence.py",
"file5": "utilities.py",
"file4": "Tags.py"
},
"deviceName": "dual_flowmeter",
"releaseVersion": "4",
"driverFileName": "dual_flowmeter.py",
"driverId": "0100"
}

View File

@@ -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)))

View File

@@ -0,0 +1,148 @@
"""Driver for dual_flowmeter"""
import threading
import json
import time
from random import randint
from device_base import deviceBase
from Channel import PLCChannel, ModbusChannel,read_tag, write_tag, TAG_DATAERROR_SLEEPTIME
import persistence
from utilities import get_public_ip_address
from file_logger import filelogger as log
PLC_IP_ADDRESS = "192.168.1.10"
from Tags import tags
_ = None
log.info("dual_flowmeter startup")
# GLOBAL VARIABLES
WAIT_FOR_CONNECTION_SECONDS = 60
IP_CHECK_PERIOD = 60
WATCHDOG_ENABLE = False
WATCHDOG_CHECK_PERIOD = 60
WATCHDOG_SEND_PERIOD = 3600 # Seconds, the longest amount of time before sending the watchdog status
CHANNELS = tags
# PERSISTENCE FILE
PERSIST = persistence.load()
class start(threading.Thread, deviceBase):
"""Start class required by Meshify."""
def __init__(self, name=None, number=None, mac=None, Q=None, mcu=None,
companyId=None, offset=None, mqtt=None, Nodes=None):
"""Initialize the driver."""
threading.Thread.__init__(self)
deviceBase.__init__(self, name=name, number=number, mac=mac, Q=Q,
mcu=mcu, companyId=companyId, offset=offset,
mqtt=mqtt, Nodes=Nodes)
self.daemon = True
self.version = "4"
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("dual_flowmeter driver will start in {} seconds".format(WAIT_FOR_CONNECTION_SECONDS - i))
time.sleep(1)
log.info("BOOM! Starting dual_flowmeter driver...")
#self._check_watchdog()
self._check_ip_address()
self.nodes["dual_flowmeter_0199"] = self
send_loops = 0
while True:
now = time.time()
if self.force_send:
log.warning("FORCE SEND: TRUE")
for chan in CHANNELS:
val = chan.read()
if chan.check(val, self.force_send):
self.sendtodbDev(1, chan.mesh_name, chan.value, 0, 'dual_flowmeter')
#time.sleep(TAG_DATAERROR_SLEEPTIME) # sleep to allow Micro800 to handle ENET requests
# print("dual_flowmeter driver still alive...")
if self.force_send:
if send_loops > 2:
log.warning("Turning off force_send")
self.force_send = False
send_loops = 0
else:
send_loops += 1
if WATCHDOG_ENABLE:
if (now - self.watchdog_last_checked) > WATCHDOG_CHECK_PERIOD:
self._check_watchdog()
if (now - self.public_ip_address_last_checked) > IP_CHECK_PERIOD:
self._check_ip_address()
def _check_watchdog(self):
"""Check the watchdog and send to Meshify if changed or stale."""
test_watchdog = self.dual_flowmeter_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, 'dual_flowmeter')
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, 'dual_flowmeter')
self.public_ip_address = test_public_ip
def dual_flowmeter_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 dual_flowmeter_sync(self, name, value):
"""Sync all data from the driver."""
self.force_send = True
# self.sendtodb("log", "synced", 0)
return True
def dual_flowmeter_writeplctag(self, name, value):
"""Write a value to the PLC."""
new_val = json.loads(str(value).replace("'", '"'))
tag_n = str(new_val['tag']) # "cmd_Start"
val_n = new_val['val']
write_res = write_tag(str(PLC_IP_ADDRESS), tag_n, val_n, plc_type="Micro800")
print("Result of dual_flowmeter_writeplctag(self, {}, {}) = {}".format(name, value, write_res))
if write_res is None:
write_res = "Error writing to PLC..."
return write_res

View File

@@ -0,0 +1,18 @@
"""Logging setup for dual_flowmeter"""
import logging
from logging.handlers import RotatingFileHandler
import sys
log_formatter = logging.Formatter('%(asctime)s %(levelname)s %(funcName)s(%(lineno)d) %(message)s')
log_file = './dual_flowmeter.log'
my_handler = RotatingFileHandler(log_file, mode='a', maxBytes=500*1024,
backupCount=2, encoding=None, delay=0)
my_handler.setFormatter(log_formatter)
my_handler.setLevel(logging.INFO)
filelogger = logging.getLogger('dual_flowmeter')
filelogger.setLevel(logging.INFO)
filelogger.addHandler(my_handler)
console_out = logging.StreamHandler(sys.stdout)
console_out.setFormatter(log_formatter)
filelogger.addHandler(console_out)

View File

@@ -0,0 +1,21 @@
"""Data persistance functions."""
# if more advanced persistence is needed, use a sqlite database
import json
def load(filename="persist.json"):
"""Load persisted settings from the specified file."""
try:
with open(filename, 'r') as persist_file:
return json.load(persist_file)
except Exception:
return False
def store(persist_obj, filename="persist.json"):
"""Store the persisting settings into the specified file."""
try:
with open(filename, 'w') as persist_file:
return json.dump(persist_obj, persist_file, indent=4)
except Exception:
return False

View File

@@ -0,0 +1,52 @@
"""Utility functions for the driver."""
import socket
import struct
def get_public_ip_address():
"""Find the public IP Address of the host device."""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(("8.8.8.8", 80))
ip_address = sock.getsockname()[0]
sock.close()
except Exception as e:
return e
return ip_address
def int_to_float16(int_to_convert):
"""Convert integer into float16 representation."""
bin_rep = ('0' * 16 + '{0:b}'.format(int_to_convert))[-16:]
sign = 1.0
if int(bin_rep[0]) == 1:
sign = -1.0
exponent = float(int(bin_rep[1:6], 2))
fraction = float(int(bin_rep[6:17], 2))
if exponent == float(0b00000):
return sign * 2 ** -14 * fraction / (2.0 ** 10.0)
elif exponent == float(0b11111):
if fraction == 0:
return sign * float("inf")
return float("NaN")
frac_part = 1.0 + fraction / (2.0 ** 10.0)
return sign * (2 ** (exponent - 15)) * frac_part
def ints_to_float(int1, int2):
"""Convert 2 registers into a floating point number."""
mypack = struct.pack('>HH', int1, int2)
f_unpacked = struct.unpack('>f', mypack)
print("[{}, {}] >> {}".format(int1, int2, f_unpacked[0]))
return f_unpacked[0]
def degf_to_degc(temp_f):
"""Convert deg F to deg C."""
return (temp_f - 32.0) * (5.0/9.0)
def degc_to_degf(temp_c):
"""Convert deg C to deg F."""
return temp_c * 1.8 + 32.0

298
dualactuator/Channel.py Normal file
View File

@@ -0,0 +1,298 @@
"""Define Meshify channel class."""
import time
import urllib
from pycomm.ab_comm.clx import Driver as ClxDriver
from pycomm.cip.cip_base import CommError, DataError
from file_logger import filelogger as log
TAG_DATAERROR_SLEEPTIME = 5
def binarray(intval):
"""Split an integer into its bits."""
bin_string = '{0:08b}'.format(intval)
bin_arr = [i for i in bin_string]
bin_arr.reverse()
return bin_arr
def read_tag(addr, tag, plc_type="CLX"):
"""Read a tag from the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
try:
if clx.open(addr, direct_connection=direct):
try:
val = clx.read_tag(tag)
clx.close()
return val
except DataError as err:
clx.close()
time.sleep(TAG_DATAERROR_SLEEPTIME)
log.error("Data Error during readTag({}, {}): {}".format(addr, tag, err))
except CommError:
# err = c.get_status()
clx.close()
log.error("Could not connect during readTag({}, {})".format(addr, tag))
except AttributeError as err:
clx.close()
log.error("AttributeError during readTag({}, {}): \n{}".format(addr, tag, err))
clx.close()
return False
def read_array(addr, tag, start, end, plc_type="CLX"):
"""Read an array from the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
if clx.open(addr, direct_connection=direct):
arr_vals = []
try:
for i in range(start, end):
tag_w_index = tag + "[{}]".format(i)
val = clx.read_tag(tag_w_index)
arr_vals.append(round(val[0], 4))
if arr_vals:
clx.close()
return arr_vals
else:
log.error("No length for {}".format(addr))
clx.close()
return False
except Exception:
log.error("Error during readArray({}, {}, {}, {})".format(addr, tag, start, end))
err = clx.get_status()
clx.close()
log.error(err)
clx.close()
def write_tag(addr, tag, val, plc_type="CLX"):
"""Write a tag value to the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
try:
if clx.open(addr, direct_connection=direct):
try:
initial_val = clx.read_tag(tag)
write_status = clx.write_tag(tag, val, initial_val[1])
clx.close()
return write_status
except DataError as err:
clx_err = clx.get_status()
clx.close()
log.error("--\nDataError during writeTag({}, {}, {}, plc_type={}) -- {}\n{}\n".format(addr, tag, val, plc_type, err, clx_err))
except CommError as err:
clx_err = clx.get_status()
log.error("--\nCommError during write_tag({}, {}, {}, plc_type={})\n{}\n--".format(addr, tag, val, plc_type, err))
clx.close()
return False
class Channel(object):
"""Holds the configuration for a Meshify channel."""
def __init__(self, mesh_name, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False):
"""Initialize the channel."""
self.mesh_name = mesh_name
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
def __str__(self):
"""Create a string for the channel."""
return "{}\nvalue: {}, last_send_time: {}".format(self.mesh_name, self.value, self.last_send_time)
def check(self, new_value, force_send=False):
"""Check to see if the new_value needs to be stored."""
send_needed = False
send_reason = ""
if self.data_type == 'BOOL' or self.data_type == 'STRING':
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif self.value != new_value:
if self.map_:
if not self.value == self.map_[new_value]:
send_needed = True
send_reason = "value change"
else:
send_needed = True
send_reason = "value change"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
else:
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif abs(self.value - new_value) > self.chg_threshold:
send_needed = True
send_reason = "change threshold"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
if send_needed:
self.last_value = self.value
if self.map_:
try:
self.value = self.map_[new_value]
except KeyError:
log.error("Cannot find a map value for {} in {} for {}".format(new_value, self.map_, self.mesh_name))
self.value = new_value
else:
self.value = new_value
self.last_send_time = time.time()
log.info("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason))
return send_needed
def read(self):
"""Read the value."""
pass
def identity(sent):
"""Return exactly what was sent to it."""
return sent
class ModbusChannel(Channel):
"""Modbus channel object."""
def __init__(self, mesh_name, register_number, data_type, chg_threshold, guarantee_sec, channel_size=1, map_=False, write_enabled=False, transform_fn=identity):
"""Initialize the channel."""
super(ModbusChannel, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.mesh_name = mesh_name
self.register_number = register_number
self.channel_size = channel_size
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
self.transform_fn = transform_fn
def read(self, mbsvalue):
"""Return the transformed read value."""
return self.transform_fn(mbsvalue)
class PLCChannel(Channel):
"""PLC Channel Object."""
def __init__(self, ip, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False, plc_type='CLX'):
"""Initialize the channel."""
super(PLCChannel, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.plc_ip = ip
self.mesh_name = mesh_name
self.plc_tag = plc_tag
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
self.plc_type = plc_type
def read(self):
"""Read the value."""
plc_value = None
if self.plc_tag and self.plc_ip:
read_value = read_tag(self.plc_ip, self.plc_tag, plc_type=self.plc_type)
if read_value:
plc_value = read_value[0]
return plc_value
class BoolArrayChannels(Channel):
"""Hold the configuration for a set of boolean array channels."""
def __init__(self, ip, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False):
"""Initialize the channel."""
super(BoolArrayChannels, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.plc_ip = ip
self.mesh_name = mesh_name
self.plc_tag = plc_tag
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
def compare_values(self, new_val_dict):
"""Compare new values to old values to see if the values need storing."""
send = False
for idx in new_val_dict:
try:
if new_val_dict[idx] != self.last_value[idx]:
send = True
except KeyError:
log.error("Key Error in self.compare_values for index {}".format(idx))
send = True
return send
def read(self, force_send=False):
"""Read the value and check to see if needs to be stored."""
send_needed = False
send_reason = ""
if self.plc_tag:
val = read_tag(self.plc_ip, self.plc_tag)
if val:
bool_arr = binarray(val[0])
new_val = {}
for idx in self.map_:
try:
new_val[self.map_[idx]] = bool_arr[idx]
except KeyError:
log.error("Not able to get value for index {}".format(idx))
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif self.compare_values(new_val):
send_needed = True
send_reason = "value change"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
if send_needed:
self.value = new_val
self.last_value = self.value
self.last_send_time = time.time()
log.info("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason))
return send_needed

25
dualactuator/Tags.py Normal file
View File

@@ -0,0 +1,25 @@
from Channel import PLCChannel, ModbusChannel
from dualactuator import PLC_IP_ADDRESS
tags = [
PLCChannel(PLC_IP_ADDRESS, "open_cmd","Open_Cmd","BOOL", 1, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "v1_open_fbk","V1_Open_FBK","BOOL", 1, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "v1_closed_fbk","V1_Closed_FBK","BOOL", 1, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "v2_open_fbk","V2_Open_FBK","BOOL", 1, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "v2_closed_fbk","V2_Closed_FBK","BOOL", 1, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "sep_valve_cmd","sep_valve_cmd","BOOL", 1, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "v1_open_cmd","V1_Open_Cmd","BOOL", 1, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "v2_open_cmd","V2_Open_Cmd","BOOL", 1, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "fm1_flowrate","FM1_FR","REAL", 10, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "fm1_totalizer1","FM1_T1","REAL", 100, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "fm1_day","FM1_Day","REAL", 100, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "fm1_yesterday","FM1_Yesterday","REAL", 100, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "fm1_month","FM1_Month","REAL", 100, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "fm1_lastmonth","FM1_LastMonth","REAL", 100, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "fm2_flowrate","FM2_FR","REAL", 10, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "fm2_totalizer1","FM2_T1","REAL", 100, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "fm2_day","FM2_Day","REAL", 100, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "fm2_yesterday","FM2_Yesterday","REAL", 100, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "fm2_month","FM2_Month","REAL", 100, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "fm2_lastmonth","FM2_LastMonth","REAL", 100, 3600, plc_type="Micro800")
]

14
dualactuator/config.txt Normal file
View File

@@ -0,0 +1,14 @@
{
"files": {
"file3": "file_logger.py",
"file2": "Channel.py",
"file1": "dualactuator.py",
"file6": "persistence.py",
"file5": "utilities.py",
"file4": "Tags.py"
},
"deviceName": "dualactuator",
"releaseVersion": "3",
"driverFileName": "dualactuator.py",
"driverId": "0100"
}

360
dualactuator/device_base.py Normal file
View File

@@ -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)))

View File

@@ -0,0 +1,135 @@
"""Driver for dualactuator"""
import threading
import json
import time
from random import randint
import os
from device_base import deviceBase
from Channel import PLCChannel, ModbusChannel,read_tag, write_tag, TAG_DATAERROR_SLEEPTIME
import persistence
from utilities import get_public_ip_address
from file_logger import filelogger as log
PLC_IP_ADDRESS = "192.168.1.12"
from Tags import tags
_ = None
log.info("dualactuator startup")
# GLOBAL VARIABLES
WAIT_FOR_CONNECTION_SECONDS = 60
IP_CHECK_PERIOD = 60
CHANNELS = tags
# PERSISTENCE FILE
PERSIST = persistence.load()
class start(threading.Thread, deviceBase):
"""Start class required by Meshify."""
def __init__(self, name=None, number=None, mac=None, Q=None, mcu=None,
companyId=None, offset=None, mqtt=None, Nodes=None):
"""Initialize the driver."""
threading.Thread.__init__(self)
deviceBase.__init__(self, name=name, number=number, mac=mac, Q=Q,
mcu=mcu, companyId=companyId, offset=offset,
mqtt=mqtt, Nodes=Nodes)
self.daemon = True
self.version = "3"
self.finished = threading.Event()
self.force_send = False
self.public_ip_address = ""
self.public_ip_address_last_checked = 0
threading.Thread.start(self)
# this is a required function for all drivers, its goal is to upload some piece of data
# about your device so it can be seen on the web
def register(self):
"""Register the driver."""
# self.sendtodb("log", "BOOM! Booted.", 0)
pass
def run(self):
"""Actually run the driver."""
for i in range(0, WAIT_FOR_CONNECTION_SECONDS):
print("dualactuator driver will start in {} seconds".format(WAIT_FOR_CONNECTION_SECONDS - i))
time.sleep(1)
log.info("BOOM! Starting dualactuator driver...")
self._check_ip_address()
self.nodes["dualactuator_0199"] = self
send_loops = 0
while True:
now = time.time()
if self.force_send:
log.warning("FORCE SEND: TRUE")
for chan in CHANNELS:
val = chan.read()
if chan.check(val, self.force_send):
self.sendtodbDev(1, chan.mesh_name, chan.value, 0, 'dualactuator')
#time.sleep(TAG_DATAERROR_SLEEPTIME) # sleep to allow Micro800 to handle ENET requests
# print("dualactuator driver still alive...")
if self.force_send:
if send_loops > 2:
log.warning("Turning off force_send")
self.force_send = False
send_loops = 0
else:
send_loops += 1
if (now - self.public_ip_address_last_checked) > IP_CHECK_PERIOD:
self._check_ip_address()
time.sleep(10)
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, 'dualactuator')
self.public_ip_address = test_public_ip
hostname = "google.com"
response = os.system("ping -c 1 " + hostname)
#and then check the response...
if response == 0:
print hostname, 'is up!'
self.ping_counter = 0
else:
print hostname, 'is down!'
self.ping_counter += 1
if self.ping_counter >= 3:
log.info("Rebooting because no internet detected")
os.system('reboot')
def dualactuator_sync(self, name, value):
"""Sync all data from the driver."""
self.force_send = True
# self.sendtodb("log", "synced", 0)
return True
def dualactuator_writeplctag(self, name, value):
"""Write a value to the PLC."""
new_val = json.loads(str(value).replace("'", '"'))
tag_n = str(new_val['tag']) # "cmd_Start"
val_n = new_val['val']
write_res = write_tag(str(PLC_IP_ADDRESS), tag_n, val_n, plc_type="Micro800")
print("Result of dualactuator_writeplctag(self, {}, {}) = {}".format(name, value, write_res))
if write_res is None:
write_res = "Error writing to PLC..."
return write_res

View File

@@ -0,0 +1,18 @@
"""Logging setup for dualactuator"""
import logging
from logging.handlers import RotatingFileHandler
import sys
log_formatter = logging.Formatter('%(asctime)s %(levelname)s %(funcName)s(%(lineno)d) %(message)s')
log_file = './dualactuator.log'
my_handler = RotatingFileHandler(log_file, mode='a', maxBytes=500*1024,
backupCount=2, encoding=None, delay=0)
my_handler.setFormatter(log_formatter)
my_handler.setLevel(logging.INFO)
filelogger = logging.getLogger('dualactuator')
filelogger.setLevel(logging.INFO)
filelogger.addHandler(my_handler)
console_out = logging.StreamHandler(sys.stdout)
console_out.setFormatter(log_formatter)
filelogger.addHandler(console_out)

View File

@@ -0,0 +1,21 @@
"""Data persistance functions."""
# if more advanced persistence is needed, use a sqlite database
import json
def load(filename="persist.json"):
"""Load persisted settings from the specified file."""
try:
with open(filename, 'r') as persist_file:
return json.load(persist_file)
except Exception:
return False
def store(persist_obj, filename="persist.json"):
"""Store the persisting settings into the specified file."""
try:
with open(filename, 'w') as persist_file:
return json.dump(persist_obj, persist_file, indent=4)
except Exception:
return False

52
dualactuator/utilities.py Normal file
View File

@@ -0,0 +1,52 @@
"""Utility functions for the driver."""
import socket
import struct
def get_public_ip_address():
"""Find the public IP Address of the host device."""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(("8.8.8.8", 80))
ip_address = sock.getsockname()[0]
sock.close()
except Exception as e:
return e
return ip_address
def int_to_float16(int_to_convert):
"""Convert integer into float16 representation."""
bin_rep = ('0' * 16 + '{0:b}'.format(int_to_convert))[-16:]
sign = 1.0
if int(bin_rep[0]) == 1:
sign = -1.0
exponent = float(int(bin_rep[1:6], 2))
fraction = float(int(bin_rep[6:17], 2))
if exponent == float(0b00000):
return sign * 2 ** -14 * fraction / (2.0 ** 10.0)
elif exponent == float(0b11111):
if fraction == 0:
return sign * float("inf")
return float("NaN")
frac_part = 1.0 + fraction / (2.0 ** 10.0)
return sign * (2 ** (exponent - 15)) * frac_part
def ints_to_float(int1, int2):
"""Convert 2 registers into a floating point number."""
mypack = struct.pack('>HH', int1, int2)
f_unpacked = struct.unpack('>f', mypack)
print("[{}, {}] >> {}".format(int1, int2, f_unpacked[0]))
return f_unpacked[0]
def degf_to_degc(temp_f):
"""Convert deg F to deg C."""
return (temp_f - 32.0) * (5.0/9.0)
def degc_to_degf(temp_c):
"""Convert deg C to deg F."""
return temp_c * 1.8 + 32.0

9
easttexas/config.txt Normal file
View File

@@ -0,0 +1,9 @@
{
"driverFileName":"easttexas.py",
"deviceName":"easttexas",
"driverId":"0110",
"releaseVersion":"1",
"files": {
"file1":"easttexas.py"}
}

49
easttexas/easttexas.py Normal file
View File

@@ -0,0 +1,49 @@
#!/usr/bin/python
import traceback
import threading
import time
import os
from device_base import deviceBase
from datetime import datetime
import requests
import json
import calendar
import pickle
import minimalmodbus
import minimalmodbusM1
class start(threading.Thread, deviceBase):
def __init__(self, name=None, number=None, mac=None, Q=None, mcu=None, companyId=None, offset=None, mqtt=None, Nodes=None):
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()
# Setup Modbus Connection
baud = 115200
mb_connected = False
while not mb_connected:
mb_connected = self.mcu.set232Baud(baud)
time.sleep(1)
ser232 = self.mcu.rs232
self.modbus_interface = minimalmodbusM1.Instrument(1, ser232)
self.modbus_interface.address = 1
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):
self.sendtodb("connected", "True", 0)
def run(self):
while True:
register = 31
test_coil = self.modbus_interface.read_bit(register, functionCode=1)
print("Here's the value from register {}: {}".format(register, test_coil))

297
email_reports/Channel.py Normal file
View File

@@ -0,0 +1,297 @@
"""Define Meshify channel class."""
import time
from pycomm.ab_comm.clx import Driver as ClxDriver
from pycomm.cip.cip_base import CommError, DataError
from file_logger import filelogger as log
TAG_DATAERROR_SLEEPTIME = 5
def binarray(intval):
"""Split an integer into its bits."""
bin_string = '{0:08b}'.format(intval)
bin_arr = [i for i in bin_string]
bin_arr.reverse()
return bin_arr
def read_tag(addr, tag, plc_type="CLX"):
"""Read a tag from the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
try:
if clx.open(addr, direct_connection=direct):
try:
val = clx.read_tag(tag)
return val
except DataError as err:
clx.close()
time.sleep(TAG_DATAERROR_SLEEPTIME)
log.error("Data Error during readTag({}, {}): {}".format(addr, tag, err))
except CommError:
# err = c.get_status()
clx.close()
log.error("Could not connect during readTag({}, {})".format(addr, tag))
except AttributeError as err:
clx.close()
log.error("AttributeError during readTag({}, {}): \n{}".format(addr, tag, err))
clx.close()
return False
def read_array(addr, tag, start, end, plc_type="CLX"):
"""Read an array from the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
if clx.open(addr, direct_connection=direct):
arr_vals = []
new_end = 0
try:
for i in range(start, end):
tag_w_index = tag + "[{}]".format(i)
val = clx.read_tag(tag_w_index)
new_end = i
if tag == "TransactionDay" and val[0] == 0:
break
arr_vals.append(round(val[0], 4))
if arr_vals:
return arr_vals,new_end + 1
else:
log.error("No length for {}".format(addr))
return arr_vals, new_end + 1
except Exception:
log.error("Error during readArray({}, {}, {}, {})".format(addr, tag, start, end))
err = clx.get_status()
clx.close()
log.error(err)
clx.close()
def write_tag(addr, tag, val, plc_type="CLX"):
"""Write a tag value to the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
try:
if clx.open(addr, direct_connection=direct):
try:
initial_val = clx.read_tag(tag)
write_status = clx.write_tag(tag, val, initial_val[1])
return write_status
except DataError as err:
clx_err = clx.get_status()
clx.close()
log.error("--\nDataError during writeTag({}, {}, {}, plc_type={}) -- {}\n{}\n".format(addr, tag, val, plc_type, err, clx_err))
except CommError as err:
clx_err = clx.get_status()
log.error("--\nCommError during write_tag({}, {}, {}, plc_type={})\n{}\n--".format(addr, tag, val, plc_type, err))
clx.close()
return False
class Channel(object):
"""Holds the configuration for a Meshify channel."""
def __init__(self, mesh_name, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False):
"""Initialize the channel."""
self.mesh_name = mesh_name
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
def __str__(self):
"""Create a string for the channel."""
return "{}\nvalue: {}, last_send_time: {}".format(self.mesh_name, self.value, self.last_send_time)
def check(self, new_value, force_send=False):
"""Check to see if the new_value needs to be stored."""
send_needed = False
send_reason = ""
if self.data_type == 'BOOL' or self.data_type == 'STRING':
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif self.value != new_value:
if self.map_:
if not self.value == self.map_[new_value]:
send_needed = True
send_reason = "value change"
else:
send_needed = True
send_reason = "value change"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
else:
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif abs(self.value - new_value) > self.chg_threshold:
send_needed = True
send_reason = "change threshold"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
if send_needed:
self.last_value = self.value
if self.map_:
try:
self.value = self.map_[new_value]
except KeyError:
log.error("Cannot find a map value for {} in {} for {}".format(new_value, self.map_, self.mesh_name))
self.value = new_value
else:
self.value = new_value
self.last_send_time = time.time()
log.info("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason))
return send_needed
def read(self):
"""Read the value."""
pass
def identity(sent):
"""Return exactly what was sent to it."""
return sent
class ModbusChannel(Channel):
"""Modbus channel object."""
def __init__(self, mesh_name, register_number, data_type, chg_threshold, guarantee_sec, channel_size=1, map_=False, write_enabled=False, transform_fn=identity):
"""Initialize the channel."""
super(ModbusChannel, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.mesh_name = mesh_name
self.register_number = register_number
self.channel_size = channel_size
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
self.transform_fn = transform_fn
def read(self, mbsvalue):
"""Return the transformed read value."""
return self.transform_fn(mbsvalue)
class PLCChannel(Channel):
"""PLC Channel Object."""
def __init__(self, ip, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False, plc_type='CLX'):
"""Initialize the channel."""
super(PLCChannel, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.plc_ip = ip
self.mesh_name = mesh_name
self.plc_tag = plc_tag
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
self.plc_type = plc_type
def read(self):
"""Read the value."""
plc_value = None
if self.plc_tag and self.plc_ip:
read_value = read_tag(self.plc_ip, self.plc_tag, plc_type=self.plc_type)
if read_value:
plc_value = read_value[0]
return plc_value
class BoolArrayChannels(Channel):
"""Hold the configuration for a set of boolean array channels."""
def __init__(self, ip, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False):
"""Initialize the channel."""
super(BoolArrayChannels, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.plc_ip = ip
self.mesh_name = mesh_name
self.plc_tag = plc_tag
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
def compare_values(self, new_val_dict):
"""Compare new values to old values to see if the values need storing."""
send = False
for idx in new_val_dict:
try:
if new_val_dict[idx] != self.last_value[idx]:
send = True
except KeyError:
log.error("Key Error in self.compare_values for index {}".format(idx))
send = True
return send
def read(self, force_send=False):
"""Read the value and check to see if needs to be stored."""
send_needed = False
send_reason = ""
if self.plc_tag:
val = read_tag(self.plc_ip, self.plc_tag)
if val:
bool_arr = binarray(val[0])
new_val = {}
for idx in self.map_:
try:
new_val[self.map_[idx]] = bool_arr[idx]
except KeyError:
log.error("Not able to get value for index {}".format(idx))
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif self.compare_values(new_val):
send_needed = True
send_reason = "value change"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
if send_needed:
self.value = new_val
self.last_value = self.value
self.last_send_time = time.time()
log.info("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason))
return send_needed

48
email_reports/Tags.py Normal file
View File

@@ -0,0 +1,48 @@
from Channel import PLCChannel, ModbusChannel
from email_reports import PLC_IP_ADDRESS
tags = [
PLCChannel(PLC_IP_ADDRESS, "driver_id","Driver_ID_This_Transcation","INT", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "transaction_day","TransactionDay","INT", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "transaction_hour","TransactionHour","INT", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "transaction_minute","TransactionMinute","INT", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "transaction_total","Total_Sold","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "today_total","Totalizer.Today_TotalFlow","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "yesterdays_total","Totalizer.Yesterdays_TotalFlow","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "monthly_total","Totalizer.Monthlys_TotalFlow","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "prevmonthly_total","Totalizer.PrevMonthlys_TotalFlow","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "year_total","Totalizer.Current_Year_Total_Sales","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "previous_year_total","Totalizer.Previous_Year_Total_Sales","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_1_today_total","Company_1.Today_TotalFlow","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_1_yesterdays_total","Company_1.Yesterdays_TotalFlow","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_1_monthly_total","Company_1.Monthlys_TotalFlow","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_1_prevmonthly_total","Company_1.PrevMonthlys_TotalFlow","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_1_year_total","Company_1.Current_Year_Total_Sales","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_1_previous_year_total","Company_1.Previous_Year_Total_Sales","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_2_today_total","Company_2.Today_TotalFlow","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_2_yesterdays_total","Company_2.Yesterdays_TotalFlow","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_2_monthly_total","Company_2.Monthlys_TotalFlow","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_2_prevmonthly_total","Company_2.PrevMonthlys_TotalFlow","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_2_year_total","Company_2.Current_Year_Total_Sales","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_2_previous_year_total","Company_2.Previous_Year_Total_Sales","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_3_today_total","Company_3.Today_TotalFlow","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_3_yesterdays_total","Company_3.Yesterdays_TotalFlow","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_3_monthly_total","Company_3.Monthlys_TotalFlow","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_3_prevmonthly_total","Company_3.PrevMonthlys_TotalFlow","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_3_year_total","Company_3.Current_Year_Total_Sales","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_3_previous_year_total","Company_3.Previous_Year_Total_Sales","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_4_today_total","Company_4.Today_TotalFlow","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_4_yesterdays_total","Company_4.Yesterdays_TotalFlow","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_4_monthly_total","Company_4.Monthlys_TotalFlow","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_4_prevmonthly_total","Company_4.PrevMonthlys_TotalFlow","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_4_year_total","Company_4.Current_Year_Total_Sales","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_4_previous_year_total","Company_4.Previous_Year_Total_Sales","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_5_today_total","Company_5.Today_TotalFlow","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_5_yesterdays_total","Company_5.Yesterdays_TotalFlow","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_5_monthly_total","Company_5.Monthlys_TotalFlow","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_5_prevmonthly_total","Company_5.PrevMonthlys_TotalFlow","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_5_year_total","Company_5.Current_Year_Total_Sales","REAL", 1, 3600, plc_type="CLX"),
PLCChannel(PLC_IP_ADDRESS, "company_5_previous_year_total","Company_5.Previous_Year_Total_Sales","REAL", 1, 3600, plc_type="CLX")
]

14
email_reports/config.txt Normal file
View File

@@ -0,0 +1,14 @@
{
"files": {
"file3": "file_logger.py",
"file2": "Channel.py",
"file1": "email_reports.py",
"file6": "persistence.py",
"file5": "utilities.py",
"file4": "Tags.py"
},
"deviceName": "email_reports",
"releaseVersion": "6",
"driverFileName": "email_reports.py",
"driverId": "0100"
}

View File

@@ -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)))

View File

@@ -0,0 +1,232 @@
"""Driver for email_reports"""
import threading
import json
import time
from random import randint
from datetime import datetime as dt
from device_base import deviceBase
from Channel import PLCChannel, ModbusChannel,read_tag, read_array, write_tag, TAG_DATAERROR_SLEEPTIME
import persistence
from utilities import get_public_ip_address, generate_report, send_email
from file_logger import filelogger as log
PLC_IP_ADDRESS = "192.168.1.10"
from Tags import tags
_ = None
log.info("email_reports startup")
# GLOBAL VARIABLES
WAIT_FOR_CONNECTION_SECONDS = 60
IP_CHECK_PERIOD = 60
WATCHDOG_ENABLE = False
WATCHDOG_CHECK_PERIOD = 60
WATCHDOG_SEND_PERIOD = 3600 # Seconds, the longest amount of time before sending the watchdog status
CHANNELS = tags
# PERSISTENCE FILE
PERSIST = persistence.load()
class start(threading.Thread, deviceBase):
"""Start class required by Meshify."""
def __init__(self, name=None, number=None, mac=None, Q=None, mcu=None,
companyId=None, offset=None, mqtt=None, Nodes=None):
"""Initialize the driver."""
threading.Thread.__init__(self)
deviceBase.__init__(self, name=name, number=number, mac=mac, Q=Q,
mcu=mcu, companyId=companyId, offset=offset,
mqtt=mqtt, Nodes=Nodes)
self.daemon = True
self.version = "5"
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("email_reports driver will start in {} seconds".format(WAIT_FOR_CONNECTION_SECONDS - i))
time.sleep(1)
log.info("BOOM! Starting email_reports driver...")
if not PERSIST:
PERSIST = {'yesterday': dt.today().day - 1,
'start': 0,
'end': 10000,
'recipients': ['nmelone@henry-pump.com']}
persistence.store(PERSIST)
#self._check_watchdog()
self._check_ip_address()
self.nodes["email_reports_0199"] = self
send_loops = 0
while True:
now = time.time()
if self.force_send:
log.warning("FORCE SEND: TRUE")
if self.isReportTime():
#read a full array from PLC
log.info("Gathering Timestamp between {} and {}".format(PERSIST['start'], PERSIST['end']))
trans_day, PERSIST['end'] = read_array(CHANNELS[1].plc_ip,CHANNELS[1].plc_tag,PERSIST['start'],PERSIST['end'],CHANNELS[1].plc_type)
trans_hour, PERSIST['end'] = read_array(CHANNELS[2].plc_ip,CHANNELS[1].plc_tag,PERSIST['start'],PERSIST['end'],CHANNELS[2].plc_type)
trans_min, PERSIST['end'] = read_array(CHANNELS[3].plc_ip,CHANNELS[3].plc_tag,PERSIST['start'],PERSIST['end'],CHANNELS[3].plc_type)
log.info("Gathering Driver IDs between {} and {}".format(PERSIST['start'], PERSIST['end']))
driver_id, PERSIST['end'] = read_array(CHANNELS[0].plc_ip, CHANNELS[0].plc_tag, PERSIST['start'], PERSIST['end'], CHANNELS[0].plc_type) #addr, tag, start, end, plc_type="CLX"
log.info("Gather Transaction Data between {} and {}".format(PERSIST['start'], PERSIST['end']))
trans_total, PERSIST['end'] = read_array(CHANNELS[4].plc_ip,CHANNELS[4].plc_tag,PERSIST['start'],PERSIST['end'],CHANNELS[4].plc_type)
PERSIST['start'] = PERSIST['end']
overall = {
'today_total': CHANNELS[5].read(),
'yesterday_total': CHANNELS[6].read(),
'monthly_total': CHANNELS[7].read(),
'prevmonthly_total': CHANNELS[8].read(),
'current_year_total': CHANNELS[9].read(),
'prev_year_total': CHANNELS[10].read()
}
company_1 = {
'today_total': CHANNELS[11].read(),
'yesterday_total': CHANNELS[12].read(),
'monthly_total': CHANNELS[13].read(),
'prevmonthly_total': CHANNELS[14].read(),
'current_year_total': CHANNELS[15].read(),
'prev_year_total': CHANNELS[16].read()
}
company_2 = {
'today_total': CHANNELS[17].read(),
'yesterday_total': CHANNELS[18].read(),
'monthly_total': CHANNELS[19].read(),
'prevmonthly_total': CHANNELS[20].read(),
'current_year_total': CHANNELS[21].read(),
'prev_year_total': CHANNELS[22].read()
}
company_3 = {
'today_total': CHANNELS[23].read(),
'yesterday_total': CHANNELS[24].read(),
'monthly_total': CHANNELS[25].read(),
'prevmonthly_total': CHANNELS[26].read(),
'current_year_total': CHANNELS[27].read(),
'prev_year_total': CHANNELS[28].read()
}
company_4 = {
'today_total': CHANNELS[29].read(),
'yesterday_total': CHANNELS[30].read(),
'monthly_total': CHANNELS[31].read(),
'prevmonthly_total': CHANNELS[32].read(),
'current_year_total': CHANNELS[33].read(),
'prev_year_total': CHANNELS[34].read()
}
company_5 = {
'today_total': CHANNELS[35].read(),
'yesterday_total': CHANNELS[36].read(),
'monthly_total': CHANNELS[37].read(),
'prevmonthly_total': CHANNELS[38].read(),
'current_year_total': CHANNELS[39].read(),
'prev_year_total': CHANNELS[40].read()
}
log.info("Generating Spreadsheet")
generate_report(driver_id,trans_day,trans_hour,trans_min, trans_total, overall, company_1, company_2, company_3, company_4, company_5)
log.info("Sending Emails")
send_email(PERSIST['recipients'])
PERSIST['yesterday'] = dt.today().day
persistence.store(PERSIST)
# print("email_reports driver still alive...")
if self.force_send:
if send_loops > 2:
log.warning("Turning off force_send")
self.force_send = False
send_loops = 0
else:
send_loops += 1
if WATCHDOG_ENABLE:
if (now - self.watchdog_last_checked) > WATCHDOG_CHECK_PERIOD:
self._check_watchdog()
if (now - self.public_ip_address_last_checked) > IP_CHECK_PERIOD:
self._check_ip_address()
def _check_watchdog(self):
"""Check the watchdog and send to Meshify if changed or stale."""
test_watchdog = self.email_reports_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, 'email_reports')
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, 'email_reports')
self.public_ip_address = test_public_ip
def email_reports_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="CLX")
time.sleep(1)
watchdog_val = read_tag(str(PLC_IP_ADDRESS), 'watchdog_INT', plc_type="CLX")
try:
return (randval - 1) == watchdog_val[0]
except (KeyError, TypeError):
return False
def email_reports_sync(self, name, value):
"""Sync all data from the driver."""
self.force_send = True
# self.sendtodb("log", "synced", 0)
return True
def email_reports_writeplctag(self, name, value):
"""Write a value to the PLC."""
new_val = json.loads(str(value).replace("'", '"'))
tag_n = str(new_val['tag']) # "cmd_Start"
val_n = new_val['val']
write_res = write_tag(str(PLC_IP_ADDRESS), tag_n, val_n, plc_type="CLX")
print("Result of email_reports_writeplctag(self, {}, {}) = {}".format(name, value, write_res))
if write_res is None:
write_res = "Error writing to PLC..."
return write_res
def isReportTime(self):
right_now = dt.today()
if right_now.hour == 23 and right_now.minute == 0 and not(right_now.day == PERSIST['yesterday']):
if right_now.day == 1:
PERSIST['start'] = 0
PERSIST['end'] = 10000
persistence.store(PERSIST)
return True
return False

View File

@@ -0,0 +1,18 @@
"""Logging setup for email_reports"""
import logging
from logging.handlers import RotatingFileHandler
import sys
log_formatter = logging.Formatter('%(asctime)s %(levelname)s %(funcName)s(%(lineno)d) %(message)s')
log_file = './email_reports.log'
my_handler = RotatingFileHandler(log_file, mode='a', maxBytes=500*1024,
backupCount=2, encoding=None, delay=0)
my_handler.setFormatter(log_formatter)
my_handler.setLevel(logging.INFO)
filelogger = logging.getLogger('email_reports')
filelogger.setLevel(logging.INFO)
filelogger.addHandler(my_handler)
console_out = logging.StreamHandler(sys.stdout)
console_out.setFormatter(log_formatter)
filelogger.addHandler(console_out)

View File

@@ -0,0 +1,21 @@
"""Data persistance functions."""
# if more advanced persistence is needed, use a sqlite database
import json
def load(filename="persist.json"):
"""Load persisted settings from the specified file."""
try:
with open(filename, 'r') as persist_file:
return json.load(persist_file)
except Exception:
return False
def store(persist_obj, filename="persist.json"):
"""Store the persisting settings into the specified file."""
try:
with open(filename, 'w') as persist_file:
return json.dump(persist_obj, persist_file, indent=4)
except Exception:
return False

192
email_reports/utilities.py Normal file
View File

@@ -0,0 +1,192 @@
"""Utility functions for the driver."""
import socket
import struct
import xlsxwriter
from datetime import datetime as dt
import email, smtplib, ssl
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
def get_public_ip_address():
"""Find the public IP Address of the host device."""
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(("8.8.8.8", 80))
ip_address = sock.getsockname()[0]
sock.close()
return ip_address
def int_to_float16(int_to_convert):
"""Convert integer into float16 representation."""
bin_rep = ('0' * 16 + '{0:b}'.format(int_to_convert))[-16:]
sign = 1.0
if int(bin_rep[0]) == 1:
sign = -1.0
exponent = float(int(bin_rep[1:6], 2))
fraction = float(int(bin_rep[6:17], 2))
if exponent == float(0b00000):
return sign * 2 ** -14 * fraction / (2.0 ** 10.0)
elif exponent == float(0b11111):
if fraction == 0:
return sign * float("inf")
return float("NaN")
frac_part = 1.0 + fraction / (2.0 ** 10.0)
return sign * (2 ** (exponent - 15)) * frac_part
def ints_to_float(int1, int2):
"""Convert 2 registers into a floating point number."""
mypack = struct.pack('>HH', int1, int2)
f_unpacked = struct.unpack('>f', mypack)
print("[{}, {}] >> {}".format(int1, int2, f_unpacked[0]))
return f_unpacked[0]
def degf_to_degc(temp_f):
"""Convert deg F to deg C."""
return (temp_f - 32.0) * (5.0/9.0)
def degc_to_degf(temp_c):
"""Convert deg C to deg F."""
return temp_c * 1.8 + 32.0
def generate_report(driver_id,trans_day,trans_hour,trans_min, trans_total,overall, company_1,company_2, company_3, company_4, company_5):
workbook = xlsxwriter.Workbook('daily_report.xlsx')
worksheet = workbook.add_worksheet()
bold = workbook.add_format({'bold': True})
row = 1
col = 0
worksheet.write('A1', 'Driver', bold)
worksheet.write('B1', 'Day', bold)
worksheet.write('C1', 'Time',bold)
worksheet.write('D1', 'Total', bold)
for i in range(len(trans_day)):
worksheet.write(row,col, driver_id[i])
worksheet.write(row,col+1,str(round(trans_day[i])).rstrip('0').rstrip('.') )
worksheet.write(row,col+2, ""+ str(round(trans_hour[i])).rstrip('0').rstrip('.')+":"+str(round(trans_min[i])).rstrip('0').rstrip('.'))
worksheet.write(row,col+3, trans_total[i])
row += 1
worksheet.write(row, col+1, "Today's Total", bold)
worksheet.write(row, col+2, "Yesterday's Total", bold)
worksheet.write(row, col+3, "Month's Total", bold)
worksheet.write(row, col+4, "Previous Month's Total", bold)
worksheet.write(row, col+5, "Current Year's Total", bold)
worksheet.write(row, col+6, "Previous Year's Total", bold)
row += 1
worksheet.write(row, col, "Company 1", bold)
worksheet.write(row, col+1, company_1['today_total'])
worksheet.write(row, col+2, company_1['yesterday_total'])
worksheet.write(row, col+3, company_1['monthly_total'])
worksheet.write(row, col+4, company_1['prevmonthly_total'])
worksheet.write(row, col+5, company_1['current_year_total'])
worksheet.write(row, col+6, company_1['prev_year_total'])
row += 1
worksheet.write(row, col, "Company 2", bold)
worksheet.write(row, col+1, company_2['today_total'])
worksheet.write(row, col+2, company_2['yesterday_total'])
worksheet.write(row, col+3, company_2['monthly_total'])
worksheet.write(row, col+4, company_2['prevmonthly_total'])
worksheet.write(row, col+5, company_2['current_year_total'])
worksheet.write(row, col+6, company_2['prev_year_total'])
row += 1
worksheet.write(row, col, "Company 3", bold)
worksheet.write(row, col+1, company_3['today_total'])
worksheet.write(row, col+2, company_3['yesterday_total'])
worksheet.write(row, col+3, company_3['monthly_total'])
worksheet.write(row, col+4, company_3['prevmonthly_total'])
worksheet.write(row, col+5, company_3['current_year_total'])
worksheet.write(row, col+6, company_3['prev_year_total'])
row += 1
worksheet.write(row, col, "Company 4", bold)
worksheet.write(row, col+1, company_4['today_total'])
worksheet.write(row, col+2, company_4['yesterday_total'])
worksheet.write(row, col+3, company_4['monthly_total'])
worksheet.write(row, col+4, company_4['prevmonthly_total'])
worksheet.write(row, col+5, company_4['current_year_total'])
worksheet.write(row, col+6, company_4['prev_year_total'])
row += 1
worksheet.write(row, col, "Company 5", bold)
worksheet.write(row, col+1, company_5['today_total'])
worksheet.write(row, col+2, company_5['yesterday_total'])
worksheet.write(row, col+3, company_5['monthly_total'])
worksheet.write(row, col+4, company_5['prevmonthly_total'])
worksheet.write(row, col+5, company_1['current_year_total'])
worksheet.write(row, col+6, company_1['prev_year_total'])
row +=1
worksheet.write(row, col, "Grand Total", bold)
worksheet.write(row, col+1, overall['today_total'])
worksheet.write(row, col+2, overall['yesterday_total'])
worksheet.write(row, col+3, overall['monthly_total'])
worksheet.write(row, col+4, overall['prevmonthly_total'])
worksheet.write(row, col+5, overall['current_year_total'])
worksheet.write(row, col+6, overall['prev_year_total'])
workbook.close()
def send_email(recipients):
subject = "Daily Sales Report"
body = "This is an automate email for M&W sales report"
smtp_user = "AKIA4QSVRJTZ4GXTPTMX"
smtp_pass = "BCodXCh7VY126D3/C3lu5SCSmq6pjjF2uxMxZmd4nQFJ"
sender_email = "alerts@henry-pump.com"
receiver_email = ", ".join(recipients)
# Create a multipart message and set headers
message = MIMEMultipart()
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = subject
message["Body"] = body
filename = "daily_report.xlsx"
# Open XLSX file in binary mode
with open(filename, "rb") as attachment:
# Add file as application/octet-stream
# Email client can usually download this automatically as attachment
part = MIMEBase("application", "octet-stream")
part.set_payload(attachment.read())
# Encode file in ASCII characters to send by email
encoders.encode_base64(part)
months = {
1: 'JAN',
2: 'FEB',
3: 'MAR',
4: 'APR',
5: 'MAY',
6: 'JUN',
7: 'JUL',
8: 'AUG',
9: 'SEP',
10: 'OCT',
11: 'NOV',
12: 'DEC'
}
# Add header as key/value pair to attachment part
part.add_header(
"Content-Disposition",
"attachment; filename= daily_report_{}_{}_{}.xlsx".format(dt.today().day, months.get(dt.today().month), dt.today().year),
)
# Add attachment to message and convert message to string
message.attach(part)
text = message.as_string()
# Log in to server using secure context and send email
server = smtplib.SMTP("email-smtp.us-east-1.amazonaws.com", 587)
server.ehlo()
server.starttls()
server.ehlo()
server.login(smtp_user,smtp_pass)
server.sendmail(sender_email, receiver_email.split(","), text)
server.quit()

11
flow-monitor/config.txt Normal file
View File

@@ -0,0 +1,11 @@
{
"files": {
"file3": "persistence.py",
"file2": "utilities.py",
"file1": "flow-monitor.py"
},
"deviceName": "flowmonitor",
"driverId": "0140",
"releaseVersion": "15",
"driverFileName": "flow-monitor.py"
}

View File

@@ -0,0 +1,627 @@
"""Driver for connecting Flow Monitor to Meshify."""
import threading
import time
from datetime import datetime
import sqlite3
import logging
import sys
import os
from device_base import deviceBase
import persistence
from utilities import get_public_ip_address
# LOGGING SETUP
from logging.handlers import RotatingFileHandler
log_formatter = logging.Formatter('%(asctime)s %(levelname)s %(funcName)s(%(lineno)d) %(message)s')
logFile = './flowmonitor.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('flowmonitor')
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("flowmonitor startup")
CREATE_FLOWDATA_TABLE = """CREATE TABLE flow_data (
id integer PRIMARY KEY,
gal_totalizer_value float,
bbl_totalizer_value float,
gal_monthly_totalizer float,
bbl_monthly_totalizer float,
last_measured_timestamp integer
);"""
INSERT_BLANK_FLOWDATA = """INSERT INTO flow_data (
id,
gal_totalizer_value,
bbl_totalizer_value,
gal_monthly_totalizer,
bbl_monthly_totalizer,
last_measured_timestamp)
VALUES (1, 0.0, 0.0, 0.0, 0.0, 0);"""
CLEAR_FLOWDATA = """UPDATE flow_data SET
gal_totalizer_value=0.0,
bbl_totalizer_value=0.0,
gal_monthly_totalizer=0.0,
bbl_monthly_totalizer=0.0,
last_measured_timestamp=0 WHERE id=1;"""
UPDATE_FLOWDATA = """UPDATE flow_data SET
gal_totalizer_value=?,
bbl_totalizer_value=?,
gal_monthly_totalizer=?,
bbl_monthly_totalizer=?,
last_measured_timestamp=? WHERE id=1"""
PERSIST = persistence.load()
def migrate_from_sqlite_to_json_scaling():
"""Migrate data from SQLite db to JSON, if needed"""
global PERSIST
try:
PERSIST['flow_raw_min']
logger.info("Don't need to migrate from SQLite to JSON.")
return False
except (KeyError, TypeError):
logger.info("Migrating from SQLite to JSON.")
if not PERSIST:
PERSIST = {}
conn = sqlite3.connect('/root/python_firmware/drivers/flow-monitor.db')
cursor = conn.cursor()
try:
cursor.execute('SELECT * FROM scaling_data WHERE id = 1') # dummy query for checking database
stored_data = cursor.fetchone()
flow_raw_min = stored_data[1]
flow_raw_max = stored_data[2]
flow_gpm_min = stored_data[3]
flow_gpm_max = stored_data[4]
except (sqlite3.OperationalError, TypeError):
logger.info("No stored data in SQLite for scaling, we'll just use default values")
return False
PERSIST["flow_raw_min"] = flow_raw_min
PERSIST["flow_raw_max"] = flow_raw_max
PERSIST["flow_gpm_min"] = flow_gpm_min
PERSIST["flow_gpm_max"] = flow_gpm_max
persistence.store(PERSIST)
class ChannelSimple(object):
"""Simple Meshify channel structure."""
def __init__(self, meshify_name, senddelta_value, senddelta_time):
"""Initialize the channel with variables."""
self.meshify_name = meshify_name
self.senddelta_time = senddelta_time
self.senddelta_value = senddelta_value
self.last_sent_value = None
self.last_sent_timestamp = 0
def check_if_send_needed(self, value, timestamp):
"""Check to see if the value needs to be pushed."""
if self.last_sent_value is None or self.last_sent_timestamp == 0:
return True
if abs(value - self.last_sent_value) > self.senddelta_value:
return True
if (timestamp - self.last_sent_timestamp) > self.senddelta_time:
return True
return False
def update(self, last_sent_value, last_sent_timestamp):
"""Update values after a push."""
self.last_sent_value = last_sent_value
self.last_sent_timestamp = last_sent_timestamp
def scale(raw_val, raw_min, raw_max, eu_min, eu_max):
"""Scale a raw value."""
if raw_val < raw_min:
raw_val = raw_min
if raw_val > raw_max:
raw_val = raw_max
slope = (eu_max - eu_min) / (raw_max - raw_min)
intercept = eu_max - (slope * raw_max)
return slope * raw_val + intercept
def is_today(tstamp):
"""Check if a given timestamp belongs to the current date."""
midnight_today = datetime.today().replace(hour=0, minute=0, second=0, microsecond=0)
midnight_ts = (midnight_today - datetime(1970, 1, 1)).total_seconds()
return tstamp >= midnight_ts
def is_thismonth(tstamp):
"""Check if a given timestamp belongs to the current month."""
today = datetime.today()
tstamp_date = datetime.fromtimestamp(tstamp)
return today.month == tstamp_date.month
class start(threading.Thread, deviceBase):
"""Start class required for driver."""
def __init__(self, name=None, number=None, mac=None, Q=None, mcu=None,
companyId=None, offset=None, mqtt=None, Nodes=None):
"""Initalize 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)
# Default Scaling Values for Flowmeter
self.flow_raw_min = 3.89
self.flow_raw_max = 19.54
self.flow_gpm_min = 0.0
self.flow_gpm_max = 100.0
self.pressure_raw_min = 0.00
self.pressure_raw_max = 10.00
self.pressure_psi_min = 0.00
self.pressure_psi_max = 600.0
self.GPM_IGNORE_LIMIT = 1.0
self.gpm_or_bpd = "gpm"
self.force_send = False
self.daemon = True
self.version = "15"
self.finished = threading.Event()
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.channels["status"]["last_value"] = ""
def run(self):
"""Run the driver."""
# Configuration Parameters
total_time_store_delta = 600 # seconds
flow_time_store_delta = 600 # seconds
pressure_time_store_delta = 600 # seconds
gal_per_bbl = 42.0
startup_wait_seconds = 30
ip_check_after = 3600 # check public IP address after an hour
# Initialization
gpm_val = 0.0
date_reset = False
month_reset = True # True because False was causing problems when starting up on the first of the month
gal_totalizer_value = 0.0
bbl_totalizer_value = 0.0
gal_monthly_totalizer = 0.0
bbl_monthly_totalizer = 0.0
# Channels
galtotal_ch = ChannelSimple('gal_total', 100.0, total_time_store_delta)
bbltotal_ch = ChannelSimple('bbl_total', galtotal_ch.senddelta_value/gal_per_bbl, total_time_store_delta)
galtotalthismonth_ch = ChannelSimple('gal_total_thismonth', galtotal_ch.senddelta_value, total_time_store_delta)
bbltotalthismonth_ch = ChannelSimple('bbl_total_thismonth', galtotalthismonth_ch.senddelta_value/gal_per_bbl, total_time_store_delta)
gpmflow_ch = ChannelSimple('gpm_flow', 10.0, flow_time_store_delta)
bpdflow_ch = ChannelSimple('bpd_flow', gpmflow_ch.senddelta_value * 34.2857, flow_time_store_delta)
psipressure_ch = ChannelSimple('psi_pressure', 10.0, pressure_time_store_delta)
runstatus_ch = ChannelSimple('run_status', 0.5, 600)
# Startup timer.
# Waits for connection to Meshify before attempting to send data
wait_loops = 0
while wait_loops < startup_wait_seconds:
logger.info("Waiting to start flowmonitor driver in {} seconds".format(startup_wait_seconds - wait_loops))
wait_loops += 1
time.sleep(1)
##############################################
# THIS IS THE ACTUAL DRIVER CODE
# (executes after waiting for startup timer to complete)
##############################################
# Determine public IP address and send to Meshify
public_ip_address = get_public_ip_address()
self.sendtodb('public_ip_address', public_ip_address, 0)
ip_checked_time = time.time()
# Attempt to retrieve data stored in the database
last_measured_timestamp = time.time()
conn = sqlite3.connect('/root/python_firmware/drivers/flow-monitor.db')
cursor = conn.cursor()
try:
cursor.execute('SELECT * FROM flow_data WHERE id = 1') # dummy query for checking database
stored_data = cursor.fetchone()
gal_totalizer_value = stored_data[1]
bbl_totalizer_value = stored_data[2]
gal_monthly_totalizer = stored_data[3]
bbl_monthly_totalizer = stored_data[4]
last_measured_timestamp = stored_data[5]
except sqlite3.OperationalError:
# Caught if the table does not exist in the database.
logger.warning("No table flow_data in the database. I'll create it now.")
cursor.execute(CREATE_FLOWDATA_TABLE)
cursor.execute(INSERT_BLANK_FLOWDATA)
conn.commit()
except IndexError:
# Reset the database if the correct size data is not in the database.
self.flowmonitor_resetdatabase(None, None)
# Load scaling data
migrate_from_sqlite_to_json_scaling()
try:
self.gpm_or_bpd = PERSIST["gpm_or_bpd"]
except KeyError:
logger.warning("No GPM or BPD read configuration in persist file. We'll add some now")
PERSIST["gpm_or_bpd"] = self.gpm_or_bpd
persistence.store(PERSIST)
self.sendtodb("gpmorbpd", self.gpm_or_bpd, 0)
try:
self.flow_raw_min = PERSIST["flow_raw_min"]
self.flow_raw_max = PERSIST["flow_raw_max"]
self.flow_gpm_min = PERSIST["flow_gpm_min"]
self.flow_gpm_max = PERSIST["flow_gpm_max"]
except KeyError:
logger.warning("No flow scaling data exists in persist file. We'll add some now")
PERSIST["flow_raw_min"] = self.flow_raw_min
PERSIST["flow_raw_max"] = self.flow_raw_max
PERSIST["flow_gpm_min"] = self.flow_gpm_min
PERSIST["flow_gpm_max"] = self.flow_gpm_max
persistence.store(PERSIST)
self.sendtodb("setrawmin", self.flow_raw_min, 0)
self.sendtodb("setrawmax", self.flow_raw_max, 0)
self.sendtodb("setgpmmin", self.flow_gpm_min, 0)
self.sendtodb("setgpmmax", self.flow_gpm_max, 0)
try:
self.pressure_raw_min = PERSIST["pressure_raw_min"]
self.pressure_raw_max = PERSIST["pressure_raw_max"]
self.pressure_psi_min = PERSIST["pressure_psi_min"]
self.pressure_psi_max = PERSIST["pressure_psi_max"]
except KeyError:
logger.warning("No flow scaling data exists in persist file. We'll add some now")
PERSIST["pressure_raw_min"] = self.pressure_raw_min
PERSIST["pressure_raw_max"] = self.pressure_raw_max
PERSIST["pressure_psi_min"] = self.pressure_psi_min
PERSIST["pressure_psi_max"] = self.pressure_psi_max
persistence.store(PERSIST)
self.sendtodb("setpressurerawmin", self.pressure_raw_min, 0)
self.sendtodb("setpressurerawmax", self.pressure_raw_max, 0)
self.sendtodb("setpressurepsimin", self.pressure_psi_min, 0)
self.sendtodb("setpressurepsimax", self.pressure_psi_max, 0)
try:
self.GPM_IGNORE_LIMIT = PERSIST['gpm_ignore_limit']
except Exception:
logger.warning("no persisted GPM Ignore Limit. Using default of {}".format(self.GPM_IGNORE_LIMIT))
self.flowmonitor_setgpmignorelimit("gpm_ignore_limit", self.GPM_IGNORE_LIMIT)
# on bootup, if the day has changed, clear the totalizers.
# this would happen if the device is off when the time changes to Midnight.
if not is_today(last_measured_timestamp):
gal_totalizer_value = 0.0
bbl_totalizer_value = 0.0
last_measured_timestamp = time.time()
send_loops = 0
# DRIVER LOOP
while True:
try:
mcu_status = self.mcu.getDict() # Gets a dictionary of the IO states
# {
# 'bat': u'23.10',
# 'ver': u'Mar 16 2016 21:29:31',
# 'dout3': 'Off',
# 'temp': u'40.37',
# 'vin': u'24.6',
# 'pulse': u'0',
# 'dout4': 'Off',
# 'dout1': 'Off',
# 'din2': 'Off',
# 'din1': 'Off',
# 'dout2': 'On',
# 'cloop': u'0.0',
# 'analog4': u'0.0',
# 'analog3': u'0.0',
# 'analog2': u'0.0',
# 'analog1': u'0.0',
# 'relay1': 'Off'
# }
cloop_val = float(mcu_status['cloop'])
analog1_val = float(mcu_status['analog1'])
din1_val = 1 if mcu_status['din1'] == 'On' else 0 # Check DIGITAL INPUT 1 for run status
scaled_cloop = scale(cloop_val, self.flow_raw_min, self.flow_raw_max, self.flow_gpm_min, self.flow_gpm_max)
psi_val = scale(analog1_val, self.pressure_raw_min, self.pressure_raw_max, self.pressure_psi_min, self.pressure_psi_max)
if din1_val == 0 and scaled_cloop > 10:
din1_val = 1
if gpm_val < self.GPM_IGNORE_LIMIT:
gpm_val = 0
if self.gpm_or_bpd == "gpm":
gpm_val = scaled_cloop
bpd_val = (gpm_val / gal_per_bbl) * 60.0 * 24.0 # Computes BPD from GPM
else:
bpd_val = scaled_cloop
gpm_val = (((bpd_val * gal_per_bbl) / 24) / 60) # Computes GPM from BPD
now = time.time()
time_diff = now - last_measured_timestamp
if time_diff > 0 and time_diff < 180:
# Volume flowed since last measuring
gal_flow_delta = (time_diff / 60.0) * gpm_val
bbl_flow_delta = (time_diff / 60.0) * (1.0 / 60.0) * (1.0 / 24.0) * bpd_val
# Increment totalizers
gal_totalizer_value += gal_flow_delta
bbl_totalizer_value += bbl_flow_delta
gal_monthly_totalizer += gal_flow_delta
bbl_monthly_totalizer += bbl_flow_delta
elif time_diff < 0:
#negative time difference means clock got reset or somehow went the wrong way
try:
os.system("/usr/sbin/ntpdate pool.ntp.org")
except:
dt = datetime.fromtimestamp(last_measured_timestamp)
os.system('date -s "{}-{}-{} {}:{}:{}"'.format(dt.year,dt.month,dt.day,dt.hour,dt.minute,dt.second))
now = time.time()
last_measured_timestamp = now
# Update the database with the most recent totalizer values.
cursor.execute(UPDATE_FLOWDATA, (gal_totalizer_value, bbl_totalizer_value, gal_monthly_totalizer, bbl_monthly_totalizer, last_measured_timestamp))
conn.commit()
logger.info('gpm: {}, bpd: {}, psi: {}, gal: {}, bbl:{}, month_gal:{}, month_bbl:{}'.format(
gpm_val, bpd_val, psi_val, gal_totalizer_value, bbl_totalizer_value, gal_monthly_totalizer, bbl_monthly_totalizer))
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
# Channel Checks:
# check to see if the value needs to be sent to Meshify
if galtotal_ch.check_if_send_needed(gal_totalizer_value, now) or self.force_send:
self.sendtodb(galtotal_ch.meshify_name, gal_totalizer_value, 0)
galtotal_ch.update(gal_totalizer_value, now)
if bbltotal_ch.check_if_send_needed(bbl_totalizer_value, now) or self.force_send:
self.sendtodb(bbltotal_ch.meshify_name, bbl_totalizer_value, 0)
bbltotal_ch.update(bbl_totalizer_value, now)
if galtotalthismonth_ch.check_if_send_needed(gal_monthly_totalizer, now) or self.force_send:
self.sendtodb(galtotalthismonth_ch.meshify_name, gal_monthly_totalizer, 0)
galtotalthismonth_ch.update(gal_monthly_totalizer, now)
if bbltotalthismonth_ch.check_if_send_needed(bbl_monthly_totalizer, now) or self.force_send:
self.sendtodb(bbltotalthismonth_ch.meshify_name, bbl_monthly_totalizer, 0)
bbltotalthismonth_ch.update(bbl_monthly_totalizer, now)
if gpmflow_ch.check_if_send_needed(gpm_val, now) or self.force_send:
self.sendtodb(gpmflow_ch.meshify_name, gpm_val, 0)
gpmflow_ch.update(gpm_val, now)
if bpdflow_ch.check_if_send_needed(bpd_val, now) or self.force_send:
self.sendtodb(bpdflow_ch.meshify_name, bpd_val, 0)
bpdflow_ch.update(bpd_val, now)
if psipressure_ch.check_if_send_needed(psi_val, now) or self.force_send:
self.sendtodb(psipressure_ch.meshify_name, psi_val, 0)
psipressure_ch.update(psi_val, now)
if runstatus_ch.check_if_send_needed(din1_val, now) or self.force_send:
self.sendtodb(runstatus_ch.meshify_name, din1_val, 0)
runstatus_ch.update(din1_val, now)
# Check for the clock hitting midnight for resetting the daily totalizer value
if time.localtime(now)[3] == 0 and not date_reset:
self.sendtodb('gal_total_yesterday', gal_totalizer_value, 0)
self.sendtodb('bbl_total_yesterday', bbl_totalizer_value, 0)
gal_totalizer_value = 0.0
bbl_totalizer_value = 0.0
# Update the database with cleared values
cursor.execute(UPDATE_FLOWDATA, (gal_totalizer_value, bbl_totalizer_value, gal_monthly_totalizer, bbl_monthly_totalizer, last_measured_timestamp))
conn.commit()
date_reset = True
# Once the hour goes to anything other than 0 (midnight), unset the Date Reset status
if time.localtime(now)[3] != 0 and date_reset:
date_reset = False
# Check for a new month for resetting the monthly totalizers
if time.localtime(now)[2] == 1 and not month_reset:
self.sendtodb('gal_total_lastmonth', gal_monthly_totalizer, 0)
self.sendtodb('bbl_total_lastmonth', bbl_monthly_totalizer, 0)
gal_monthly_totalizer = 0.0
bbl_monthly_totalizer = 0.0
# Update the database with cleared values
cursor.execute(UPDATE_FLOWDATA, (gal_totalizer_value, bbl_totalizer_value, gal_monthly_totalizer, bbl_monthly_totalizer, last_measured_timestamp))
conn.commit()
month_reset = True
# once it's no longer the 1st of the month, unset the Month Reset status
if time.localtime(now)[2] != 1 and month_reset:
month_reset = False
# Periodically check the public IP address to see if it has changed.
if (now - ip_checked_time) > ip_check_after:
test_public_ip = get_public_ip_address()
if not test_public_ip == public_ip_address:
self.sendtodb('public_ip_address', test_public_ip, 0)
public_ip_address = test_public_ip
ip_checked_time = now
if mcu_status['din2'] == 'On':
self.flowmonitor_startcmd(None, None)
if mcu_status['din1'] == 'Off' and mcu_status['relay1'] == "On":
self.flowmonitor_stopcmd(None, None)
except Exception as e:
logger.error("problem in the driver: {}".format(e))
time.sleep(2)
def flowmonitor_sync(self, name, value):
"""Sync all data from the driver."""
self.force_send = True
# self.sendtodb("log", "synced", 0)
return True
def flowmonitor_startcmd(self, name, value):
"""Start the well."""
self.mcu.relay1(str(1))
return True
def flowmonitor_stopcmd(self, name, value):
"""Stop the well."""
self.mcu.relay1(str(0))
return True
def flowmonitor_resetdatabase(self, name, value):
"""Reset the database back to blank."""
conn = sqlite3.connect('/root/python_firmware/drivers/flow-monitor.db')
cursor = conn.cursor()
try:
cursor.execute('SELECT * FROM flow_data WHERE id = 1')
cursor.execute('DROP TABLE flow_data')
cursor.execute(CREATE_FLOWDATA_TABLE)
cursor.execute(INSERT_BLANK_FLOWDATA)
conn.commit()
logger.info("DATABASE HAS BEEN RESET!")
except sqlite3.OperationalError:
logger.warning("No table flow_data in the database. I'll create it now.")
cursor.execute(CREATE_FLOWDATA_TABLE)
cursor.execute(INSERT_BLANK_FLOWDATA)
conn.commit()
return(True)
def flowmonitor_setrawmin(self, name, value):
"""Set the raw min scaling value."""
try:
self.flow_raw_min = float(value)
self.sendtodb("setrawmin", self.flow_raw_min, 0)
PERSIST['flow_raw_min'] = self.flow_raw_min
persistence.store(PERSIST)
except Exception as e:
logger.error("Could not set self.flow_raw_min: {}".format(e))
return(True)
def flowmonitor_setrawmax(self, name, value):
"""Set the raw max scaling value."""
try:
self.flow_raw_max = float(value)
self.sendtodb("setrawmax", self.flow_raw_max, 0)
PERSIST['flow_raw_max'] = self.flow_raw_max
persistence.store(PERSIST)
except Exception as e:
logger.error("Could not set self.flow_raw_max: {}".format(e))
return(True)
def flowmonitor_setgpmmin(self, name, value):
"""Set the gpm min scaling value."""
try:
self.flow_gpm_min = float(value)
self.sendtodb("setgpmmin", self.flow_gpm_min, 0)
PERSIST['flow_gpm_min'] = self.flow_gpm_min
persistence.store(PERSIST)
except Exception as e:
logger.error("Could not set self.flow_gpm_min: {}".format(e))
return(True)
def flowmonitor_setgpmmax(self, name, value):
"""Set the gpm max scaling value."""
try:
self.flow_gpm_max = float(value)
self.sendtodb("setgpmmax", self.flow_gpm_max, 0)
PERSIST['flow_gpm_max'] = self.flow_gpm_max
persistence.store(PERSIST)
except Exception as e:
logger.error("Could not set self.flow_gpm_max: {}".format(e))
return(True)
def flowmonitor_setpressurerawmin(self, name, value):
"""Set the pressure raw min scaling value."""
try:
self.pressure_raw_min = float(value)
self.sendtodb("setpressurerawmin", self.pressure_raw_min, 0)
PERSIST['pressure_raw_min'] = self.pressure_raw_min
persistence.store(PERSIST)
except Exception as e:
logger.error("Could not set self.pressure_raw_min: {}".format(e))
return(True)
def flowmonitor_setpressurerawmax(self, name, value):
"""Set the pressure raw max scaling value."""
try:
self.pressure_raw_max = float(value)
self.sendtodb("setpressurerawmax", self.pressure_raw_max, 0)
PERSIST['pressure_raw_max'] = self.pressure_raw_max
persistence.store(PERSIST)
except Exception as e:
logger.error("Could not set self.pressure_raw_max: {}".format(e))
return(True)
def flowmonitor_setpressurepsimin(self, name, value):
"""Set the pressure psi min scaling value."""
try:
self.pressure_psi_min = float(value)
self.sendtodb("setpressurepsimin", self.pressure_psi_min, 0)
PERSIST['pressure_psi_min'] = self.pressure_psi_min
persistence.store(PERSIST)
except Exception as e:
logger.error("Could not set self.pressure_psi_min: {}".format(e))
return(True)
def flowmonitor_setpressurepsimax(self, name, value):
"""Set the pressure psi max scaling value."""
try:
self.pressure_psi_max = float(value)
self.sendtodb("setpressurepsimax", self.pressure_psi_max, 0)
PERSIST['pressure_psi_max'] = self.pressure_psi_max
persistence.store(PERSIST)
except Exception as e:
logger.error("Could not set self.pressure_psi_max: {}".format(e))
return(True)
def flowmonitor_setgpmignorelimit(self, name, value):
"""Set the GPM Ignore Limit."""
try:
self.GPM_IGNORE_LIMIT = float(value)
self.sendtodb("setgpmignorelimit", self.GPM_IGNORE_LIMIT, 0)
return True
except Exception as e:
logger.error("Error during flowmonitor_setgpmignorelimit: {}".format(e))
return False
def flowmonitor_gpmorbpd(self, name, value):
"""Set the read in value to GPM or BPD"""
try:
self.gpm_or_bpd = str(value)
self.sendtodb("gpmorbpd", self.gpm_or_bpd, 0)
return True
except Exception as e:
logger.error("Error during flowmonitor_setgpmorbpd: {}".format(e))
return False

View File

@@ -0,0 +1,21 @@
"""Data persistance functions."""
# if more advanced persistence is needed, use a sqlite database
import json
def load(filename="persist.json"):
"""Load persisted settings from the specified file."""
try:
with open(filename, 'r') as persist_file:
return json.load(persist_file)
except Exception:
return False
def store(persist_obj, filename="persist.json"):
"""Store the persisting settings into the specified file."""
try:
with open(filename, 'w') as persist_file:
return json.dump(persist_obj, persist_file, indent=4)
except Exception:
return False

11
flow-monitor/utilities.py Normal file
View File

@@ -0,0 +1,11 @@
"""Utility functions for the driver."""
import socket
def get_public_ip_address():
"""Find the public IP Address of the host device."""
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(("8.8.8.8", 80))
public_ip = sock.getsockname()[0]
sock.close()
return public_ip

298
flowmeterskid/Channel.py Normal file
View File

@@ -0,0 +1,298 @@
"""Define Meshify channel class."""
import time
import urllib
from pycomm.ab_comm.clx import Driver as ClxDriver
from pycomm.cip.cip_base import CommError, DataError
from file_logger import filelogger as log
TAG_DATAERROR_SLEEPTIME = 5
def binarray(intval):
"""Split an integer into its bits."""
bin_string = '{0:08b}'.format(intval)
bin_arr = [i for i in bin_string]
bin_arr.reverse()
return bin_arr
def read_tag(addr, tag, plc_type="CLX"):
"""Read a tag from the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
try:
if clx.open(addr, direct_connection=direct):
try:
val = clx.read_tag(tag)
clx.close()
return val
except DataError as err:
clx.close()
time.sleep(TAG_DATAERROR_SLEEPTIME)
log.error("Data Error during readTag({}, {}): {}".format(addr, tag, err))
except CommError:
# err = c.get_status()
clx.close()
log.error("Could not connect during readTag({}, {})".format(addr, tag))
except AttributeError as err:
clx.close()
log.error("AttributeError during readTag({}, {}): \n{}".format(addr, tag, err))
clx.close()
return False
def read_array(addr, tag, start, end, plc_type="CLX"):
"""Read an array from the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
if clx.open(addr, direct_connection=direct):
arr_vals = []
try:
for i in range(start, end):
tag_w_index = tag + "[{}]".format(i)
val = clx.read_tag(tag_w_index)
arr_vals.append(round(val[0], 4))
if arr_vals:
clx.close()
return arr_vals
else:
log.error("No length for {}".format(addr))
clx.close()
return False
except Exception:
log.error("Error during readArray({}, {}, {}, {})".format(addr, tag, start, end))
err = clx.get_status()
clx.close()
log.error(err)
clx.close()
def write_tag(addr, tag, val, plc_type="CLX"):
"""Write a tag value to the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
try:
if clx.open(addr, direct_connection=direct):
try:
initial_val = clx.read_tag(tag)
write_status = clx.write_tag(tag, val, initial_val[1])
clx.close()
return write_status
except DataError as err:
clx_err = clx.get_status()
clx.close()
log.error("--\nDataError during writeTag({}, {}, {}, plc_type={}) -- {}\n{}\n".format(addr, tag, val, plc_type, err, clx_err))
except CommError as err:
clx_err = clx.get_status()
log.error("--\nCommError during write_tag({}, {}, {}, plc_type={})\n{}\n--".format(addr, tag, val, plc_type, err))
clx.close()
return False
class Channel(object):
"""Holds the configuration for a Meshify channel."""
def __init__(self, mesh_name, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False):
"""Initialize the channel."""
self.mesh_name = mesh_name
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
def __str__(self):
"""Create a string for the channel."""
return "{}\nvalue: {}, last_send_time: {}".format(self.mesh_name, self.value, self.last_send_time)
def check(self, new_value, force_send=False):
"""Check to see if the new_value needs to be stored."""
send_needed = False
send_reason = ""
if self.data_type == 'BOOL' or self.data_type == 'STRING':
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif self.value != new_value:
if self.map_:
if not self.value == self.map_[new_value]:
send_needed = True
send_reason = "value change"
else:
send_needed = True
send_reason = "value change"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
else:
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif abs(self.value - new_value) > self.chg_threshold:
send_needed = True
send_reason = "change threshold"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
if send_needed:
self.last_value = self.value
if self.map_:
try:
self.value = self.map_[new_value]
except KeyError:
log.error("Cannot find a map value for {} in {} for {}".format(new_value, self.map_, self.mesh_name))
self.value = new_value
else:
self.value = new_value
self.last_send_time = time.time()
log.info("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason))
return send_needed
def read(self):
"""Read the value."""
pass
def identity(sent):
"""Return exactly what was sent to it."""
return sent
class ModbusChannel(Channel):
"""Modbus channel object."""
def __init__(self, mesh_name, register_number, data_type, chg_threshold, guarantee_sec, channel_size=1, map_=False, write_enabled=False, transform_fn=identity):
"""Initialize the channel."""
super(ModbusChannel, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.mesh_name = mesh_name
self.register_number = register_number
self.channel_size = channel_size
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
self.transform_fn = transform_fn
def read(self, mbsvalue):
"""Return the transformed read value."""
return self.transform_fn(mbsvalue)
class PLCChannel(Channel):
"""PLC Channel Object."""
def __init__(self, ip, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False, plc_type='CLX'):
"""Initialize the channel."""
super(PLCChannel, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.plc_ip = ip
self.mesh_name = mesh_name
self.plc_tag = plc_tag
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
self.plc_type = plc_type
def read(self):
"""Read the value."""
plc_value = None
if self.plc_tag and self.plc_ip:
read_value = read_tag(self.plc_ip, self.plc_tag, plc_type=self.plc_type)
if read_value:
plc_value = read_value[0]
return plc_value
class BoolArrayChannels(Channel):
"""Hold the configuration for a set of boolean array channels."""
def __init__(self, ip, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False):
"""Initialize the channel."""
super(BoolArrayChannels, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.plc_ip = ip
self.mesh_name = mesh_name
self.plc_tag = plc_tag
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
def compare_values(self, new_val_dict):
"""Compare new values to old values to see if the values need storing."""
send = False
for idx in new_val_dict:
try:
if new_val_dict[idx] != self.last_value[idx]:
send = True
except KeyError:
log.error("Key Error in self.compare_values for index {}".format(idx))
send = True
return send
def read(self, force_send=False):
"""Read the value and check to see if needs to be stored."""
send_needed = False
send_reason = ""
if self.plc_tag:
val = read_tag(self.plc_ip, self.plc_tag)
if val:
bool_arr = binarray(val[0])
new_val = {}
for idx in self.map_:
try:
new_val[self.map_[idx]] = bool_arr[idx]
except KeyError:
log.error("Not able to get value for index {}".format(idx))
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif self.compare_values(new_val):
send_needed = True
send_reason = "value change"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
if send_needed:
self.value = new_val
self.last_value = self.value
self.last_send_time = time.time()
log.info("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason))
return send_needed

23
flowmeterskid/Tags.py Normal file
View File

@@ -0,0 +1,23 @@
from Channel import PLCChannel, ModbusChannel
from flowmeterskid import PLC_IP_ADDRESS
tags = [
PLCChannel(PLC_IP_ADDRESS, "val_pt1","Val_PT1_Scaled","REAL", 5, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "val_pt2","Val_PT2_Scaled","REAL", 5, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "val_fm1_fr","Val_Flowmeter_1_FR","REAL", 5, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "val_fm1_t1","Val_Flowmeter_1_T1","REAL", 50, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "val_fm1_t2","Val_Flowmeter_1_T2","REAL", 50, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "val_fm1_t3","Val_Flowmeter_1_T3","REAL", 50, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "val_fm1_yesterday","Val_Flowmeter_1_T1_Yesterdays","REAL", 1, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "val_fm1_today","Val_Flowmeter_1_T1_Todays","REAL", 25, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "val_fm1_lastmonth","Val_Flowmeter_1_T1_LastMonth","REAL", 1, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "val_fm1_month","Val_Flowmeter_1_T1_Months","REAL", 100, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "val_fm2_fr","Val_Flowmeter_2_FR","REAL", 5, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "val_fm2_t1","Val_Flowmeter_2_T1","REAL", 50, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "val_fm2_t2","Val_Flowmeter_2_T2","REAL", 50, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "val_fm2_t3","Val_Flowmeter_2_T3","REAL", 50, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "val_fm2_yesterday","Val_Flowmeter_2_T1_Yesterdays","REAL", 1, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "val_fm2_today","Val_Flowmeter_2_T1_Todays","REAL", 25, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "val_fm2_lastmonth","Val_Flowmeter_2_T1_LastMonth","REAL", 1, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "val_fm2_month","Val_Flowmeter_2_T1_Months","REAL", 100, 3600, plc_type="Micro800")
]

14
flowmeterskid/config.txt Normal file
View File

@@ -0,0 +1,14 @@
{
"files": {
"file3": "file_logger.py",
"file2": "Channel.py",
"file1": "flowmeterskid.py",
"file6": "persistence.py",
"file5": "utilities.py",
"file4": "Tags.py"
},
"deviceName": "flowmeterskid",
"releaseVersion": "1",
"driverFileName": "flowmeterskid.py",
"driverId": "0100"
}

View File

@@ -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)))

View File

@@ -0,0 +1,18 @@
"""Logging setup for flowmeterskid"""
import logging
from logging.handlers import RotatingFileHandler
import sys
log_formatter = logging.Formatter('%(asctime)s %(levelname)s %(funcName)s(%(lineno)d) %(message)s')
log_file = './flowmeterskid.log'
my_handler = RotatingFileHandler(log_file, mode='a', maxBytes=500*1024,
backupCount=2, encoding=None, delay=0)
my_handler.setFormatter(log_formatter)
my_handler.setLevel(logging.INFO)
filelogger = logging.getLogger('flowmeterskid')
filelogger.setLevel(logging.INFO)
filelogger.addHandler(my_handler)
console_out = logging.StreamHandler(sys.stdout)
console_out.setFormatter(log_formatter)
filelogger.addHandler(console_out)

View File

@@ -0,0 +1,133 @@
"""Driver for flowmeterskid"""
import threading
import json
import time
from random import randint
import os
from device_base import deviceBase
from Channel import PLCChannel, ModbusChannel,read_tag, write_tag, TAG_DATAERROR_SLEEPTIME
import persistence
from utilities import get_public_ip_address
from file_logger import filelogger as log
PLC_IP_ADDRESS = "192.168.1.12"
from Tags import tags
_ = None
log.info("flowmeterskid startup")
# GLOBAL VARIABLES
WAIT_FOR_CONNECTION_SECONDS = 60
IP_CHECK_PERIOD = 60
CHANNELS = tags
# PERSISTENCE FILE
PERSIST = persistence.load()
class start(threading.Thread, deviceBase):
"""Start class required by Meshify."""
def __init__(self, name=None, number=None, mac=None, Q=None, mcu=None,
companyId=None, offset=None, mqtt=None, Nodes=None):
"""Initialize the driver."""
threading.Thread.__init__(self)
deviceBase.__init__(self, name=name, number=number, mac=mac, Q=Q,
mcu=mcu, companyId=companyId, offset=offset,
mqtt=mqtt, Nodes=Nodes)
self.daemon = True
self.version = "1"
self.finished = threading.Event()
self.force_send = False
self.public_ip_address = ""
self.public_ip_address_last_checked = 0
threading.Thread.start(self)
# this is a required function for all drivers, its goal is to upload some piece of data
# about your device so it can be seen on the web
def register(self):
"""Register the driver."""
# self.sendtodb("log", "BOOM! Booted.", 0)
pass
def run(self):
"""Actually run the driver."""
for i in range(0, WAIT_FOR_CONNECTION_SECONDS):
print("flowmeterskid driver will start in {} seconds".format(WAIT_FOR_CONNECTION_SECONDS - i))
time.sleep(1)
log.info("BOOM! Starting flowmeterskid driver...")
self._check_ip_address()
self.nodes["flowmeterskid_0199"] = self
send_loops = 0
while True:
now = time.time()
if self.force_send:
log.warning("FORCE SEND: TRUE")
for chan in CHANNELS:
val = chan.read()
if chan.check(val, self.force_send):
self.sendtodbDev(1, chan.mesh_name, chan.value, 0, 'flowmeterskid')
#time.sleep(TAG_DATAERROR_SLEEPTIME) # sleep to allow Micro800 to handle ENET requests
# print("flowmeterskid driver still alive...")
if self.force_send:
if send_loops > 2:
log.warning("Turning off force_send")
self.force_send = False
send_loops = 0
else:
send_loops += 1
time.sleep(10)
if (now - self.public_ip_address_last_checked) > IP_CHECK_PERIOD:
self._check_ip_address()
def _check_ip_address(self):
"""Check the public IP address and send to Meshify if changed."""
self.public_ip_address_last_checked = time.time()
test_public_ip = get_public_ip_address()
if not test_public_ip == self.public_ip_address:
self.sendtodbDev(1, 'public_ip_address', test_public_ip, 0, 'flowmeterskid')
self.public_ip_address = test_public_ip
hostname = "google.com"
response = os.system("ping -c 1 " + hostname)
#and then check the response...
if response == 0:
print hostname, 'is up!'
self.ping_counter = 0
else:
print hostname, 'is down!'
self.ping_counter += 1
if self.ping_counter >= 3:
log.info("Rebooting because no internet detected")
os.system('reboot')
def flowmeterskid_sync(self, name, value):
"""Sync all data from the driver."""
self.force_send = True
# self.sendtodb("log", "synced", 0)
return True
def flowmeterskid_writeplctag(self, name, value):
"""Write a value to the PLC."""
new_val = json.loads(str(value).replace("'", '"'))
tag_n = str(new_val['tag']) # "cmd_Start"
val_n = new_val['val']
write_res = write_tag(str(PLC_IP_ADDRESS), tag_n, val_n, plc_type="Micro800")
print("Result of flowmeterskid_writeplctag(self, {}, {}) = {}".format(name, value, write_res))
if write_res is None:
write_res = "Error writing to PLC..."
return write_res

View File

@@ -0,0 +1,21 @@
"""Data persistance functions."""
# if more advanced persistence is needed, use a sqlite database
import json
def load(filename="persist.json"):
"""Load persisted settings from the specified file."""
try:
with open(filename, 'r') as persist_file:
return json.load(persist_file)
except Exception:
return False
def store(persist_obj, filename="persist.json"):
"""Store the persisting settings into the specified file."""
try:
with open(filename, 'w') as persist_file:
return json.dump(persist_obj, persist_file, indent=4)
except Exception:
return False

View File

@@ -0,0 +1,52 @@
"""Utility functions for the driver."""
import socket
import struct
def get_public_ip_address():
"""Find the public IP Address of the host device."""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(("8.8.8.8", 80))
ip_address = sock.getsockname()[0]
sock.close()
except Exception as e:
return e
return ip_address
def int_to_float16(int_to_convert):
"""Convert integer into float16 representation."""
bin_rep = ('0' * 16 + '{0:b}'.format(int_to_convert))[-16:]
sign = 1.0
if int(bin_rep[0]) == 1:
sign = -1.0
exponent = float(int(bin_rep[1:6], 2))
fraction = float(int(bin_rep[6:17], 2))
if exponent == float(0b00000):
return sign * 2 ** -14 * fraction / (2.0 ** 10.0)
elif exponent == float(0b11111):
if fraction == 0:
return sign * float("inf")
return float("NaN")
frac_part = 1.0 + fraction / (2.0 ** 10.0)
return sign * (2 ** (exponent - 15)) * frac_part
def ints_to_float(int1, int2):
"""Convert 2 registers into a floating point number."""
mypack = struct.pack('>HH', int1, int2)
f_unpacked = struct.unpack('>f', mypack)
print("[{}, {}] >> {}".format(int1, int2, f_unpacked[0]))
return f_unpacked[0]
def degf_to_degc(temp_f):
"""Convert deg F to deg C."""
return (temp_f - 32.0) * (5.0/9.0)
def degc_to_degf(temp_c):
"""Convert deg C to deg F."""
return temp_c * 1.8 + 32.0

285
fracskid/Channel.py Normal file
View File

@@ -0,0 +1,285 @@
"""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, 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()
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)
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

12
fracskid/config.txt Normal file
View File

@@ -0,0 +1,12 @@
{
"files": {
"file3": "persistence.py",
"file2": "utilities.py",
"file1": "fracskid.py",
"file4": "Channel.py"
},
"deviceName": "fracskid",
"driverId": "0200",
"releaseVersion": "1",
"driverFileName": "fracskid.py"
}

142
fracskid/fracskid.py Normal file
View File

@@ -0,0 +1,142 @@
"""Driver for fracskid"""
import threading
import sys
from device_base import deviceBase
from Channel import Channel, read_tag, write_tag
import persistence
from random import randint
from utilities import get_public_ip_address
import json
import time
import logging
# LOGGING SETUP
from logging.handlers import RotatingFileHandler
_ = None
log_formatter = logging.Formatter('%(asctime)s %(levelname)s %(funcName)s(%(lineno)d) %(message)s')
logFile = './fracskid.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('fracskid')
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("fracskid startup")
# GLOBAL VARIABLES
WATCHDOG_SEND_PERIOD = 3600 # Seconds, the longest amount of time before sending the watchdog status
PLC_IP_ADDRESS = "192.168.1.10"
CHANNELS = []
# PERSISTENCE FILE
persist = persistence.load()
class start(threading.Thread, deviceBase):
"""Start class required by Meshify."""
def __init__(self, name=None, number=None, mac=None, Q=None, mcu=None, companyId=None, offset=None, mqtt=None, Nodes=None):
"""Initialize the driver."""
threading.Thread.__init__(self)
deviceBase.__init__(self, name=name, number=number, mac=mac, Q=Q, mcu=mcu, companyId=companyId, offset=offset, mqtt=mqtt, Nodes=Nodes)
self.daemon = True
self.version = "1"
self.finished = threading.Event()
self.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("fracskid driver will start in {} seconds".format(wait_sec - i))
time.sleep(1)
logger.info("BOOM! Starting fracskid driver...")
public_ip_address = get_public_ip_address()
self.sendtodbDev(1, 'public_ip_address', public_ip_address, 0, 'fracskid')
watchdog = self.fracskid_watchdog()
self.sendtodbDev(1, 'watchdog', watchdog, 0, 'fracskid')
watchdog_send_timestamp = time.time()
send_loops = 0
watchdog_loops = 0
watchdog_check_after = 5000
while True:
if self.forceSend:
logger.warning("FORCE SEND: TRUE")
gps = self.mcu.gps.split(',')
latitude = float(gps[0])
longitude = float(gps[1])
write_tag(PLC_IP_ADDRESS, "skid_latitude", latitude, plc_type="Micro800")
write_tag(PLC_IP_ADDRESS, "skid_longitude", longitude, plc_type="Micro800")
logger.info("Wrote {},{} to PLC at {}.".format(latitude, longitude, PLC_IP_ADDRESS))
if self.forceSend:
if send_loops > 2:
logger.warning("Turning off forceSend")
self.forceSend = False
send_loops = 0
else:
send_loops += 1
watchdog_loops += 1
if (watchdog_loops >= watchdog_check_after):
test_watchdog = self.fracskid_watchdog()
if not test_watchdog == watchdog or (time.time() - watchdog_send_timestamp) > WATCHDOG_SEND_PERIOD:
self.sendtodbDev(1, 'watchdog', test_watchdog, 0, 'fracskid')
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, 'fracskid')
public_ip_address = test_public_ip
watchdog_loops = 0
time.sleep(10)
def fracskid_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 fracskid_sync(self, name, value):
"""Sync all data from the driver."""
self.forceSend = True
# self.sendtodb("log", "synced", 0)
return True
def fracskid_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 fracskid_writeplctag(self, {}, {}) = {}".format(name, value, w))
if w is None:
w = "Error writing to PLC..."
return w

21
fracskid/persistence.py Normal file
View File

@@ -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

54
fracskid/utilities.py Normal file
View File

@@ -0,0 +1,54 @@
"""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))
if exponent == 30:
fraction = float(int("1" + bin_rep[7:17], 2))
else:
fraction = float(int(bin_rep[7: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

11
henryhyd/config.txt Normal file
View File

@@ -0,0 +1,11 @@
{
"driverFileName":"henryhyd.py",
"deviceName":"henryhyd",
"driverId":"0090",
"releaseVersion":"3",
"files": {
"file1":"henryhyd.py",
"file2":"micro800.py" }
}

351
henryhyd/henryhyd.py Normal file
View File

@@ -0,0 +1,351 @@
#!/usr/bin/python
import threading
import time
from device_base import deviceBase
import micro800 as u800
channels = {}
addr = '192.168.1.40'
class Channel():
def read(self):
valData = u800.readMicroTag(self.device_addr, self.tag)
if valData:
nowVal = valData[0]
if self.map_obj:
nowVal = self.map_obj[nowVal]
self.data_type = valData[1]
if self.data_type == "BOOL" or self.map_obj:
if self.last_value == "":
self.sendFn(self.name, nowVal, 0)
self.last_time_uploaded = time.time()
self.last_value = nowVal
elif (not (self.last_value == nowVal)) or ((time.time() - self.last_time_uploaded) > self.max_time_between_uploads):
self.sendFn(self.name, nowVal, 0)
self.last_time_uploaded = time.time()
self.last_value = nowVal
elif (self.data_type == "REAL") or (self.data_type[-3:] == "INT"):
if self.last_value == "":
self.sendFn(self.name, nowVal, 0)
self.last_time_uploaded = time.time()
self.last_value = nowVal
elif (abs(self.last_value - nowVal) > self.change_threshold) or ((time.time() - self.last_time_uploaded) > self.max_time_between_uploads):
self.sendFn(self.name, nowVal, 0)
self.last_time_uploaded = time.time()
self.last_value = nowVal
return True
return False
def __init__(self, name, tag, max_time_between_uploads, sendFn, change_threshold=0.0, writeable=False, map_obj=None):
global addr
self.name = name
self.tag = tag
self.data_type = ''
self.last_value = ''
self.last_time_uploaded = 0
self.change_threshold = change_threshold
self.max_time_between_uploads = int(max_time_between_uploads)
self.sendFn = sendFn
self.device_addr = addr
self.writeable = bool(writeable)
self.map_obj = map_obj
self.read()
def write(self, val, handshake=None, handshake_val=None):
if self.writeable:
h = handshake
hval = handshake_val
if h is None:
if u800.writeMicroTag(self.device_addr, self.tag, val):
self.sendFn(self.name, val, time.time())
self.last_value = val
return True
else:
return False
else:
return u800.writeMicroTag(self.device_addr, self.tag, val, handshake=h, handshake_val=hval)
else:
print("NOT ALLOWED TO WRITE TO {}".format(self.name))
return False
class start(threading.Thread, deviceBase):
def __init__(self, name=None, number=None, mac=None, Q=None, mcu=None, companyId=None, offset=None, mqtt=None, Nodes=None):
global addr, channels
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)
print("!!! Made it to Driver init() !!!")
self.daemon = True
self.version = "3"
self.finished = threading.Event()
threading.Thread.start(self)
self.last_channel_send = 0
self.device_address = addr
def setupChannels(self):
global channels
channels = {
'alarmengineoilpressurehifault': Channel('alarmengineoilpressurehifault', 'alarm_EngineOilPressure_HiFault', 360, self.sendtodbJSON, writeable=False),
'alarmengineoilpressurelofault': Channel('alarmengineoilpressurelofault', 'alarm_EngineOilPressure_LoFault', 360, self.sendtodbJSON, writeable=False),
'alarmhydraulicfluidtemphifault': Channel('alarmhydraulicfluidtemphifault', 'alarm_HydraulicFluidTemp_HiFault', 360, self.sendtodbJSON, writeable=False),
'alarmhydraulicpressurehifault': Channel('alarmhydraulicpressurehifault', 'alarm_HydraulicPressure_HiFault', 360, self.sendtodbJSON, writeable=False),
'alarminvalidstate': Channel('alarminvalidstate', 'alarm_InvalidState', 360, self.sendtodbJSON, writeable=False),
'alarmmotorstartuptimeout': Channel('alarmmotorstartuptimeout', 'alarm_MotorStartupTimeout', 360, self.sendtodbJSON, writeable=False),
'alarmstrokeoverlap': Channel('alarmstrokeoverlap', 'alarm_StrokeOverlap', 360, self.sendtodbJSON, writeable=False),
'alarmtimeoutduringlifting': Channel('alarmtimeoutduringlifting', 'alarm_TimeoutDuringLifting', 360, self.sendtodbJSON, writeable=False),
'cfgcylinderid': Channel('cfgcylinderid', 'cfg_CylinderID', 360, self.sendtodbJSON, change_threshold=0.5, writeable=True),
'cfgenginehourswarning': Channel('cfgenginehourswarning', 'cfg_EngineHours_Warning', 360, self.sendtodbJSON, change_threshold=0.01, writeable=True),
'cfgengineoilpressureeumax': Channel('cfgengineoilpressureeumax', 'cfg_EngineOilPressure_EUMax', 360, self.sendtodbJSON, change_threshold=0.01, writeable=True),
'cfgengineoilpressureeumin': Channel('cfgengineoilpressureeumin', 'cfg_EngineOilPressure_EUMin', 360, self.sendtodbJSON, change_threshold=0.01, writeable=True),
'cfgengineoilpressurehifault': Channel('cfgengineoilpressurehifault', 'cfg_EngineOilPressure_HiFault', 360, self.sendtodbJSON, change_threshold=0.01, writeable=True),
'cfgengineoilpressurelofault': Channel('cfgengineoilpressurelofault', 'cfg_EngineOilPressure_LoFault', 360, self.sendtodbJSON, change_threshold=0.01, writeable=True),
'cfgenginerunningoilpressure': Channel('cfgenginerunningoilpressure', 'cfg_EngineRunning_OilPressure', 360, self.sendtodbJSON, change_threshold=0.01, writeable=True),
'cfgfaultedmotorshutdownmins': Channel('cfgfaultedmotorshutdownmins', 'cfg_FaultedMotorShutdownMins', 360, self.sendtodbJSON, change_threshold=0.01, writeable=True),
'cfghydfluidcoolerenabled': Channel('cfghydfluidcoolerenabled', 'cfg_HydFluidCooler_Enabled', 360, self.sendtodbJSON, writeable=True),
'cfghydfluidcoolertempoff': Channel('cfghydfluidcoolertempoff', 'cfg_HydFluidCooler_TempOff', 360, self.sendtodbJSON, change_threshold=0.01, writeable=True),
'cfghydfluidcoolertempon': Channel('cfghydfluidcoolertempon', 'cfg_HydFluidCooler_TempOn', 360, self.sendtodbJSON, change_threshold=0.01, writeable=True),
'cfghydfluidtempeumax': Channel('cfghydfluidtempeumax', 'cfg_HydFluidTemp_EUMax', 360, self.sendtodbJSON, change_threshold=0.01, writeable=True),
'cfghydfluidtempeumin': Channel('cfghydfluidtempeumin', 'cfg_HydFluidTemp_EUMin', 360, self.sendtodbJSON, change_threshold=0.01, writeable=True),
'cfghydfluidtemphifault': Channel('cfghydfluidtemphifault', 'cfg_HydFluidTemp_HiFault', 360, self.sendtodbJSON, change_threshold=0.01, writeable=True),
'cfghydfluidtemphiwarning': Channel('cfghydfluidtemphiwarning', 'cfg_HydFluidTemp_HiWarning', 360, self.sendtodbJSON, change_threshold=0.01, writeable=True),
'cfghydfluidtemphiwarningmultiplier': Channel('cfghydfluidtemphiwarningmultiplier', 'cfg_HydFluidTemp_HiWarningMultiplier', 360, self.sendtodbJSON, change_threshold=0.01, writeable=True),
'cfghydpressclosesp': Channel('cfghydpressclosesp', 'cfg_HydPress_CloseSP', 360, self.sendtodbJSON, change_threshold=0.01, writeable=True),
'cfghydpresseumax': Channel('cfghydpresseumax', 'cfg_HydPress_EUMax', 360, self.sendtodbJSON, change_threshold=0.01, writeable=True),
'cfghydpresseumin': Channel('cfghydpresseumin', 'cfg_HydPress_EUMin', 360, self.sendtodbJSON, change_threshold=0.01, writeable=True),
'cfghydpresshifault': Channel('cfghydpresshifault', 'cfg_HydPress_HiFault', 360, self.sendtodbJSON, change_threshold=0.01, writeable=True),
'cfghydpressliftingsp': Channel('cfghydpressliftingsp', 'cfg_HydPress_LiftingSP', 360, self.sendtodbJSON, change_threshold=0.01, writeable=True),
'cfgliftingtimetimeout': Channel('cfgliftingtimetimeout', 'cfg_LiftingTimeTimeout', 360, self.sendtodbJSON, change_threshold=0.01, writeable=True),
'cfgpolishedrodod': Channel('cfgpolishedrodod', 'cfg_PolishedRodOD', 360, self.sendtodbJSON, change_threshold=0.01, writeable=True),
'cfgspmtarget': Channel('cfgspmtarget', 'cfg_SPMTarget', 360, self.sendtodbJSON, change_threshold=0.001, writeable=True),
'cmdengineresethourmeter': Channel('cmdengineresethourmeter', 'cmd_EngineResetHourMeter', 360, self.sendtodbJSON, writeable=True),
'cmdenginestart': Channel('cmdenginestart', 'cmd_EngineStart', 360, self.sendtodbJSON, writeable=True),
'cmdenginestop': Channel('cmdenginestop', 'cmd_EngineStop', 360, self.sendtodbJSON, writeable=True),
'cmdresetfault': Channel('cmdresetfault', 'cmd_ResetFault', 360, self.sendtodbJSON, writeable=True),
'cmdresetstrokelifetime': Channel('cmdresetstrokelifetime', 'cmd_ResetStrokeLifetime', 360, self.sendtodbJSON, writeable=True),
'cmdrun': Channel('cmdrun', 'cmd_Run', 360, self.sendtodbJSON, writeable=False),
'cmdstart': Channel('cmdstart', 'cmd_Start', 360, self.sendtodbJSON, writeable=True),
'cmdstop': Channel('cmdstop', 'cmd_Stop', 360, self.sendtodbJSON, writeable=True),
'countstrokelifetime': Channel('countstrokelifetime', 'count_StrokeLifetime', 360, self.sendtodbJSON, change_threshold=0.5, writeable=False),
'countstrokestartup': Channel('countstrokestartup', 'count_StrokeStartup', 360, self.sendtodbJSON, change_threshold=0.5, writeable=False),
'countstroketoday': Channel('countstroketoday', 'count_StrokeToday', 360, self.sendtodbJSON, change_threshold=0.5, writeable=False),
'outenginestart': Channel('outenginestart', 'out_EngineStart', 360, self.sendtodbJSON, writeable=False),
'outenginestop': Channel('outenginestop', 'out_EngineStop', 360, self.sendtodbJSON, writeable=False),
'outhydfluidcooler': Channel('outhydfluidcooler', 'out_HydFluidCooler', 360, self.sendtodbJSON, writeable=False),
'rpallok': Channel('rpallok', 'rp_AllOK', 360, self.sendtodbJSON, writeable=False),
'rpengine': Channel('rpengine', 'rp_Engine', 360, self.sendtodbJSON, writeable=False),
'spallok': Channel('spallok', 'sp_AllOK', 360, self.sendtodbJSON, writeable=False),
'spengine': Channel('spengine', 'sp_Engine', 360, self.sendtodbJSON, writeable=False),
'stsautomode': Channel('stsautomode', 'sts_AutoMode', 360, self.sendtodbJSON, writeable=False),
'stsenginehourswarning': Channel('stsenginehourswarning', 'sts_EngineHours_Warning', 360, self.sendtodbJSON, writeable=False),
'stsengineint': Channel('stsengineint', 'sts_EngineINT', 360, self.sendtodbJSON, change_threshold=0.5, writeable=False, map_obj={0: 'Stopped', 1: 'Starting', 2: 'Running', 3: 'Stopping'}),
'stsengineoilpressurehifault': Channel('stsengineoilpressurehifault', 'sts_EngineOilPressure_HiFault', 360, self.sendtodbJSON, writeable=False),
'stsengineoilpressurelofault': Channel('stsengineoilpressurelofault', 'sts_EngineOilPressure_LoFault', 360, self.sendtodbJSON, writeable=False),
'stsenginerunning': Channel('stsenginerunning', 'sts_EngineRunning', 360, self.sendtodbJSON, writeable=False),
'stserrorcodeint': Channel('stserrorcodeint', 'sts_ErrorCode_INT', 360, self.sendtodbJSON, change_threshold=0.5, writeable=False, map_obj={0: 'No Error', 1: 'Motor Startup Timeout', 2: 'Stroke Overlap', 3: 'Timeout During Lifting', 4: 'Invalid State', 5: 'Lost Run Permissive', 6: 'Lost Engine Run Permissive'}),
'stshydraulicfluidtemphifault': Channel('stshydraulicfluidtemphifault', 'sts_HydraulicFluidTemp_HiFault', 360, self.sendtodbJSON, writeable=False),
'stshydraulicfluidtemphiwarning': Channel('stshydraulicfluidtemphiwarning', 'sts_HydraulicFluidTemp_HiWarning', 360, self.sendtodbJSON, writeable=False),
'stshydraulicpressurehifault': Channel('stshydraulicpressurehifault', 'sts_HydraulicPressure_HiFault', 360, self.sendtodbJSON, writeable=False),
'stsmanualmode': Channel('stsmanualmode', 'sts_ManualMode', 360, self.sendtodbJSON, writeable=False),
'stsstep': Channel('stsstep', 'sts_Step', 360, self.sendtodbJSON, change_threshold=0.5, writeable=False, map_obj={0: 'Engine Off', 1: 'Engine Startup', 2: 'Engine Idle', 3: 'Lifting', 4: 'Falling', 5: 'Faulted', 6: 'IO Test', 7: 'Manual Operation'}),
'ststestmode': Channel('ststestmode', 'sts_TestMode', 360, self.sendtodbJSON, writeable=False),
'timeenginehourmeter': Channel('timeenginehourmeter', 'time_EngineHourMeter', 360, self.sendtodbJSON, change_threshold=0.5, writeable=False),
'timelastfallingtime': Channel('timelastfallingtime', 'time_LastFallingTime', 360, self.sendtodbJSON, change_threshold=0.5, writeable=False),
'timelastliftingtime': Channel('timelastliftingtime', 'time_LastLiftingTime', 360, self.sendtodbJSON, change_threshold=0.5, writeable=False),
'timesecondsperstroke': Channel('timesecondsperstroke', 'time_SecondsPerStroke', 360, self.sendtodbJSON, change_threshold=0.5, writeable=False),
'valengineoilpressure': Channel('valengineoilpressure', 'val_EngineOilPressure', 360, self.sendtodbJSON, change_threshold=2.0, writeable=False),
'valhydraulicfluidtemp': Channel('valhydraulicfluidtemp', 'val_HydraulicFluidTemp', 360, self.sendtodbJSON, change_threshold=2.0, writeable=False)
# 'valhydraulicpressure': Channel('valhydraulicpressure', 'val_HydraulicPressure', 360, self.sendtodbJSON, change_threshold=2.0, writeable=False),
# 'valpolishedrodload': Channel('valpolishedrodload', 'val_PolishedRodLoad', 360, self.sendtodbJSON, change_threshold=5.0, writeable=False),
}
def register(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
self.channels["status"]["last_value"] = ""
def readChannelData(self):
global channels
print "Executing readChannelData()"
runLoopStatus = ""
try:
for i in channels:
runLoopStatus = i
channels[i].read()
runLoopStatus = "Complete"
return True
except Exception, e:
print "Error during {0} of run loop: {1}".format(runLoopStatus, e)
return False
def checkLifting(self):
return u800.readMicroTag(self.device_address, 'sts_Step')[0] == 3
def checkFalling(self):
return u800.readMicroTag(self.device_address, 'sts_Step')[0] == 4
def checkIdle(self):
s = u800.readMicroTag(self.device_address, 'sts_Step')[0]
return not (s == 3) and not (s == 4)
def run(self):
global channels
self.sendLastStroke = True
self.sendCardEvery = 5
cardScanIndex = 0
PRLoadPoints = []
status = ""
self.setupChannels()
print("!!! Made it to Driver run() !!!")
while True:
if self.checkLifting():
# hydPressurePoints.append([time.time(), u800.readMicroTag(addr, 'val_HydraulicPressure')[0]])
PRLoadPoints.append([time.time(), u800.readMicroTag(addr, 'val_PolishedRodLoad')[0]])
if not status == "lifting":
status = "lifting"
self.sendtodbJSON('stsstep', 'Lifting', time.time())
print(status)
elif self.checkFalling():
# hydPressurePoints.append([time.time(), u800.readMicroTag(addr, 'val_HydraulicPressure')[0]])
PRLoadPoints.append([time.time(), u800.readMicroTag(addr, 'val_PolishedRodLoad')[0]])
if not status == "falling":
status = "falling"
self.sendtodbJSON('stsstep', 'Falling', time.time())
print(status)
elif self.checkIdle():
if not status == "idle":
status = "idle"
print(status)
cardScanIndex = cardScanIndex + 1
if cardScanIndex == self.sendCardEvery:
self.sendLastStroke = True
cardScanIndex = 0
if self.sendLastStroke:
pr_load_string = "["
for x in PRLoadPoints:
pr_load_string = pr_load_string + "[{0},{1}],".format(x[0], x[1])
pr_load_string = pr_load_string[:-1] + "]"
# hyd_press_string = "["
# for y in PRLoadPoints:
# hyd_press_string = hyd_press_string + "[{0},{1}],".format(y[0], y[1])
# hyd_press_string = hyd_press_string[:-1] + "]"
if len(PRLoadPoints) > 0:
self.sendtodbJSON('polishedrodloadcard', pr_load_string, time.time())
# self.sendtodbJSON('hydraulicpressurecard', hyd_press_string, time.time())
self.sendLastStroke = False
PRLoadPoints = []
self.readChannelData()
self.last_channel_send = time.time()
elif (time.time() - self.last_channel_send) > 10.0:
print('Checking channel data')
self.readChannelData()
self.last_channel_send = time.time()
time.sleep(0.1)
def henryhyd_sync(self, name, value):
self.sendtodb("connected", "true", 0)
return True
def genericSet(self, name, value, id):
try:
global channels
print("Trying to set {} to {} (type {})".format(channels[name].tag, value))
return channels[name].write(value)
except Exception, e:
print("Exception during genericSet: {}".format(e))
def henryhyd_cmdengineresethourmeter(self, name, value):
global channels
print('trying to set cmd_EngineResetHourMeter to {}'.format(value))
if int(value) == 1:
if channels[name].write(int(value), handshake="val_EngineHourMeter", handshake_val=0):
self.sendtodb('valenginehourmeter', 0, time.time())
channels['valenginehourmeter'].last_value = 0
channels['valenginehourmeter'].last_time_uploaded = time.time()
return True
return False
else:
return channels[name].write(value)
def henryhyd_cmdenginestart(self, name, value):
global channels
print('trying to set cmd_EngineStart to {}'.format(value))
if int(value) == 1:
if channels[name].write(int(value), handshake="sts_EngineRunning", handshake_val=1):
self.sendtodb('stsenginerunning', 1, time.time())
channels['stsenginerunning'].last_value = 1
channels['stsenginerunning'].last_time_uploaded = time.time()
return True
return False
else:
return channels[name].write(value)
def henryhyd_cmdenginestop(self, name, value):
global channels
print('trying to set cmd_EngineStop to {} for {}'.format(value, addr))
if int(value) == 1:
if channels[name].write(int(value), handshake="sts_EngineRunning", handshake_val=0):
self.sendtodb('stsenginerunning', 0, time.time())
channels['stsenginerunning'].last_value = 0
channels['stsenginerunning'].last_time_uploaded = time.time()
return True
return False
else:
return channels[name].write(value)
def henryhyd_cmdresetfault(self, name, value):
global channels
print('trying to set cmd_ResetFault to {}'.format(value))
if int(value) == 1:
if channels[name].write(int(value), handshake="sts_ErrorCode_INT", handshake_val=0):
self.sendtodb('stserrorcodeint', "No Error", time.time())
channels['stserrorcodeint'].last_value = 0
channels['stserrorcodeint'].last_time_uploaded = time.time()
return True
return False
else:
return channels[name].write(value)
def henryhyd_cmdresetstrokelifetime(self, name, value):
global channels
print('trying to set cmd_ResetStrokeLifetime to {}'.format(value))
if int(value) == 1:
if channels[name].write(int(value), handshake="count_StrokeLifetime", handshake_val=0):
self.sendtodb('countstrokelifetime', 0, time.time())
channels['countstrokelifetime'].last_value = 0
channels['countstrokelifetime'].last_time_uploaded = time.time()
return True
return False
else:
return channels[name].write(value)
def henryhyd_cmdstart(self, name, value):
global channels
print('trying to set cmd_Start to {}'.format(value))
if int(value) == 1:
if channels[name].write(int(value), handshake="cmd_Run", handshake_val=1):
self.sendtodb("cmdrun", 1, time.time())
channels['cmdrun'].last_value = 1
channels['cmdrun'].last_time_uploaded = time.time()
return True
else:
return "Error while attempting to start"
else:
return channels[name].write(value)
def henryhyd_cmdstop(self, name, value):
global channels
print('trying to set cmd_Stop to {}'.format(value))
if int(value) == 1:
if channels[name].write(int(value), handshake="cmd_Run", handshake_val=0):
self.sendtodb("cmdrun", 0, time.time())
channels['cmdrun'].last_value = 0
channels['cmdrun'].last_time_uploaded = time.time()
return True
else:
return "Error while attempting to stop."
else:
return channels[name].write(value)

119
henryhyd/micro800.py Normal file
View File

@@ -0,0 +1,119 @@
from pycomm.ab_comm.clx import Driver as plcDriver
import sys
def readMicroTag(addr, tag):
addr = str(addr)
tag = str(tag)
c = plcDriver()
if c.open(addr, True):
try:
v = c.read_tag(tag)
# print(v)
return v
except Exception:
err = c.get_status()
c.close()
print "{} on reading {} from {}".format(err, tag, addr)
pass
c.close()
def getTagType(addr, tag):
addr = str(addr)
tag = str(tag)
c = plcDriver()
if c.open(addr, True):
try:
return c.read_tag(tag)[1]
except Exception:
err = c.get_status()
c.close()
print err
pass
c.close()
def write(addr, tag, val, t):
addr = str(addr)
tag = str(tag)
c = plcDriver()
if c.open(addr, True):
try:
wt = c.write_tag(tag, val, t)
return wt
except Exception:
err = c.get_status()
c.close()
print("Write Error: {} setting {} at {} to {} type {}".format(err, tag, addr, val, t))
return False
c.close()
def closeEnough(a, b):
return abs(a - b) <= 0.001
def writeMicroTag(addr, tag, val, handshake=None, handshake_val=None):
addr = str(addr)
tag = str(tag)
print("handshake: {}, handshake_val: {}".format(handshake, handshake_val))
chk_tag = tag
if not(handshake is None) and not(handshake == "None"):
chk_tag = str(handshake)
print("Handshake tag passed, using {}".format(chk_tag))
chk_val = val
if not (handshake_val is None) and not(handshake_val == "None"):
chk_val = handshake_val
print("Handshake value passed, using {}".format(chk_val))
attempts_allowed = 5
attempts = 1
while attempts <= attempts_allowed:
try:
attempts = attempts + 1
cv = readMicroTag(addr, tag)
print("Val Before Write: {}".format(cv))
if cv:
if cv[1] == "REAL":
val = float(val)
chk_val = float(chk_val)
else:
val = int(val)
chk_val = int(chk_val)
wt = write(addr, tag, val, cv[1])
if wt:
print("write: {}".format(wt))
chk = readMicroTag(addr, chk_tag)
if chk:
print("chk: {}, chk_val: {}".format(chk, chk_val))
if closeEnough(chk[0], chk_val):
return True
except Exception as e:
print e
return False
def readMicroTagList(addr, tList):
addr = str(addr)
c = plcDriver()
if c.open(addr, True):
vals = []
try:
for t in tList:
v = c.read_tag(t)
vals.append({"tag": t, "val": v[0], "type": v[1]})
# print(v)
# print("{0} - {1}".format(t, v))
except Exception:
err = c.get_status()
c.close()
print err
pass
c.close()
return vals
if __name__ == '__main__':
if len(sys.argv) > 2:
print(readMicroTag(sys.argv[1], sys.argv[2]))
else:
print ("Did not pass a target and tag name.")

139
henrypump/Channel.py Normal file
View File

@@ -0,0 +1,139 @@
"""Define Meshify channel class."""
from pycomm.ab_comm.clx import Driver as ClxDriver
from pycomm.cip.cip_base import CommError
import time
def read_tag(addr, tag):
"""Read a tag from the PLC."""
c = ClxDriver()
try:
if c.open(addr):
v = c.read_tag(tag)
return v
except CommError:
# err = c.get_status()
c.close()
print("Could not connect during readTag({}, {})".format(addr, tag))
# print err
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:
# typ = getTagType(addr, tag)
cv = c.read_tag(tag)
wt = c.write_tag(tag, val, cv[1])
# print(wt)
return wt
except Exception:
print("Error during writeTag({}, {}, {})".format(addr, tag, val))
err = c.get_status()
c.close()
print err
pass
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_obj=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_obj = map_obj
def __str__(self):
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]):
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_obj:
try:
self.value = self.map_obj[v[0]]
except KeyError:
print("Cannot find a map value for {} in {} for {}".format(v[0], self.map_obj, 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

9
henrypump/config.txt Normal file
View File

@@ -0,0 +1,9 @@
{
"driverFileName":"thehenrypump.py",
"deviceName":"thehenrypump",
"driverId":"0120",
"releaseVersion":"2",
"files": {
"file1":"thehenrypump.py",
"file2":"Channel.py" }
}

60
henrypump/thehenrypump.py Normal file
View File

@@ -0,0 +1,60 @@
"""Connect the Henry Pump to Meshify."""
import threading
import time
from device_base import deviceBase
from Channel import Channel
data_source = "PLC"
plc_ip = '192.168.1.10'
channels = [
Channel(plc_ip, "faultcode", "sts_FaultCode", "INT", 1.0, 3600),
Channel(plc_ip, "voltagedrop", "val_CableVoltageDrop", "REAL", 1.0, 3600),
Channel(plc_ip, "strokelength", "val_CalculatedStrokeLength", "REAL", 1.0, 3600),
Channel(plc_ip, "flowrate", "val_FlowRate", "REAL", 1.0, 3600),
Channel(plc_ip, "motorcurrent", "val_MotorCurrent", "REAL", 1.0, 3600),
Channel(plc_ip, "strokeslifetime", "val_StrokeCountLifetime", "DINT", 100.0, 3600),
Channel(plc_ip, "strokestoday", "val_StrokeCountToday", "DINT", 100.0, 3600),
Channel(plc_ip, "strokesperminute", "val_StrokesPerMinute", "REAL", 0.5, 3600),
Channel(plc_ip, "surfacepressure", "val_SurfacePressure", "REAL", 1.0, 3600),
]
class start(threading.Thread, deviceBase):
"""Start the class for the driver."""
def __init__(self, name=None, number=None, mac=None, Q=None, mcu=None, companyId=None, offset=None, mqtt=None, Nodes=None):
"""Initialize the class."""
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.forceSend = True
self.daemon = True
self.version = "2"
self.finished = threading.Event()
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 device."""
self.sendtodb("log", "registered", 0)
def run(self):
"""Run the main loop."""
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)
if self.forceSend:
self.forceSend = False
time.sleep(5)
print("thehenrypump driver still alive... check back in 5 sec.")
def thehenrypump_sync(self, name, value):
"""Sync all data from the driver."""
self.forceSend = True
self.sendtodb("log", "synced", 0)
return True

287
ipp/Channel.py Normal file
View File

@@ -0,0 +1,287 @@
"""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, 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()
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

12
ipp/config.txt Normal file
View File

@@ -0,0 +1,12 @@
{
"files": {
"file3": "Channel.py",
"file2": "utilities.py",
"file1": "ipp.py",
"file4": "maps.py"
},
"deviceName": "ipp",
"driverId": "0090",
"releaseVersion": "9",
"driverFileName": "ipp.py"
}

316
ipp/ipp.py Normal file
View File

@@ -0,0 +1,316 @@
"""Driver for ipp"""
import threading
import sys
from device_base import deviceBase
from Channel import PLCChannel
from utilities import get_public_ip_address
from maps import *
import time
import logging
_ = None
# LOGGING SETUP
from logging.handlers import RotatingFileHandler
log_formatter = logging.Formatter('%(asctime)s %(levelname)s %(funcName)s(%(lineno)d) %(message)s')
logFile = './ipp.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('ipp')
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("ipp startup")
# GLOBAL VARIABLES
WATCHDOG_SEND_PERIOD = 3600 # Seconds, the longest amount of time before sending the watchdog status
PLC_IP_ADDRESS = "10.20.4.5"
CHANNELS = [
PLCChannel(PLC_IP_ADDRESS, 'alarmdhpressure', 'alarm_DHPressure', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'alarmdhtemperature', 'alarm_DHTemperature', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'alarme300', 'alarm_E300', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'alarmtubingpressure', 'alarm_TubingPressure', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'automode', 'Auto_Mode', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'cfgcflasetting', 'cfg_C_FLASetting', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgcleartripcountafter', 'cfg_ClearTripCountAfter', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgdhsensordisttointake', 'cfg_DHSensorDistToIntake', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfggfgroundfaultinhibittime', 'cfg_GF_GroundFaultInhibitTime', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfggfgroundfaulttripdelay', 'cfg_GF_GroundFaultTripDelay', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfggfgroundfaulttriplevel', 'cfg_GF_GroundFaultTripLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfggfgroundfaultwarningdelay', 'cfg_GF_GroundFaultWarningDelay', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfggfgroundfaultwarninglevel', 'cfg_GF_GroundFaultWarningLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgictprimary', 'cfg_I_CTPrimary', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgictsecondary', 'cfg_I_CTSecondary', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgicurrentimbalanceinhibittim', 'cfg_I_CurrentImbalanceInhibitTime', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgicurrentimbalancetripdelay', 'cfg_I_CurrentImbalanceTripDelay', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgicurrentimbalancetriplevel', 'cfg_I_CurrentImbalanceTripLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgicurrentimbalancewarninglev', 'cfg_I_CurrentImbalanceWarningLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgijaminhibittime', 'cfg_I_JamInhibitTime', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgijamtripdelay', 'cfg_I_JamTripDelay', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgijamtriplevel', 'cfg_I_JamTripLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgijamwarninglevel', 'cfg_I_JamWarningLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgilinelossinhibittime', 'cfg_I_LineLossInhibitTime', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgilinelosstripdelay', 'cfg_I_LineLossTripDelay', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgiovercurrentinhibittime', 'cfg_I_OvercurrentInhibitTime', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgiovercurrenttripdelay', 'cfg_I_OvercurrentTripDelay', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgiovercurrenttriplevel', 'cfg_I_OvercurrentTripLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgiovercurrentwarninglevel', 'cfg_I_OvercurrentWarningLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgistallenabledtime', 'cfg_I_StallEnabledTime', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgistalltriplevel', 'cfg_I_StallTripLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgiundercurrentinhibittime', 'cfg_I_UndercurrentInhibitTime', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgiundercurrenttripdelay', 'cfg_I_UndercurrentTripDelay', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgiundercurrenttriplevel', 'cfg_I_UndercurrentTripLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgiundercurrentwarninglevel', 'cfg_I_UndercurrentWarningLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgiunderloadinhibittime', 'cfg_I_UnderloadInhibitTime', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgiunderloadtripdelay', 'cfg_I_UnderloadTripDelay', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgiunderloadtriplevel', 'cfg_I_UnderloadTripLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgiunderloadwarninglevel', 'cfg_I_UnderloadWarningLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgoverloadtripcountlimit', 'cfg_OverloadTripCountLimit', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgplphaselossinhibittime', 'cfg_PL_PhaseLossInhibitTime', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgplphaselosstripdelay', 'cfg_PL_PhaseLossTripDelay', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgread', 'cfg_READ', 'REAL', 0.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgspecificgravity', 'cfg_SpecificGravity', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgtcuolresetlevel', 'cfg_TCU_OLResetLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgtcuolwarninglevel', 'cfg_TCU_OLWarningLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgtcutripclass', 'cfg_TCU_TripClass', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgtimermodeenabled', 'cfg_TimerModeEnabled', 'REAL', 0.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgtimerruntime', 'cfg_TimerRunTime', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgtimerwaittime', 'cfg_TimerWaitTime', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgtripcountlimit', 'cfg_TripCountLimit', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvoverfrequencyinhibittime', 'cfg_V_OverfrequencyInhibitTime', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvoverfrequencytripdelay', 'cfg_V_OverfrequencyTripDelay', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvoverfrequencytriplevel', 'cfg_V_OverfrequencyTripLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvoverfrequencywarninglevel', 'cfg_V_OverfrequencyWarningLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvovervoltageinhibittime', 'cfg_V_OvervoltageInhibitTime', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvovervoltagetripdelay', 'cfg_V_OvervoltageTripDelay', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvovervoltagetriplevel', 'cfg_V_OvervoltageTripLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvovervoltagewarninglevel', 'cfg_V_OvervoltageWarningLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvphaserotationinhibittime', 'cfg_V_PhaseRotationInhibitTime', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvphaserotationtriptype', 'cfg_V_PhaseRotationTripType', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvptprimary', 'cfg_V_PTPrimary', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvptsecondary', 'cfg_V_PTSecondary', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvunderfrequencyinhibittime', 'cfg_V_UnderfrequencyInhibitTime', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvunderfrequencytripdelay', 'cfg_V_UnderfrequencyTripDelay', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvunderfrequencytriplevel', 'cfg_V_UnderfrequencyTripLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvunderfrequencywarninglevel', 'cfg_V_UnderfrequencyWarningLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvundervoltageinhibittime', 'cfg_V_UndervoltageInhibitTime', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvundervoltagetripdelay', 'cfg_V_UndervoltageTripDelay', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvundervoltagetriplevel', 'cfg_V_UndervoltageTripLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvundervoltagewarninglevel', 'cfg_V_UndervoltageWarningLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvvoltageimbalanceinhibittim', 'cfg_V_VoltageImbalanceInhibitTime', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvvoltageimbalancetripdelay', 'cfg_V_VoltageImbalanceTripDelay', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvvoltageimbalancetriplevel', 'cfg_V_VoltageImbalanceTripLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvvoltageimbalancewarninglev', 'cfg_V_VoltageImbalanceWarningLevel', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgvvoltagemode', 'cfg_V_VoltageMode', 'REAL', 1.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'cfgwrite', 'cfg_WRITE', 'REAL', 0.0, 86400, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'contactorstatus', 'Contactor_Status', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'devicestatus', 'Device_Status_INT', 'STRING', 0.0, 3600, plc_type='Micro800', map_=map_device_status),
PLCChannel(PLC_IP_ADDRESS, 'dhdownholestatusint', 'DH_DownholeStatus_INT', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'dhfluidlevel', 'DH_Fluid_Level', 'REAL', 1.0, 600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'dhintakepressure', 'DH_IntakePressure', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'dhintaketemperature', 'DH_IntakeTemperature', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'dhmaxintakepressureforever', 'DH_MaxIntakePressure_Forever', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'dhmaxintakepressurestartup', 'DH_MaxIntakePressure_Startup', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'dhmaxintaketemperatureforever', 'DH_MaxIntakeTemperature_Forever', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'dhmaxintaketemperaturestartup', 'DH_MaxIntakeTemperature_Startup', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'dhnumchannels', 'DH_NumChannels', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'dhpsirating', 'DH_PSIRating', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'dhtooltype', 'DH_ToolType', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'dhtoolvoltage', 'DH_ToolVoltage', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'downholetoolenabled', 'Downhole_Tool_Enabled', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'downtimetimeparameter', 'Downtime_Time_Parameter', 'REAL', 1.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'downtimetimeparameterol', 'Downtime_Time_Parameter_OL', 'REAL', 1.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'e300averagecurrent', 'E300_AverageCurrent', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300averagellvoltage', 'E300_AverageLLVoltage', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300averagelnvoltage', 'E300_AverageLNVoltage', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300kwh', 'E300_kWh', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300kwhregen', 'E300_kWh_Regen', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300l1apparentpower', 'E300_L1ApparentPower', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300l1current', 'E300_L1Current', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300l1l2voltage', 'E300_L1L2Voltage', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300l1nvoltage', 'E300_L1NVoltage', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300l1reactivepower', 'E300_L1ReactivePower', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300l1realpower', 'E300_L1RealPower', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300l1truepowerfactor', 'E300_L1TruePowerFactor', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300l2apparentpower', 'E300_L2ApparentPower', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300l2current', 'E300_L2Current', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300l2l3voltage', 'E300_L2L3Voltage', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300l2nvoltage', 'E300_L2NVoltage', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300l2reactivepower', 'E300_L2ReactivePower', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300l2realpower', 'E300_L2RealPower', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300l2truepowerfactor', 'E300_L2TruePowerFactor', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300l3apparentpower', 'E300_L3ApparentPower', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300l3current', 'E300_L3Current', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300l3l1voltage', 'E300_L3L1Voltage', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300l3nvoltage', 'E300_L3NVoltage', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300l3reactivepower', 'E300_L3ReactivePower', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300l3realpower', 'E300_L3RealPower', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300l3truepowerfactor', 'E300_L3TruePowerFactor', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300linefrequency', 'E300_LineFrequency', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300percentcurrentunbalance', 'E300_PercentCurrentUnbalance', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300percentvoltageunbalance', 'E300_PercentVoltageUnbalance', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300threephasetruepowerfactor', 'E300_ThreePhaseTruePowerFactor', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300totalapparentpower', 'E300_TotalApparentPower', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300totalreactivepower', 'E300_TotalReactivePower', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'e300totalrealpower', 'E300_TotalRealPower', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'flowrate', 'Flowrate', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'flowtoday', 'Flow_Today', 'REAL', 100.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'flowyesterday', 'Flow_Yesterday', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'handmode', 'Hand_Mode', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'overloadtrip', 'OverloadTrip', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'pressurealarmdelay', 'Pressure_Alarm_Delay', 'REAL', 1.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'pressurealarmstartupdelay', 'Pressure_Alarm_Startup_Delay', 'REAL', 1.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'pressureeumax', 'Pressure_EU_Max', 'REAL', 1.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'pressureeumin', 'Pressure_EU_Min', 'REAL', 1.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'pressurehi', 'Pressure_Hi', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'pressurehisp', 'Pressure_Hi_SP', 'REAL', 1.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'pressurein', 'Pressure_In', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'pressurelo', 'Pressure_Lo', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'pressurelosp', 'Pressure_Lo_SP', 'REAL', 1.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'pressureok', 'Pressure_OK', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'pressureshutdown', 'Pressure_Shutdown', 'REAL', 1.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'pressureshutdownenabled', 'Pressure_Shutdown_Enabled', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'pressurestartup', 'Pressure_Startup', 'REAL', 1.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'pressurestartupenabled', 'Pressure_Startup_Enabled', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'pressureswitchenabled', 'Pressure_Switch_Enabled', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'pressuretransducerenabled', 'Pressure_Transducer_Enabled', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'rpmode', 'RP_Mode', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'rppressure', 'RP_Pressure', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'rptemperature', 'RP_Temperature', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'rptrip', 'RP_Trip', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'rptubingpressure', 'RP_TubingPressure', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'runpermissive', 'Run_Permissive', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'spmode', 'SP_Mode', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'sppressure', 'SP_Pressure', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'sptemperature', 'SP_Temperature', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'sptrip', 'SP_Trip', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'spvoltage', 'SP_Voltage', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'startbutton', 'Start_Button', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'startcommand', 'Start_Command', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'startpermissive', 'Start_Permissive', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'stopcommand', 'Stop_Command', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'tempshutdown', 'Temp_Shutdown', 'REAL', 1.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'tempshutdownenabled', 'Temp_Shutdown_Enabled', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'tempstartup', 'Temp_Startup', 'REAL', 1.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'tempstartupenabled', 'Temp_Startup_Enabled', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'testmode', 'Test_Mode', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'tripenabledicurrentimbalance', 'TripEnabled_I_CurrentImbalance', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'tripenabledigroundfault', 'TripEnabled_I_GroundFault', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'tripenabledijam', 'TripEnabled_I_Jam', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'tripenabledilineloss', 'TripEnabled_I_LineLoss', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'tripenablediovercurrent', 'TripEnabled_I_Overcurrent', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'tripenabledioverload', 'TripEnabled_I_Overload', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'tripenablediphaseloss', 'TripEnabled_I_PhaseLoss', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'tripenabledistall', 'TripEnabled_I_Stall', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'tripenablediundercurrent', 'TripEnabled_I_Undercurrent', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'tripenablediunderload', 'TripEnabled_I_Underload', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'tripenablevoverfrequency', 'TripEnable_V_Overfrequency', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'tripenablevovervoltage', 'TripEnable_V_Overvoltage', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'tripenablevphaserotation', 'TripEnable_V_PhaseRotation', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'tripenablevunderfrequency', 'TripEnable_V_Underfrequency', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'tripenablevundervoltage', 'TripEnable_V_Undervoltage', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'tripenablevvoltageunbalance', 'TripEnable_V_VoltageUnbalance', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'tripresetcmd', 'TripResetCmd', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'tripstatus', 'TripStatus', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'tripstatuscontrolint', 'TripStatusControl_INT', 'STRING', 1.0, 3600, plc_type='Micro800', map_=map_e300_control),
PLCChannel(PLC_IP_ADDRESS, 'tripstatuscurrentint', 'TripStatusCurrent_INT', 'STRING', 1.0, 3600, plc_type='Micro800', map_=map_e300_current),
PLCChannel(PLC_IP_ADDRESS, 'tripstatuspowerint', 'TripStatusPower_INT', 'STRING', 1.0, 3600, plc_type='Micro800', map_=map_e300_power),
PLCChannel(PLC_IP_ADDRESS, 'tripstatusvoltageint', 'TripStatusVoltage_INT', 'STRING', 1.0, 3600, plc_type='Micro800', map_=map_e300_voltage),
PLCChannel(PLC_IP_ADDRESS, 'valoverloadtripcount', 'val_OverloadTripCount', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'valtripcount', 'val_TripCount', 'REAL', 1.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'voltageok', 'VoltageOK', 'REAL', 0.0, 3600, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, 'warningenabledicurrentimbalanc', 'WarningEnabled_I_CurrentImbalance', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'warningenabledigroundfault', 'WarningEnabled_I_GroundFault', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'warningenabledijam', 'WarningEnabled_I_Jam', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'warningenabledilineloss', 'WarningEnabled_I_LineLoss', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'warningenablediovercurrent', 'WarningEnabled_I_Overcurrent', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'warningenabledioverload', 'WarningEnabled_I_Overload', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'warningenablediphaseloss', 'WarningEnabled_I_PhaseLoss', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'warningenabledistall', 'WarningEnabled_I_Stall', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'warningenablediundercurrent', 'WarningEnabled_I_Undercurrent', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'warningenablediunderload', 'WarningEnabled_I_Underload', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'warningenablevoverfrequency', 'WarningEnable_V_Overfrequency', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'warningenablevovervoltage', 'WarningEnable_V_Overvoltage', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'warningenablevphaserotation', 'WarningEnable_V_PhaseRotation', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'warningenablevunderfrequency', 'WarningEnable_V_Underfrequency', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'warningenablevundervoltage', 'WarningEnable_V_Undervoltage', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'warningenablevvoltageunbalance', 'WarningEnable_V_VoltageUnbalance', 'REAL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'warningstatus', 'WarningStatus', 'BOOL', 0.0, 3600, plc_type='Micro800', write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'warningstatuscontrolint', 'WarningStatusControl_INT', 'STRING', 1.0, 3600, plc_type='Micro800', map_=map_e300_control, write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'warningstatuscurrentint', 'WarningStatusCurrent_INT', 'STRING', 1.0, 3600, plc_type='Micro800', map_=map_e300_current, write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'warningstatuspowerint', 'WarningStatusPower_INT', 'STRING', 1.0, 3600, plc_type='Micro800', map_=map_e300_power, write_enabled=True),
PLCChannel(PLC_IP_ADDRESS, 'warningstatusvoltageint', 'WarningStatusVoltage_INT', 'STRING', 1.0, 3600, plc_type='Micro800', map_=map_e300_voltage, write_enabled=True)
]
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 = "9"
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."""
pass
def run(self):
"""Actually run the driver."""
wait_sec = 30
for i in range(0, wait_sec):
print("ipp driver will start in {} seconds".format(wait_sec - i))
time.sleep(1)
logger.info("BOOM! Starting ipp driver...")
public_ip_address = get_public_ip_address()
self.sendtodb('public_ip_address', public_ip_address, 0)
send_loops = 0
watchdog_loops = 0
watchdog_check_after = 5000
while True:
if self.forceSend:
logger.warning("FORCE SEND: TRUE")
for c in CHANNELS:
v = c.read()
if c.check(v, self.forceSend):
self.sendtodb(c.mesh_name, c.value, 0)
if self.forceSend:
if send_loops > 2:
logger.warning("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.sendtodb('public_ip_address', test_public_ip, 0)
public_ip_address = test_public_ip
watchdog_loops = 0
time.sleep(10)
def ipp_sync(self, name, value):
"""Sync all data from the driver."""
self.sendtodb("connected", "true", 0)
return True

77
ipp/maps.py Normal file
View File

@@ -0,0 +1,77 @@
map_e300_current = {
0: 'None',
1: 'Overload',
2: 'Phase Loss',
4: 'Ground Fault',
8: 'Stall',
16: 'Jam',
32: 'Underload',
64: 'Current Imbalance',
128: 'L1 Undercurrent',
256: 'L2 Undercurrent',
512: 'L3 Undercurrent',
1024: 'L1 Overcurrent',
2048: 'L2 Overcurrent',
4096: 'L3 Overcurrent',
8192: 'L1 Line Loss',
16384: 'L2 Line Loss',
32768: 'L3 Line Loss'
}
map_e300_voltage = {
0: 'None',
1: 'Undervoltage',
2: 'Overvoltage',
4: 'Voltage Unbalance',
8: 'Phase Rotation',
16: 'Overfrequency'
}
map_e300_control = {
0: 'None',
1: 'Test Trip',
2: 'DLX Trip',
4: 'PTC Trip',
8: 'Operator Station Trip',
16: 'Remote Trip',
32: 'Blocked Start Trip',
64: 'Hardware Fault Trip',
128: 'Config Trip',
256: 'Option Match Trip',
512: 'DLX FB Timeout Trip',
1024: 'Expansion Bus Trip',
2048: 'Reserved',
4096: 'Reserved',
8192: 'NVS Trip',
16384: 'Test Mode Trip'
}
map_e300_power = {
0: 'None',
1: 'Under kW',
2: 'Over kW',
4: 'Under kVAR Consumed',
8: 'Over kVAR Consumed',
16: 'Under kVAR Generated',
32: 'Over kVAR Generated',
64: 'Under kVA',
128: 'Over kVA',
256: 'Under PF Lag',
512: 'Over PF Lag',
1024: 'Under PF Lead',
2048: 'Over PF Lead'
}
map_device_status = {
1: "Startup",
2: "Not ready to start",
3: "Ready to start",
4: "Lost run permissive",
5: "Not able to restart - Overload Limit",
6: "Not able to restart - Trip Limit",
7: "Waiting to attempt restart",
8: "Waiting to attempt restart (Overload)",
9: "Running",
10: "User stopped",
11: "Waiting to start (Timer Mode)"
}

119
ipp/micro800.py Normal file
View File

@@ -0,0 +1,119 @@
from pycomm.ab_comm.clx import Driver as plcDriver
import sys
def readMicroTag(addr, tag):
addr = str(addr)
tag = str(tag)
c = plcDriver()
if c.open(addr, True):
try:
v = c.read_tag(tag)
# print(v)
return v
except Exception:
err = c.get_status()
c.close()
print "{} on reading {} from {}".format(err, tag, addr)
pass
c.close()
def getTagType(addr, tag):
addr = str(addr)
tag = str(tag)
c = plcDriver()
if c.open(addr, True):
try:
return c.read_tag(tag)[1]
except Exception:
err = c.get_status()
c.close()
print err
pass
c.close()
def write(addr, tag, val, t):
addr = str(addr)
tag = str(tag)
c = plcDriver()
if c.open(addr, True):
try:
wt = c.write_tag(tag, val, t)
return wt
except Exception:
err = c.get_status()
c.close()
print("Write Error: {} setting {} at {} to {} type {}".format(err, tag, addr, val, t))
return False
c.close()
def closeEnough(a, b):
return abs(a - b) <= 0.001
def writeMicroTag(addr, tag, val, handshake=None, handshake_val=None):
addr = str(addr)
tag = str(tag)
print("handshake: {}, handshake_val: {}".format(handshake, handshake_val))
chk_tag = tag
if not(handshake is None) and not(handshake == "None"):
chk_tag = str(handshake)
print("Handshake tag passed, using {}".format(chk_tag))
chk_val = val
if not (handshake_val is None) and not(handshake_val == "None"):
chk_val = handshake_val
print("Handshake value passed, using {}".format(chk_val))
attempts_allowed = 5
attempts = 1
while attempts <= attempts_allowed:
try:
attempts = attempts + 1
cv = readMicroTag(addr, tag)
print("Val Before Write: {}".format(cv))
if cv:
if cv[1] == "REAL":
val = float(val)
chk_val = float(chk_val)
else:
val = int(val)
chk_val = int(chk_val)
wt = write(addr, tag, val, cv[1])
if wt:
print("write: {}".format(wt))
chk = readMicroTag(addr, chk_tag)
if chk:
print("chk: {}, chk_val: {}".format(chk, chk_val))
if closeEnough(chk[0], chk_val):
return True
except Exception as e:
print e
return False
def readMicroTagList(addr, tList):
addr = str(addr)
c = plcDriver()
if c.open(addr, True):
vals = []
try:
for t in tList:
v = c.read_tag(t)
vals.append({"tag": t, "val": v[0], "type": v[1]})
# print(v)
# print("{0} - {1}".format(t, v))
except Exception:
err = c.get_status()
c.close()
print err
pass
c.close()
return vals
if __name__ == '__main__':
if len(sys.argv) > 2:
print(readMicroTag(sys.argv[1], sys.argv[2]))
else:
print ("Did not pass a target and tag name.")

51
ipp/utilities.py Normal file
View File

@@ -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

293
maplehmi/Channel.py Normal file
View File

@@ -0,0 +1,293 @@
"""Define Meshify channel class."""
import time
from pycomm.ab_comm.clx import Driver as ClxDriver
from pycomm.cip.cip_base import CommError, DataError
from file_logger import filelogger as log
TAG_DATAERROR_SLEEPTIME = 5
def binarray(intval):
"""Split an integer into its bits."""
bin_string = '{0:08b}'.format(intval)
bin_arr = [i for i in bin_string]
bin_arr.reverse()
return bin_arr
def read_tag(addr, tag, plc_type="CLX"):
"""Read a tag from the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
try:
if clx.open(addr, direct_connection=direct):
try:
val = clx.read_tag(tag)
return val
except DataError as err:
clx.close()
time.sleep(TAG_DATAERROR_SLEEPTIME)
log.error("Data Error during readTag({}, {}): {}".format(addr, tag, err))
except CommError:
# err = c.get_status()
clx.close()
log.error("Could not connect during readTag({}, {})".format(addr, tag))
except AttributeError as err:
clx.close()
log.error("AttributeError during readTag({}, {}): \n{}".format(addr, tag, err))
clx.close()
return False
def read_array(addr, tag, start, end, plc_type="CLX"):
"""Read an array from the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
if clx.open(addr, direct_connection=direct):
arr_vals = []
try:
for i in range(start, end):
tag_w_index = tag + "[{}]".format(i)
val = clx.read_tag(tag_w_index)
arr_vals.append(round(val[0], 4))
if arr_vals:
return arr_vals
else:
log.error("No length for {}".format(addr))
return False
except Exception:
log.error("Error during readArray({}, {}, {}, {})".format(addr, tag, start, end))
err = clx.get_status()
clx.close()
log.error(err)
clx.close()
def write_tag(addr, tag, val, plc_type="CLX"):
"""Write a tag value to the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
try:
if clx.open(addr, direct_connection=direct):
try:
initial_val = clx.read_tag(tag)
write_status = clx.write_tag(tag, val, initial_val[1])
return write_status
except DataError as err:
clx_err = clx.get_status()
clx.close()
log.error("--\nDataError during writeTag({}, {}, {}, plc_type={}) -- {}\n{}\n".format(addr, tag, val, plc_type, err, clx_err))
except CommError as err:
clx_err = clx.get_status()
log.error("--\nCommError during write_tag({}, {}, {}, plc_type={})\n{}\n--".format(addr, tag, val, plc_type, err))
clx.close()
return False
class Channel(object):
"""Holds the configuration for a Meshify channel."""
def __init__(self, mesh_name, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False):
"""Initialize the channel."""
self.mesh_name = mesh_name
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
def __str__(self):
"""Create a string for the channel."""
return "{}\nvalue: {}, last_send_time: {}".format(self.mesh_name, self.value, self.last_send_time)
def check(self, new_value, force_send=False):
"""Check to see if the new_value needs to be stored."""
send_needed = False
send_reason = ""
if self.data_type == 'BOOL' or self.data_type == 'STRING':
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif self.value != new_value:
if self.map_:
if not self.value == self.map_[new_value]:
send_needed = True
send_reason = "value change"
else:
send_needed = True
send_reason = "value change"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
else:
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif abs(self.value - new_value) > self.chg_threshold:
send_needed = True
send_reason = "change threshold"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
if send_needed:
self.last_value = self.value
if self.map_:
try:
self.value = self.map_[new_value]
except KeyError:
log.error("Cannot find a map value for {} in {} for {}".format(new_value, self.map_, self.mesh_name))
self.value = new_value
else:
self.value = new_value
self.last_send_time = time.time()
log.info("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason))
return send_needed
def read(self):
"""Read the value."""
pass
def identity(sent):
"""Return exactly what was sent to it."""
return sent
class ModbusChannel(Channel):
"""Modbus channel object."""
def __init__(self, mesh_name, register_number, data_type, chg_threshold, guarantee_sec, channel_size=1, map_=False, write_enabled=False, transform_fn=identity):
"""Initialize the channel."""
super(ModbusChannel, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.mesh_name = mesh_name
self.register_number = register_number
self.channel_size = channel_size
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
self.transform_fn = transform_fn
def read(self, mbsvalue):
"""Return the transformed read value."""
return self.transform_fn(mbsvalue)
class PLCChannel(Channel):
"""PLC Channel Object."""
def __init__(self, ip, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False, plc_type='CLX'):
"""Initialize the channel."""
super(PLCChannel, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.plc_ip = ip
self.mesh_name = mesh_name
self.plc_tag = plc_tag
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
self.plc_type = plc_type
def read(self):
"""Read the value."""
plc_value = None
if self.plc_tag and self.plc_ip:
read_value = read_tag(self.plc_ip, self.plc_tag, plc_type=self.plc_type)
if read_value:
plc_value = read_value[0]
return plc_value
class BoolArrayChannels(Channel):
"""Hold the configuration for a set of boolean array channels."""
def __init__(self, ip, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False):
"""Initialize the channel."""
super(BoolArrayChannels, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.plc_ip = ip
self.mesh_name = mesh_name
self.plc_tag = plc_tag
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
def compare_values(self, new_val_dict):
"""Compare new values to old values to see if the values need storing."""
send = False
for idx in new_val_dict:
try:
if new_val_dict[idx] != self.last_value[idx]:
send = True
except KeyError:
log.error("Key Error in self.compare_values for index {}".format(idx))
send = True
return send
def read(self, force_send=False):
"""Read the value and check to see if needs to be stored."""
send_needed = False
send_reason = ""
if self.plc_tag:
val = read_tag(self.plc_ip, self.plc_tag)
if val:
bool_arr = binarray(val[0])
new_val = {}
for idx in self.map_:
try:
new_val[self.map_[idx]] = bool_arr[idx]
except KeyError:
log.error("Not able to get value for index {}".format(idx))
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif self.compare_values(new_val):
send_needed = True
send_reason = "value change"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
if send_needed:
self.value = new_val
self.last_value = self.value
self.last_send_time = time.time()
log.info("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason))
return send_needed

4
maplehmi/Tags.py Normal file
View File

@@ -0,0 +1,4 @@
from Channel import PLCChannel, ModbusChannel
from maplehmi import PLC_IP_ADDRESS
tags = [ ]

14
maplehmi/config.txt Normal file
View File

@@ -0,0 +1,14 @@
{
"files": {
"file3": "file_logger.py",
"file2": "Channel.py",
"file1": "maplehmi.py",
"file6": "persistence.py",
"file5": "utilities.py",
"file4": "Tags.py"
},
"deviceName": "maplehmi",
"releaseVersion": "1",
"driverFileName": "maplehmi.py",
"driverId": "0100"
}

360
maplehmi/device_base.py Normal file
View File

@@ -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)))

View File

@@ -0,0 +1,14 @@
{
"name": "maplehmi",
"driverFilename": "maplehmi.py",
"driverId": "0000",
"additionalDriverFiles": [
"utilities.py",
"persistence.py",
"Channel.py",
"logger.py",
"Tags.py"
],
"version": 1,
"s3BucketName": "maplehmi"
}

18
maplehmi/file_logger.py Normal file
View File

@@ -0,0 +1,18 @@
"""Logging setup for maplehmi"""
import logging
from logging.handlers import RotatingFileHandler
import sys
log_formatter = logging.Formatter('%(asctime)s %(levelname)s %(funcName)s(%(lineno)d) %(message)s')
log_file = './maplehmi.log'
my_handler = RotatingFileHandler(log_file, mode='a', maxBytes=500*1024,
backupCount=2, encoding=None, delay=0)
my_handler.setFormatter(log_formatter)
my_handler.setLevel(logging.INFO)
filelogger = logging.getLogger('maplehmi')
filelogger.setLevel(logging.INFO)
filelogger.addHandler(my_handler)
console_out = logging.StreamHandler(sys.stdout)
console_out.setFormatter(log_formatter)
filelogger.addHandler(console_out)

148
maplehmi/maplehmi.py Normal file
View File

@@ -0,0 +1,148 @@
"""Driver for maplehmi"""
import threading
import json
import time
from random import randint
from device_base import deviceBase
from Channel import PLCChannel, ModbusChannel,read_tag, write_tag, TAG_DATAERROR_SLEEPTIME
import persistence
from utilities import get_public_ip_address
from file_logger import filelogger as log
PLC_IP_ADDRESS = "192.168.1.10"
from Tags import tags
_ = None
log.info("maplehmi startup")
# GLOBAL VARIABLES
WAIT_FOR_CONNECTION_SECONDS = 60
IP_CHECK_PERIOD = 60
WATCHDOG_ENABLE = True
WATCHDOG_CHECK_PERIOD = 60
WATCHDOG_SEND_PERIOD = 3600 # Seconds, the longest amount of time before sending the watchdog status
CHANNELS = tags
# PERSISTENCE FILE
PERSIST = persistence.load()
class start(threading.Thread, deviceBase):
"""Start class required by Meshify."""
def __init__(self, name=None, number=None, mac=None, Q=None, mcu=None,
companyId=None, offset=None, mqtt=None, Nodes=None):
"""Initialize the driver."""
threading.Thread.__init__(self)
deviceBase.__init__(self, name=name, number=number, mac=mac, Q=Q,
mcu=mcu, companyId=companyId, offset=offset,
mqtt=mqtt, Nodes=Nodes)
self.daemon = True
self.version = "1"
self.finished = threading.Event()
self.force_send = False
self.public_ip_address = ""
self.public_ip_address_last_checked = 0
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("maplehmi driver will start in {} seconds".format(WAIT_FOR_CONNECTION_SECONDS - i))
time.sleep(1)
log.info("BOOM! Starting maplehmi driver...")
self._check_watchdog()
self._check_ip_address()
self.nodes["maplehmi_0199"] = self
send_loops = 0
while True:
now = time.time()
if self.force_send:
log.warning("FORCE SEND: TRUE")
for chan in CHANNELS:
val = chan.read()
if chan.check(val, self.force_send):
self.sendtodbDev(1, chan.mesh_name, chan.value, 0, 'maplehmi')
time.sleep(TAG_DATAERROR_SLEEPTIME) # sleep to allow Micro800 to handle ENET requests
# print("maplehmi driver still alive...")
if self.force_send:
if send_loops > 2:
log.warning("Turning off force_send")
self.force_send = False
send_loops = 0
else:
send_loops += 1
if WATCHDOG_ENABLE:
if (now - self.watchdog_last_checked) > WATCHDOG_CHECK_PERIOD:
self._check_watchdog()
if (now - self.public_ip_address_last_checked) > IP_CHECK_PERIOD:
self._check_ip_address()
def _check_watchdog(self):
"""Check the watchdog and send to Meshify if changed or stale."""
test_watchdog = self.maplehmi_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, 'maplehmi')
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, 'maplehmi')
self.public_ip_address = test_public_ip
def maplehmi_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="CLX")
time.sleep(1)
watchdog_val = read_tag(str(PLC_IP_ADDRESS), 'watchdog_INT', plc_type="CLX")
try:
return (randval - 1) == watchdog_val[0]
except (KeyError, TypeError):
return False
def maplehmi_sync(self, name, value):
"""Sync all data from the driver."""
self.force_send = True
# self.sendtodb("log", "synced", 0)
return True
def maplehmi_writeplctag(self, name, value):
"""Write a value to the PLC."""
new_val = json.loads(str(value).replace("'", '"'))
tag_n = str(new_val['tag']) # "cmd_Start"
val_n = new_val['val']
write_res = write_tag(str(PLC_IP_ADDRESS), tag_n, val_n, plc_type="CLX")
print("Result of maplehmi_writeplctag(self, {}, {}) = {}".format(name, value, write_res))
if write_res is None:
write_res = "Error writing to PLC..."
return write_res

21
maplehmi/persistence.py Normal file
View File

@@ -0,0 +1,21 @@
"""Data persistance functions."""
# if more advanced persistence is needed, use a sqlite database
import json
def load(filename="persist.json"):
"""Load persisted settings from the specified file."""
try:
with open(filename, 'r') as persist_file:
return json.load(persist_file)
except Exception:
return False
def store(persist_obj, filename="persist.json"):
"""Store the persisting settings into the specified file."""
try:
with open(filename, 'w') as persist_file:
return json.dump(persist_obj, persist_file, indent=4)
except Exception:
return False

52
maplehmi/utilities.py Normal file
View File

@@ -0,0 +1,52 @@
"""Utility functions for the driver."""
import socket
import struct
def get_public_ip_address():
"""Find the public IP Address of the host device."""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(("8.8.8.8", 80))
ip_address = sock.getsockname()[0]
sock.close()
except Exception as e:
return e
return ip_address
def int_to_float16(int_to_convert):
"""Convert integer into float16 representation."""
bin_rep = ('0' * 16 + '{0:b}'.format(int_to_convert))[-16:]
sign = 1.0
if int(bin_rep[0]) == 1:
sign = -1.0
exponent = float(int(bin_rep[1:6], 2))
fraction = float(int(bin_rep[6:17], 2))
if exponent == float(0b00000):
return sign * 2 ** -14 * fraction / (2.0 ** 10.0)
elif exponent == float(0b11111):
if fraction == 0:
return sign * float("inf")
return float("NaN")
frac_part = 1.0 + fraction / (2.0 ** 10.0)
return sign * (2 ** (exponent - 15)) * frac_part
def ints_to_float(int1, int2):
"""Convert 2 registers into a floating point number."""
mypack = struct.pack('>HH', int1, int2)
f_unpacked = struct.unpack('>f', mypack)
print("[{}, {}] >> {}".format(int1, int2, f_unpacked[0]))
return f_unpacked[0]
def degf_to_degc(temp_f):
"""Convert deg F to deg C."""
return (temp_f - 32.0) * (5.0/9.0)
def degc_to_degf(temp_c):
"""Convert deg C to deg F."""
return temp_c * 1.8 + 32.0

298
multisensor/channel.py Normal file
View File

@@ -0,0 +1,298 @@
"""Define Meshify channel class."""
import time
import urllib
from pycomm.ab_comm.clx import Driver as ClxDriver
from pycomm.cip.cip_base import CommError, DataError
from file_logger import filelogger as log
TAG_DATAERROR_SLEEPTIME = 5
def binarray(intval):
"""Split an integer into its bits."""
bin_string = '{0:08b}'.format(intval)
bin_arr = [i for i in bin_string]
bin_arr.reverse()
return bin_arr
def read_tag(addr, tag, plc_type="CLX"):
"""Read a tag from the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
try:
if clx.open(addr, direct_connection=direct):
try:
val = clx.read_tag(tag)
clx.close()
return val
except DataError as err:
clx.close()
time.sleep(TAG_DATAERROR_SLEEPTIME)
log.error("Data Error during readTag({}, {}): {}".format(addr, tag, err))
except CommError:
# err = c.get_status()
clx.close()
log.error("Could not connect during readTag({}, {})".format(addr, tag))
except AttributeError as err:
clx.close()
log.error("AttributeError during readTag({}, {}): \n{}".format(addr, tag, err))
clx.close()
return False
def read_array(addr, tag, start, end, plc_type="CLX"):
"""Read an array from the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
if clx.open(addr, direct_connection=direct):
arr_vals = []
try:
for i in range(start, end):
tag_w_index = tag + "[{}]".format(i)
val = clx.read_tag(tag_w_index)
arr_vals.append(round(val[0], 4))
if arr_vals:
clx.close()
return arr_vals
else:
log.error("No length for {}".format(addr))
clx.close()
return False
except Exception:
log.error("Error during readArray({}, {}, {}, {})".format(addr, tag, start, end))
err = clx.get_status()
clx.close()
log.error(err)
clx.close()
def write_tag(addr, tag, val, plc_type="CLX"):
"""Write a tag value to the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
try:
if clx.open(addr, direct_connection=direct):
try:
initial_val = clx.read_tag(tag)
write_status = clx.write_tag(tag, val, initial_val[1])
clx.close()
return write_status
except DataError as err:
clx_err = clx.get_status()
clx.close()
log.error("--\nDataError during writeTag({}, {}, {}, plc_type={}) -- {}\n{}\n".format(addr, tag, val, plc_type, err, clx_err))
except CommError as err:
clx_err = clx.get_status()
log.error("--\nCommError during write_tag({}, {}, {}, plc_type={})\n{}\n--".format(addr, tag, val, plc_type, err))
clx.close()
return False
class Channel(object):
"""Holds the configuration for a Meshify channel."""
def __init__(self, mesh_name, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False):
"""Initialize the channel."""
self.mesh_name = mesh_name
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
def __str__(self):
"""Create a string for the channel."""
return "{}\nvalue: {}, last_send_time: {}".format(self.mesh_name, self.value, self.last_send_time)
def check(self, new_value, force_send=False):
"""Check to see if the new_value needs to be stored."""
send_needed = False
send_reason = ""
if self.data_type == 'BOOL' or self.data_type == 'STRING':
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif self.value != new_value:
if self.map_:
if not self.value == self.map_[new_value]:
send_needed = True
send_reason = "value change"
else:
send_needed = True
send_reason = "value change"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
else:
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif abs(self.value - new_value) > self.chg_threshold:
send_needed = True
send_reason = "change threshold"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
if send_needed:
self.last_value = self.value
if self.map_:
try:
self.value = self.map_[new_value]
except KeyError:
log.error("Cannot find a map value for {} in {} for {}".format(new_value, self.map_, self.mesh_name))
self.value = new_value
else:
self.value = new_value
self.last_send_time = time.time()
log.info("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason))
return send_needed
def read(self):
"""Read the value."""
pass
def identity(sent):
"""Return exactly what was sent to it."""
return sent
class ModbusChannel(Channel):
"""Modbus channel object."""
def __init__(self, mesh_name, register_number, data_type, chg_threshold, guarantee_sec, channel_size=1, map_=False, write_enabled=False, transform_fn=identity):
"""Initialize the channel."""
super(ModbusChannel, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.mesh_name = mesh_name
self.register_number = register_number
self.channel_size = channel_size
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
self.transform_fn = transform_fn
def read(self, mbsvalue):
"""Return the transformed read value."""
return self.transform_fn(mbsvalue)
class PLCChannel(Channel):
"""PLC Channel Object."""
def __init__(self, ip, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False, plc_type='CLX'):
"""Initialize the channel."""
super(PLCChannel, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.plc_ip = ip
self.mesh_name = mesh_name
self.plc_tag = plc_tag
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
self.plc_type = plc_type
def read(self):
"""Read the value."""
plc_value = None
if self.plc_tag and self.plc_ip:
read_value = read_tag(self.plc_ip, self.plc_tag, plc_type=self.plc_type)
if read_value:
plc_value = read_value[0]
return plc_value
class BoolArrayChannels(Channel):
"""Hold the configuration for a set of boolean array channels."""
def __init__(self, ip, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False):
"""Initialize the channel."""
super(BoolArrayChannels, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.plc_ip = ip
self.mesh_name = mesh_name
self.plc_tag = plc_tag
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
def compare_values(self, new_val_dict):
"""Compare new values to old values to see if the values need storing."""
send = False
for idx in new_val_dict:
try:
if new_val_dict[idx] != self.last_value[idx]:
send = True
except KeyError:
log.error("Key Error in self.compare_values for index {}".format(idx))
send = True
return send
def read(self, force_send=False):
"""Read the value and check to see if needs to be stored."""
send_needed = False
send_reason = ""
if self.plc_tag:
val = read_tag(self.plc_ip, self.plc_tag)
if val:
bool_arr = binarray(val[0])
new_val = {}
for idx in self.map_:
try:
new_val[self.map_[idx]] = bool_arr[idx]
except KeyError:
log.error("Not able to get value for index {}".format(idx))
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif self.compare_values(new_val):
send_needed = True
send_reason = "value change"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
if send_needed:
self.value = new_val
self.last_value = self.value
self.last_send_time = time.time()
log.info("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason))
return send_needed

13
multisensor/config.txt Normal file
View File

@@ -0,0 +1,13 @@
{
"driverFileName": "multisensor.py",
"deviceName": "multisensor",
"driverId": "0240",
"releaseVersion": "5",
"files": {
"file1": "multisensor.py",
"file2": "utilities.py",
"file3": "channel.py",
"file4": "file_logger.py",
"file5": "persistence.py"
}
}

View File

@@ -0,0 +1,18 @@
"""Logging setup for multisensor"""
import logging
from logging.handlers import RotatingFileHandler
import sys
log_formatter = logging.Formatter('%(asctime)s %(levelname)s %(funcName)s(%(lineno)d) %(message)s')
log_file = './multisensor.log'
my_handler = RotatingFileHandler(log_file, mode='a', maxBytes=500*1024,
backupCount=2, encoding=None, delay=0)
my_handler.setFormatter(log_formatter)
my_handler.setLevel(logging.INFO)
filelogger = logging.getLogger('multisensor')
filelogger.setLevel(logging.INFO)
filelogger.addHandler(my_handler)
console_out = logging.StreamHandler(sys.stdout)
console_out.setFormatter(log_formatter)
filelogger.addHandler(console_out)

22
multisensor/logger.py Normal file
View File

@@ -0,0 +1,22 @@
"""Logging setup for multisensor"""
# LOGGING SETUP
import logging
from logging.handlers import RotatingFileHandler
import sys
def setup():
"""Set up the logger and return it."""
log_formatter = logging.Formatter('%(asctime)s %(levelname)s %(funcName)s(%(lineno)d) %(message)s')
log_file = './multisensor.log'
my_handler = RotatingFileHandler(log_file, mode='a', maxBytes=500*1024,
backupCount=2, encoding=None, delay=0)
my_handler.setFormatter(log_formatter)
my_handler.setLevel(logging.INFO)
filelogger = logging.getLogger('multisensor')
filelogger.setLevel(logging.INFO)
filelogger.addHandler(my_handler)
console_out = logging.StreamHandler(sys.stdout)
console_out.setFormatter(log_formatter)
filelogger.addHandler(console_out)
return filelogger

323
multisensor/multisensor.py Normal file
View File

@@ -0,0 +1,323 @@
"""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 file_logger import filelogger as log
import persistence
# PERSISTENCE FILE
PERSIST = persistence.load('persist.json')
if not PERSIST:
PERSIST = {'an0low': 1.0, 'an0high': 20.0, 'an0active': 0,
'an1low': 1.0, 'an1high': 20.0, 'an1active': 0,
'an2low': 1.0, 'an2high': 20.0, 'an2active': 0,
'an3low': 1.0, 'an3high': 20.0, 'an3active': 0,
'an4low': 1.0, 'an4high': 20.0, 'an4active': 0,
'an5low': 1.0, 'an5high': 20.0, 'an5active': 0,
'an6low': 1.0, 'an6high': 20.0, 'an6active': 0,
'an7low': 1.0, 'an7high': 20.0, 'an7active': 0}
persistence.store(PERSIST, 'persist.json')
_ = None
# log = file_logger.setup()
log.info("multisensor startup")
# GLOBAL VARIABLES
WAIT_FOR_CONNECTION_SECONDS = 60
PLC_IP_ADDRESS = "192.168.1.12"
CALIBRATION_TABLES = [[] for x in xrange(8)]
CHANNELS = [
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"),
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")
]
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 = "5"
self.finished = threading.Event()
self.force_send = False
self.public_ip_address = ""
self.public_ip_address_last_checked = 0
threading.Thread.start(self)
# this is a required function for all drivers, its goal is to upload some piece of data
# about your device so it can be seen on the web
def register(self):
"""Register the driver."""
# self.sendtodb("log", "BOOM! Booted.", 0)
pass
def run(self):
"""Actually run the driver."""
for i in range(0, WAIT_FOR_CONNECTION_SECONDS):
print("multisensor driver will start in {} seconds".format(WAIT_FOR_CONNECTION_SECONDS - i))
time.sleep(1)
log.info("BOOM! Starting multisensor driver...")
self._check_ip_address()
self.nodes["multisensor_0199"] = self
send_loops = 0
ip_check_after = 60
for i in range(0,8):
self.read_pond_calibration(i)
while True:
now = time.time()
if self.force_send:
log.warning("FORCE SEND: TRUE")
for chan in CHANNELS:
val = chan.read()
if "active" in chan.plc_tag:
PERSIST[chan.mesh_name] = val
persistence.store(PERSIST,'persist.json')
if "scaledValue" in chan.plc_tag:
if val <= PERSIST[chan.mesh_name[:3]+"low"] and PERSIST[chan.mesh_name[:3]+"active"] == 1:
self.sendtodbDev(1, chan.mesh_name[:3]+"alarm", "LOW", 0, 'multisensor')
elif val >= PERSIST[chan.mesh_name[:3]+"high"] and PERSIST[chan.mesh_name[:3]+"active"] == 1:
self.sendtodbDev(1, chan.mesh_name[:3]+"alarm", "HIGH", 0, 'multisensor')
else:
self.sendtodbDev(1, chan.mesh_name[:3]+"alarm", "OK", 0, 'multisensor')
if chan.check(val, self.force_send):
self.sendtodbDev(1, chan.mesh_name, chan.value, 0, 'multisensor')
#time.sleep(5)
# print("multisensor driver still alive...")
if self.force_send:
if send_loops > 2:
log.warning("Turning off force_send")
self.force_send = False
send_loops = 0
for i in range(0,8):
self.read_pond_calibration(i)
else:
send_loops += 1
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 = "input{}.calibrationLevel[{}]".format(input_number, cal_index)
cal_tag_volume = "input{}.calibrationVolume[{}]".format(input_number, cal_index)
read_height = read_tag(PLC_IP_ADDRESS, cal_tag_height, plc_type="Micro800")
time.sleep(5)
read_volume = read_tag(PLC_IP_ADDRESS, cal_tag_volume, plc_type="Micro800")
time.sleep(5)
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, 'multisensor')
CALIBRATION_TABLES[input_number] = cal_values
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_sync(self, name, value):
"""Sync all data from the driver."""
self.force_send = True
print("got sync({}, {})".format(name, value))
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, plc_type="Micro800")
print("Result of multisensor_writeplctag(self, {}, {}) = {}".format(name, value, write_res))
if write_res is None:
write_res = "Error writing to PLC..."
if write_res:
if "scalingConfig" in tag_n:
if tag_n[-1] == "x":
self.sendtodbDev(1, 'an{}max'.format(tag_n[5]), val_n, 0, 'multisensor')
else:
self.sendtodbDev(1, 'an{}min'.format(tag_n[5]), val_n, 0, 'multisensor')
elif "isPondLevel" in tag_n:
self.sendtodbDev(1, 'an{}ispond'.format(tag_n[5]), val_n, 0, 'multisensor')
return write_res
def multisensor_an0lowpsialarm(self, name, value):
value = int(value)
PERSIST["an0low"] = value
self.sendtodbDev(1, 'an0lowpsialarm', value, 0, 'multisensor')
return persistence.store(PERSIST,"persist.json")
def multisensor_an1lowpsialarm(self, name, value):
value = int(value)
PERSIST["an1low"] = value
self.sendtodbDev(1, 'an1lowpsialarm', value, 0, 'multisensor')
return persistence.store(PERSIST,"persist.json")
def multisensor_an2lowpsialarm(self, name, value):
value = int(value)
PERSIST["an2low"] = value
self.sendtodbDev(1, 'an2lowpsialarm', value, 0, 'multisensor')
return persistence.store(PERSIST,"persist.json")
def multisensor_an3lowpsialarm(self, name, value):
value = int(value)
PERSIST["an3low"] = value
self.sendtodbDev(1, 'an3lowpsialarm', value, 0, 'multisensor')
return persistence.store(PERSIST,"persist.json")
def multisensor_an4lowpsialarm(self, name, value):
value = int(value)
PERSIST["an4low"] = value
self.sendtodbDev(1, 'an4lowpsialarm', value, 0, 'multisensor')
return persistence.store(PERSIST,"persist.json")
def multisensor_an5lowpsialarm(self, name, value):
value = int(value)
PERSIST["an5low"] = value
self.sendtodbDev(1, 'an5lowpsialarm', value, 0, 'multisensor')
return persistence.store(PERSIST,"persist.json")
def multisensor_an6lowpsialarm(self, name, value):
value = int(value)
PERSIST["an6low"] = value
self.sendtodbDev(1, 'an6lowpsialarm', value, 0, 'multisensor')
return persistence.store(PERSIST,"persist.json")
def multisensor_an7lowpsialarm(self, name, value):
value = int(value)
PERSIST["an7low"] = value
self.sendtodbDev(1, 'an7lowpsialarm', value, 0, 'multisensor')
return persistence.store(PERSIST,"persist.json")
def multisensor_an0highpsialarm(self, name, value):
value = int(value)
PERSIST["an0high"] = value
self.sendtodbDev(1, 'an0highpsialarm', value, 0, 'multisensor')
return persistence.store(PERSIST,"persist.json")
def multisensor_an1highpsialarm(self, name, value):
value = int(value)
PERSIST["an1high"] = value
self.sendtodbDev(1, 'an1highpsialarm', value, 0, 'multisensor')
return persistence.store(PERSIST,"persist.json")
def multisensor_an2highpsialarm(self, name, value):
value = int(value)
PERSIST["an2high"] = value
self.sendtodbDev(1, 'an2highpsialarm', value, 0, 'multisensor')
return persistence.store(PERSIST,"persist.json")
def multisensor_an3highpsialarm(self, name, value):
value = int(value)
PERSIST["an3high"] = value
self.sendtodbDev(1, 'an3highpsialarm', value, 0, 'multisensor')
return persistence.store(PERSIST,"persist.json")
def multisensor_an4highpsialarm(self, name, value):
value = int(value)
PERSIST["an4high"] = value
self.sendtodbDev(1, 'an4highpsialarm', value, 0, 'multisensor')
return persistence.store(PERSIST,"persist.json")
def multisensor_an5highpsialarm(self, name, value):
value = int(value)
PERSIST["an5high"] = value
self.sendtodbDev(1, 'an5highpsialarm', value, 0, 'multisensor')
return persistence.store(PERSIST,"persist.json")
def multisensor_an6highpsialarm(self, name, value):
value = int(value)
PERSIST["an6high"] = value
self.sendtodbDev(1, 'an6highpsialarm', value, 0, 'multisensor')
return persistence.store(PERSIST,"persist.json")
def multisensor_an7highpsialarm(self, name, value):
value = int(value)
PERSIST["an7high"] = value
self.sendtodbDev(1, 'an7highpsialarm', value, 0, 'multisensor')
return persistence.store(PERSIST,"persist.json")

View File

@@ -0,0 +1,21 @@
"""Data persistance functions."""
# if more advanced persistence is needed, use a sqlite database
import json
def load(filename="persist.json"):
"""Load persisted settings from the specified file."""
try:
with open(filename, 'r') as persist_file:
return json.load(persist_file)
except Exception:
return False
def store(persist_obj, filename="persist.json"):
"""Store the persisting settings into the specified file."""
try:
with open(filename, 'w') as persist_file:
return json.dump(persist_obj, persist_file, indent=4)
except Exception:
return False

51
multisensor/utilities.py Normal file
View File

@@ -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

418
piflow/Channel.py Normal file
View File

@@ -0,0 +1,418 @@
"""Define Meshify channel class."""
import time
from pycomm.ab_comm.clx import Driver as ClxDriver
from pycomm.cip.cip_base import CommError, DataError
from file_logger import filelogger as log
import minimalmodbus
minimalmodbus.BAUDRATE = 9600
minimalmodbus.STOPBITS = 1
TAG_DATAERROR_SLEEPTIME = 5
def binarray(intval):
"""Split an integer into its bits."""
bin_string = '{0:08b}'.format(intval)
bin_arr = [i for i in bin_string]
bin_arr.reverse()
return bin_arr
def read_tag(addr, tag, plc_type="CLX"):
"""Read a tag from the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
try:
if clx.open(addr, direct_connection=direct):
try:
val = clx.read_tag(tag)
return val
except DataError as err:
clx.close()
time.sleep(TAG_DATAERROR_SLEEPTIME)
log.error("Data Error during readTag({}, {}): {}".format(addr, tag, err))
except CommError:
# err = c.get_status()
clx.close()
log.error("Could not connect during readTag({}, {})".format(addr, tag))
except AttributeError as err:
clx.close()
log.error("AttributeError during readTag({}, {}): \n{}".format(addr, tag, err))
clx.close()
return False
def read_array(addr, tag, start, end, plc_type="CLX"):
"""Read an array from the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
if clx.open(addr, direct_connection=direct):
arr_vals = []
try:
for i in range(start, end):
tag_w_index = tag + "[{}]".format(i)
val = clx.read_tag(tag_w_index)
arr_vals.append(round(val[0], 4))
if arr_vals:
return arr_vals
else:
log.error("No length for {}".format(addr))
return False
except Exception:
log.error("Error during readArray({}, {}, {}, {})".format(addr, tag, start, end))
err = clx.get_status()
clx.close()
log.error(err)
clx.close()
def write_tag(addr, tag, val, plc_type="CLX"):
"""Write a tag value to the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
try:
if clx.open(addr, direct_connection=direct):
try:
initial_val = clx.read_tag(tag)
write_status = clx.write_tag(tag, val, initial_val[1])
return write_status
except DataError as err:
clx_err = clx.get_status()
clx.close()
log.error("--\nDataError during writeTag({}, {}, {}, plc_type={}) -- {}\n{}\n".format(addr, tag, val, plc_type, err, clx_err))
except CommError as err:
clx_err = clx.get_status()
log.error("--\nCommError during write_tag({}, {}, {}, plc_type={})\n{}\n--".format(addr, tag, val, plc_type, err))
clx.close()
return False
class Channel(object):
"""Holds the configuration for a Meshify channel."""
def __init__(self, mesh_name, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False):
"""Initialize the channel."""
self.mesh_name = mesh_name
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
def __str__(self):
"""Create a string for the channel."""
return "{}\nvalue: {}, last_send_time: {}".format(self.mesh_name, self.value, self.last_send_time)
def check(self, new_value, force_send=False):
"""Check to see if the new_value needs to be stored."""
send_needed = False
send_reason = ""
if self.data_type == 'BOOL' or self.data_type == 'STRING':
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif self.value != new_value:
if self.map_:
if not self.value == self.map_[new_value]:
send_needed = True
send_reason = "value change"
else:
send_needed = True
send_reason = "value change"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
else:
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif abs(self.value - new_value) > self.chg_threshold:
send_needed = True
send_reason = "change threshold"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
if send_needed:
self.last_value = self.value
if self.map_:
try:
self.value = self.map_[new_value]
except KeyError:
log.error("Cannot find a map value for {} in {} for {}".format(new_value, self.map_, self.mesh_name))
self.value = new_value
else:
self.value = new_value
self.last_send_time = time.time()
log.info("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason))
return send_needed
def read(self):
"""Read the value."""
pass
def identity(sent):
"""Return exactly what was sent to it."""
return sent
def int_to_bits(n,x):
return pad_to_x([int(digit) for digit in bin(n)[2:]],x) # [2:] to chop off the "0b" part
def pad_to_x(n,x):
while len(n) < x:
n = [0] + n
return n
def status_codes(n):
status_array = int_to_bits(n,16)
status = {
0: "Stopped",
1: "Operating Forward",
2: "Operating Reverse",
3: "Fault Trip",
4: "Accelerating",
5: "Decelerating",
6: "Speed Reached",
7: "DC Braking",
8: "Drive Stopped",
9: "Not Used",
10: "Brake Release Signal",
11: "Forward Cmd",
12: "Reverse Cmd",
13: "REM, R/S",
14: "REM, Freq",
15: "Reversed"
}
stats = []
counter = 15
for x in range(16):
if status_array[x] == 1:
stats = [status[counter]] + stats
counter = counter - 1
return '; '.join(stats)
def fault_code_a(n):
fault_code_array = int_to_bits(n,16)
fault = {
0: "OCT",
1: "OVT",
2: "EXT-A",
3: "EST",
4: "COL",
5: "GFT",
6: "OHT",
7: "ETH",
8: "OLT",
9: "Reserved",
10: "EXT-B",
11: "EEP",
12: "FAN",
13: "POT",
14: "IOLT",
15: "LVT"
}
faults = []
counter = 15
for x in range(16):
if fault_code_array[x] == 1:
faults = [fault[counter]] + faults
counter = counter - 1
return '; '.join(faults)
def fault_code_b(n):
fault_code_array = int_to_bits(n,8)
fault = {
0: "COM",
1: "Reserved",
2: "NTC",
3: "REEP",
4: "OC2",
5: "NBR",
6: "SAFA",
7: "SAFB"
}
faults = []
counter = 7
for x in range(8):
if fault_code_array[x] == 1:
faults = [fault[counter]] + faults
counter = counter - 1
return '; '.join(faults)
class ModbusChannel(Channel):
"""Modbus channel object."""
def __init__(self, mesh_name, register_number, data_type, chg_threshold, guarantee_sec, channel_size=1, map_=False, write_enabled=False, transform_fn=identity, unit_number=1, scaling=0):
"""Initialize the channel."""
super(ModbusChannel, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.mesh_name = mesh_name
self.register_number = register_number
self.channel_size = channel_size
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
self.transform_fn = transform_fn
self.unit_number = unit_number
self.instrument = minimalmodbus.Instrument('/dev/ttyS0', self.unit_number)
self.scaling= scaling
def read(self):
"""Return the transformed read value."""
if self.data_type == "FLOAT":
try:
read_value = self.instrument.read_float(self.register_number,4,self.channel_size)
except IOError as e:
log.info(e)
return None
elif self.data_type == "INTEGER" or self.data_type == "STRING":
try:
read_value = self.instrument.read_register(self.register_number, self.scaling, 4)
except IOError as e:
log.info(e)
return None
read_value = self.transform_fn(read_value)
return read_value
def write(self, value):
"""Write a value to a register"""
if self.data_type == "FLOAT":
value = float(value)
elif self.data_type == "INTEGER":
value = int(value)
else:
value = str(value)
try:
self.instrument.write_register(self.register_number,value, self.scaling, 16 if self.channel_size > 1 else 6 )
return True
except Exception as e:
log.info("Failed to write value: {}".format(e))
return False
class PLCChannel(Channel):
"""PLC Channel Object."""
def __init__(self, ip, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False, plc_type='CLX'):
"""Initialize the channel."""
super(PLCChannel, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.plc_ip = ip
self.mesh_name = mesh_name
self.plc_tag = plc_tag
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
self.plc_type = plc_type
def read(self):
"""Read the value."""
plc_value = None
if self.plc_tag and self.plc_ip:
read_value = read_tag(self.plc_ip, self.plc_tag, plc_type=self.plc_type)
if read_value:
plc_value = read_value[0]
return plc_value
class BoolArrayChannels(Channel):
"""Hold the configuration for a set of boolean array channels."""
def __init__(self, ip, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False):
"""Initialize the channel."""
super(BoolArrayChannels, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.plc_ip = ip
self.mesh_name = mesh_name
self.plc_tag = plc_tag
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
def compare_values(self, new_val_dict):
"""Compare new values to old values to see if the values need storing."""
send = False
for idx in new_val_dict:
try:
if new_val_dict[idx] != self.last_value[idx]:
send = True
except KeyError:
log.error("Key Error in self.compare_values for index {}".format(idx))
send = True
return send
def read(self, force_send=False):
"""Read the value and check to see if needs to be stored."""
send_needed = False
send_reason = ""
if self.plc_tag:
val = read_tag(self.plc_ip, self.plc_tag)
if val:
bool_arr = binarray(val[0])
new_val = {}
for idx in self.map_:
try:
new_val[self.map_[idx]] = bool_arr[idx]
except KeyError:
log.error("Not able to get value for index {}".format(idx))
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif self.compare_values(new_val):
send_needed = True
send_reason = "value change"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
if send_needed:
self.value = new_val
self.last_value = self.value
self.last_send_time = time.time()
log.info("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason))
return send_needed

213
piflow/PiFlow.py Normal file
View File

@@ -0,0 +1,213 @@
"""Driver for PiFlow"""
import os
import threading
import json
import time
from random import randint
from datetime import datetime as dt
from device_base import deviceBase
import persistence
from utilities import get_public_ip_address, get_private_ip_address
from file_logger import filelogger as log
_ = None
os.system('sudo timedatectl set-timezone America/Chicago')
log.info("PiFlow startup")
# GLOBAL VARIABLES
WAIT_FOR_CONNECTION_SECONDS = 5
IP_CHECK_PERIOD = 60
# PERSISTENCE FILE
PERSIST = persistence.load('persist.json')
if not PERSIST:
PERSIST = {'flowmeter': 1, 'drive': 1, 'drive_enabled': False, 'yesterday_totalizer_1': dt.today().day, 'yesterday_totalizer_2': dt.today().day,'yesterday_totalizer_3': dt.today().day,
'yesterday_total_totalizer_1': 0, 'yesterday_total_midnight_totalizer_1': 0,
'yesterday_total_totalizer_2': 0, 'yesterday_total_midnight_totalizer_2': 0,
'yesterday_total_totalizer_3': 0, 'yesterday_total_midnight_totalizer_3': 0}
persistence.store(PERSIST, 'persist.json')
drive_enabled = PERSIST['drive_enabled']
from Tags import tags
CHANNELS = tags
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 = "17"
self.finished = threading.Event()
self.force_send = False
self.public_ip_address = ""
self.private_ip_address = ""
self.public_ip_address_last_checked = 0
threading.Thread.start(self)
# this is a required function for all drivers, its goal is to upload some piece of data
# about your device so it can be seen on the web
def register(self):
"""Register the driver."""
# self.sendtodb("log", "BOOM! Booted.", 0)
pass
def run(self):
"""Actually run the driver."""
for i in range(0, WAIT_FOR_CONNECTION_SECONDS):
print("PiFlow driver will start in {} seconds".format(WAIT_FOR_CONNECTION_SECONDS - i))
time.sleep(1)
log.info("BOOM! Starting PiFlow driver...")
#self._check_watchdog()
self._check_ip_address()
self.nodes["PiFlow_0199"] = self
send_loops = 0
while True:
now = time.time()
if self.force_send:
log.warning("FORCE SEND: TRUE")
for chan in CHANNELS:
try:
for x in range(3):
val = chan.read()
if not val == None:
break
if val == None:
log.info("No modbus read sending previous value")
val = chan.value
if chan.mesh_name in ['totalizer_1','totalizer_2','totalizer_3']:
right_now = dt.today()
today_total, yesterday_total = self.totalize(val, PERSIST['yesterday_'+chan.mesh_name], right_now.day, right_now.hour, right_now.minute, PERSIST['yesterday_total_midnight_'+chan.mesh_name], PERSIST['yesterday_total_'+chan.mesh_name], chan.mesh_name)
if chan.check(val, self.force_send):
self.sendtodbDev(1, chan.mesh_name, chan.value, 0, 'PiFlow')
self.sendtodbDev(1,"today_"+chan.mesh_name, today_total,0,'PiFlow')
self.sendtodbDev(1,"yesterday_"+chan.mesh_name, yesterday_total,0,'PiFlow')
else:
if chan.check(val, self.force_send):
self.sendtodbDev(1, chan.mesh_name, chan.value, 0, 'PiFlow')
except Exception as e:
log.warning("An error occured: {}".format(e))
time.sleep(3)
# print("PiFlow driver still alive...")
if self.force_send:
if send_loops > 2:
log.warning("Turning off force_send")
self.force_send = False
send_loops = 0
else:
send_loops += 1
if (now - self.public_ip_address_last_checked) > IP_CHECK_PERIOD:
self._check_ip_address()
time.sleep(10)
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()
test_public_ip = test_public_ip[:-1]
test_private_ip = get_private_ip_address()
if not test_public_ip == self.public_ip_address:
self.sendtodbDev(1, 'public_ip_address', test_public_ip, 0, 'PiFlow')
self.public_ip_address = test_public_ip
if not test_private_ip == self.private_ip_address:
self.sendtodbDev(1, 'private_ip_address', test_private_ip, 0, 'PiFlow')
self.private_ip_address = test_private_ip
def PiFlow_sync(self, name, value):
"""Sync all data from the driver."""
self.force_send = True
# self.sendtodb("log", "synced", 0)
return True
def PiFlow_flowmeternumber(self, name, unit_number):
"""Change the unit number for the PiFlow flow meter"""
unit_number = int(unit_number)
if drive_enabled:
for chan in CHANNELS[0:8]:
chan.unit_number = unit_number
PERSIST['flowmeter'] = unit_number
persistence.store(PERSIST, 'persist.json')
return True
else:
for chan in CHANNELS:
chan.unit_number = unit_number
PERSIST['flowmeter'] = unit_number
persistence.store(PERSIST, 'persist.json')
self.sendtodbDev(1, 'flowmeternumber', unit_number, 0,'PiFlow')
return True
return False
def PiFlow_drivenumber(self, name, unit_number):
"""Change the unit number for the PiFlow drive"""
unit_number = int(unit_number)
for chan in CHANNELS[8:]:
chan.unit_number = unit_number
PERSIST['drive'] = unit_number
persistence.store(PERSIST, 'persist.json')
self.sendtodbDev(1, 'drivenumber', unit_number, 0,'PiFlow')
return True
def PiFlow_reboot(self, name, value):
os.system('reboot')
return True
def PiFlow_drive_enabled(self, name, value):
value = int(value)
if value == 1:
PERSIST['drive_enabled'] = True
else:
PERSIST['drive_enabled'] = False
persistence.store(PERSIST, 'persist.json')
self.sendtodbDev(1, 'drive_enabled', value, 0,'PiFlow')
return True
def PiFlow_write(self, name, value):
"""Write a value to the device via modbus"""
new_val = json.loads(str(value).replace("'", '"'))
addr_n = int(new_val['addr'])
reg_n = int(new_val['reg'])
val_n = new_val['val']
for chan in CHANNELS:
if chan.unit_number == addr_n and chan.register_number == reg_n:
write_res = chan.write(val_n)
log.info("Result of PiFlow_write(self, {}, {}) = {}".format(name, value, write_res))
return write_res
def totalize(self,val, yesterday, day, hour, minute, yesterday_total_midnight, yesterday_total,channel):
if (yesterday_total == 0 and yesterday_total_midnight == 0) or (yesterday_total == None or yesterday_total_midnight == None):
yesterday_total_midnight = val
PERSIST['yesterday_total_midnight_'+channel] = yesterday_total_midnight
persistence.store(PERSIST, 'persist.json')
today_total = val - yesterday_total_midnight
if hour == 0 and minute == 0 and not(day == yesterday):
yesterday_total = today_total
yesterday_total_midnight = val
today_total = val - yesterday_total_midnight
yesterday = day
PERSIST['yesterday_'+channel] = yesterday
PERSIST['yesterday_total_'+channel] = yesterday_total
PERSIST['yesterday_total_midnight_'+channel] = yesterday_total_midnight
persistence.store(PERSIST,'persist.json')
return today_total,yesterday_total

Some files were not shown because too many files have changed in this diff Show More