Added Folders
Add all the driver folders
This commit is contained in:
53
abbflow/abbflow.py
Normal file
53
abbflow/abbflow.py
Normal 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
11
abbflow/config.txt
Normal 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
893
abbflow/modbusMap.p
Normal 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
49
abbflow/utilities.py
Normal 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
149
advvfdipp/Channel.py
Normal 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
116
advvfdipp/advvfdipp.py
Normal 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
|
||||||
298
advvfdipp/advvfdippv2/Channel.py
Normal file
298
advvfdipp/advvfdippv2/Channel.py
Normal 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
|
||||||
49
advvfdipp/advvfdippv2/Tags.py
Normal file
49
advvfdipp/advvfdippv2/Tags.py
Normal 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")
|
||||||
|
]
|
||||||
200
advvfdipp/advvfdippv2/advvfdipp.py
Normal file
200
advvfdipp/advvfdippv2/advvfdipp.py
Normal 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
|
||||||
14
advvfdipp/advvfdippv2/config.txt
Normal file
14
advvfdipp/advvfdippv2/config.txt
Normal 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"
|
||||||
|
}
|
||||||
360
advvfdipp/advvfdippv2/device_base.py
Normal file
360
advvfdipp/advvfdippv2/device_base.py
Normal 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)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
14
advvfdipp/advvfdippv2/driverConfig.json
Normal file
14
advvfdipp/advvfdippv2/driverConfig.json
Normal 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"
|
||||||
|
}
|
||||||
18
advvfdipp/advvfdippv2/file_logger.py
Normal file
18
advvfdipp/advvfdippv2/file_logger.py
Normal 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)
|
||||||
21
advvfdipp/advvfdippv2/persistence.py
Normal file
21
advvfdipp/advvfdippv2/persistence.py
Normal 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
|
||||||
226
advvfdipp/advvfdippv2/utilities.py
Normal file
226
advvfdipp/advvfdippv2/utilities.py
Normal 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
12
advvfdipp/config.txt
Normal 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
4675
advvfdipp/modbusMap.p
Normal file
File diff suppressed because it is too large
Load Diff
79
advvfdipp/utilities.py
Normal file
79
advvfdipp/utilities.py
Normal 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
27
deviceIds.csv
Normal 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
|
||||||
|
345
dh_monitor/dhMonitor.py
Normal file
345
dh_monitor/dhMonitor.py
Normal 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
282
dhsensor/Channel.py
Normal 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
47
dhsensor/Maps.py
Normal 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
12
dhsensor/config.txt
Normal 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
84
dhsensor/dhsensor.py
Normal 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
578
dhsensor/modbusMap.p
Normal 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
21
dhsensor/persistence.py
Normal 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
53
dhsensor/utilities.py
Normal 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
298
dual_flowmeter/Channel.py
Normal 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
21
dual_flowmeter/Tags.py
Normal 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
14
dual_flowmeter/config.txt
Normal 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"
|
||||||
|
}
|
||||||
360
dual_flowmeter/device_base.py
Normal file
360
dual_flowmeter/device_base.py
Normal 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)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
148
dual_flowmeter/dual_flowmeter.py
Normal file
148
dual_flowmeter/dual_flowmeter.py
Normal 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
|
||||||
18
dual_flowmeter/file_logger.py
Normal file
18
dual_flowmeter/file_logger.py
Normal 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)
|
||||||
21
dual_flowmeter/persistence.py
Normal file
21
dual_flowmeter/persistence.py
Normal 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
dual_flowmeter/utilities.py
Normal file
52
dual_flowmeter/utilities.py
Normal 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
298
dualactuator/Channel.py
Normal 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
25
dualactuator/Tags.py
Normal 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
14
dualactuator/config.txt
Normal 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
360
dualactuator/device_base.py
Normal 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)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
135
dualactuator/dualactuator.py
Normal file
135
dualactuator/dualactuator.py
Normal 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
|
||||||
18
dualactuator/file_logger.py
Normal file
18
dualactuator/file_logger.py
Normal 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)
|
||||||
21
dualactuator/persistence.py
Normal file
21
dualactuator/persistence.py
Normal 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
52
dualactuator/utilities.py
Normal 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
9
easttexas/config.txt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
"driverFileName":"easttexas.py",
|
||||||
|
"deviceName":"easttexas",
|
||||||
|
"driverId":"0110",
|
||||||
|
"releaseVersion":"1",
|
||||||
|
"files": {
|
||||||
|
"file1":"easttexas.py"}
|
||||||
|
}
|
||||||
49
easttexas/easttexas.py
Normal file
49
easttexas/easttexas.py
Normal 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
297
email_reports/Channel.py
Normal 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
48
email_reports/Tags.py
Normal 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
14
email_reports/config.txt
Normal 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"
|
||||||
|
}
|
||||||
360
email_reports/device_base.py
Normal file
360
email_reports/device_base.py
Normal 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)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
232
email_reports/email_reports.py
Normal file
232
email_reports/email_reports.py
Normal 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
|
||||||
|
|
||||||
18
email_reports/file_logger.py
Normal file
18
email_reports/file_logger.py
Normal 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)
|
||||||
21
email_reports/persistence.py
Normal file
21
email_reports/persistence.py
Normal 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
192
email_reports/utilities.py
Normal 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
11
flow-monitor/config.txt
Normal 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"
|
||||||
|
}
|
||||||
627
flow-monitor/flow-monitor.py
Normal file
627
flow-monitor/flow-monitor.py
Normal 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
|
||||||
21
flow-monitor/persistence.py
Normal file
21
flow-monitor/persistence.py
Normal 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
11
flow-monitor/utilities.py
Normal 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
298
flowmeterskid/Channel.py
Normal 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
23
flowmeterskid/Tags.py
Normal 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
14
flowmeterskid/config.txt
Normal 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"
|
||||||
|
}
|
||||||
360
flowmeterskid/device_base.py
Normal file
360
flowmeterskid/device_base.py
Normal 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)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
18
flowmeterskid/file_logger.py
Normal file
18
flowmeterskid/file_logger.py
Normal 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)
|
||||||
133
flowmeterskid/flowmeterskid.py
Normal file
133
flowmeterskid/flowmeterskid.py
Normal 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
|
||||||
21
flowmeterskid/persistence.py
Normal file
21
flowmeterskid/persistence.py
Normal 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
flowmeterskid/utilities.py
Normal file
52
flowmeterskid/utilities.py
Normal 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
285
fracskid/Channel.py
Normal 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
12
fracskid/config.txt
Normal 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
142
fracskid/fracskid.py
Normal 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
21
fracskid/persistence.py
Normal 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
54
fracskid/utilities.py
Normal 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
11
henryhyd/config.txt
Normal 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
351
henryhyd/henryhyd.py
Normal 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
119
henryhyd/micro800.py
Normal 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
139
henrypump/Channel.py
Normal 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
9
henrypump/config.txt
Normal 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
60
henrypump/thehenrypump.py
Normal 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
287
ipp/Channel.py
Normal 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
12
ipp/config.txt
Normal 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
316
ipp/ipp.py
Normal 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
77
ipp/maps.py
Normal 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
119
ipp/micro800.py
Normal 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
51
ipp/utilities.py
Normal 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
293
maplehmi/Channel.py
Normal 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
4
maplehmi/Tags.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
from Channel import PLCChannel, ModbusChannel
|
||||||
|
from maplehmi import PLC_IP_ADDRESS
|
||||||
|
|
||||||
|
tags = [ ]
|
||||||
14
maplehmi/config.txt
Normal file
14
maplehmi/config.txt
Normal 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
360
maplehmi/device_base.py
Normal 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)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
14
maplehmi/driverConfig.json
Normal file
14
maplehmi/driverConfig.json
Normal 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
18
maplehmi/file_logger.py
Normal 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
148
maplehmi/maplehmi.py
Normal 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
21
maplehmi/persistence.py
Normal 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
52
maplehmi/utilities.py
Normal 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
298
multisensor/channel.py
Normal 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
13
multisensor/config.txt
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
18
multisensor/file_logger.py
Normal file
18
multisensor/file_logger.py
Normal 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
22
multisensor/logger.py
Normal 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
323
multisensor/multisensor.py
Normal 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")
|
||||||
21
multisensor/persistence.py
Normal file
21
multisensor/persistence.py
Normal 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
51
multisensor/utilities.py
Normal 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
418
piflow/Channel.py
Normal 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
213
piflow/PiFlow.py
Normal 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
Reference in New Issue
Block a user