import math import random from bots.base import BaseBot # ISO 14644-1 Class 8 limits ISO8_0_5UM = 3_520_000 # particles/m³ ≥0.5 µm ISO8_5UM = 29_300 # particles/m³ ≥5 µm class ParticleBot(BaseBot): """Air quality / particle count sensor — one per room.""" interval = 60 # publish every 60 s def __init__(self, site_id: str, room_id: str) -> None: super().__init__() self.site_id = site_id self.room_id = room_id # Normal operating baseline — well below ISO 8 self._base_0_5 = random.uniform(60_000, 120_000) self._base_5 = random.uniform(200, 600) def get_topic(self) -> str: return f"bms/{self.site_id}/{self.room_id}/particles" def get_payload(self) -> dict: drift = math.sin(self._scenario_step * 0.05) * 0.1 p0_5 = self._base_0_5 * (1 + drift) + random.gauss(0, 3_000) p5 = self._base_5 * (1 + drift) + random.gauss(0, 20) if self._scenario == "PARTICLE_SPIKE": spike = min(self._scenario_step * 50_000, ISO8_0_5UM * 1.5) p0_5 += spike p5 += spike * (ISO8_5UM / ISO8_0_5UM) return { "particles_0_5um": max(0, round(p0_5)), "particles_5um": max(0, round(p5)), }