Initial commit

This commit is contained in:
Patrick McDonagh
2018-05-14 10:29:41 -05:00
commit 059fc58ed3
19 changed files with 4376 additions and 0 deletions

View File

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

View File

@@ -0,0 +1,42 @@
id,name,deviceTypeId,fromMe,io,subTitle,helpExplanation,channelType,dataType,defaultValue,regex,regexErrMsg,units,min,max,change,guaranteedReportPeriod,minReportTime
13648,log,462,FALSE,readonly,Log,Device Log,device,string,Initialized,,,,,,,,
13650,voltage_average,462,FALSE,readonly,Average Voltage,Volts,device,float,,,,,,,,,
13651,voltage_ab,462,FALSE,readonly,Voltage: A-B,Volts,device,float,,,,,,,,,
13652,voltage_bc,462,FALSE,readonly,Voltage: B-C,Volts,device,float,,,,,,,,,
13653,voltage_ca,462,FALSE,readonly,Voltage: C-A,Volts,device,float,,,,,,,,,
13654,voltage_ln,462,FALSE,readonly,Voltage: L-N,Volts,device,float,,,,,,,,,
13655,frequency,462,FALSE,readonly,Frequency,Hz,device,float,,,,,,,,,
13656,phase_order,462,FALSE,readonly,Phase Order,ABC/ACB,device,string,,,,,,,,,
13657,voltage_unbalance,462,FALSE,readonly,Voltage Unbalance,%,device,float,,,,,,,,,
13658,voltage_ab_min,462,FALSE,readonly,Voltage Min: A-B,Volts,device,float,,,,,,,,,
13659,voltage_bc_min,462,FALSE,readonly,Voltage Min: B-C,Volts,device,float,,,,,,,,,
13660,voltage_ca_min,462,FALSE,readonly,Voltage Min: C-A,Volts,device,float,,,,,,,,,
13661,current_average,462,FALSE,readonly,Average Current,Amps,device,float,,,,,,,,,
13662,current_a,462,FALSE,readonly,Current: A Phase,Amps,device,float,,,,,,,,,
13663,current_b,462,FALSE,readonly,Current: B Phase,Amps,device,float,,,,,,,,,
13664,current_c,462,FALSE,readonly,Current: C Phase,Amps,device,float,,,,,,,,,
13665,current_ground,462,FALSE,readonly,Current: Ground,Amps,device,float,,,,,,,,,
13666,current_unbalance,462,FALSE,readonly,Current Unbalance,%,device,float,,,,,,,,,
13667,current_peak,462,FALSE,readonly,Peak Current,Amps,device,float,,,,,,,,,
13668,power_kw,462,FALSE,readonly,Power: kW,kW,device,float,,,,,,,,,
13669,power_kva,462,FALSE,readonly,Power: kVA,kVA,device,float,,,,,,,,,
13670,power_kvar,462,FALSE,readonly,Power: kVAR,kVAR,device,float,,,,,,,,,
13671,power_pf_average,462,FALSE,readonly,Power Factor: Average,pf,device,float,,,,,,,,,
13672,power_pf_a,462,FALSE,readonly,Power Factor: A Phase,pf,device,float,,,,,,,,,
13673,power_pf_b,462,FALSE,readonly,Power Factor: B Phase,pf,device,float,,,,,,,,,
13674,power_pf_c,462,FALSE,readonly,Power Factor C Phase,pf,device,float,,,,,,,,,
13675,power_kwh,462,FALSE,readonly,Power: kWh Elapsed,kWh,device,float,,,,,,,,,
13676,motor_insulation,462,FALSE,readonly,Motor Insulation,Ohms,device,float,,,,,,,,,
13677,motor_temp_rtd,462,FALSE,readonly,Motor Temp RTD,deg,device,float,,,,,,,,,
13678,motor_temp_subtrolx,462,FALSE,readonly,Motor Temp SubtrolX,deg,device,float,,,,,,,,,
,system_state,462,FALSE,readonly,System State,state,device,string,,,,,,,,,
,hoa_mode,462,FALSE,readonly,HOA Mode,state,device,string,,,,,,,,,
,motor_state,462,FALSE,readonly,Motor State,state,device,string,,,,,,,,,
,fault_current,462,FALSE,readonly,Current Fault,fault,device,string,,,,,,,,,
,alarm_1,462,FALSE,readonly,Alarm 1,alarm,device,string,,,,,,,,,
,alarm_2,462,FALSE,readonly,Alarm 2,alarm,device,string,,,,,,,,,
,alarm_3,462,FALSE,readonly,Alarm 3,alarm,device,string,,,,,,,,,
,alarm_4,462,FALSE,readonly,Alarm 4,alarm,device,string,,,,,,,,,
,alarm_5,462,FALSE,readonly,Alarm 5,alarm,device,string,,,,,,,,,
,terminal_v1_v2,462,FALSE,readonly,V1/V2 Terminal,on/off,device,string,,,,,,,,,
,motor_run_Time,462,FALSE,readonly,Motor Run Time,hours,device,float,,,,,,,,,
1 id name deviceTypeId fromMe io subTitle helpExplanation channelType dataType defaultValue regex regexErrMsg units min max change guaranteedReportPeriod minReportTime
2 13648 log 462 FALSE readonly Log Device Log device string Initialized
3 13650 voltage_average 462 FALSE readonly Average Voltage Volts device float
4 13651 voltage_ab 462 FALSE readonly Voltage: A-B Volts device float
5 13652 voltage_bc 462 FALSE readonly Voltage: B-C Volts device float
6 13653 voltage_ca 462 FALSE readonly Voltage: C-A Volts device float
7 13654 voltage_ln 462 FALSE readonly Voltage: L-N Volts device float
8 13655 frequency 462 FALSE readonly Frequency Hz device float
9 13656 phase_order 462 FALSE readonly Phase Order ABC/ACB device string
10 13657 voltage_unbalance 462 FALSE readonly Voltage Unbalance % device float
11 13658 voltage_ab_min 462 FALSE readonly Voltage Min: A-B Volts device float
12 13659 voltage_bc_min 462 FALSE readonly Voltage Min: B-C Volts device float
13 13660 voltage_ca_min 462 FALSE readonly Voltage Min: C-A Volts device float
14 13661 current_average 462 FALSE readonly Average Current Amps device float
15 13662 current_a 462 FALSE readonly Current: A Phase Amps device float
16 13663 current_b 462 FALSE readonly Current: B Phase Amps device float
17 13664 current_c 462 FALSE readonly Current: C Phase Amps device float
18 13665 current_ground 462 FALSE readonly Current: Ground Amps device float
19 13666 current_unbalance 462 FALSE readonly Current Unbalance % device float
20 13667 current_peak 462 FALSE readonly Peak Current Amps device float
21 13668 power_kw 462 FALSE readonly Power: kW kW device float
22 13669 power_kva 462 FALSE readonly Power: kVA kVA device float
23 13670 power_kvar 462 FALSE readonly Power: kVAR kVAR device float
24 13671 power_pf_average 462 FALSE readonly Power Factor: Average pf device float
25 13672 power_pf_a 462 FALSE readonly Power Factor: A Phase pf device float
26 13673 power_pf_b 462 FALSE readonly Power Factor: B Phase pf device float
27 13674 power_pf_c 462 FALSE readonly Power Factor C Phase pf device float
28 13675 power_kwh 462 FALSE readonly Power: kWh Elapsed kWh device float
29 13676 motor_insulation 462 FALSE readonly Motor Insulation Ohms device float
30 13677 motor_temp_rtd 462 FALSE readonly Motor Temp RTD deg device float
31 13678 motor_temp_subtrolx 462 FALSE readonly Motor Temp SubtrolX deg device float
32 system_state 462 FALSE readonly System State state device string
33 hoa_mode 462 FALSE readonly HOA Mode state device string
34 motor_state 462 FALSE readonly Motor State state device string
35 fault_current 462 FALSE readonly Current Fault fault device string
36 alarm_1 462 FALSE readonly Alarm 1 alarm device string
37 alarm_2 462 FALSE readonly Alarm 2 alarm device string
38 alarm_3 462 FALSE readonly Alarm 3 alarm device string
39 alarm_4 462 FALSE readonly Alarm 4 alarm device string
40 alarm_5 462 FALSE readonly Alarm 5 alarm device string
41 terminal_v1_v2 462 FALSE readonly V1/V2 Terminal on/off device string
42 motor_run_Time 462 FALSE readonly Motor Run Time hours device float

