66 lines
2 KiB
Python
66 lines
2 KiB
Python
import random
|
|
from bots.base import BaseBot
|
|
|
|
|
|
# VESDA alarm level thresholds (obscuration % per metre)
|
|
LEVELS = [
|
|
("normal", 0.0, 0.08),
|
|
("alert", 0.08, 0.20),
|
|
("action", 0.20, 0.50),
|
|
("fire", 0.50, 1.00),
|
|
]
|
|
|
|
|
|
def _level(obscuration: float) -> str:
|
|
for name, lo, hi in LEVELS:
|
|
if lo <= obscuration < hi:
|
|
return name
|
|
return "fire"
|
|
|
|
|
|
class VesdaBot(BaseBot):
|
|
"""VESDA aspirating smoke detector for a fire zone."""
|
|
|
|
interval = 30
|
|
|
|
def __init__(self, site_id: str, zone_id: str, room_id: str) -> None:
|
|
super().__init__()
|
|
self.site_id = site_id
|
|
self.zone_id = zone_id
|
|
self.room_id = room_id
|
|
self._base_obscuration = random.uniform(0.005, 0.015) # normal background
|
|
self._alert = False
|
|
self._fire = False
|
|
|
|
def get_topic(self) -> str:
|
|
return f"bms/{self.site_id}/fire/{self.zone_id}"
|
|
|
|
def set_scenario(self, name: str | None) -> None:
|
|
super().set_scenario(name)
|
|
self._alert = (name == "VESDA_ALERT")
|
|
self._fire = (name == "VESDA_FIRE")
|
|
|
|
def get_payload(self) -> dict:
|
|
if self._fire:
|
|
# Escalate rapidly to fire level
|
|
obs = min(0.85, 0.50 + self._scenario_step * 0.03) + random.gauss(0, 0.01)
|
|
elif self._alert:
|
|
# Sit in alert/action band
|
|
obs = min(0.45, 0.08 + self._scenario_step * 0.012) + random.gauss(0, 0.005)
|
|
else:
|
|
# Normal background with tiny random drift
|
|
obs = self._base_obscuration + random.gauss(0, 0.002)
|
|
obs = max(0.001, obs)
|
|
|
|
level = _level(obs)
|
|
|
|
return {
|
|
"zone_id": self.zone_id,
|
|
"room_id": self.room_id,
|
|
"level": level,
|
|
"obscuration_pct_m": round(obs * 100, 3),
|
|
"detector_1_ok": True,
|
|
"detector_2_ok": True,
|
|
"power_ok": True,
|
|
"flow_ok": not self._fire, # flow sensor trips in severe fire
|
|
}
|