188 lines
9.0 KiB
Python
188 lines
9.0 KiB
Python
from django.db import models
|
||
from decimal import Decimal
|
||
from sitemanagement.constants.image_file_path import register_object_upload_path
|
||
|
||
class Multiplexor(models.Model):
|
||
"""Устройство-мультиплексор"""
|
||
name = models.CharField(max_length=50, unique=True)
|
||
ip = models.GenericIPAddressField(null=True, blank=True)
|
||
subnet = models.GenericIPAddressField(null=True, blank=True)
|
||
gateway = models.GenericIPAddressField(null=True, blank=True)
|
||
sd_path = models.CharField(max_length=255, null=True, blank=True)
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
updated_at = models.DateTimeField(auto_now=True)
|
||
class Meta:
|
||
verbose_name = "Мультиплексор"
|
||
verbose_name_plural = "Мультиплексоры"
|
||
ordering = ["name"]
|
||
|
||
def __str__(self):
|
||
return self.name
|
||
|
||
|
||
class Channel(models.Model):
|
||
"""Физический канал мультиплексора"""
|
||
multiplexor = models.ForeignKey(Multiplexor, on_delete=models.CASCADE, related_name="channels")
|
||
number = models.PositiveSmallIntegerField() # CH-1 ... CH-14
|
||
# для "полканалов" можно использовать дробное значение (1, 1.5, 2, ...)
|
||
position = models.DecimalField(max_digits=4, decimal_places=1, default=Decimal("1.0"))
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
updated_at = models.DateTimeField(auto_now=True)
|
||
class Meta:
|
||
unique_together = ("multiplexor", "number", "position")
|
||
verbose_name = "Канал"
|
||
verbose_name_plural = "Каналы"
|
||
ordering = ["multiplexor", "number", "position"]
|
||
|
||
def __str__(self):
|
||
return f"{self.multiplexor.name} - CH{self.number} ({self.position})"
|
||
|
||
|
||
class SensorType(models.Model):
|
||
"""Тип датчика: GA, PE, GLE"""
|
||
code = models.CharField(max_length=10, unique=True) # GA, PE, GLE
|
||
name = models.CharField(max_length=100)
|
||
description = models.TextField(blank=True, null=True)
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
updated_at = models.DateTimeField(auto_now=True)
|
||
# пороговые значения для всех сенсоров этого типа
|
||
min_value = models.DecimalField(max_digits=12, decimal_places=4, null=True, blank=True)
|
||
max_value = models.DecimalField(max_digits=12, decimal_places=4, null=True, blank=True)
|
||
|
||
class Meta:
|
||
verbose_name = "Тип сенсора"
|
||
verbose_name_plural = "Типы сенсоров"
|
||
ordering = ["name"]
|
||
|
||
def __str__(self):
|
||
return self.code
|
||
|
||
|
||
class SignalFormat(models.Model):
|
||
"""Формат сигнала и правило преобразования"""
|
||
sensor_type = models.ForeignKey(SensorType, on_delete=models.CASCADE, related_name="formats")
|
||
code = models.CharField(max_length=50, verbose_name="Код") # например "4-20мА", "VW f<1600Hz", "NTC R>250Ohm"
|
||
unit = models.CharField(max_length=20, blank=True, null=True, verbose_name="Единица измерения") # °C, мкм/м, мм и т.п.
|
||
conversion_rule = models.CharField(max_length=255, blank=True, null=True, verbose_name="Правило преобразования")
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
updated_at = models.DateTimeField(auto_now=True)
|
||
class Meta:
|
||
unique_together = ("sensor_type", "code")
|
||
verbose_name = "Формат сигнала"
|
||
verbose_name_plural = "Форматы сигналов"
|
||
ordering = ["sensor_type", "code"]
|
||
|
||
def __str__(self):
|
||
return f"{self.sensor_type.code} - {self.code}"
|
||
|
||
|
||
class Sensor(models.Model):
|
||
"""Конкретный датчик, установленный в канале"""
|
||
channel = models.ForeignKey(Channel, on_delete=models.CASCADE, related_name="sensors")
|
||
sensor_type = models.ForeignKey(SensorType, on_delete=models.CASCADE, verbose_name="Тип сенсора")
|
||
signal_format = models.ForeignKey(SignalFormat, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Формат сигнала")
|
||
serial_number = models.CharField(max_length=50, blank=True, null=True, verbose_name="Серийный номер") # CL 2106009
|
||
name = models.CharField(max_length=50, blank=True, null=True, verbose_name="Название") # GA-1, HLE-1 и т.п.
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
updated_at = models.DateTimeField(auto_now=True)
|
||
math_formula = models.CharField(null=True, blank=True, max_length=255, verbose_name="Математическая формула")
|
||
|
||
class Meta:
|
||
verbose_name = "Датчик"
|
||
verbose_name_plural = "Датчики"
|
||
ordering = ["channel", "sensor_type", "serial_number"]
|
||
|
||
def __str__(self):
|
||
return f"{self.name or self.sensor_type.code} ({self.serial_number})"
|
||
|
||
|
||
class Metric(models.Model):
|
||
"""Значения, которые приходят из CSV"""
|
||
timestamp = models.DateTimeField()
|
||
sensor = models.ForeignKey(Sensor, on_delete=models.CASCADE, related_name="metrics", verbose_name="Датчик")
|
||
raw_value = models.CharField(max_length=50, verbose_name="Исходное значение") # исходное значение из файла (например "11.964 (A)")
|
||
value = models.FloatField(null=True, blank=True, verbose_name="Преобразованное значение") # преобразованное значение
|
||
status = models.CharField(max_length=20, blank=True, null=True, verbose_name="Статус") # No Rx, Error, NotAv и т.д.
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
updated_at = models.DateTimeField(auto_now=True)
|
||
|
||
class Meta:
|
||
verbose_name = "Метрика"
|
||
verbose_name_plural = "Метрики"
|
||
ordering = ["timestamp", "sensor"]
|
||
indexes = [
|
||
models.Index(fields=["timestamp"]),
|
||
models.Index(fields=["sensor"]),
|
||
]
|
||
|
||
def __str__(self):
|
||
return f"{self.timestamp} {self.sensor} = {self.value} {self.sensor.signal_format.unit if self.sensor.signal_format else ''}"
|
||
|
||
|
||
class Alert(models.Model):
|
||
"""Тревоги по метрикам"""
|
||
sensor = models.ForeignKey(Sensor, on_delete=models.CASCADE, related_name="alerts", verbose_name="Датчик")
|
||
metric = models.ForeignKey(Metric, on_delete=models.CASCADE, related_name="alerts", verbose_name="Метрика")
|
||
sensor_type = models.ForeignKey(SensorType, on_delete=models.CASCADE, related_name="alerts", verbose_name="Тип сенсора")
|
||
message = models.CharField(max_length=255, verbose_name="Сообщение")
|
||
severity = models.CharField(
|
||
max_length=20,
|
||
choices=[
|
||
("warning", "Warning"),
|
||
("critical", "Critical"),
|
||
],
|
||
default="warning",
|
||
verbose_name="Уровень тревоги"
|
||
)
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
updated_at = models.DateTimeField(auto_now=True)
|
||
resolved = models.BooleanField(default=False, verbose_name="Статус обработки")
|
||
|
||
class Meta:
|
||
indexes = [
|
||
models.Index(fields=["created_at"]),
|
||
models.Index(fields=["sensor"]),
|
||
models.Index(fields=["sensor_type"]),
|
||
]
|
||
verbose_name = "Тревога"
|
||
verbose_name_plural = "Тревоги"
|
||
ordering = ["created_at", "sensor"]
|
||
|
||
def __str__(self):
|
||
return f"ALERT {self.sensor} @ {self.metric.timestamp}: {self.message}"
|
||
|
||
class Object(models.Model):
|
||
"""Объект"""
|
||
title = models.CharField(max_length=255, verbose_name="Название")
|
||
description = models.TextField(blank=True, null=True, verbose_name="Описание")
|
||
image = models.ImageField(upload_to=register_object_upload_path, null=True, blank=True, verbose_name="Изображение")
|
||
address = models.CharField(max_length=255, verbose_name="Адрес")
|
||
floors = models.PositiveSmallIntegerField(verbose_name="Количество этажей")
|
||
area = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="Площадь")
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
updated_at = models.DateTimeField(auto_now=True)
|
||
|
||
class Meta:
|
||
verbose_name = "Объект"
|
||
verbose_name_plural = "Объекты"
|
||
ordering = ["title"]
|
||
|
||
def __str__(self):
|
||
return self.title
|
||
|
||
class Zone(models.Model):
|
||
"""Зона"""
|
||
object = models.ForeignKey(Object, on_delete=models.CASCADE, related_name="zones", verbose_name="Объект")
|
||
name = models.CharField(max_length=255, verbose_name="Название")
|
||
sensors = models.ManyToManyField(Sensor, related_name="zones", verbose_name="Датчики")
|
||
created_at = models.DateTimeField(auto_now_add=True)
|
||
updated_at = models.DateTimeField(auto_now=True)
|
||
|
||
class Meta:
|
||
verbose_name = "Зона"
|
||
verbose_name_plural = "Зоны"
|
||
ordering = ["object", "name"]
|
||
|
||
def __str__(self):
|
||
return f"{self.object.title} - {self.name}"
|
||
|