41 lines
1.3 KiB
Python
41 lines
1.3 KiB
Python
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)),
|
|
}
|