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

76
M1-Modbus-Bug.md Normal file
View File

@@ -0,0 +1,76 @@
# M1 Modbus Bug
When adding more than one register that uses the multiplier feature, there is an error in the processing of the value. This is logged to the console:
```python
error reading modbus value
No communication with the instrument (no answer)
Error reading register, retries exhausted
local variable 'val' referenced before assignment
Sending log to log-modbus: (X) Retries Exhausted: local variable 'val' referenced before assignment
```
## Steps to re-create the error
1. Set up a single channel with no multiplier value.
2. Set Modbus settings and Sync to Device.
```
here is the answer
<20><>
'\xf7\x03\x02\x13\x16\xfc\xaf'
'\xf7\x03\x02\x13\x16\xfc\xaf'
here is the payload from slave
'\x02\x13\x16'
here is the payload
'\x02\x13\x16'
success reading
4886
4886
```
3. Set up another channel with Multiplier parameter of "Divide" and Multiplier Value of "10".
4. Sync to Device.
on first register:
```
here is the answer
''
''
error reading modbus value
No communication with the instrument (no answer)
had an error on the modbus loop, retrying read
here is your period: 120
23.4706990719
here is your send value: False
getting int
writing 485: <20>,P<>
```
on second register:
```
here is the answer
''
''
error reading modbus value
No communication with the instrument (no answer)
Error reading register, retries exhausted
local variable 'val' referenced before assignment
here is your period: 120
1525193769.02
here is your send value: True
getting int
writing 485: <20>7 <20>
```

BIN
M1-Modbus-Bug.pdf Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1 @@
<module>Alerts</module>

View File

@@ -0,0 +1,42 @@
<div class="row row-flex box-me">
<div class="col-md-6 text-center">
<h2 class="uppercase">Public IP Address</h2>
<p><%= channels["submonitor.public_ip_address"].value %><p>
</div>
</div>
<style>
.uppercase {
text-transform: uppercase;
font-size: 14px;
color: #666;
font-weight: 400;
letter-spacing: 1px;
z-index: 100;
}
.box-me {
position: relative;
padding: 0.5em;
padding-bottom: 1.5em;
border: 1px solid #eee;
/*margin: 1em 0;*/
}
.row-flex {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
flex-wrap: wrap;
}
.row-flex > [class*='col-'] {
display: flex;
flex-direction: column;
}
p {
font-size: 1.25em;
}
</style>

View File

@@ -0,0 +1,6 @@
<div class='col-xs-1'>
<div class="<%= nodecolors.statuscolor %> nodecolor"></div>
</div>
<div class='col-xs-6'>
<h3><%= node.vanityname %></h3>
</div>

View File

@@ -0,0 +1,39 @@
<style>
.header h4 {
position: relative;
top: 0.9em;
}
.header h2 {
text-transform: uppercase;
font-size: 14px;
color: #aaa;
margin: 0.75em 0;
}
.header p {
font-size: 24px;
color: black;
font-weight: 600;
}
</style>
<div class="row header">
<div class="col-xs-1">
<div class="<%= nodecolors.statuscolor %> nodecolor"></div>
</div>
<div class="col-xs-2">
<img src="<%= nodeimgurl %>" />
</div>
<div class="col-xs-3">
<h4><%= node.vanityname %></h4>
</div>
<div class="col-xs-3">
<h2>Avg. Current</h2>
<p><%= Math.round(channels['submonitor.current_average'].value * 100) / 100 %> A.</p>
</div>
<div class="col-xs-3">
<h2>Avg. Voltage</h2>
<p><%= Math.round(channels['submonitor.voltage_average'].value * 100) / 100 %> V.</p>
</div>
</div>

View File

@@ -0,0 +1,308 @@
<div class="box-me">
<div class="row">
<div class="make-gauge col-xs-4"
data-deviceName="submonitor"
data-channelName="current_average"
data-displayName="Average Current"
data-units="Amps"
data-min="0"
data-max="100"
data-decimalPlaces="2"
data-channelId="<%= channels['submonitor.current_average'].channelId %>"
data-timestamp="<%= channels['submonitor.current_average'].timestamp %>"
></div>
<div class="make-gauge col-xs-4"
data-deviceName="submonitor"
data-channelName="voltage_average"
data-displayName="Average Voltage"
data-units="Volts"
data-min="0"
data-max="600"
data-decimalPlaces="2"
data-channelId="<%= channels['submonitor.voltage_average'].channelId %>"
data-timestamp="<%= channels['submonitor.voltage_average'].timestamp %>"
></div>
<div class="make-gauge col-xs-4"
data-deviceName="submonitor"
data-channelName="frequency"
data-displayName="Frequency"
data-units="Hz"
data-min="55"
data-max="65"
data-decimalPlaces="2"
data-channelId="<%= channels['submonitor.frequency'].channelId %>"
data-timestamp="<%= channels['submonitor.frequency'].timestamp %>"
></div>
</div>
</div>
<div class="box-me">
<div class="row">
<div class="col-xs-12 text-center">
<h1>Voltage</h1>
</div>
</div>
<div class-"row">
<div class="make-gauge col-xs-4"
data-deviceName="submonitor"
data-channelName="voltage_ab"
data-displayName="Voltage: A-B"
data-units="Volts"
data-min="0"
data-max="600"
data-decimalPlaces="2"
data-channelId="<%= channels['submonitor.voltage_ab'].channelId %>"
data-timestamp="<%= channels['submonitor.voltage_ab'].timestamp %>"
></div>
<div class="make-gauge col-xs-4"
data-deviceName="submonitor"
data-channelName="voltage_bc"
data-displayName="Voltage: B-C"
data-units="Volts"
data-min="0"
data-max="600"
data-decimalPlaces="2"
data-channelId="<%= channels['submonitor.voltage_bc'].channelId %>"
data-timestamp="<%= channels['submonitor.voltage_bc'].timestamp %>"
></div>
<div class="make-gauge col-xs-4"
data-deviceName="submonitor"
data-channelName="voltage_ca"
data-displayName="Voltage: C-A"
data-units="Volts"
data-min="0"
data-max="600"
data-decimalPlaces="2"
data-channelId="<%= channels['submonitor.voltage_ca'].channelId %>"
data-timestamp="<%= channels['submonitor.voltage_ca'].timestamp %>"
></div>
</div>
<div class="row">
<div class='col-xs-12'>
<div style="height:300px"
id="chart-overview"
data-chart="chart"
data-nodename1="submonitor.voltage_average"
data-datalabel1="Average Voltage"
data-nodename2="submonitor.voltage_ab"
data-datalabel2="Voltage: A-B"
data-nodename3="submonitor.voltage_bc"
data-datalabel3="Voltage: B-C"
data-nodename4="submonitor.voltage_ca"
data-datalabel4="Voltage: C-A"
data-daysofhistory="2"
data-chartlabel="Last 48 Hours"
data-ylabel=""
data-xlabel="Date"
data-units="Volts">
</div>
</div>
</div>
</div>
<div class="box-me">
<div class="row">
<div class="col-xs-12 text-center">
<h1>Current</h1>
</div>
</div>
<div class-"row">
<div class="make-gauge col-xs-4"
data-deviceName="submonitor"
data-channelName="current_a"
data-displayName="Current: A Phase"
data-units="Amps"
data-min="0"
data-max="100"
data-decimalPlaces="2"
data-channelId="<%= channels['submonitor.current_a'].channelId %>"
data-timestamp="<%= channels['submonitor.current_a'].timestamp %>"
></div>
<div class="make-gauge col-xs-4"
data-deviceName="submonitor"
data-channelName="current_b"
data-displayName="Current: B Phase"
data-units="Amps"
data-min="0"
data-max="100"
data-decimalPlaces="2"
data-channelId="<%= channels['submonitor.current_b'].channelId %>"
data-timestamp="<%= channels['submonitor.current_b'].timestamp %>"
></div>
<div class="make-gauge col-xs-4"
data-deviceName="submonitor"
data-channelName="current_c"
data-displayName="Current: C Phase"
data-units="Amps"
data-min="0"
data-max="100"
data-decimalPlaces="2"
data-channelId="<%= channels['submonitor.current_c'].channelId %>"
data-timestamp="<%= channels['submonitor.current_c'].timestamp %>"
></div>
</div>
<div class="row">
<div class='col-xs-12'>
<div style="height:300px"
id="chart-overview"
data-chart="chart"
data-nodename1="submonitor.current_average"
data-datalabel1="Average Current"
data-nodename2="submonitor.current_a"
data-datalabel2="Current: A Phase"
data-nodename3="submonitor.current_b"
data-datalabel3="Current: B Phase"
data-nodename4="submonitor.current_c"
data-datalabel4="Current: C PHase"
data-daysofhistory="2"
data-chartlabel="Last 48 Hours"
data-ylabel=""
data-xlabel="Date"
data-units="Amps">
</div>
</div>
</div>
</div>
<style>
.box-me {
position: relative;
padding: 0.5em;
padding-bottom: 1.5em;
border: 1px solid #eee;
/*margin: 1em 0;*/
}
.box-me .gauge-box {
margin-top: -0.25em;
}
h2 {
text-transform: uppercase;
font-size: 14px;
color: #666;
font-weight: 400;
letter-spacing: 1px;
z-index: 100;
}
.dynamic-chart-form {
background-color: whiteSmoke;
padding: 1em 0.5em;
margin-top: 1em;
}
.row-flex {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
flex-wrap: wrap;
}
.row-flex > [class*='col-'] {
display: flex;
flex-direction: column;
}
#systemStatusTimelineContainer h2 {
text-transform: uppercase;
font-size: 14px;
color: #666;
font-weight: 400;
letter-spacing: 1px;
z-index: 100;
}
.slice.node-detail hr {
border-color: #ccc;
}
.slice.node-detail #alarms li {
margin-bottom: 1em;
padding: 0.5em;
}
.slice.node-detail #alarms li:nth-child(even){
background-color: whiteSmoke;
}
.slice.node-detail #alarms li span {
margin-left: 1em;
color: #aaa;
}
</style>
<script>
$('.val_box').each(function(topLevel){
$(this).change(function(){
var id = "#" + $(this).closest(".entry-top-level").attr('id');
if (id !== "#undefined"){
var val = $(id).find('.val_box').val();
var tag = $(id).find('.setstatic').attr('data-staticsend', val);
console.log($(id).find('.setstatic').attr('data-staticsend'));
}
});
});
$('.make-gauge').each(function(gaugeDiv) {
const deviceName = $(this).attr("data-deviceName");
const channelName = $(this).attr("data-channelName");
const displayName = $(this).attr("data-displayName");
const units = $(this).attr("data-units");
const min = $(this).attr("data-min");
const max = $(this).attr("data-max");
const decimalPlaces = $(this).attr("data-decimalPlaces");
const channelId = $(this).attr("data-channelId");
const timestamp = $(this).attr("data-timestamp");
const gaugeHTML = makeGauge(deviceName, channelName, displayName, units, min, max, decimalPlaces, channelId, timestamp);
$(this).html(gaugeHTML);
});
function makeGauge(deviceName, channelName, displayName, units, min, max, decimalPlaces, channelId, timestamp) {
return (
'<div class="text-center"> \
<h2>' + displayName + '</h2> \
<div class="gauge-box"> \
<div data-labelheight="10" \
style="height: 170px; background: transparent; margin: 0 auto;" \
id="gauge-' + channelName + '" \
data-chart="solidgauge" \
data-nodename="' + deviceName + '.' + channelName + '" \
data-units="'+ units + '" \
data-min="' + min + '" \
data-max="' + max + '" \
data-decimalplaces="' + decimalPlaces + '" \
data-colors="0.1:#DF5353,0.5:#DDDF0D,0.9:#55BF3B" \
data-valuefontsize="18px"> \
</div> \
<div class- "timestamp-box"> \
<a href="#" data-channelId="' + channelId + '" class="data-table" title="Download Channel History"> \
<i class="fa fa-download"></i> \
</a> \
</div> \
<span data-timeupdate="' + channelName + '">'+ timestamp + '</span> \
</div> \
</div>'
);
}
</script>

View File

@@ -0,0 +1,15 @@
<a href="#"
data-channelId="<%= channels["submonitor.log"].channelId %>"
class="data-table btn-block btn btn-theme animated"
title="Device Log"><i style='margin-left: 0.5em; cursor: pointer' class="fa fa-th-list icon-theme"></i> Device Log</a>
<a href="#"
data-refreshpause="1"
data-staticsend="1"
data-channelId="<%= channels["submonitor.sync"].channelId %>"
data-techname="<%=channels["submonitor.sync"].techName %>"
data-name="<%= channels["submonitor.sync"].name%>"
data-nodechannelcurrentId="<%= channels["submonitor.sync"].nodechannelcurrentId %>"
id="<%= channels["submonitor.sync"].channelId %>"
class="btn btn-large btn-block btn-theme animated setstatic mqtt">
<i class="icon-repeat icon-white mqtt" ></i>Sync All Data</a>

View File

@@ -0,0 +1,37 @@
<div class='col-xs-12' style="padding-top: 1em; margin-bottom: 1em;">
<div class="input-daterange input-group" id="datepicker">
<input data-chartid="dynamicChart" id="fromDate" data-daysofhistory="7" type="text" class="form-control" name="start">
<span class="input-group-addon">to</span>
<input class="form-control" data-chartid="dynamicChart" id="toDate" type="text" name="end">
<span class='input-group-btn'>
<a href="#!" data-chartid="dynamicChart" data-otherchartids="statusTimeline" class="btn chart-update btn-theme">Run</a>
</span>
</div>
</div>
<hr>
<div class='clearfix col-xs-12'
style='height: 450px'
id="dynamicChart"
data-chart="dynamicchart"
data-daysofhistory="7"
data-chartlabel="Data"
data-ylabel=""
data-xlabel="Date"
data-units=""
data-channelnames="submonitor.channel_1,">
</div>
<style>
.dynamic-chart-form {
background-color: whiteSmoke;
padding: 1em 0.5em;
margin-top: 1em;
}
#systemStatusTimelineContainer h2 {
text-transform: uppercase;
font-size: 14px;
color: #666;
font-weight: 400;
letter-spacing: 1px;
z-index: 100;
}
</style>

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