rules 2
This commit is contained in:
@@ -21,14 +21,18 @@ class RuleDescriptor(BaseModel):
|
||||
|
||||
This is the validated representation of a rule from rules.yaml.
|
||||
The engine loads these and passes them to rule implementations.
|
||||
|
||||
The 'objects' field is intentionally flexible (dict) to allow different
|
||||
rule types to define their own object structures.
|
||||
"""
|
||||
|
||||
id: str = Field(..., description="Unique identifier for this rule instance")
|
||||
name: Optional[str] = Field(None, description="Optional human-readable name")
|
||||
type: str = Field(..., description="Rule type with version (e.g., 'window_setback@1.0')")
|
||||
targets: dict[str, Any] = Field(
|
||||
enabled: bool = Field(default=True, description="Whether this rule is enabled")
|
||||
objects: dict[str, Any] = Field(
|
||||
default_factory=dict,
|
||||
description="Rule-specific target specification (rooms, devices, etc.)"
|
||||
description="Objects this rule monitors or controls (structure varies by rule type)"
|
||||
)
|
||||
params: dict[str, Any] = Field(
|
||||
default_factory=dict,
|
||||
@@ -309,16 +313,22 @@ class MQTTClient:
|
||||
else:
|
||||
print(f"[{level.upper()}] {msg}")
|
||||
|
||||
async def connect(self):
|
||||
async def connect(self, topics: list[str] = None):
|
||||
"""
|
||||
Connect to MQTT broker with automatic reconnection.
|
||||
|
||||
This method manages the connection and automatically reconnects
|
||||
with exponential backoff if the connection is lost.
|
||||
|
||||
Args:
|
||||
topics: List of MQTT topics to subscribe to. If None, subscribes to nothing.
|
||||
"""
|
||||
import aiomqtt
|
||||
from aiomqtt import Client
|
||||
|
||||
if topics is None:
|
||||
topics = []
|
||||
|
||||
reconnect_delay = self._reconnect_interval
|
||||
|
||||
while True:
|
||||
@@ -333,10 +343,11 @@ class MQTTClient:
|
||||
self._client = client
|
||||
self._log("info", f"Connected to MQTT broker {self._broker}:{self._port}")
|
||||
|
||||
# Subscribe to device state topics
|
||||
await client.subscribe("home/contact/+/state")
|
||||
await client.subscribe("home/thermostat/+/state")
|
||||
self._log("info", "Subscribed to home/contact/+/state, home/thermostat/+/state")
|
||||
# Subscribe to provided topics
|
||||
if topics:
|
||||
for topic in topics:
|
||||
await client.subscribe(topic)
|
||||
self._log("info", f"Subscribed to {len(topics)} topic(s): {', '.join(topics[:5])}{'...' if len(topics) > 5 else ''}")
|
||||
|
||||
# Reset reconnect delay on successful connection
|
||||
reconnect_delay = self._reconnect_interval
|
||||
@@ -542,6 +553,13 @@ class Rule(ABC):
|
||||
Example implementation:
|
||||
|
||||
class WindowSetbackRule(Rule):
|
||||
def get_subscriptions(self, desc: RuleDescriptor) -> list[str]:
|
||||
# Subscribe to contact sensor state topics
|
||||
topics = []
|
||||
for contact_id in desc.objects.contacts or []:
|
||||
topics.append(f"home/contact/{contact_id}/state")
|
||||
return topics
|
||||
|
||||
async def on_event(self, evt: dict, desc: RuleDescriptor, ctx: RuleContext) -> None:
|
||||
device_id = evt['device_id']
|
||||
cap = evt['cap']
|
||||
@@ -550,11 +568,31 @@ class Rule(ABC):
|
||||
contact_state = evt['payload'].get('contact')
|
||||
if contact_state == 'open':
|
||||
# Window opened - set thermostats to eco
|
||||
for thermo_id in desc.targets.get('thermostats', []):
|
||||
for thermo_id in desc.objects.thermostats or []:
|
||||
eco_temp = desc.params.get('eco_target', 16.0)
|
||||
await ctx.mqtt.publish_set_thermostat(thermo_id, eco_temp)
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_subscriptions(self, desc: RuleDescriptor) -> list[str]:
|
||||
"""
|
||||
Return list of MQTT topics this rule needs to subscribe to.
|
||||
|
||||
Called once during rule engine setup. The rule examines its configuration
|
||||
(desc.objects) and returns the specific state topics it needs to monitor.
|
||||
|
||||
Args:
|
||||
desc: Rule configuration from rules.yaml
|
||||
|
||||
Returns:
|
||||
List of MQTT topic patterns/strings to subscribe to
|
||||
|
||||
Example:
|
||||
For a window setback rule monitoring 2 contacts:
|
||||
['home/contact/sensor_bedroom/state', 'home/contact/sensor_kitchen/state']
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def on_event(
|
||||
self,
|
||||
|
||||
Reference in New Issue
Block a user