условно работающий логгинг резервуара

This commit is contained in:
VladislavOstapov 2024-01-22 20:17:06 +03:00
parent a5a77b864c
commit 8a7b15f093
8 changed files with 238 additions and 15 deletions

View File

@ -13,4 +13,14 @@ DB_PORT=5432
DJANGO_SECRET="django-secure" DJANGO_SECRET="django-secure"
# включение/отключение сервисов ModBus # включение/отключение сервисов ModBus
ENABLE_MB_SERVICES=1 ENABLE_MB_TANK_SERVICE=1
MB_TANK_ADDRESS="10.8.105.3"
MB_TANK_PORT=504
MB_TANK_SCAN_RATE_MS=60000
ENABLE_MB_PUMP_SERVICE=1
MB_PUMP_ADDRESS="10.8.105.2"
MB_PUMP_PORT=503
MB_PUMP_SCAN_RATE_MS=5000
LOGGER_SAVE_DAYS=60

5
logs_service/admin.py Normal file
View File

@ -0,0 +1,5 @@
from django.contrib import admin
from .models import MbPumpRecord, MbTankRecord
admin.site.register(MbPumpRecord)
admin.site.register(MbTankRecord)

View File

@ -1,14 +1,58 @@
import os
from django.apps import AppConfig from django.apps import AppConfig
from .services import MbTankService, MbPumpService from ospaz_site.settings import DEBUG
_is_ready = False
class LogsServiceConfig(AppConfig): class LogsServiceConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField' default_auto_field = 'django.db.models.BigAutoField'
name = 'logs_service' name = 'logs_service'
mb_tank_service = MbTankService('10.105.0.3') mb_tank_service = None
mb_pump_service = MbPumpService('10.105.0.2') mb_pump_service = None
mb_tank_service_enable = True
mb_pump_service_enable = True
def ready(self): def ready(self):
LogsServiceConfig.mb_tank_service.start() # защита от двойного запуска
LogsServiceConfig.mb_pump_service.start() if os.environ.get('RUN_MAIN', None) == 'true':
return
from .services import MbTankService, MbPumpService
# включение/выключение сервисов
mb_tank_service_enable = os.getenv('ENABLE_MB_TANK_SERVICE', '1')
LogsServiceConfig.mb_tank_service_enable = int(mb_tank_service_enable)
mb_pump_service_enable = os.getenv('ENABLE_MB_PUMP_SERVICE', '1')
LogsServiceConfig.mb_pump_service_enable = int(mb_pump_service_enable)
save_days = int(os.getenv('LOGGER_SAVE_DAYS'))
# создание сервисов
if LogsServiceConfig.mb_tank_service_enable:
LogsServiceConfig.mb_tank_service = MbTankService(ip_addr=os.getenv('MB_TANK_ADDRESS'),
port=int(os.getenv('MB_TANK_PORT')),
scan_rate=int(os.getenv('MB_TANK_SCAN_RATE_MS')),
save_days=save_days)
if LogsServiceConfig.mb_pump_service_enable:
LogsServiceConfig.mb_pump_service = MbPumpService(ip_addr=os.getenv('MB_PUMP_ADDRESS'),
port=int(os.getenv('MB_PUMP_PORT')),
scan_rate=int(os.getenv('MB_PUMP_SCAN_RATE_MS')),
save_days=save_days)
# запуск сервисов
if LogsServiceConfig.mb_tank_service_enable:
if DEBUG:
print('Creating service MbTankService...')
LogsServiceConfig.mb_tank_service.start()
if LogsServiceConfig.mb_pump_service_enable:
if DEBUG:
print('Creating service MbPumpService...')
LogsServiceConfig.mb_pump_service.start()
return True

View File

@ -7,6 +7,7 @@ class MbTankRecord(models.Model):
dt = models.DateTimeField(verbose_name="Время записи", default=timezone.now) dt = models.DateTimeField(verbose_name="Время записи", default=timezone.now)
radar_raw = models.PositiveIntegerField(verbose_name="Уровень воды по радару радара") radar_raw = models.PositiveIntegerField(verbose_name="Уровень воды по радару радара")
level = models.PositiveSmallIntegerField(verbose_name="Уровень воды в % (пересчитан ПЛК)") level = models.PositiveSmallIntegerField(verbose_name="Уровень воды в % (пересчитан ПЛК)")
status = models.PositiveSmallIntegerField(verbose_name="Регистр статуса")
def __str__(self): def __str__(self):
return f"({self.id}) {self.dt}: radar={self.radar_raw}, level={self.level}" return f"({self.id}) {self.dt}: radar={self.radar_raw}, level={self.level}"

View File

