184 lines
5.9 KiB
Python
184 lines
5.9 KiB
Python
from .models import *
|
|
from ospaz_site.settings import DEBUG
|
|
from threading import Thread
|
|
import time
|
|
from datetime import datetime, timedelta
|
|
from pyModbusTCP.client import ModbusClient
|
|
from threading import Lock
|
|
|
|
|
|
class MbClearHistoryService(Thread):
|
|
def __init__(self, model, save_days):
|
|
super().__init__()
|
|
self.model = model
|
|
self._days = save_days
|
|
|
|
def run(self):
|
|
while True:
|
|
self.model.objects.filter(dt__lt=(datetime.now() - timedelta(days=60))).delete()
|
|
time.sleep(10)
|
|
|
|
|
|
class MbService(Thread):
|
|
def __init__(self, ip_addr, port, scan_rate, poll_time_ms=60000, save_days=60):
|
|
super().__init__(daemon=True)
|
|
self._poll_time_ms = poll_time_ms
|
|
self._scan_rate = scan_rate
|
|
|
|
self.mb = ModbusClient(host=ip_addr, port=port, debug=DEBUG, auto_open=True)
|
|
|
|
def _init_state(self):
|
|
"""
|
|
Функция вызывается один раз при создании сервиса, в ней нужно загрузить содержимое регистров из базы данных.
|
|
"""
|
|
pass
|
|
|
|
def _push_current_state(self):
|
|
"""
|
|
Функция вызывается в момент, когда требуется сохранить состояние в базу данных
|
|
"""
|
|
pass
|
|
|
|
def _load_current_state(self):
|
|
"""
|
|
Функция должна совершить операцию чтения регистров modbus
|
|
"""
|
|
pass
|
|
|
|
def _check_need_save(self):
|
|
"""
|
|
Функция должна вернуть результат: нужно ли сохранять текущее состояние в базу данных
|
|
"""
|
|
return False
|
|
|
|
def run(self):
|
|
# if self.log_type == 'on-change':
|
|
# pass
|
|
# else:
|
|
print(f"[{datetime.now().isoformat()}] MbService:{type(self).__name__}: service started!")
|
|
self._init_state()
|
|
|
|
last_query = datetime.now()
|
|
scan_rate = timedelta(milliseconds=self._scan_rate)
|
|
|
|
while True:
|
|
try:
|
|
self._load_current_state()
|
|
if self._check_need_save():
|
|
self._push_current_state()
|
|
except Exception as ex:
|
|
print(f"[{datetime.now().isoformat()}] MbService:{type(self).__name__}: Exception: {ex}")
|
|
|
|
# вычислим время до следующего опроса и подождем
|
|
# время следующего опроса
|
|
need_time = last_query + scan_rate
|
|
curr_time = datetime.now()
|
|
if need_time > curr_time:
|
|
delta = need_time - curr_time
|
|
time.sleep(delta.seconds + (delta.microseconds / 1000000))
|
|
last_query = need_time
|
|
else:
|
|
last_query = datetime.now()
|
|
|
|
|
|
class MbTankService(MbService):
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs)
|
|
|
|
self._curr_state = None
|
|
# {
|
|
# "level": int,
|
|
# "status": int,
|
|
# "radar": int
|
|
# }
|
|
|
|
def _init_state(self):
|
|
pass
|
|
|
|
def _check_need_save(self):
|
|
return True
|
|
|
|
def _push_current_state(self):
|
|
record = MbTankRecord(radar_raw=self._curr_state['radar'],
|
|
level=self._curr_state['level'],
|
|
status=self._curr_state['status'])
|
|
record.save()
|
|
|
|
def _load_current_state(self):
|
|
# D0: level_percentage
|
|
# D5: status_reg
|
|
# D1: radar_high_reg
|
|
# D2: radar_low_reg
|
|
values = self.mb.read_holding_registers(0, 6)
|
|
if values is None:
|
|
return
|
|
self._curr_state = {
|
|
"level": values[0],
|
|
"status": values[5],
|
|
"radar": (values[1] << 16) | values[2]
|
|
}
|
|
|
|
|
|
class MbPumpService(MbService):
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs)
|
|
self._lock = Lock()
|
|
|
|
self._curr_state = None
|
|
|
|
def _load_current_state(self):
|
|
# D16: flow_meter
|
|
# D28: pump_stage
|
|
# D30: half_auto_control
|
|
# D31: watch_1
|
|
# D32: watch_2
|
|
# D33.14: sc_st_pump_31_run
|
|
# D33.15: sc_st_pump_31_run
|
|
# D35: alarms
|
|
# D25: vfd_freq
|
|
# D26: vfd_current
|
|
# D27: vfd_error
|
|
off = 16
|
|
values = self.mb.read_holding_registers(16, 36 - off)
|
|
if values is None:
|
|
return
|
|
with self._lock:
|
|
# определение запущенного насоса
|
|
pr = -1
|
|
if values[33 - off] & (1 << 14):
|
|
pr = 1
|
|
elif values[33 - off] & (1 << 15):
|
|
pr = 2
|
|
|
|
self._curr_state = {
|
|
'alarms': values[35 - off],
|
|
'flow_meter': values[16 - off] / 10,
|
|
'last_update': int(datetime.now().timestamp()),
|
|
'pump_stage': values[28 - off],
|
|
'vfd_curr': values[26 - off] / 100,
|
|
'vfd_err': values[27 - off],
|
|
'vfd_freq': values[25 - off] / 100,
|
|
'pump_running': pr,
|
|
'moto_watch_1': values[31 - off],
|
|
'moto_watch_2': values[32 - off],
|
|
'half_auto_control': values[30 - off]
|
|
}
|
|
|
|
def get_stats(self):
|
|
with self._lock:
|
|
# копируем данные, если они есть в текущем состоянии (иначе пустые поля)
|
|
return self._curr_state or {
|
|
'alarms': None,
|
|
'flow_meter': None,
|
|
'last_update': None,
|
|
'pump_stage': None,
|
|
'vfd_curr': None,
|
|
'vfd_err': None,
|
|
'vfd_freq': None,
|
|
'pump_running': None,
|
|
'moto_watch_1': None,
|
|
'moto_watch_2': None,
|
|
'half_auto_control': None
|
|
}
|
|
|