Updates to Piflow, removed html folders

This commit is contained in:
2020-05-29 12:13:07 -05:00
parent eb1f7bc33c
commit eb7da58d75
66 changed files with 401 additions and 3505 deletions

View File

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

View File

@@ -1,42 +0,0 @@
<div class="row row-flex box-me">
<div class="col-md-6 text-center">
<h2 class="uppercase">Public IP Address</h2>
<p><%= channels["abbflow.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

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

View File

@@ -1,31 +0,0 @@
<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-4">
<h4><%= node.vanityname %></h4>
</div>
</div>

View File

@@ -1,121 +0,0 @@
<div class="row row-flex box-me">
<div class="col-xs-12 text-center">
<h1>HEADER 1</h1>
</div>
<div class="col-xs-4 text-center">
<h2>CHANNEL 1</h2>
<div class="gauge-box">
<div data-labelheight="10"
style="height: 170px; background: transparent; margin: 0 auto;"
id="gauge-channel_1"
data-chart="solidgauge"
data-nodename="abbflow.channel_1"
data-units="UNITS"
data-min="0"
data-max="100"
data-decimalplaces="2"
data-colors="0.1:#DF5353,0.5:#DDDF0D,0.9:#55BF3B"
data-valuefontsize="18px">
</div>
<div class= "timestamp-box">
<a href="#" data-channelId="<%= channels['abbflow.channel_1'].channelId %>" class="data-table" title="Download Channel History">
<i class="fa fa-download"></i>
</a>
</div>
<span data-timeupdate="channel_1">
<%= channels["abbflow.channel_1"].timestamp %>
</span>
</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;
}
.pad15 {
margin: 15px 15px;
}
.box-me 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'));
}
});
});
</script>

View File

@@ -1,15 +0,0 @@
<a href="#"
data-channelId="<%= channels["abbflow.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["abbflow.sync"].channelId %>"
data-techname="<%=channels["abbflow.sync"].techName %>"
data-name="<%= channels["abbflow.sync"].name%>"
data-nodechannelcurrentId="<%= channels["abbflow.sync"].nodechannelcurrentId %>"
id="<%= channels["abbflow.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

@@ -1,37 +0,0 @@
<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="abbflow.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

@@ -3,7 +3,7 @@ from dual_flowmeter import PLC_IP_ADDRESS
tags = [
PLCChannel(PLC_IP_ADDRESS, "pump_1_daily_total","Pump_1_Daily_Flow_Rate_Total","REAL", 100, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pump_1_run_status","Pump_1_Run_Status","BOOL", 1, 3600, plc_type="Micro800", map_={0: "Off", 1: "On"}),
PLCChannel(PLC_IP_ADDRESS, "pump_1_run_status","Pump_1_Run_Status","BOOL", 1, 3600, plc_type="Micro800", map_={0: "Off", 1: "On", None: "N/A"}),
PLCChannel(PLC_IP_ADDRESS, "pump_1_flowrate","Pump_1_SCL_Flow_Meter","REAL", 1000, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pump_1_yesterdays_total","Pump_1_Yesterdays_Total","REAL", 1000, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pump_1_prevmonth_total","Pump_1_PrevMonth_Total","REAL", 1000, 3600, plc_type="Micro800"),
@@ -11,7 +11,7 @@ tags = [
PLCChannel(PLC_IP_ADDRESS, "pump_1_lifetime_total","Pump_1_Lifetime_Flow","REAL", 1000, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pump_1_suction","Suction_PSI_TP1_Scaled","REAL", 10, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pump_2_daily_total","Pump_2_Daily_Flow_Rate_Total","REAL", 100, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pump_2_run_status","Pump_2_Run_Status","BOOL", 1, 3600, plc_type="Micro800", map_={0: "Off", 1: "On"}),
PLCChannel(PLC_IP_ADDRESS, "pump_2_run_status","Pump_2_Run_Status","BOOL", 1, 3600, plc_type="Micro800", map_={0: "Off", 1: "On", None: "N/A"}),
PLCChannel(PLC_IP_ADDRESS, "pump_2_flowrate","Pump_2_SCL_Flow_Meter","REAL", 1000, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pump_2_yesterdays_total","Pump_2_Yesterdays_Total","REAL", 1000, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pump_2_prevmonth_total","Pump_2_PrevMonth_Total","REAL", 1000, 3600, plc_type="Micro800"),
@@ -21,5 +21,5 @@ tags = [
PLCChannel(PLC_IP_ADDRESS, "pump_charge_psi_tp1", "Charge_PSI_TP1_Scaled", "REAL", 10, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pond_1_height", "Pond_level_tp1_scaled", "REAL", 2, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "pond_1_volume", "pond1volume", "REAL", 100000, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "charge_pump_run_status","CHARGE_PUMP_Run_Status","BOOL", 1, 3600, plc_type="Micro800", map_={0: "Off", 1: "On"})
PLCChannel(PLC_IP_ADDRESS, "charge_pump_run_status","CHARGE_PUMP_Run_Status","BOOL", 1, 3600, plc_type="Micro800", map_={0: "Off", 1: "On", None: "N/A"})
]

View File

@@ -8,7 +8,7 @@
"file4": "Tags.py"
},
"deviceName": "dual_flowmeter",
"releaseVersion": "8",
"releaseVersion": "9",
"driverFileName": "dual_flowmeter.py",
"driverId": "0100"
}

View File

@@ -10,7 +10,19 @@ from Channel import PLCChannel, ModbusChannel,read_tag, write_tag, TAG_DATAERROR
import persistence
from utilities import get_public_ip_address
from file_logger import filelogger as log
PLC_IP_ADDRESS = "192.168.1.12"
# PERSISTENCE FILE
PERSIST = persistence.load('persist.json')
if not PERSIST:
PERSIST = { "plc_ip" : "192.168.1.12" }
persistence.store(PERSIST, 'persist.json')
try:
PLC_IP_ADDRESS = PERSIST["plc_ip"]
except Exception as e:
PERSIST["plc_ip"] = "192.168.1.12"
PLC_IP_ADDRESS = PERSIST["plc_ip"]
persistence.store(PERSIST,"persist.json")
from Tags import tags
_ = None
@@ -26,8 +38,6 @@ WATCHDOG_SEND_PERIOD = 3600 # Seconds, the longest amount of time before sendin
CHANNELS = tags
# PERSISTENCE FILE
PERSIST = persistence.load()
class start(threading.Thread, deviceBase):
@@ -42,7 +52,7 @@ class start(threading.Thread, deviceBase):
mqtt=mqtt, Nodes=Nodes)
self.daemon = True
self.version = "8"
self.version = "9"
self.finished = threading.Event()
self.force_send = False
self.public_ip_address = ""

View File

@@ -16,5 +16,6 @@ tags = [
PLCChannel(PLC_IP_ADDRESS, "fm2_lastmonth","Val_FlowmeterLastMonthTotal","REAL", 1000, 86400, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "v1_open_fbk","FBK_Valve_Open","STRING", 1, 3600, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "v2_open_fbk","FBK_Valve_Open","STRING", 1, 86400, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "single_control","single_control","STRING", 1, 86400, plc_type="Micro800")
PLCChannel(PLC_IP_ADDRESS, "single_control","single_control","STRING", 1, 86400, plc_type="Micro800"),
PLCChannel(PLC_IP_ADDRESS, "flowrate_setpoint", "SPT_DailyMaxSetpoint", "REAL", 1, 3600, plc_type="Micro800")
]

View File

@@ -8,7 +8,7 @@
"file4": "Tags.py"
},
"deviceName": "dualactuator",
"releaseVersion": "2",
"releaseVersion": "3",
"driverFileName": "dualactuator.py",
"driverId": "0100"
}

View File

@@ -40,7 +40,7 @@ class start(threading.Thread, deviceBase):
mqtt=mqtt, Nodes=Nodes)
self.daemon = True
self.version = "2"
self.version = "3"
self.finished = threading.Event()
self.force_send = False
self.public_ip_address = ""

View File

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

View File

@@ -1,42 +0,0 @@
<div class="row row-flex box-me">
<div class="col-md-6 text-center">
<h2 class="uppercase">Public IP Address</h2>
<p><%= channels["dualactuator.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

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

View File

@@ -1,31 +0,0 @@
<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-4">
<h4><%= node.vanityname %></h4>
</div>
</div>

View File

@@ -1,121 +0,0 @@
<div class="row row-flex box-me">
<div class="col-xs-12 text-center">
<h1>HEADER 1</h1>
</div>
<div class="col-xs-4 text-center">
<h2>CHANNEL 1</h2>
<div class="gauge-box">
<div data-labelheight="10"
style="height: 170px; background: transparent; margin: 0 auto;"
id="gauge-channel_1"
data-chart="solidgauge"
data-nodename="dualactuator.channel_1"
data-units="UNITS"
data-min="0"
data-max="100"
data-decimalplaces="2"
data-colors="0.1:#DF5353,0.5:#DDDF0D,0.9:#55BF3B"
data-valuefontsize="18px">
</div>
<div class= "timestamp-box">
<a href="#" data-channelId="<%= channels['dualactuator.channel_1'].channelId %>" class="data-table" title="Download Channel History">
<i class="fa fa-download"></i>
</a>
</div>
<span data-timeupdate="channel_1">
<%= channels["dualactuator.channel_1"].timestamp %>
</span>
</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;
}
.pad15 {
margin: 15px 15px;
}
.box-me 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'));
}
});
});
</script>

View File

@@ -1,15 +0,0 @@
<a href="#"
data-channelId="<%= channels["dualactuator.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["dualactuator.sync"].channelId %>"
data-techname="<%=channels["dualactuator.sync"].techName %>"
data-name="<%= channels["dualactuator.sync"].name%>"
data-nodechannelcurrentId="<%= channels["dualactuator.sync"].nodechannelcurrentId %>"
id="<%= channels["dualactuator.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

