Files
2024-10-04 18:53:54 -05:00

578 lines
25 KiB
Python

"""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
"""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.output(Relay_Ch1, GPIO.HIGH)
GPIO.setup(Relay_Ch2,GPIO.OUT)
GPIO.output(Relay_Ch2, GPIO.HIGH)
GPIO.setup(Relay_Ch3,GPIO.OUT)
GPIO.output(Relay_Ch3, GPIO.HIGH)
"""
_ = None
os.system('sudo timedatectl set-timezone America/Chicago')
log.info("PiFlow startup")
# GLOBAL VARIABLES
WAIT_FOR_CONNECTION_SECONDS = 5
IP_CHECK_PERIOD = 300
SEND_PERIOD = 600
# PERSISTENCE FILE
PERSIST = persistence.load('persist.json')
if not PERSIST:
PERSIST = {
'flowmeter': 247,
'drive': 1,
'isVFD': False,
'drive_enabled': True,
'state': False,
'state_timer': 0,
'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')
"""
try:
if time.time() - PERSIST['state_timer'] >= 60:
GPIO.output(Relay_Ch1, GPIO.HIGH)
PERSIST['state'] = False
persistence.store(PERSIST, "persist.json")
elif PERSIST['state']:
GPIO.output(Relay_Ch1, GPIO.LOW)
else:
GPIO.output(Relay_Ch1, GPIO.HIGH)
except:
PERSIST['state'] = False
PERSIST['state_time'] = time.time()
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
from runtimeStats import RuntimeStats as RTS
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 = "29"
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 = ""
self.rts = RTS()
self.rts.loadDataFromFile()
self.rts.saveDataToFile()
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:
try:
now = time.time()
if self.force_send:
log.warning("FORCE SEND: TRUE")
if int(time.time()) % SEND_PERIOD == 0 or self.force_send:
if self.force_send:
payload = {"ts": time.time()*1000, "values": {}}
else:
payload = {"ts": round(time.time()/SEND_PERIOD)*SEND_PERIOD*1000, "values": {}}
if isVFD:
status = {}
for chan in CHANNELS[:24]: #build status/alarm strings
try:
val = chan.read()
if val == None or val == "None":
continue
#chan.check(val, self.force_send)
status[chan.mesh_name] = val
except Exception as e:
log.warning("An error occured in status check: {}".format(e))
try:
status_payload = self.sendStatus(status)
for key, value in status_payload.items():
payload["values"][key] = value
except Exception as e:
log.warning("An error occured in send status: {}".format(e))
for chan in CHANNELS[24:]:
try:
val = chan.read()
if val == None or val == "None":
continue
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)
payload["values"][chan.mesh_name] = val
payload["values"]["today_" + chan.mesh_name] = today_total
payload["values"]["yesterday_" + chan.mesh_name] = yesterday_total
payload["values"][chan.mesh_name + "_units"] = "bbl (us;oil)"
elif chan.mesh_name == "frequency":
if val > 0:
self.rts.addHertzDataPoint(val)
self.rts.saveDataToFile()
payload["values"][chan.mesh_name] = val
payload["values"]["avgFrequency30Days"] = self.rts.calculateAverageHertzMultiDay()
else:
payload["values"][chan.mesh_name] = val
except Exception as e:
log.warning("An error occured in data collection: {}".format(e))
else:
for chan in CHANNELS:
try:
if chan.mesh_name == "remote_start":
val = PERSIST["state"]
else:
val = None
for _ in range(3):
temp = chan.read()
if not temp == None:
val = temp
break
if val == None:
log.info("No modbus data sending previous value")
val = chan.value
if val == None or val == "None":
continue
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)
payload["values"][chan.mesh_name] = val
payload["values"]["today_" + chan.mesh_name] = today_total
payload["values"]["yesterday_" + chan.mesh_name] = yesterday_total
elif chan.mesh_name == "volume_flow" and not PERSIST['drive_enabled']:
payload["values"][chan.mesh_name] = val
if chan.value > 0:
payload["values"]["run_status"] = "Running"
if not self.rts.runs[self.rts.todayString]["run_" + str(self.rts.currentRun)]["start"]:
self.rts.startRun()
self.rts.saveDataToFile()
else:
payload["values"]["run_status"] = "Stopped"
if self.rts.runs[self.rts.todayString]["run_" + str(self.rts.currentRun)]["start"] and not self.rts.runs[self.rts.todayString]["run_" + str(self.rts.currentRun)]["end"]:
self.rts.endRun()
self.rts.saveDataToFile()
payload["values"]["percentRunTime30Days"] = self.rts.calculateRunPercentMultiDay()
elif chan.mesh_name == "run_status":
if "Operating" in val and not self.rts.runs[self.rts.todayString]["run_" + str(self.rts.currentRun)]["start"]:
self.rts.startRun()
self.rts.saveDataToFile()
elif "Stopped" in val and self.rts.runs[self.rts.todayString]["run_" + str(self.rts.currentRun)]["start"] and not self.rts.runs[self.rts.todayString]["run_" + str(self.rts.currentRun)]["end"]:
self.rts.endRun()
self.rts.saveDataToFile()
payload["values"][chan.mesh_name] = val
payload["values"]["percentRunTime30Days"] = self.rts.calculateRunPercentMultiDay()
elif chan.mesh_name == "frequency":
if val > 0:
self.rts.addHertzDataPoint(val)
self.rts.saveDataToFile()
payload["values"][chan.mesh_name] = val
payload["values"]["avgFrequency30Days"] = self.rts.calculateAverageHertzMultiDay()
elif chan.mesh_name == "remote_start":
payload["values"][chan.mesh_name] = val
PERSIST["state_timer"] = time.time()
persistence.store(PERSIST, "persist.json")
else:
payload["values"][chan.mesh_name] = val
except Exception as e:
log.warning("An error occured: {}".format(e))
time.sleep(3)
if (now - self.public_ip_address_last_checked) > IP_CHECK_PERIOD:
self._check_ip_address()
payload["values"]["public_ip_address"] = self.public_ip_address
payload["values"]["private_ip_address"] = self.private_ip_address
self.sendToTB(json.dumps(payload))
attribute_payload = {
"latestReportTime": round(time.time()/SEND_PERIOD)*SEND_PERIOD*1000
}
for key, value in PERSIST.items():
attribute_payload[key] = value
self.sendToTBAttributes(json.dumps(attribute_payload))
time.sleep(5)
# 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
except Exception as e:
log.error(e)
time.sleep(1)
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.public_ip_address = test_public_ip
if not test_private_ip == self.private_ip_address:
self.private_ip_address = test_private_ip
def PiFlow_sync(self, name, value):
"""Sync all data from the driver."""
self.force_send = True
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.sendToTBAttributes(json.dumps({'flowmeternumber': unit_number}))
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.sendToTBAttributes(json.dumps({'drivenumber': unit_number}))
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.sendToTBAttributes(json.dumps({'drive_enabled': value}))
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 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)
PERSIST["state"] = True
PERSIST["state_timer"] = time.time()
persistence.store(PERSIST,"persist.json")
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)
PERSIST["state"] = False
PERSIST["state_timer"] = time.time()
persistence.store(PERSIST, "persist.json")
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):
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):
self.rts.manageTime()
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))
self.status = status_string
return_payload = {}
log.info("Sending {} for {}".format(status_string, 'run_status'))
#TODO setup return for run_status, percentRunTime, and faults
return_payload["run_status"] = status_string
if "Operating" in status_string and not self.rts.runs[self.rts.todayString]["run_" + str(self.rts.currentRun)]["start"]:
self.rts.startRun()
self.rts.saveDataToFile()
elif "Stopped" in status_string and self.rts.runs[self.rts.todayString]["run_" + str(self.rts.currentRun)]["start"] and not self.rts.runs[self.rts.todayString]["run_" + str(self.rts.currentRun)]["end"]:
self.rts.endRun()
self.rts.saveDataToFile()
return_payload["percentRunTime30Days"] = self.rts.calculateRunPercentMultiDay()
if self.alarm != alarm_string:
self.alarm = alarm_string
log.info("Sending {} for {}".format(alarm_string, 'fault_a'))
return_payload["fault_a"] = alarm_string
return return_payload