View File

@@ -0,0 +1,10 @@
{
"driverFileName": "submonitor.py",
"deviceName": "submonitor",
"driverId": "0210",
"releaseVersion": "2",
"files": {
"file1": "submonitor.py",
"file2": "modbusMap.p"
}
}

View File

@@ -0,0 +1,2 @@
class deviceBase(object):
pass

View File

@@ -0,0 +1,10 @@
{
"name": "submonitor",
"driverFilename": "submonitor.py",
"driverId": "0210",
"additionalDriverFiles": [
"modbusMap.p"
],
"version": 2,
"s3BucketName": "submonitor"
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,62 @@
"""Driver for submonitor"""
import threading
import sys
from device_base import deviceBase
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 = './submonitor.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('submonitor')
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("submonitor startup")
class start(threading.Thread, deviceBase):
"""Start class required by Meshify."""
def __init__(self, name=None, number=None, mac=None, Q=None, mcu=None, companyId=None, offset=None, mqtt=None, Nodes=None):
"""Initialize the driver."""
threading.Thread.__init__(self)
deviceBase.__init__(self, name=name, number=number, mac=mac, Q=Q, mcu=mcu, companyId=companyId, offset=offset, mqtt=mqtt, Nodes=Nodes)
self.daemon = True
self.version = "2"
self.finished = threading.Event()
self.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."""
wait_sec = 60
for i in range(0, wait_sec):
print("submonitor driver will start in {} seconds".format(wait_sec - i))
time.sleep(1)
logger.info("BOOM! Starting submonitor driver...")
while True:
time.sleep(15)
print("submonitor driver still alive...")

View File

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