@@ -1,37 +0,0 @@
<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="dualactuator.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

@@ -201,7 +201,7 @@ class start(threading.Thread, deviceBase):
if write_cmd:
read_val = read_tag(PLC_IP_ADDRESS, "input{}.insertSuccess", plc_type="Micro800")
if read_val[0] == 1:
self.read_pond_calibration(pond_number)
self.read_pond_calibration(input_number)
return True
return "Wrote everything successfully, but delete didn't succeed (Check pond and point values)."
return "Didn't write delete command correctly."

View File

@@ -26,6 +26,7 @@ def read_tag(addr, tag, plc_type="CLX"):
if clx.open(addr, direct_connection=direct):
try:
val = clx.read_tag(tag)
clx.close()
return val
except DataError as err:
clx.close()
@@ -54,9 +55,11 @@ def read_array(addr, tag, start, end, plc_type="CLX"):
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))
@@ -75,6 +78,7 @@ def write_tag(addr, tag, val, plc_type="CLX"):
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()

View File

@@ -9,6 +9,19 @@ 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
import RPi.GPIO as GPIO
Relay_Ch1 = 26
Relay_Ch2 = 20
Relay_Ch3 = 21
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(Relay_Ch1,GPIO.OUT)
GPIO.setup(Relay_Ch2,GPIO.OUT)
GPIO.setup(Relay_Ch3,GPIO.OUT)
_ = None
os.system('sudo timedatectl set-timezone America/Chicago')
@@ -22,13 +35,39 @@ 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}
PERSIST = {
'flowmeter': 247,
'drive': 1,
'isVFD': False,
'drive_enabled': True,
'plc_ip': '192.168.1.12',
'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']
try:
isVFD = PERSIST['isVFD']
except:
PERSIST['isVFD'] = False
isVFD = PERSIST['isVFD']
persistence.store(PERSIST)
try:
plc_ip = PERSIST['plc_ip']
except:
PERSIST['plc_ip'] = '192.168.1.12'
plc_ip = PERSIST['plc_ip']
persistence.store(PERSIST)
from Tags import tags
CHANNELS = tags
@@ -45,12 +84,14 @@ class start(threading.Thread, deviceBase):
mqtt=mqtt, Nodes=Nodes)
self.daemon = True
self.version = "20"
self.version = "25"
self.finished = threading.Event()
self.force_send = False
self.public_ip_address = ""
self.private_ip_address = ""
self.public_ip_address_last_checked = 0
self.status = ""
self.alarm = ""
threading.Thread.start(self)
# this is a required function for all drivers, its goal is to upload some piece of data
@@ -78,30 +119,66 @@ class start(threading.Thread, deviceBase):
now = time.time()
if self.force_send:
log.warning("FORCE SEND: TRUE")
for chan in CHANNELS:
try:
for x in range(3):
if isVFD:
status = {}
for chan in CHANNELS[:24]: #build status/alarm strings
try:
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')
chan.check(val, self.force_send)
status[chan.mesh_name] = chan.value
except Exception as e:
log.warning("An error occured in status check: {}".format(e))
try:
self.sendStatus(status)
except Exception as e:
log.warning("An error occured: {}".format(e))
time.sleep(3)
log.warning("An error occured in send status: {}".format(e))
for chan in CHANNELS[24:]:
try:
val = chan.read()
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')
self.sendtodbDev(1, chan.mesh_name + "_units", "BBL",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 in data collection: {}".format(e))
else:
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')
elif chan.mesh_name == "volume_flow" and not PERSIST['drive_enabled']:
if chan.check(val, self.force_send):
self.sendtodbDev(1, chan.mesh_name, chan.value, 0, 'PiFlow')
if chan.value > 0:
self.sendtodbDev(1, "run_status", "Running", 0, 'PiFlow')
else:
self.sendtodbDev(1,"run_status", "Stopped", 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...")
@@ -193,6 +270,24 @@ class start(threading.Thread, deviceBase):
log.info("Result of PiFlow_write(self, {}, {}) = {}".format(name, value, write_res))
return write_res
def PiFlow_start(self, name, value):
if isVFD:
#do something with the plc
log.info("Sending START signal to PLC")
else:
log.info("Sending START signal to Drive via relay {}".format(Relay_Ch1))
GPIO.output(Relay_Ch1,GPIO.LOW)
return True
def PiFlow_stop(self, name, value):
if isVFD:
log.info("Sending STOP signal to PLC")
#do something with the plc
else:
log.info("Sending STOP signal to Drive")
GPIO.output(Relay_Ch1,GPIO.HIGH)
return True
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):
@@ -210,4 +305,179 @@ class start(threading.Thread, deviceBase):
PERSIST['yesterday_total_midnight_'+channel] = yesterday_total_midnight
persistence.store(PERSIST,'persist.json')
return today_total,yesterday_total
return today_total,yesterday_total
def sendStatus(self,status):
status_string = ""
fault_codes = {
0: "",
2: "Auxiliary Input",
3: "Power Loss",
4: "UnderVoltage",
5: "OverVoltage",
7: "Motor Overload",
8: "Heatsink OvrTemp",
9: "Thermister OvrTemp",
10: "DynBrake OverTemp",
12: "HW OverCurrent",
13: "Ground Fault",
14: "Ground Warning",
15: "Load Loss",
17: "Input Phase Loss",
18: "Motor PTC Trip",
19: "Task Overrun",
20: "TorqPrv Spd Band",
21: "Output PhaseLoss",
24: "Decel Inhibit",
25: "OverSpeed Limit",
26: "Brake Slipped",
27: "Torq Prove Cflct",
28: "TP Encls Config",
29: "Analog In Loss",
33: "AuRsts Exhausted",
35: "IPM OverCurrent",
36: "SW OverCurrent",
38: "Phase U to Grnd",
39: "Phase V to Grnd",
40: "Phase W to Grnd",
41: "Phase UV Short",
42: "Phase VW Short",
43: "Phase WU Short",
44: "Phase UNegToGrnd",
45: "Phase VNegToGrnd",
46: "Phase WNegToGrnd",
48: "System Defaulted",
49: "Drive Powerup",
51: "Clr Fault Queue",
55: "Ctrl Bd Overtemp",
59: "Invalid Code",
61: "Shear Pin 1",
62: "Shear Pin 2",
64: "Drive Overload",
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: "FluxAmpsRef Rang",
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 VoltageRange",
91: "Pri VelFdbk Loss",
93: "Hw Enable Check",
94: "Alt VelFdbk Loss",
95: "Aux VelFdbk Loss",
96: "PositionFdbkLoss",
97: "Auto Tach Switch",
100: "Parameter Chksum",
101: "PwrDn NVS Blank",
102: "NVS Not Blank",
103: "PwrDn Nvs Incomp",
104: "Pwr Brd Checksum",
106: "Incompat MCB-PB",
107: "Replaced MCB-PB",
108: "Anlg Cal Chksum",
110: "Ivld Pwr Bd Data",
111: "PwrBd Invalid ID",
112: "PwrBd App MinVer",
113: "Tracking DataErr",
115: "PwrDn Table Full",
116: "PwrDnEntry2Large",
117: "PwrDn Data Chksm",
118: "PwrBd PwrDn Chks",
124: "App ID Changed",
125: "Using Backup App",
134: "Start on PowerUp",
137: "Ext Prechrg Err",
138: "Precharge Open",
141: "Autn Enc Angle",
142: "Autn Spd Rstrct",
143: "AutoTune CurReg",
144: "AutoTune Inertia",
145: "AutoTune Travel",
13037: "Net IO Timeout"
}
if status['vfd_active'] == "Stopped":
status_string = status_string + status['vfd_active'] + "; " + status['vfd_ready']
else:
status_string = status_string + status['vfd_active']
if status['vfd_rev']:
status_string = status_string + '; ' + status['vfd_rev']
if status['vfd_fwd']:
status_string = status_string + '; ' + status['vfd_fwd']
if status['vfd_atreference']:
status_string = status_string + '; ' + status['vfd_atreference']
alarm_string = ""
if status['vfd_faulted'] == "Drive Faulted":
status_string = status_string + '; ' + status['vfd_faulted']
if status['vfd_commloss']:
alarm_string = alarm_string + '; ' + status['vfd_commloss']
if status['vfd_fbkalarm']:
alarm_string = alarm_string + '; ' + status['vfd_fbkalarm']
if status['vfd_faultcode']:
alarm_string = alarm_string + '; ' + "Fault: {} Fault code: {}".format(fault_codes[status['vfd_faultcode']],str(status['vfd_faultcode']))
if status['minspeedalarm']:
alarm_string = alarm_string + '; ' + status['minspeedalarm']
if status['pumpedoff']:
alarm_string = alarm_string + '; ' + status['pumpedoff']
if status['lockedout']:
alarm_string = alarm_string + '; ' + status['lockedout']
if status['tubingpressurehi']:
alarm_string = alarm_string + '; ' + status['tubingpressurehi']
if status['tubingpressurehihi']:
alarm_string = alarm_string + '; ' + status['tubingpressurehihi']
if status['tubingpressurelo']:
alarm_string = alarm_string + '; ' + status['tubingpressurelo']
if status['tubingpressurelolo']:
alarm_string = alarm_string + '; ' + status['tubingpressurelolo']
if status['flowmeterhihi']:
alarm_string = alarm_string + '; ' + status['flowmeterhihi']
if status['flowmeterhi']:
alarm_string = alarm_string + '; ' + status['flowmeterhi']
if status['flowmeterlolo']:
alarm_string = alarm_string + '; ' + status['flowmeterlolo']
if status['flowmeterlo']:
alarm_string = alarm_string + '; ' + status['flowmeterlo']
if status['fluidlevellolo']:
alarm_string = alarm_string + '; ' + status['fluidlevellolo']
if status['fluidlevello']:
alarm_string = alarm_string + '; ' + status['fluidlevello']
if status['fluidlevelhi']:
alarm_string = alarm_string + '; ' + status['fluidlevelhi']
if status['fluidlevelhihi']:
alarm_string = alarm_string + '; ' + status['fluidlevelhihi']
try:
if status_string and status_string[0] == '; ':
status_string = status_string[1:]
if status_string and status_string[-1] == '; ':
status_string = status_string[:-1]
if alarm_string and alarm_string[0] == '; ':
alarm_string = alarm_string[1:]
if alarm_string and alarm_string[-1] == '; ':
alarm_string = alarm_string[:-1]
except Exception as e:
log.warning("Error in send status semicolon: {}".format(e))
if self.status != status_string:
self.status = status_string
log.info("Sending {} for {}".format(status_string, 'run_status'))
self.sendtodbDev(1, 'run_status', status_string, 0, 'PiFlow')
if self.alarm != alarm_string:
self.alarm = alarm_string
log.info("Sending {} for {}".format(alarm_string, 'fault_a'))
self.sendtodbDev(1, 'fault_a', alarm_string, 0 , 'PiFlow')

View File

@@ -1,45 +1,84 @@
from Channel import PLCChannel,Channel, ModbusChannel, status_codes, fault_code_a, fault_code_b, volume_units, totalizer_units
import persistence
data = persistence.load('persist.json')
flowmeter_unit_number = data['flowmeter']
drive_enabled = data['drive_enabled']
if drive_enabled:
drive_unit_number = data['drive']
PERSIST = persistence.load('persist.json')
flowmeter_unit_number = PERSIST['flowmeter']
drive_enabled = PERSIST['drive_enabled']
isVFD = PERSIST['isVFD']
if drive_enabled:
drive_unit_number = PERSIST['drive']
try:
plc_ip = PERSIST['plc_ip']
except:
PERSIST['plc_ip'] = '192.168.1.12'
persistence.store(PERSIST)
if isVFD:
tags = [
ModbusChannel('volume_flow', 3873, 'FLOAT', 1,600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_1', 2609, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_2', 2809, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_3', 3009, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('volume_flow_units', 2102, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=volume_units),
ModbusChannel('totalizer_1_units', 4603, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('totalizer_2_units', 4604, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('totalizer_3_units', 4605, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('run_status', 772, 'STRING', 0, 3600, channel_size=1, unit_number=drive_unit_number, transform_fn=status_codes),
ModbusChannel('frequency', 784, 'INTEGER', 0.5, 600, channel_size=2, unit_number=drive_unit_number,scaling=2 ),
ModbusChannel('current', 783, 'INTEGER', 0.5, 600, channel_size=2, unit_number=drive_unit_number,scaling=1 ),
ModbusChannel('fault_a', 815, 'STRING', 1, 3600, channel_size=1, unit_number=drive_unit_number,transform_fn=fault_code_a),
ModbusChannel('fault_b', 816, 'STRING', 1, 3600, channel_size=1, unit_number=drive_unit_number,transform_fn=fault_code_b),
ModbusChannel('pid_ref', 791, 'INTEGER', 1, 600, channel_size=1, unit_number=drive_unit_number,scaling=1),
ModbusChannel('pid_feedback', 792, 'INTEGER', 1, 600, channel_size=1, unit_number=drive_unit_number,scaling=1),
ModbusChannel('motor_rated_current', 4896, 'INTEGER', 0, 3600, channel_size=1, unit_number=drive_unit_number,scaling=1),
ModbusChannel('sleep_delay', 4924, 'INTEGER', 1, 600, channel_size=1, unit_number=drive_unit_number, scaling=1)
]
else:
tags = [
ModbusChannel('volume_flow', 3873, 'FLOAT', 1,600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_1', 2609, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_2', 2809, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_3', 3009, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('volume_flow_units', 2102, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=volume_units),
ModbusChannel('totalizer_1_units', 4603, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('totalizer_2_units', 4604, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('totalizer_3_units', 4605, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units)
PLCChannel(plc_ip,'vfd_atreference','sts_VFD_AtReference','BOOL',0,3600,map_={0: "", 1: "At speed"},plc_type='Micro800'),
PLCChannel(plc_ip,'vfd_rev','sts_VFD_REV','BOOL',0,3600,map_={0: "", 1: "Operating in Reverse"},plc_type='Micro800'),
PLCChannel(plc_ip,'vfd_fwd','sts_VFD_FWD','BOOL',0,3600,map_={0: "", 1: "Operating in Forward"},plc_type='Micro800'),
PLCChannel(plc_ip,'vfd_active','sts_VFD_Active','BOOL',0,3600,map_={0: "Stopped", 1: "Running"},plc_type='Micro800'),
PLCChannel(plc_ip,'vfd_ready','sts_VFD_Ready','BOOL',0,3600,map_={0: "Drive Not Ready", 1: "Drive Ready"},plc_type='Micro800'),
PLCChannel(plc_ip,'vfd_faultcode','sts_VFD_FaultCode','REAL',0,3600, plc_type='Micro800'),
PLCChannel(plc_ip,'vfd_faulted','AL0_VFD','BOOL',0,3600,map_={0: "", 1: "Drive Faulted"},plc_type='Micro800'),
PLCChannel(plc_ip,'vfd_commloss','AL0_VFDComLoss','BOOL',0,3600,map_={0: "", 1: "Drive Comms Loss"},plc_type='Micro800'),
PLCChannel(plc_ip,'vfd_fbkalarm','AL0_VFD_FBAlarm','BOOL',0,3600,map_={0: "", 1: "Drive Lost Feedback"},plc_type='Micro800'),
PLCChannel(plc_ip,'tubingpressurehi','AL0_TubingPressureHi','BOOL',0,3600,map_={0: "", 1: "High Tubing Pressure"},plc_type='Micro800'),
PLCChannel(plc_ip,'tubingpressurehihi','AL0_TubingPressureHiHi','BOOL',0,3600,map_={0: "", 1: "High High Tubing Pressure"},plc_type='Micro800'),
PLCChannel(plc_ip,'tubingpressurelo','AL0_TubingPressureLo','BOOL',0,3600,map_={0: "", 1: "Low Tubing Pressure"},plc_type='Micro800'),
PLCChannel(plc_ip,'tubingpressurelolo','AL0_TubingPressureLoLo','BOOL',0,3600,map_={0: "", 1: "Low Low Tubing Pressure"},plc_type='Micro800'),
PLCChannel(plc_ip,'flowmeterhihi','AL0_FlowMeterHiHi','BOOL',0,3600,map_={0: "", 1: "High High FM Flow Rate"},plc_type='Micro800'),
PLCChannel(plc_ip,'flowmeterhi','AL0_FlowMeterHi','BOOL',0,3600,map_={0: "", 1: "High FM Flow Rate"},plc_type='Micro800'),
PLCChannel(plc_ip,'flowmeterlolo','AL0_FlowMeterLoLo','BOOL',0,3600,map_={0: "", 1: "Low Low FM Flow Rate"},plc_type='Micro800'),
PLCChannel(plc_ip,'flowmeterlo','AL0_FlowMeterLo','BOOL',0,3600,map_={0: "", 1: "Low FM Flow Rate"},plc_type='Micro800'),
PLCChannel(plc_ip,'minspeedalarm','AL0_MinSpeedAlarm','BOOL',0,3600,map_={0: "", 1: "Drive not able to maintain min speed"},plc_type='Micro800'),
PLCChannel(plc_ip,'pumpedoff','AL0_PumpedOff','BOOL',0,3600,map_={0: "", 1: "Pumped Off"},plc_type='Micro800'),
PLCChannel(plc_ip,'fluidlevellolo','AL0_FluidLevelLoLo','BOOL',0,3600,map_={0: "", 1: "Low Low Fluid Level"},plc_type='Micro800'),
PLCChannel(plc_ip,'fluidlevello','AL0_FluidLevelLo','BOOL',0,3600,map_={0: "", 1: "Low Fluid Level"},plc_type='Micro800'),
PLCChannel(plc_ip,'fluidlevelhi','AL0_FluidLevelHi','BOOL',0,3600,map_={0: "", 1: "High Fluid Level"},plc_type='Micro800'),
PLCChannel(plc_ip,'fluidlevelhihi','AL0_FluidLevelHiHi','BOOL',0,3600,map_={0: "", 1: "High High Fluid Level"},plc_type='Micro800'),
PLCChannel(plc_ip,'lockedout','AlarmLockOut','BOOL',0,3600,map_={0: "", 1: "Locked Out Repeated Alarms"},plc_type='Micro800'),
PLCChannel(plc_ip,'volume_flow','Val_FlowmeterFR','REAL',5,3600,plc_type='Micro800'),
PLCChannel(plc_ip,'current','val_VFD_OutputCurrent','REAL',5,3600,plc_type='Micro800'),
PLCChannel(plc_ip,'frequency','val_VFD_ActualSpeed','REAL',5,3600,plc_type='Micro800'),
PLCChannel(plc_ip,'pid_feedback','val_FluidLevel','REAL',5,3600,plc_type='Micro800'),
PLCChannel(plc_ip,'totalizer_1','Val_FlowMeterT1','REAL',5,3600,plc_type='Micro800'),
PLCChannel(plc_ip,'totalizer_2','Val_FlowMeterT2','REAL',5,3600,plc_type='Micro800'),
PLCChannel(plc_ip,'totalizer_3','Val_FlowMeterT3','REAL',5,3600,plc_type='Micro800'),
PLCChannel(plc_ip,'volume_flow_units','CMD_FlowMeterUnit','BOOL',1,3600,map_={0: "GPM", 1: "BPD"},plc_type='Micro800')
]
else:
if drive_enabled:
tags = [
ModbusChannel('volume_flow', 3873, 'FLOAT', 10, 3600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_1', 2609, 'FLOAT', 100, 3600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_2', 2809, 'FLOAT', 100, 3600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_3', 3009, 'FLOAT', 100, 3600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('volume_flow_units', 2102, 'INTEGER', 1,86400,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=volume_units),
ModbusChannel('totalizer_1_units', 4603, 'INTEGER', 1,86400,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('totalizer_2_units', 4604, 'INTEGER', 1,86400,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('totalizer_3_units', 4605, 'INTEGER', 1,86400,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('run_status', 772, 'STRING', 0, 3600, channel_size=1, unit_number=drive_unit_number, transform_fn=status_codes),
ModbusChannel('frequency', 784, 'INTEGER', 2, 3600, channel_size=2, unit_number=drive_unit_number,scaling=2 ),
ModbusChannel('current', 783, 'INTEGER', 2, 3600, channel_size=2, unit_number=drive_unit_number,scaling=1 ),
ModbusChannel('fault_a', 815, 'STRING', 1, 3600, channel_size=1, unit_number=drive_unit_number,transform_fn=fault_code_a),
ModbusChannel('fault_b', 816, 'STRING', 1, 3600, channel_size=1, unit_number=drive_unit_number,transform_fn=fault_code_b),
ModbusChannel('pid_ref', 791, 'INTEGER', 5, 3600, channel_size=1, unit_number=drive_unit_number,scaling=1),
ModbusChannel('pid_feedback', 792, 'INTEGER', 5, 3600, channel_size=1, unit_number=drive_unit_number,scaling=1),
ModbusChannel('motor_rated_current', 4896, 'INTEGER', 300, 86400, channel_size=1, unit_number=drive_unit_number,scaling=1),
ModbusChannel('sleep_delay', 4924, 'INTEGER', 5, 86400, channel_size=1, unit_number=drive_unit_number, scaling=1)
]
else:
tags = [
ModbusChannel('volume_flow', 3873, 'FLOAT', 10, 3600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_1', 2609, 'FLOAT', 100, 3600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_2', 2809, 'FLOAT', 100, 3600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_3', 3009, 'FLOAT', 100, 3600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('volume_flow_units', 2102, 'INTEGER', 1, 86400,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=volume_units),
ModbusChannel('totalizer_1_units', 4603, 'INTEGER', 1, 86400,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('totalizer_2_units', 4604, 'INTEGER', 1, 86400,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('totalizer_3_units', 4605, 'INTEGER', 1, 86400,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units)
]

View File

@@ -1,114 +0,0 @@
# The default ``config.py``
# flake8: noqa
def set_prefs(prefs):
"""This function is called before opening the project"""
# Specify which files and folders to ignore in the project.
# Changes to ignored resources are not added to the history and
# VCSs. Also they are not returned in `Project.get_files()`.
# Note that ``?`` and ``*`` match all characters but slashes.
# '*.pyc': matches 'test.pyc' and 'pkg/test.pyc'
# 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc'
# '.svn': matches 'pkg/.svn' and all of its children
# 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o'
# 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o'
prefs['ignored_resources'] = ['*.pyc', '*~', '.ropeproject',
'.hg', '.svn', '_svn', '.git', '.tox']
# Specifies which files should be considered python files. It is
# useful when you have scripts inside your project. Only files
# ending with ``.py`` are considered to be python files by
# default.
# prefs['python_files'] = ['*.py']
# Custom source folders: By default rope searches the project
# for finding source folders (folders that should be searched
# for finding modules). You can add paths to that list. Note
# that rope guesses project source folders correctly most of the
# time; use this if you have any problems.
# The folders should be relative to project root and use '/' for
# separating folders regardless of the platform rope is running on.
# 'src/my_source_folder' for instance.
# prefs.add('source_folders', 'src')
# You can extend python path for looking up modules
# prefs.add('python_path', '~/python/')
# Should rope save object information or not.
prefs['save_objectdb'] = True
prefs['compress_objectdb'] = False
# If `True`, rope analyzes each module when it is being saved.
prefs['automatic_soa'] = True
# The depth of calls to follow in static object analysis
prefs['soa_followed_calls'] = 0
# If `False` when running modules or unit tests "dynamic object
# analysis" is turned off. This makes them much faster.
prefs['perform_doa'] = True
# Rope can check the validity of its object DB when running.
prefs['validate_objectdb'] = True
# How many undos to hold?
prefs['max_history_items'] = 32
# Shows whether to save history across sessions.
prefs['save_history'] = True
prefs['compress_history'] = False
# Set the number spaces used for indenting. According to
# :PEP:`8`, it is best to use 4 spaces. Since most of rope's
# unit-tests use 4 spaces it is more reliable, too.
prefs['indent_size'] = 4
# Builtin and c-extension modules that are allowed to be imported
# and inspected by rope.
prefs['extension_modules'] = []
# Add all standard c-extensions to extension_modules list.
prefs['import_dynload_stdmods'] = True
# If `True` modules with syntax errors are considered to be empty.
# The default value is `False`; When `False` syntax errors raise
# `rope.base.exceptions.ModuleSyntaxError` exception.
prefs['ignore_syntax_errors'] = False
# If `True`, rope ignores unresolvable imports. Otherwise, they
# appear in the importing namespace.
prefs['ignore_bad_imports'] = False
# If `True`, rope will insert new module imports as
# `from <package> import <module>` by default.
prefs['prefer_module_from_imports'] = False
# If `True`, rope will transform a comma list of imports into
# multiple separate import statements when organizing
# imports.
prefs['split_imports'] = False
# If `True`, rope will remove all top-level import statements and
# reinsert them at the top of the module when making changes.
prefs['pull_imports_to_top'] = True
# If `True`, rope will sort imports alphabetically by module name instead
# of alphabetically by import statement, with from imports after normal
# imports.
prefs['sort_imports_alphabetically'] = False
# Location of implementation of
# rope.base.oi.type_hinting.interfaces.ITypeHintingFactory In general
# case, you don't have to change this value, unless you're an rope expert.
# Change this value to inject you own implementations of interfaces
# listed in module rope.base.oi.type_hinting.providers.interfaces
# For example, you can add you own providers for Django Models, or disable
# the search type-hinting in a class hierarchy, etc.
prefs['type_hinting_factory'] = (
'rope.base.oi.type_hinting.factory.default_type_hinting_factory')
def project_opened(project):
"""This function is called after opening the project"""
# Do whatever you like here!

View File

@@ -1,4 +0,0 @@
}qUpersistence.pyq}qUloadqcrope.base.oi.memorydb
ScopeInfo
q)<29>q}qUbuiltinqUstrq †q
…Uunknownq …q s}q

View File

@@ -1,615 +0,0 @@
"""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)
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' or type(new_value) == str:
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 volume_units(vunit):
units = {
0: "cm cubed/s",
1: "cm cubed/min",
2: "cm cubed/h",
3: "cm cubed/d",
4: "dm cubed/s",
5: "dm cubed/min",
6: "dm cubed/h",
7: "dm cubed/d",
8: "m cubed/s",
9: "m cubed/min",
10: "m cubed/h",
11: "m cubed/d",
12: "ml/s",
13: "ml/min",
14: "ml/h",
15: "ml/d",
16: "l/s",
17: "l/min",
18: "l/h (+)",
19: "l/d",
20: "hl/s",
21: "hl/min",
22: "hl/h",
23: "hl/d",
24: "Ml/s",
25: "Ml/min",
26: "Ml/h",
27: "Ml/d",
32: "af/s",
33: "af/min",
34: "af/h",
35: "af/d",
36: "ft cubed/s",
37: "ft cubed/min",
38: "ft cubed/h",
39: "ft cubed/d",
40: "fl oz/s (us)",
41: "fl oz/min (us)",
42: "fl oz/h (us)",
43: "fl oz/d (us)",
44: "gal/s (us)",
45: "gal/min (us)",
46: "gal/h (us)",
47: "gal/d (us)",
48: "Mgal/s (us)",
49: "Mgal/min (us)",
50: "Mgal/h (us)",
51: "Mgal/d (us)",
52: "bbl/s (us;liq.)",
53: "bbl/min (us;liq.)",
54: "bbl/h (us;liq.)",
55: "bbl/d (us;liq.)",
56: "bbl/s (us;beer)",
57: "bbl/min (us;beer)",
58: "bbl/h (us;beer)",
59: "bbl/d (us;beer)",
60: "bbl/s (us;oil)",
61: "bbl/min (us;oil)",
62: "bbl/h (us;oil)",
63: "bbl/d (us;oil)",
64: "bbl/s (us;tank)",
65: "bbl/min (us;tank)",
66: "bbl/h (us;tank)",
67: "bbl/d (us;tank)",
68: "gal/s (imp)",
69: "gal/min (imp)",
70: "gal/h (imp)",
71: "gal/d (imp)",
72: "Mgal/s (imp)",
73: "Mgal/min (imp)",
74: "Mgal/h (imp)",
75: "Mgal/d (imp)",
76: "bbl/s (imp;beer)",
77: "bbl/min (imp;beer)",
78: "bbl/h (imp;beer)",
79: "bbl/d (imp;beer)",
80: "bbl/s (imp;oil)",
81: "bbl/min (imp;oil)",
82: "bbl/h (imp;oil)",
83: "bbl/d (imp;oil)",
88: "kgal/s (us)",
89: "kgal/min (us)",
90: "kgal/h (us)",
91: "kgal/d (us)",
92: "MMft cubed/s",
93: "MMft cubed/min",
94: "MMft cubed/h",
96: "Mft cubed/d"
}
return units[vunit]
def totalizer_units(tunit):
units = {
0: "cm cubed",
1: "dm cubed",
2: "m cubed",
3: "ml",
4: "l",
5: "hl",
6: "Ml Mega",
8: "af",
9: "ft cubed",
10: "fl oz (us)",
11: "gal (us)",
12: "Mgal (us)",
13: "bbl (us;liq.)",
14: "bbl (us;beer)",
15: "bbl (us;oil)",
16: "bbl (us;tank)",
17: "gal (imp)",
18: "Mgal (imp)",
19: "bbl (imp;beer)",
20: "bbl (imp;oil)",
22: "kgal (us)",
23: "Mft cubed",
50: "g",
51: "kg",
52: "t",
53: "oz",
54: "lb",
55: "STon",
100: "Nl",
101: "Nm cubed",
102: "Sm cubed",
103: "Sft cubed",
104: "Sl",
105: "Sgal (us)",
106: "Sbbl (us;liq.)",
107: "Sgal (imp)",
108: "Sbbl (us;oil)",
109: "MMSft cubed",
110: "Nhl",
251: "None"
}
return units[tunit]
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_low = {
0: "Stopped;",
1: "Operating in Forward;",
2: "Operating in Reverse;",
3: "DC operating;"
}
status_mid = {
0: "",
1: "Speed searching;",
2: "Accelerating;",
3: "At constant speed;",
4: "Decelerating;",
5: "Decelerating to stop;",
6: "H/W OCS;",
7: "S/W OCS;",
8: "Dwell operating;"
}
status_high = {
0: "Normal state",
4: "Warning occurred",
8: "Fault occurred"
}
values = {
0: 8,
1: 4,
2: 2,
3: 1
}
stats_low = status_array[12:]
stats_mid = status_array[8:12]
stats_high = status_array[:4]
low = 0
mid = 0
high = 0
for x in range(4):
if stats_low[x] == 1:
low = low + values[x]
if stats_mid[x] == 1:
mid = mid + values[x]
if stats_high[x] == 1:
high = high + values[x]
return status_low[low] + " " + status_mid[mid] + ' ' + status_high[high]
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"
} """
fault = {
0: "Overload Trip",
1: "Underload Trip",
2: "Inverter Overload Trip",
3: "E-Thermal Trip",
4: "Ground Fault Trip",
5: "Output Image Trip",
6: "Inmput Imaging Trip",
7: "Reserved",
8: "Reserved",
9: "NTC Trip",
10: "Overcurrent Trip",
11: "Overvoltage Trip",
12: "External Trip",
13: "Arm Short",
14: "Over Heat Trip",
15: "Fuse Open Trip"
}
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"
} """
fault = {
0: "Reserved",
1: "Reserved",
2: "Reserved",
3: "FAN Trip",
4: "Reserved",
5: "Reserved",
6: "Pre PID Fail",
7: "Bad contact at basic I/O board",
8: "External Brake Trip",
9: "No Motor Trip",
10: "Bad Option Card",
11: "Reserved",
12: "Reserved",
13: "Reserved",
14: "Pre Over Heat Trip",
15: "Reserved"
}
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

View File

@@ -1,452 +0,0 @@
"""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': 247,
'drive': 1,
'isVFD': False,
'drive_enabled': True,
'plc_ip': '192.168.1.12',
'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']
try:
isVFD = PERSIST['isVFD']
except:
PERSIST['isVFD'] = False
isVFD = PERSIST['isVFD']
persistence.store(PERSIST)
try:
plc_ip = PERSIST['plc_ip']
except:
PERSIST['plc_ip'] = '192.168.1.12'
plc_ip = PERSIST['plc_ip']
persistence.store(PERSIST)
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 = "25"
self.finished = threading.Event()
self.force_send = False
self.public_ip_address = ""
self.private_ip_address = ""
self.public_ip_address_last_checked = 0
self.status = ""
self.alarm = ""
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")
if isVFD:
status = {}
for chan in CHANNELS[:24]: #build status/alarm strings
try:
val = chan.read()
chan.check(val, self.force_send)
status[chan.mesh_name] = chan.value
except Exception as e:
log.warning("An error occured in status check: {}".format(e))
try:
self.sendStatus(status)
except Exception as e:
log.warning("An error occured in send status: {}".format(e))
for chan in CHANNELS[24:]:
try:
val = chan.read()
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')
self.sendtodbDev(1, chan.mesh_name + "_units", "BBL",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 in data collection: {}".format(e))
else:
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')
elif chan.mesh_name == "volume_flow" and not PERSIST['drive_enabled']:
if chan.check(val, self.force_send):
self.sendtodbDev(1, chan.mesh_name, chan.value, 0, 'PiFlow')
if chan.value > 0:
self.sendtodbDev(1, "run_status", "Running", 0, 'PiFlow')
else:
self.sendtodbDev(1,"run_status", "Stopped", 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 and not test_public_ip == "0.0.0.0":
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
def sendStatus(self,status):
status_string = ""
fault_codes = {
0: "",
2: "Auxiliary Input",
3: "Power Loss",
4: "UnderVoltage",
5: "OverVoltage",
7: "Motor Overload",
8: "Heatsink OvrTemp",
9: "Thermister OvrTemp",
10: "DynBrake OverTemp",
12: "HW OverCurrent",
13: "Ground Fault",
14: "Ground Warning",
15: "Load Loss",
17: "Input Phase Loss",
18: "Motor PTC Trip",
19: "Task Overrun",
20: "TorqPrv Spd Band",
21: "Output PhaseLoss",
24: "Decel Inhibit",
25: "OverSpeed Limit",
26: "Brake Slipped",
27: "Torq Prove Cflct",
28: "TP Encls Config",
29: "Analog In Loss",
33: "AuRsts Exhausted",
35: "IPM OverCurrent",
36: "SW OverCurrent",
38: "Phase U to Grnd",
39: "Phase V to Grnd",
40: "Phase W to Grnd",
41: "Phase UV Short",
42: "Phase VW Short",
43: "Phase WU Short",
44: "Phase UNegToGrnd",
45: "Phase VNegToGrnd",
46: "Phase WNegToGrnd",
48: "System Defaulted",
49: "Drive Powerup",
51: "Clr Fault Queue",
55: "Ctrl Bd Overtemp",
59: "Invalid Code",
61: "Shear Pin 1",
62: "Shear Pin 2",
64: "Drive Overload",
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: "FluxAmpsRef Rang",
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 VoltageRange",
91: "Pri VelFdbk Loss",
93: "Hw Enable Check",
94: "Alt VelFdbk Loss",
95: "Aux VelFdbk Loss",
96: "PositionFdbkLoss",
97: "Auto Tach Switch",
100: "Parameter Chksum",
101: "PwrDn NVS Blank",
102: "NVS Not Blank",
103: "PwrDn Nvs Incomp",
104: "Pwr Brd Checksum",
106: "Incompat MCB-PB",
107: "Replaced MCB-PB",
108: "Anlg Cal Chksum",
110: "Ivld Pwr Bd Data",
111: "PwrBd Invalid ID",
112: "PwrBd App MinVer",
113: "Tracking DataErr",
115: "PwrDn Table Full",
116: "PwrDnEntry2Large",
117: "PwrDn Data Chksm",
118: "PwrBd PwrDn Chks",
124: "App ID Changed",
125: "Using Backup App",
134: "Start on PowerUp",
137: "Ext Prechrg Err",
138: "Precharge Open",
141: "Autn Enc Angle",
142: "Autn Spd Rstrct",
143: "AutoTune CurReg",
144: "AutoTune Inertia",
145: "AutoTune Travel",
13037: "Net IO Timeout"
}
if status['vfd_active'] == "Stopped":
status_string = status_string + status['vfd_active'] + "; " + status['vfd_ready']
else:
status_string = status_string + status['vfd_active']
if status['vfd_rev']:
status_string = status_string + '; ' + status['vfd_rev']
if status['vfd_fwd']:
status_string = status_string + '; ' + status['vfd_fwd']
if status['vfd_atreference']:
status_string = status_string + '; ' + status['vfd_atreference']
alarm_string = ""
if status['vfd_faulted'] == "Drive Faulted":
status_string = status_string + '; ' + status['vfd_faulted']
if status['vfd_commloss']:
alarm_string = alarm_string + '; ' + status['vfd_commloss']
if status['vfd_fbkalarm']:
alarm_string = alarm_string + '; ' + status['vfd_fbkalarm']
if status['vfd_faultcode']:
alarm_string = alarm_string + '; ' + "Fault: {} Fault code: {}".format(fault_codes[status['vfd_faultcode']],str(status['vfd_faultcode']))
if status['minspeedalarm']:
alarm_string = alarm_string + '; ' + status['minspeedalarm']
if status['pumpedoff']:
alarm_string = alarm_string + '; ' + status['pumpedoff']
if status['lockedout']:
alarm_string = alarm_string + '; ' + status['lockedout']
if status['tubingpressurehi']:
alarm_string = alarm_string + '; ' + status['tubingpressurehi']
if status['tubingpressurehihi']:
alarm_string = alarm_string + '; ' + status['tubingpressurehihi']
if status['tubingpressurelo']:
alarm_string = alarm_string + '; ' + status['tubingpressurelo']
if status['tubingpressurelolo']:
alarm_string = alarm_string + '; ' + status['tubingpressurelolo']
if status['flowmeterhihi']:
alarm_string = alarm_string + '; ' + status['flowmeterhihi']
if status['flowmeterhi']:
alarm_string = alarm_string + '; ' + status['flowmeterhi']
if status['flowmeterlolo']:
alarm_string = alarm_string + '; ' + status['flowmeterlolo']
if status['flowmeterlo']:
alarm_string = alarm_string + '; ' + status['flowmeterlo']
if status['fluidlevellolo']:
alarm_string = alarm_string + '; ' + status['fluidlevellolo']
if status['fluidlevello']:
alarm_string = alarm_string + '; ' + status['fluidlevello']
if status['fluidlevelhi']:
alarm_string = alarm_string + '; ' + status['fluidlevelhi']
if status['fluidlevelhihi']:
alarm_string = alarm_string + '; ' + status['fluidlevelhihi']
try:
if status_string and status_string[0] == '; ':
status_string = status_string[1:]
if status_string and status_string[-1] == '; ':
status_string = status_string[:-1]
if alarm_string and alarm_string[0] == '; ':
alarm_string = alarm_string[1:]
if alarm_string and alarm_string[-1] == '; ':
alarm_string = alarm_string[:-1]
except Exception as e:
log.warning("Error in send status semicolon: {}".format(e))
if self.status != status_string:
self.status = status_string
log.info("Sending {} for {}".format(status_string, 'run_status'))
self.sendtodbDev(1, 'run_status', status_string, 0, 'PiFlow')
if self.alarm != alarm_string:
self.alarm = alarm_string
log.info("Sending {} for {}".format(alarm_string, 'fault_a'))
self.sendtodbDev(1, 'fault_a', alarm_string, 0 , 'PiFlow')

View File

@@ -1,90 +0,0 @@
from Channel import PLCChannel,Channel, ModbusChannel, status_codes, fault_code_a, fault_code_b, volume_units, totalizer_units
import persistence
PERSIST = persistence.load('persist.json')
flowmeter_unit_number = PERSIST['flowmeter']
drive_enabled = PERSIST['drive_enabled']
isVFD = PERSIST['isVFD']
if drive_enabled:
drive_unit_number = PERSIST['drive']
try:
plc_ip = PERSIST['plc_ip']
except:
PERSIST['plc_ip'] = '192.168.1.12'
persistence.store(PERSIST)
if isVFD:
tags = [
PLCChannel(plc_ip,'vfd_atreference','sts_VFD_AtReference','BOOL',0,3600,map_={0: "", 1: "At speed"},plc_type='Micro800'),
PLCChannel(plc_ip,'vfd_rev','sts_VFD_REV','BOOL',0,3600,map_={0: "", 1: "Operating in Reverse"},plc_type='Micro800'),
PLCChannel(plc_ip,'vfd_fwd','sts_VFD_FWD','BOOL',0,3600,map_={0: "", 1: "Operating in Forward"},plc_type='Micro800'),
PLCChannel(plc_ip,'vfd_active','sts_VFD_Active','BOOL',0,3600,map_={0: "Stopped", 1: "Running"},plc_type='Micro800'),
PLCChannel(plc_ip,'vfd_ready','sts_VFD_Ready','BOOL',0,3600,map_={0: "Drive Not Ready", 1: "Drive Ready"},plc_type='Micro800'),
PLCChannel(plc_ip,'vfd_faultcode','sts_VFD_FaultCode','REAL',0,3600, plc_type='Micro800'),
PLCChannel(plc_ip,'vfd_faulted','AL0_VFD','BOOL',0,3600,map_={0: "", 1: "Drive Faulted"},plc_type='Micro800'),
PLCChannel(plc_ip,'vfd_commloss','AL0_VFDComLoss','BOOL',0,3600,map_={0: "", 1: "Drive Comms Loss"},plc_type='Micro800'),
PLCChannel(plc_ip,'vfd_fbkalarm','AL0_VFD_FBAlarm','BOOL',0,3600,map_={0: "", 1: "Drive Lost Feedback"},plc_type='Micro800'),
PLCChannel(plc_ip,'tubingpressurehi','AL0_TubingPressureHi','BOOL',0,3600,map_={0: "", 1: "High Tubing Pressure"},plc_type='Micro800'),
PLCChannel(plc_ip,'tubingpressurehihi','AL0_TubingPressureHiHi','BOOL',0,3600,map_={0: "", 1: "High High Tubing Pressure"},plc_type='Micro800'),
PLCChannel(plc_ip,'tubingpressurelo','AL0_TubingPressureLo','BOOL',0,3600,map_={0: "", 1: "Low Tubing Pressure"},plc_type='Micro800'),
PLCChannel(plc_ip,'tubingpressurelolo','AL0_TubingPressureLoLo','BOOL',0,3600,map_={0: "", 1: "Low Low Tubing Pressure"},plc_type='Micro800'),
PLCChannel(plc_ip,'flowmeterhihi','AL0_FlowMeterHiHi','BOOL',0,3600,map_={0: "", 1: "High High FM Flow Rate"},plc_type='Micro800'),
PLCChannel(plc_ip,'flowmeterhi','AL0_FlowMeterHi','BOOL',0,3600,map_={0: "", 1: "High FM Flow Rate"},plc_type='Micro800'),
PLCChannel(plc_ip,'flowmeterlolo','AL0_FlowMeterLoLo','BOOL',0,3600,map_={0: "", 1: "Low Low FM Flow Rate"},plc_type='Micro800'),
PLCChannel(plc_ip,'flowmeterlo','AL0_FlowMeterLo','BOOL',0,3600,map_={0: "", 1: "Low FM Flow Rate"},plc_type='Micro800'),
PLCChannel(plc_ip,'minspeedalarm','AL0_MinSpeedAlarm','BOOL',0,3600,map_={0: "", 1: "Drive not able to maintain min speed"},plc_type='Micro800'),
PLCChannel(plc_ip,'pumpedoff','AL0_PumpedOff','BOOL',0,3600,map_={0: "", 1: "Pumped Off"},plc_type='Micro800'),
PLCChannel(plc_ip,'fluidlevellolo','AL0_FluidLevelLoLo','BOOL',0,3600,map_={0: "", 1: "Low Low Fluid Level"},plc_type='Micro800'),
PLCChannel(plc_ip,'fluidlevello','AL0_FluidLevelLo','BOOL',0,3600,map_={0: "", 1: "Low Fluid Level"},plc_type='Micro800'),
PLCChannel(plc_ip,'fluidlevelhi','AL0_FluidLevelHi','BOOL',0,3600,map_={0: "", 1: "High Fluid Level"},plc_type='Micro800'),
PLCChannel(plc_ip,'fluidlevelhihi','AL0_FluidLevelHiHi','BOOL',0,3600,map_={0: "", 1: "High High Fluid Level"},plc_type='Micro800'),
PLCChannel(plc_ip,'lockedout','AlarmLockOut','BOOL',0,3600,map_={0: "", 1: "Locked Out Repeated Alarms"},plc_type='Micro800'),
PLCChannel(plc_ip,'volume_flow','Val_FlowmeterFR','REAL',5,3600,plc_type='Micro800'),
PLCChannel(plc_ip,'current','val_VFD_OutputCurrent','REAL',5,3600,plc_type='Micro800'),
PLCChannel(plc_ip,'frequency','val_VFD_ActualSpeed','REAL',5,3600,plc_type='Micro800'),
PLCChannel(plc_ip,'pid_feedback','val_FluidLevel','REAL',5,3600,plc_type='Micro800'),
PLCChannel(plc_ip,'totalizer_1','Val_FlowMeterT1','REAL',5,3600,plc_type='Micro800'),
PLCChannel(plc_ip,'totalizer_2','Val_FlowMeterT2','REAL',5,3600,plc_type='Micro800'),
PLCChannel(plc_ip,'totalizer_3','Val_FlowMeterT3','REAL',5,3600,plc_type='Micro800'),
PLCChannel(plc_ip,'volume_flow_units','CMD_FlowMeterUnit','BOOL',1,3600,map_={0: "GPM", 1: "BPD"},plc_type='Micro800')
]
else:
if drive_enabled:
tags = [
ModbusChannel('volume_flow', 3873, 'FLOAT', 10, 3600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_1', 2609, 'FLOAT', 100, 3600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_2', 2809, 'FLOAT', 100, 3600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_3', 3009, 'FLOAT', 100, 3600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('volume_flow_units', 2102, 'INTEGER', 1,86400,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=volume_units),
ModbusChannel('totalizer_1_units', 4603, 'INTEGER', 1,86400,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('totalizer_2_units', 4604, 'INTEGER', 1,86400,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('totalizer_3_units', 4605, 'INTEGER', 1,86400,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('run_status', 772, 'STRING', 0, 3600, channel_size=1, unit_number=drive_unit_number, transform_fn=status_codes),
ModbusChannel('frequency', 784, 'INTEGER', 2, 3600, channel_size=2, unit_number=drive_unit_number,scaling=2 ),
ModbusChannel('current', 783, 'INTEGER', 2, 3600, channel_size=2, unit_number=drive_unit_number,scaling=1 ),
ModbusChannel('fault_a', 815, 'STRING', 1, 3600, channel_size=1, unit_number=drive_unit_number,transform_fn=fault_code_a),
ModbusChannel('fault_b', 816, 'STRING', 1, 3600, channel_size=1, unit_number=drive_unit_number,transform_fn=fault_code_b),
ModbusChannel('pid_ref', 791, 'INTEGER', 5, 3600, channel_size=1, unit_number=drive_unit_number,scaling=1),
ModbusChannel('pid_feedback', 792, 'INTEGER', 5, 3600, channel_size=1, unit_number=drive_unit_number,scaling=1),
ModbusChannel('motor_rated_current', 4896, 'INTEGER', 300, 86400, channel_size=1, unit_number=drive_unit_number,scaling=1),
ModbusChannel('sleep_delay', 4924, 'INTEGER', 5, 86400, channel_size=1, unit_number=drive_unit_number, scaling=1)
]
else:
tags = [
ModbusChannel('volume_flow', 3873, 'FLOAT', 10, 3600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_1', 2609, 'FLOAT', 100, 3600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_2', 2809, 'FLOAT', 100, 3600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_3', 3009, 'FLOAT', 100, 3600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('volume_flow_units', 2102, 'INTEGER', 1, 86400,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=volume_units),
ModbusChannel('totalizer_1_units', 4603, 'INTEGER', 1, 86400,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('totalizer_2_units', 4604, 'INTEGER', 1, 86400,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('totalizer_3_units', 4605, 'INTEGER', 1, 86400,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units)
]

View File

@@ -1,16 +0,0 @@
{
"driverFileName":"PiFlow.py",
"deviceName":"piflow",
"driverId":"0280",
"releaseVersion":"25",
"files": {
"file1":"PiFlow.py",
"file2":"Channel.py",
"file3":"file_logger.py",
"file4":"Tags.py",
"file5":"utilities.py",
"file6":"persistence.py"
}
}

View File

@@ -1,18 +0,0 @@
"""Logging setup for PiFlow"""
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 = './PiFlow.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('PiFlow')
filelogger.setLevel(logging.INFO)
filelogger.addHandler(my_handler)
console_out = logging.StreamHandler(sys.stdout)
console_out.setFormatter(log_formatter)
filelogger.addHandler(console_out)

View File

@@ -1,71 +0,0 @@
"""Utility functions for the driver."""
import socket
import struct
import urllib
import contextlib
def get_private_ip_address():
"""Find the private 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 get_public_ip_address():
ip_address = "0.0.0.0"
try:
with contextlib.closing(urllib.urlopen("httpd://checkip.amazonaws.com")) as url:
ip_address = url.read()
except Exception as e:
print("Could not resolve address: {}".format(e))
return ip_address
return ip_address[:-1]
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_public_ip_address():
try:
url = urllib.urlopen("http://checkip.amazonaws.com")
ip_address = url.read()
except Exception as e:
return e
return ip_address

View File

@@ -3,7 +3,7 @@
"driverFileName":"PiFlow.py",
"deviceName":"piflow",
"driverId":"0280",
"releaseVersion":"20",
"releaseVersion":"25",
"files": {
"file1":"PiFlow.py",
"file2":"Channel.py",

View File

@@ -1,557 +0,0 @@
"""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' or type(new_value) == str:
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 volume_units(vunit):
units = {
0: "cm cubed/s",
1: "cm cubed/min",
2: "cm cubed/h",
3: "cm cubed/d",
4: "dm cubed/s",
5: "dm cubed/min",
6: "dm cubed/h",
7: "dm cubed/d",
8: "m cubed/s",
9: "m cubed/min",
10: "m cubed/h",
11: "m cubed/d",
12: "ml/s",
13: "ml/min",
14: "ml/h",
15: "ml/d",
16: "l/s",
17: "l/min",
18: "l/h (+)",
19: "l/d",
20: "hl/s",
21: "hl/min",
22: "hl/h",
23: "hl/d",
24: "Ml/s",
25: "Ml/min",
26: "Ml/h",
27: "Ml/d",
32: "af/s",
33: "af/min",
34: "af/h",
35: "af/d",
36: "ft cubed/s",
37: "ft cubed/min",
38: "ft cubed/h",
39: "ft cubed/d",
40: "fl oz/s (us)",
41: "fl oz/min (us)",
42: "fl oz/h (us)",
43: "fl oz/d (us)",
44: "gal/s (us)",
45: "gal/min (us)",
46: "gal/h (us)",
47: "gal/d (us)",
48: "Mgal/s (us)",
49: "Mgal/min (us)",
50: "Mgal/h (us)",
51: "Mgal/d (us)",
52: "bbl/s (us;liq.)",
53: "bbl/min (us;liq.)",
54: "bbl/h (us;liq.)",
55: "bbl/d (us;liq.)",
56: "bbl/s (us;beer)",
57: "bbl/min (us;beer)",
58: "bbl/h (us;beer)",
59: "bbl/d (us;beer)",
60: "bbl/s (us;oil)",
61: "bbl/min (us;oil)",
62: "bbl/h (us;oil)",
63: "bbl/d (us;oil)",
64: "bbl/s (us;tank)",
65: "bbl/min (us;tank)",
66: "bbl/h (us;tank)",
67: "bbl/d (us;tank)",
68: "gal/s (imp)",
69: "gal/min (imp)",
70: "gal/h (imp)",
71: "gal/d (imp)",
72: "Mgal/s (imp)",
73: "Mgal/min (imp)",
74: "Mgal/h (imp)",
75: "Mgal/d (imp)",
76: "bbl/s (imp;beer)",
77: "bbl/min (imp;beer)",
78: "bbl/h (imp;beer)",
79: "bbl/d (imp;beer)",
80: "bbl/s (imp;oil)",
81: "bbl/min (imp;oil)",
82: "bbl/h (imp;oil)",
83: "bbl/d (imp;oil)",
88: "kgal/s (us)",
89: "kgal/min (us)",
90: "kgal/h (us)",
91: "kgal/d (us)",
92: "MMft cubed/s",
93: "MMft cubed/min",
94: "MMft cubed/h",
96: "Mft cubed/d"
}
return units[vunit]
def totalizer_units(tunit):
units = {
0: "cm cubed",
1: "dm cubed",
2: "m cubed",
3: "ml",
4: "l",
5: "hl",
6: "Ml Mega",
8: "af",
9: "ft cubed",
10: "fl oz (us)",
11: "gal (us)",
12: "Mgal (us)",
13: "bbl (us;liq.)",
14: "bbl (us;beer)",
15: "bbl (us;oil)",
16: "bbl (us;tank)",
17: "gal (imp)",
18: "Mgal (imp)",
19: "bbl (imp;beer)",
20: "bbl (imp;oil)",
22: "kgal (us)",
23: "Mft cubed",
50: "g",
51: "kg",
52: "t",
53: "oz",
54: "lb",
55: "STon",
100: "Nl",
101: "Nm cubed",
102: "Sm cubed",
103: "Sft cubed",
104: "Sl",
105: "Sgal (us)",
106: "Sbbl (us;liq.)",
107: "Sgal (imp)",
108: "Sbbl (us;oil)",
109: "MMSft cubed",
110: "Nhl",
251: "None"
}
return units[tunit]
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

View File

@@ -1,50 +0,0 @@
from Channel import PLCChannel,Channel, ModbusChannel, status_codes, fault_code_a, fault_code_b, volume_units, totalizer_units
import persistence
data = persistence.load('persist.json')
flowmeter_unit_number = data['flowmeter']
drive_enabled = data['drive_enabled']
if drive_enabled:
drive_unit_number = data['drive']
if drive_enabled:
tags = [
ModbusChannel('volume_flow', 3873, 'FLOAT', 1,600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_1', 2609, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_2', 2809, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_3', 3009, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('volume_flow_units', 2102, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=volume_units),
ModbusChannel('totalizer_1_units', 4603, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('totalizer_2_units', 4604, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('totalizer_3_units', 4605, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('run_status', 13, 'STRING', 0, 3600, channel_size=1, unit_number=drive_unit_number, transform_fn=status_codes),
ModbusChannel('frequency', 9, 'INTEGER', 0.5, 600, channel_size=2, unit_number=drive_unit_number,scaling=2 ),
ModbusChannel('current', 8, 'INTEGER', 0.5, 600, channel_size=2, unit_number=drive_unit_number,scaling=1 ),
ModbusChannel('fault_a', 14, 'STRING', 1, 3600, channel_size=1, unit_number=drive_unit_number,transform_fn=fault_code_a),
ModbusChannel('fault_b', 28, 'STRING', 1, 3600, channel_size=1, unit_number=drive_unit_number,transform_fn=fault_code_b),
ModbusChannel('pid_ref', 4367, 'INTEGER', 1, 600, channel_size=1, unit_number=drive_unit_number,scaling=1),
ModbusChannel('pid_feedback', 4368, 'INTEGER', 1, 600, channel_size=1, unit_number=drive_unit_number,scaling=1),
ModbusChannel('motor_rated_current', 4896, 'INTEGER', 0, 3600, channel_size=1, unit_number=drive_unit_number,scaling=1),
ModbusChannel('sleep_delay', 4924, 'INTEGER', 1, 600, channel_size=1, unit_number=drive_unit_number, scaling=1)
]
else:
tags = [
ModbusChannel('volume_flow', 3873, 'FLOAT', 1,600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_1', 2609, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_2', 2809, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_3', 3009, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('volume_flow_units', 2102, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=volume_units),
ModbusChannel('totalizer_1_units', 4603, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('totalizer_2_units', 4604, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('totalizer_3_units', 4605, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units)
]

View File

@@ -1,610 +0,0 @@
"""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' or type(new_value) == str:
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 volume_units(vunit):
units = {
0: "cm cubed/s",
1: "cm cubed/min",
2: "cm cubed/h",
3: "cm cubed/d",
4: "dm cubed/s",
5: "dm cubed/min",
6: "dm cubed/h",
7: "dm cubed/d",
8: "m cubed/s",
9: "m cubed/min",
10: "m cubed/h",
11: "m cubed/d",
12: "ml/s",
13: "ml/min",
14: "ml/h",
15: "ml/d",
16: "l/s",
17: "l/min",
18: "l/h (+)",
19: "l/d",
20: "hl/s",
21: "hl/min",
22: "hl/h",
23: "hl/d",
24: "Ml/s",
25: "Ml/min",
26: "Ml/h",
27: "Ml/d",
32: "af/s",
33: "af/min",
34: "af/h",
35: "af/d",
36: "ft cubed/s",
37: "ft cubed/min",
38: "ft cubed/h",
39: "ft cubed/d",
40: "fl oz/s (us)",
41: "fl oz/min (us)",
42: "fl oz/h (us)",
43: "fl oz/d (us)",
44: "gal/s (us)",
45: "gal/min (us)",
46: "gal/h (us)",
47: "gal/d (us)",
48: "Mgal/s (us)",
49: "Mgal/min (us)",
50: "Mgal/h (us)",
51: "Mgal/d (us)",
52: "bbl/s (us;liq.)",
53: "bbl/min (us;liq.)",
54: "bbl/h (us;liq.)",
55: "bbl/d (us;liq.)",
56: "bbl/s (us;beer)",
57: "bbl/min (us;beer)",
58: "bbl/h (us;beer)",
59: "bbl/d (us;beer)",
60: "bbl/s (us;oil)",
61: "bbl/min (us;oil)",
62: "bbl/h (us;oil)",
63: "bbl/d (us;oil)",
64: "bbl/s (us;tank)",
65: "bbl/min (us;tank)",
66: "bbl/h (us;tank)",
67: "bbl/d (us;tank)",
68: "gal/s (imp)",
69: "gal/min (imp)",
70: "gal/h (imp)",
71: "gal/d (imp)",
72: "Mgal/s (imp)",
73: "Mgal/min (imp)",
74: "Mgal/h (imp)",
75: "Mgal/d (imp)",
76: "bbl/s (imp;beer)",
77: "bbl/min (imp;beer)",
78: "bbl/h (imp;beer)",
79: "bbl/d (imp;beer)",
80: "bbl/s (imp;oil)",
81: "bbl/min (imp;oil)",
82: "bbl/h (imp;oil)",
83: "bbl/d (imp;oil)",
88: "kgal/s (us)",
89: "kgal/min (us)",
90: "kgal/h (us)",
91: "kgal/d (us)",
92: "MMft cubed/s",
93: "MMft cubed/min",
94: "MMft cubed/h",
96: "Mft cubed/d"
}
return units[vunit]
def totalizer_units(tunit):
units = {
0: "cm cubed",
1: "dm cubed",
2: "m cubed",
3: "ml",
4: "l",
5: "hl",
6: "Ml Mega",
8: "af",
9: "ft cubed",
10: "fl oz (us)",
11: "gal (us)",
12: "Mgal (us)",
13: "bbl (us;liq.)",
14: "bbl (us;beer)",
15: "bbl (us;oil)",
16: "bbl (us;tank)",
17: "gal (imp)",
18: "Mgal (imp)",
19: "bbl (imp;beer)",
20: "bbl (imp;oil)",
22: "kgal (us)",
23: "Mft cubed",
50: "g",
51: "kg",
52: "t",
53: "oz",
54: "lb",
55: "STon",
100: "Nl",
101: "Nm cubed",
102: "Sm cubed",
103: "Sft cubed",
104: "Sl",
105: "Sgal (us)",
106: "Sbbl (us;liq.)",
107: "Sgal (imp)",
108: "Sbbl (us;oil)",
109: "MMSft cubed",
110: "Nhl",
251: "None"
}
return units[tunit]
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_low = {
0: "Stopped",
1: "Operating in Forward",
2: "Operating in Reverse",
3: "DC operating"
}
status_mid = {
1: "Speed searching",
2: "Accelerating",
3: "At constant speed",
4: "Decelerating",
5: "Decelerating to stop",
6: "H/W OCS",
7: "S/W OCS",
8: "Dwell operating"
}
status_high = {
0: "Normal state",
4: "Warning occurred",
8: "Fault occurred"
}
values = {
0: 8,
1: 4,
2: 2,
3: 1
}
stats_low = status_array[12:]
stats_mid = status_array[8:12]
stats_high = status_array[:4]
low = 0
mid = 0
high = 0
for x in range(4):
if stats_low[x] == 1:
low = low + values[x]
if stats_mid[x] == 1:
mid = mid + values[x]
if stats_high[x] == 1:
high = high + values[x]
return status_low[low] + ";" + status_mid[mid] + ';' + status_high[high]
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"
} """
fault = {
0: "Overload Trip",
1: "Underload Trip",
2: "Inverter Overload Trip",
3: "E-Thermal Trip",
4: "Ground Fault Trip",
5: "Output Image Trip",
6: "Inmput Imaging Trip",
7: "Reserved",
8: "Reserved",
9: "NTC Trip",
10: "Overcurrent Trip",
11: "Overvoltage Trip",
12: "External Trip",
13: "Arm Short",
14: "Over Heat Trip",
15: "Fuse Open Trip"
}
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"
} """
fault = {
0: "Reserved",
1: "Reserved",
2: "Reserved",
3: "FAN Trip",
4: "Reserved",
5: "Reserved",
6: "Pre PID Fail",
7: "Bad contact at basic I/O board",
8: "External Brake Trip",
9: "No Motor Trip",
10: "Bad Option Card",
11: "Reserved",
12: "Reserved",
13: "Reserved",
14: "Pre Over Heat Trip",
15: "Reserved"
}
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

View File

@@ -1,51 +0,0 @@
from Channel import PLCChannel,Channel, ModbusChannel, status_codes, fault_code_a, fault_code_b, volume_units, totalizer_units
import persistence
data = persistence.load('persist.json')
flowmeter_unit_number = data['flowmeter']
drive_enabled = data['drive_enabled']
if drive_enabled:
drive_unit_number = data['drive']
if drive_enabled:
tags = [
ModbusChannel('volume_flow', 3873, 'FLOAT', 1,600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_1', 2609, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_2', 2809, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_3', 3009, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('volume_flow_units', 2102, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=volume_units),
ModbusChannel('totalizer_1_units', 4603, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('totalizer_2_units', 4604, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('totalizer_3_units', 4605, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('run_status', 772, 'STRING', 0, 3600, channel_size=1, unit_number=drive_unit_number, transform_fn=status_codes),
ModbusChannel('frequency', 784, 'INTEGER', 0.5, 600, channel_size=2, unit_number=drive_unit_number,scaling=2 ),
ModbusChannel('current', 783, 'INTEGER', 0.5, 600, channel_size=2, unit_number=drive_unit_number,scaling=1 ),
ModbusChannel('fault_a', 815, 'STRING', 1, 3600, channel_size=1, unit_number=drive_unit_number,transform_fn=fault_code_a),
ModbusChannel('fault_b', 816, 'STRING', 1, 3600, channel_size=1, unit_number=drive_unit_number,transform_fn=fault_code_b),
ModbusChannel('pid_ref', 791, 'INTEGER', 1, 600, channel_size=1, unit_number=drive_unit_number,scaling=1),
ModbusChannel('pid_feedback', 792, 'INTEGER', 1, 600, channel_size=1, unit_number=drive_unit_number,scaling=1),
ModbusChannel('motor_rated_current', 4896, 'INTEGER', 0, 3600, channel_size=1, unit_number=drive_unit_number,scaling=1),
ModbusChannel('sleep_delay', 4924, 'INTEGER', 1, 600, channel_size=1, unit_number=drive_unit_number, scaling=1)
]
else:
tags = [
ModbusChannel('volume_flow', 3873, 'FLOAT', 1,600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_1', 2609, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_2', 2809, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('totalizer_3', 3009, 'FLOAT', 10,600,channel_size=2, unit_number=flowmeter_unit_number),
ModbusChannel('volume_flow_units', 2102, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=volume_units),
ModbusChannel('totalizer_1_units', 4603, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('totalizer_2_units', 4604, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units),
ModbusChannel('totalizer_3_units', 4605, 'INTEGER', 1,3600,channel_size=1, unit_number=flowmeter_unit_number, transform_fn=totalizer_units)
]

View File

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

View File

@@ -1,42 +0,0 @@
<div class="row row-flex box-me">
<div class="col-md-6 text-center">
<h2 class="uppercase">Public IP Address</h2>
<p><%= channels["tenflowmeterskid.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

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

View File

@@ -1,31 +0,0 @@
<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-4">
<h4><%= node.vanityname %></h4>
</div>
</div>

View File

@@ -1,121 +0,0 @@
<div class="row row-flex box-me">
<div class="col-xs-12 text-center">
<h1>HEADER 1</h1>
</div>
<div class="col-xs-4 text-center">
<h2>CHANNEL 1</h2>
<div class="gauge-box">
<div data-labelheight="10"
style="height: 170px; background: transparent; margin: 0 auto;"
id="gauge-channel_1"
data-chart="solidgauge"
data-nodename="tenflowmeterskid.channel_1"
data-units="UNITS"
data-min="0"
data-max="100"
data-decimalplaces="2"
data-colors="0.1:#DF5353,0.5:#DDDF0D,0.9:#55BF3B"
data-valuefontsize="18px">
</div>
<div class= "timestamp-box">
<a href="#" data-channelId="<%= channels['tenflowmeterskid.channel_1'].channelId %>" class="data-table" title="Download Channel History">
<i class="fa fa-download"></i>
</a>
</div>
<span data-timeupdate="channel_1">
<%= channels["tenflowmeterskid.channel_1"].timestamp %>
</span>
</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;
}
.pad15 {
margin: 15px 15px;
}
.box-me 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'));
}
});
});
</script>

View File

@@ -1,15 +0,0 @@
<a href="#"
data-channelId="<%= channels["tenflowmeterskid.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["tenflowmeterskid.sync"].channelId %>"
data-techname="<%=channels["tenflowmeterskid.sync"].techName %>"
data-name="<%= channels["tenflowmeterskid.sync"].name%>"
data-nodechannelcurrentId="<%= channels["tenflowmeterskid.sync"].nodechannelcurrentId %>"
id="<%= channels["tenflowmeterskid.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

@@ -1,37 +0,0 @@
<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="tenflowmeterskid.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

@@ -1,21 +0,0 @@
"""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