From 33cbb8176fe24024a6d447905f6cc34e93617969 Mon Sep 17 00:00:00 2001 From: Benjamin Braatz Date: Sun, 21 Mar 2021 04:38:18 +0100 Subject: [PATCH] Add AndState and OrState, document/test state.py. --- controlpi-plugins/state.py | 582 +++++++++++++++++++++++++++++++++++-- 1 file changed, 560 insertions(+), 22 deletions(-) diff --git a/controlpi-plugins/state.py b/controlpi-plugins/state.py index b6c762f..bd9d128 100644 --- a/controlpi-plugins/state.py +++ b/controlpi-plugins/state.py @@ -1,42 +1,580 @@ """Provide state plugins for all kinds of systems. -TODO: documentation, doctests -TODO: AndState, OrState +- State represents a Boolean state. +- StateAlias translates to another state-like client. +- AndState combines several state clients by conjunction. +- OrState combines several state clients by disjunction. + +All these plugins use the following conventions: + +- If their state changes they send a message containing "event": "changed" + and "state": . +- If their state is reported due to a message, but did not change they send + a message containing just "state": . +- If they receive a message containing "target": and + "command": "get state" they report their current state. +- If State (or any other settable state using these conventions) receives + a message containing "target": , "command": "set state" and + "new state": it changes the state accordingly. If this + was really a change the corresponding event is sent. If it was already in + this state a report message without "event": "changed" is sent. +- AndState and OrState instances cannot be set. +- AndState and OrState can combine any message bus clients using these + conventions, not just State instances. They only use the "get state" + command (to initialise their own internal state) and react to messages + containing "state" information. + +>>> import asyncio +>>> import controlpi +>>> asyncio.run(controlpi.test( +... {"Test State": {"plugin": "State"}, +... "Test State 2": {"plugin": "State"}, +... "Test StateAlias": {"plugin": "StateAlias", +... "alias for": "Test State 2"}, +... "Test AndState": {"plugin": "AndState", +... "states": ["Test State", "Test StateAlias"]}, +... "Test OrState": {"plugin": "OrState", +... "states": ["Test State", "Test StateAlias"]}}, +... [{"target": "Test AndState", +... "command": "get state"}, +... {"target": "Test OrState", +... "command": "get state"}, +... {"target": "Test State", +... "command": "set state", "new state": True}, +... {"target": "Test StateAlias", +... "command": "set state", "new state": True}, +... {"target": "Test State", +... "command": "set state", "new state": False}])) +... # doctest: +NORMALIZE_WHITESPACE +test(): {'sender': '', 'event': 'registered', + 'client': 'Test State', 'plugin': 'State', + 'sends': [{'event': {'const': 'changed'}, + 'state': {'type': 'boolean'}}, + {'state': {'type': 'boolean'}}], + 'receives': [{'target': {'const': 'Test State'}, + 'command': {'const': 'get state'}}, + {'target': {'const': 'Test State'}, + 'command': {'const': 'set state'}, + 'new state': {'type': 'boolean'}}]} +test(): {'sender': '', 'event': 'registered', + 'client': 'Test State 2', 'plugin': 'State', + 'sends': [{'event': {'const': 'changed'}, + 'state': {'type': 'boolean'}}, + {'state': {'type': 'boolean'}}], + 'receives': [{'target': {'const': 'Test State 2'}, + 'command': {'const': 'get state'}}, + {'target': {'const': 'Test State 2'}, + 'command': {'const': 'set state'}, + 'new state': {'type': 'boolean'}}]} +test(): {'sender': '', 'event': 'registered', + 'client': 'Test StateAlias', 'plugin': 'StateAlias', + 'sends': [{'event': {'const': 'changed'}, + 'state': {'type': 'boolean'}}, + {'state': {'type': 'boolean'}}, + {'target': {'const': 'Test State 2'}, + 'command': {'const': 'get state'}}, + {'target': {'const': 'Test State 2'}, + 'command': {'const': 'set state'}, + 'new state': {'type': 'boolean'}}], + 'receives': [{'target': {'const': 'Test StateAlias'}, + 'command': {'const': 'get state'}}, + {'target': {'const': 'Test StateAlias'}, + 'command': {'const': 'set state'}, + 'new state': {'type': 'boolean'}}, + {'sender': {'const': 'Test State 2'}, + 'event': {'const': 'changed'}, + 'state': {'type': 'boolean'}}, + {'sender': {'const': 'Test State 2'}, + 'state': {'type': 'boolean'}}]} +test(): {'sender': '', 'event': 'registered', + 'client': 'Test AndState', 'plugin': 'AndState', + 'sends': [{'event': {'const': 'changed'}, + 'state': {'type': 'boolean'}}, + {'state': {'type': 'boolean'}}], + 'receives': [{'target': {'const': 'Test AndState'}, + 'command': {'const': 'get state'}}, + {'sender': {'const': 'Test State'}, + 'state': {'type': 'boolean'}}, + {'sender': {'const': 'Test StateAlias'}, + 'state': {'type': 'boolean'}}]} +test(): {'sender': '', 'event': 'registered', + 'client': 'Test OrState', 'plugin': 'OrState', + 'sends': [{'event': {'const': 'changed'}, + 'state': {'type': 'boolean'}}, + {'state': {'type': 'boolean'}}], + 'receives': [{'target': {'const': 'Test OrState'}, + 'command': {'const': 'get state'}}, + {'sender': {'const': 'Test State'}, + 'state': {'type': 'boolean'}}, + {'sender': {'const': 'Test StateAlias'}, + 'state': {'type': 'boolean'}}]} +test(): {'sender': 'test()', 'target': 'Test AndState', + 'command': 'get state'} +test(): {'sender': 'Test AndState', 'state': False} +test(): {'sender': 'test()', 'target': 'Test OrState', + 'command': 'get state'} +test(): {'sender': 'Test OrState', 'state': False} +test(): {'sender': 'test()', 'target': 'Test State', + 'command': 'set state', 'new state': True} +test(): {'sender': 'Test State', 'event': 'changed', 'state': True} +test(): {'sender': 'Test OrState', 'event': 'changed', 'state': True} +test(): {'sender': 'test()', 'target': 'Test StateAlias', + 'command': 'set state', 'new state': True} +test(): {'sender': 'Test StateAlias', 'target': 'Test State 2', + 'command': 'set state', 'new state': True} +test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True} +test(): {'sender': 'Test StateAlias', 'event': 'changed', 'state': True} +test(): {'sender': 'Test AndState', 'event': 'changed', 'state': True} +test(): {'sender': 'test()', 'target': 'Test State', + 'command': 'set state', 'new state': False} +test(): {'sender': 'Test State', 'event': 'changed', 'state': False} +test(): {'sender': 'Test AndState', 'event': 'changed', 'state': False} """ -from controlpi import BasePlugin, Message +from controlpi import BasePlugin, Message, MessageTemplate + +from typing import Dict class State(BasePlugin): + """Provide a Boolean state. + + The state of a State plugin instance can be queried with the "get state" + command and set with the "set state" command to the new state given by + the "new state" key: + >>> import asyncio + >>> import controlpi + >>> asyncio.run(controlpi.test( + ... {"Test State": {"plugin": "State"}}, + ... [{"target": "Test State", "command": "get state"}, + ... {"target": "Test State", "command": "set state", + ... "new state": True}, + ... {"target": "Test State", "command": "set state", + ... "new state": True}, + ... {"target": "Test State", "command": "get state"}])) + ... # doctest: +NORMALIZE_WHITESPACE + test(): {'sender': '', 'event': 'registered', + 'client': 'Test State', 'plugin': 'State', + 'sends': [{'event': {'const': 'changed'}, + 'state': {'type': 'boolean'}}, + {'state': {'type': 'boolean'}}], + 'receives': [{'target': {'const': 'Test State'}, + 'command': {'const': 'get state'}}, + {'target': {'const': 'Test State'}, + 'command': {'const': 'set state'}, + 'new state': {'type': 'boolean'}}]} + test(): {'sender': 'test()', 'target': 'Test State', + 'command': 'get state'} + test(): {'sender': 'Test State', 'state': False} + test(): {'sender': 'test()', 'target': 'Test State', + 'command': 'set state', 'new state': True} + test(): {'sender': 'Test State', 'event': 'changed', 'state': True} + test(): {'sender': 'test()', 'target': 'Test State', + 'command': 'set state', 'new state': True} + test(): {'sender': 'Test State', 'state': True} + test(): {'sender': 'test()', 'target': 'Test State', + 'command': 'get state'} + test(): {'sender': 'Test State', 'state': True} + """ + CONF_SCHEMA = True + """Schema for State plugin configuration. + + There are no required or optional configuration keys. + """ async def receive(self, message: Message) -> None: + """Process commands to get/set state.""" if message['command'] == 'get state': - answer = {'sender': self.name, 'state': self.state} - await self.bus.send(answer) + await self.bus.send(Message(self.name, {'state': self.state})) elif message['command'] == 'set state': if self.state != message['new state']: + assert isinstance(message['new state'], bool) self.state: bool = message['new state'] - event = {'sender': self.name, 'event': 'changed', - 'state': self.state} - await self.bus.send(event) + await self.bus.send(Message(self.name, + {'event': 'changed', + 'state': self.state})) else: - answer = {'sender': self.name, 'state': self.state} - await self.bus.send(answer) + await self.bus.send(Message(self.name, + {'state': self.state})) def process_conf(self) -> None: + """Register plugin as bus client.""" self.state = False - sends = [{'event': {'const': 'changed'}, - 'state': {'type': 'boolean'}}, - {'state': {'type': 'boolean'}}] - receives = [{'target': {'const': self.name}, - 'command': {'const': 'get state'}}, - {'target': {'const': self.name}, - 'command': {'const': 'set state'}, - 'new state': {'type': 'boolean'}}] - self.bus.register(self.name, 'State', - sends, - receives, - self.receive) + sends = [MessageTemplate({'event': {'const': 'changed'}, + 'state': {'type': 'boolean'}}), + MessageTemplate({'state': {'type': 'boolean'}})] + receives = [MessageTemplate({'target': {'const': self.name}, + 'command': {'const': 'get state'}}), + MessageTemplate({'target': {'const': self.name}, + 'command': {'const': 'set state'}, + 'new state': {'type': 'boolean'}})] + self.bus.register(self.name, 'State', sends, receives, self.receive) + + async def run(self) -> None: + """Run no code proactively.""" + pass + + +class StateAlias(BasePlugin): + """Define an alias for another state. + + The "alias for" configuration key gets the name for the other state that + is aliased by the StateAlias plugin instance. + + The "get state" and "set state" commands are forwarded to and the + "changed" events and "state" messages are forwarded from this other + state: + >>> import asyncio + >>> import controlpi + >>> asyncio.run(controlpi.test( + ... {"Test State": {"plugin": "State"}, + ... "Test StateAlias": {"plugin": "StateAlias", + ... "alias for": "Test State"}}, + ... [{"target": "Test State", "command": "get state"}, + ... {"target": "Test StateAlias", "command": "set state", + ... "new state": True}, + ... {"target": "Test State", "command": "set state", + ... "new state": True}, + ... {"target": "Test StateAlias", "command": "get state"}])) + ... # doctest: +NORMALIZE_WHITESPACE + test(): {'sender': '', 'event': 'registered', + 'client': 'Test State', 'plugin': 'State', + 'sends': [{'event': {'const': 'changed'}, + 'state': {'type': 'boolean'}}, + {'state': {'type': 'boolean'}}], + 'receives': [{'target': {'const': 'Test State'}, + 'command': {'const': 'get state'}}, + {'target': {'const': 'Test State'}, + 'command': {'const': 'set state'}, + 'new state': {'type': 'boolean'}}]} + test(): {'sender': '', 'event': 'registered', + 'client': 'Test StateAlias', 'plugin': 'StateAlias', + 'sends': [{'event': {'const': 'changed'}, + 'state': {'type': 'boolean'}}, + {'state': {'type': 'boolean'}}, + {'target': {'const': 'Test State'}, + 'command': {'const': 'get state'}}, + {'target': {'const': 'Test State'}, + 'command': {'const': 'set state'}, + 'new state': {'type': 'boolean'}}], + 'receives': [{'target': {'const': 'Test StateAlias'}, + 'command': {'const': 'get state'}}, + {'target': {'const': 'Test StateAlias'}, + 'command': {'const': 'set state'}, + 'new state': {'type': 'boolean'}}, + {'sender': {'const': 'Test State'}, + 'event': {'const': 'changed'}, + 'state': {'type': 'boolean'}}, + {'sender': {'const': 'Test State'}, + 'state': {'type': 'boolean'}}]} + test(): {'sender': 'test()', 'target': 'Test State', + 'command': 'get state'} + test(): {'sender': 'Test State', 'state': False} + test(): {'sender': 'Test StateAlias', 'state': False} + test(): {'sender': 'test()', 'target': 'Test StateAlias', + 'command': 'set state', 'new state': True} + test(): {'sender': 'Test StateAlias', 'target': 'Test State', + 'command': 'set state', 'new state': True} + test(): {'sender': 'Test State', 'event': 'changed', 'state': True} + test(): {'sender': 'Test StateAlias', 'event': 'changed', 'state': True} + test(): {'sender': 'test()', 'target': 'Test State', + 'command': 'set state', 'new state': True} + test(): {'sender': 'Test State', 'state': True} + test(): {'sender': 'Test StateAlias', 'state': True} + test(): {'sender': 'test()', 'target': 'Test StateAlias', + 'command': 'get state'} + test(): {'sender': 'Test StateAlias', 'target': 'Test State', + 'command': 'get state'} + test(): {'sender': 'Test State', 'state': True} + test(): {'sender': 'Test StateAlias', 'state': True} + """ + + CONF_SCHEMA = {'properties': {'alias for': {'type': 'string'}}, + 'required': ['alias for']} + """Schema for StateAlias plugin configuration. + + Required configuration key: + + - 'alias for': name of aliased state. + """ + + async def receive(self, message: Message) -> None: + """Translate states from and commands to aliased state.""" + alias_message = Message(self.name) + if ('target' in message and message['target'] == self.name and + 'command' in message): + alias_message['target'] = self.conf['alias for'] + if message['command'] == 'get state': + alias_message['command'] = 'get state' + await self.bus.send(alias_message) + elif (message['command'] == 'set state' and + 'new state' in message): + alias_message['command'] = 'set state' + alias_message['new state'] = message['new state'] + await self.bus.send(alias_message) + if (message['sender'] == self.conf['alias for'] and + 'state' in message): + if 'event' in message and message['event'] == 'changed': + alias_message['event'] = 'changed' + alias_message['state'] = message['state'] + await self.bus.send(alias_message) + + def process_conf(self) -> None: + """Register plugin as bus client.""" + sends = [MessageTemplate({'target': {'const': self.conf['alias for']}, + 'command': {'const': 'get state'}}), + MessageTemplate({'target': {'const': self.conf['alias for']}, + 'command': {'const': 'set state'}, + 'new state': {'type': 'boolean'}}), + MessageTemplate({'event': {'const': 'changed'}, + 'state': {'type': 'boolean'}}), + MessageTemplate({'state': {'type': 'boolean'}})] + receives = [MessageTemplate({'target': {'const': self.name}, + 'command': {'const': 'get state'}}), + MessageTemplate({'target': {'const': self.name}, + 'command': {'const': 'set state'}, + 'new state': {'type': 'boolean'}}), + MessageTemplate({'sender': + {'const': self.conf['alias for']}, + 'event': {'const': 'changed'}, + 'state': {'type': 'boolean'}}), + MessageTemplate({'sender': + {'const': self.conf['alias for']}, + 'state': {'type': 'boolean'}})] + self.bus.register(self.name, 'StateAlias', + sends, receives, self.receive) + + async def run(self) -> None: + """Run no code proactively.""" + pass + + +class AndState(BasePlugin): + """Implement conjunction of states. + + The "states" configuration key gets an array of states to be combined. + An AndState plugin client reacts to "get state" commands and sends + "changed" events when a change in one of the combined states leads to + a change for the conjunction: + >>> import asyncio + >>> import controlpi + >>> asyncio.run(controlpi.test( + ... {"Test State 1": {"plugin": "State"}, + ... "Test State 2": {"plugin": "State"}, + ... "Test AndState": {"plugin": "AndState", + ... "states": ["Test State 1", "Test State 2"]}}, + ... [{"target": "Test State 1", "command": "set state", + ... "new state": True}, + ... {"target": "Test State 2", "command": "set state", + ... "new state": True}, + ... {"target": "Test State 1", "command": "set state", + ... "new state": False}, + ... {"target": "Test AndState", "command": "get state"}])) + ... # doctest: +NORMALIZE_WHITESPACE + test(): {'sender': '', 'event': 'registered', + 'client': 'Test State 1', 'plugin': 'State', + 'sends': [{'event': {'const': 'changed'}, + 'state': {'type': 'boolean'}}, + {'state': {'type': 'boolean'}}], + 'receives': [{'target': {'const': 'Test State 1'}, + 'command': {'const': 'get state'}}, + {'target': {'const': 'Test State 1'}, + 'command': {'const': 'set state'}, + 'new state': {'type': 'boolean'}}]} + test(): {'sender': '', 'event': 'registered', + 'client': 'Test State 2', 'plugin': 'State', + 'sends': [{'event': {'const': 'changed'}, + 'state': {'type': 'boolean'}}, + {'state': {'type': 'boolean'}}], + 'receives': [{'target': {'const': 'Test State 2'}, + 'command': {'const': 'get state'}}, + {'target': {'const': 'Test State 2'}, + 'command': {'const': 'set state'}, + 'new state': {'type': 'boolean'}}]} + test(): {'sender': '', 'event': 'registered', + 'client': 'Test AndState', 'plugin': 'AndState', + 'sends': [{'event': {'const': 'changed'}, + 'state': {'type': 'boolean'}}, + {'state': {'type': 'boolean'}}], + 'receives': [{'target': {'const': 'Test AndState'}, + 'command': {'const': 'get state'}}, + {'sender': {'const': 'Test State 1'}, + 'state': {'type': 'boolean'}}, + {'sender': {'const': 'Test State 2'}, + 'state': {'type': 'boolean'}}]} + test(): {'sender': 'test()', 'target': 'Test State 1', + 'command': 'set state', 'new state': True} + test(): {'sender': 'Test State 1', 'event': 'changed', 'state': True} + test(): {'sender': 'test()', 'target': 'Test State 2', + 'command': 'set state', 'new state': True} + test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True} + test(): {'sender': 'Test AndState', 'event': 'changed', 'state': True} + test(): {'sender': 'test()', 'target': 'Test State 1', + 'command': 'set state', 'new state': False} + test(): {'sender': 'Test State 1', 'event': 'changed', 'state': False} + test(): {'sender': 'Test AndState', 'event': 'changed', 'state': False} + test(): {'sender': 'test()', 'target': 'Test AndState', + 'command': 'get state'} + test(): {'sender': 'Test AndState', 'state': False} + """ + + CONF_SCHEMA = {'properties': {'states': {'type': 'array', + 'items': {'type': 'string'}}}, + 'required': ['states']} + """Schema for AndState plugin configuration. + + Required configuration key: + + - 'states': list of names of combined states. + """ + + async def receive(self, message: Message) -> None: + """.""" + if ('target' in message and message['target'] == self.name and + 'command' in message and message['command'] == 'get state'): + await self.bus.send(Message(self.name, {'state': self.state})) + if 'state' in message and message['sender'] in self.conf['states']: + assert isinstance(message['sender'], str) + assert isinstance(message['state'], bool) + self.states[message['sender']] = message['state'] + new_state = all(self.states.values()) + if self.state != new_state: + self.state: bool = new_state + await self.bus.send(Message(self.name, + {'event': 'changed', + 'state': self.state})) + + def process_conf(self) -> None: + """Register plugin as bus client.""" + sends = [MessageTemplate({'event': {'const': 'changed'}, + 'state': {'type': 'boolean'}}), + MessageTemplate({'state': {'type': 'boolean'}})] + receives = [MessageTemplate({'target': {'const': self.name}, + 'command': {'const': 'get state'}})] + self.states: Dict[str, bool] = {} + for state in self.conf['states']: + receives.append(MessageTemplate({'sender': {'const': state}, + 'state': {'type': 'boolean'}})) + self.states[state] = False + self.state = all(self.states.values()) + self.bus.register(self.name, 'AndState', + sends, receives, self.receive) + + async def run(self) -> None: + """Run no code proactively.""" + pass + + +class OrState(BasePlugin): + """Implement disjunction of states. + + The "states" configuration key gets an array of states to be combined. + An OrState plugin client reacts to "get state" commands and sends + "changed" events when a change in one of the combined states leads to + a change for the disjunction: + >>> import asyncio + >>> import controlpi + >>> asyncio.run(controlpi.test( + ... {"Test State 1": {"plugin": "State"}, + ... "Test State 2": {"plugin": "State"}, + ... "Test OrState": {"plugin": "OrState", + ... "states": ["Test State 1", "Test State 2"]}}, + ... [{"target": "Test State 1", "command": "set state", + ... "new state": True}, + ... {"target": "Test State 2", "command": "set state", + ... "new state": True}, + ... {"target": "Test State 1", "command": "set state", + ... "new state": False}, + ... {"target": "Test OrState", "command": "get state"}])) + ... # doctest: +NORMALIZE_WHITESPACE + test(): {'sender': '', 'event': 'registered', + 'client': 'Test State 1', 'plugin': 'State', + 'sends': [{'event': {'const': 'changed'}, + 'state': {'type': 'boolean'}}, + {'state': {'type': 'boolean'}}], + 'receives': [{'target': {'const': 'Test State 1'}, + 'command': {'const': 'get state'}}, + {'target': {'const': 'Test State 1'}, + 'command': {'const': 'set state'}, + 'new state': {'type': 'boolean'}}]} + test(): {'sender': '', 'event': 'registered', + 'client': 'Test State 2', 'plugin': 'State', + 'sends': [{'event': {'const': 'changed'}, + 'state': {'type': 'boolean'}}, + {'state': {'type': 'boolean'}}], + 'receives': [{'target': {'const': 'Test State 2'}, + 'command': {'const': 'get state'}}, + {'target': {'const': 'Test State 2'}, + 'command': {'const': 'set state'}, + 'new state': {'type': 'boolean'}}]} + test(): {'sender': '', 'event': 'registered', + 'client': 'Test OrState', 'plugin': 'OrState', + 'sends': [{'event': {'const': 'changed'}, + 'state': {'type': 'boolean'}}, + {'state': {'type': 'boolean'}}], + 'receives': [{'target': {'const': 'Test OrState'}, + 'command': {'const': 'get state'}}, + {'sender': {'const': 'Test State 1'}, + 'state': {'type': 'boolean'}}, + {'sender': {'const': 'Test State 2'}, + 'state': {'type': 'boolean'}}]} + test(): {'sender': 'test()', 'target': 'Test State 1', + 'command': 'set state', 'new state': True} + test(): {'sender': 'Test State 1', 'event': 'changed', 'state': True} + test(): {'sender': 'Test OrState', 'event': 'changed', 'state': True} + test(): {'sender': 'test()', 'target': 'Test State 2', + 'command': 'set state', 'new state': True} + test(): {'sender': 'Test State 2', 'event': 'changed', 'state': True} + test(): {'sender': 'test()', 'target': 'Test State 1', + 'command': 'set state', 'new state': False} + test(): {'sender': 'Test State 1', 'event': 'changed', 'state': False} + test(): {'sender': 'test()', 'target': 'Test OrState', + 'command': 'get state'} + test(): {'sender': 'Test OrState', 'state': True} + """ + + CONF_SCHEMA = {'properties': {'states': {'type': 'array', + 'items': {'type': 'string'}}}, + 'required': ['states']} + """Schema for OrState plugin configuration. + + Required configuration key: + + - 'states': list of names of combined states. + """ + + async def receive(self, message: Message) -> None: + """.""" + if ('target' in message and message['target'] == self.name and + 'command' in message and message['command'] == 'get state'): + await self.bus.send(Message(self.name, {'state': self.state})) + if 'state' in message and message['sender'] in self.conf['states']: + assert isinstance(message['sender'], str) + assert isinstance(message['state'], bool) + self.states[message['sender']] = message['state'] + new_state = any(self.states.values()) + if self.state != new_state: + self.state: bool = new_state + await self.bus.send(Message(self.name, + {'event': 'changed', + 'state': self.state})) + + def process_conf(self) -> None: + """Register plugin as bus client.""" + sends = [MessageTemplate({'event': {'const': 'changed'}, + 'state': {'type': 'boolean'}}), + MessageTemplate({'state': {'type': 'boolean'}})] + receives = [MessageTemplate({'target': {'const': self.name}, + 'command': {'const': 'get state'}})] + self.states: Dict[str, bool] = {} + for state in self.conf['states']: + receives.append(MessageTemplate({'sender': {'const': state}, + 'state': {'type': 'boolean'}})) + self.states[state] = False + self.state = any(self.states.values()) + self.bus.register(self.name, 'OrState', + sends, receives, self.receive) async def run(self) -> None: + """Run no code proactively.""" pass -- 2.34.1