@ -1,21 +1,173 @@
from .models import * from .models import *
from ospaz_site.settings import DEBUG
from threading import Thread from threading import Thread
import time
from datetime import datetime, timedelta
from pyModbusTCP.client import ModbusClient
class MbService(Thread): class MbService(Thread):
def __init__(self, ip_addr): def __init__(self, ip_addr, port, scan_rate, poll_time_ms=60000, save_days=60):
super().__init__() super().__init__()
self._ip_addr = ip_addr 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 True
def run(self):
# if self.log_type == 'on-change':
# pass
# else:
print(f"[MbService:{self.__class__.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"[MbService:{self.__class__.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()
def to_json(self): def to_json(self):
return {} return {}
class MbTankService(MbService): class MbClearHistoryService(Thread):
def __init__(self, model, save_days):
super().__init__()
self.model = model
self._days = save_days
def run(self): def run(self):
pass while True:
self.model.objects.filter(dt__lt=(timezone.now() - timezone.timedelta(days=60))).delete()
time.sleep(10)
class MbTankService(MbService):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.registers = {}
# {
# 'last_update': None, # последнее обновление, unix time
# 'level': None, # текущее показание, пересчитанное в %
# 'radar': None, # текущее показание с радара
# 'status': None, # статусный регистр
# 'last_radar_values': [] # последние показания за 15 минут
# }
self._curr_state = None
# {
# "level": int,
# "status": int,
# "radar": int
# }
# для фильтрации уровня, чтобы его в бд писать
self.level_filter = []
def _update_last_radar_values(self):
res = [[item.dt.timestamp(), item.radar_raw]
for item in MbTankRecord.objects.filter(dt__gt=(datetime.now() - timedelta(minutes=15))).order_by('-dt')]
self.registers['last_radar_values'] = res
def _init_state(self):
self.last_save = MbTankRecord.objects.order_by('-dt').first()
print(f"Initializing MbTankService: last_save={self.last_save}")
if self.last_save is not None:
self.registers['last_update'] = self.last_save.dt
self.registers['level'] = self.last_save.level
self.registers['radar'] = self.last_save.radar_raw
self.registers['status'] = self.last_save.status
self._update_last_radar_values()
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)
self._curr_state = {
"level": values[0],
"status": values[5],
"radar": (values[1] << 16) | values[2]
}
self.registers['last_update'] = datetime.now()
def _check_need_save(self):
return True
def to_json(self):
return self.registers
class MbPumpService(MbService): class MbPumpService(MbService):
def run(self): _config = {
pass "modbus": {
"host": "10.8.105.2",
"port": 503,
"log-type": "on-change"
},
"registers": [
["D0", "v231_v236"],
["D1", "v236_v29"],
["D15", "ps_39"],
["D16", "flow_meter"],
["D28", "pump_stage"],
["D30", "half_auto_control"],
["D31", "watch_1"],
["D32", "watch_2"],
["D33", "st_valves"],
["D35", "alarms"],
["D25", "vfd_freq"],
["D26", "vfd_current"],
["D27", "vfd_error"]
]
}

View File

@ -51,7 +51,8 @@ INSTALLED_APPS = [
# custom apps # custom apps
'users.apps.UsersConfig', 'users.apps.UsersConfig',
'index.apps.IndexConfig' 'index.apps.IndexConfig',
'logs_service'
] ]
MIDDLEWARE = [ MIDDLEWARE = [

View File

@ -113,7 +113,7 @@ const updateFunctions = {
const now = Date.now() / 1000 const now = Date.now() / 1000
element.innerHTML = moment(new Date(dataset['pump']['last_update'] * 1000)).format(DATETIME_FORMAT) element.innerHTML = moment(new Date(dataset['pump']['last_update'] * 1000)).format(DATETIME_FORMAT)
// для насосной можно допустить последнее обновление 10 минут назад // для насосной можно допустить последнее обновление 10 минут назад
_setIndicatorClass(element, now - dataset['tank']['last_update'] < 600) _setIndicatorClass(element, now - dataset['pump']['last_update'] < 600)
}, },
// Уровень воды <span id="tank-level-dir"></span> // Уровень воды <span id="tank-level-dir"></span>

View File

@ -49,7 +49,17 @@ class User(AbstractBaseUser):
'users.add_user': 1, 'users.add_user': 1,
'users.change_user': 1, 'users.change_user': 1,
'users.delete_user': 1, 'users.delete_user': 1,
'users.view_user': 1 'users.view_user': 1,
'logs_service.add_mbpumprecord': 1,
'logs_service.change_mbpumprecord': 1,
'logs_service.delete_mbpumprecord': 1,
'logs_service.view_mbpumprecord': 1,
'logs_service.add_mbtankrecord': 1,
'logs_service.change_mbtankrecord': 1,
'logs_service.delete_mbtankrecord': 1,
'logs_service.view_mbtankrecord': 0,
} }
if perm in permissions: if perm in permissions:
if permissions[perm] <= secure_level: if permissions[perm] <= secure_level: