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 pyModbusTCP.utils import decode_ieee 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: raise AssertionError('failed to load current state') 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: raise AssertionError('failed to load current state') 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 } class MbHartService(MbService): def __init__(self, **kwargs): super().__init__(**kwargs) self._lock = Lock() self._curr_state = None def _load_current_state(self): # D?: flow_meter values = self.mb.read_input_registers(1304, 2) if values is None: raise AssertionError('failed to load current state') def swap_bytes(source): return (source & 0xFF) << 8 | (source & 0xFF00) >> 8 with self._lock: self._curr_state = { 'accumulated_flow': int(decode_ieee((swap_bytes(values[0]) << 16) | swap_bytes(values[1]))), 'last_update': int(datetime.now().timestamp()), } def get_stats(self): with self._lock: # копируем данные, если они есть в текущем состоянии (иначе пустые поля) return self._curr_state or { 'accumulated_flow': None, 'last_update': None, }