- StateAlias translates to another state-like client.
- AndState combines several state-like clients by conjunction.
- OrState combines several state-like clients by disjunction.
+- AndSet sets a state due to a conjunction of other state-like clients.
+- OrSet sets a state due to a disjunction of other state-like clients.
All these plugins use the following conventions:
>>> asyncio.run(controlpi.test(
... {"Test State": {"plugin": "State"},
... "Test State 2": {"plugin": "State"},
+... "Test State 3": {"plugin": "State"},
+... "Test State 4": {"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"]}},
+... "states": ["Test State", "Test StateAlias"]},
+... "Test AndSet": {"plugin": "AndSet",
+... "input states": ["Test State", "Test StateAlias"],
+... "output state": "Test State 3"},
+... "Test OrSet": {"plugin": "OrSet",
+... "input states": ["Test State", "Test StateAlias"],
+... "output state": "Test State 4"}},
... [{"target": "Test AndState",
... "command": "get state"},
... {"target": "Test OrState",
... "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'}
+... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+test(): {'sender': '', 'event': 'registered', ...
+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()', '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 OrSet', 'target': 'Test State 4',
+ 'command': 'set state', 'new state': True}
+test(): {'sender': 'Test State 4', 'event': 'changed', 'state': True}
test(): {'sender': 'test()', 'target': 'Test StateAlias',
'command': 'set state', 'new state': True}
test(): {'sender': 'Test StateAlias', 'target': 'Test State 2',
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 AndSet', 'target': 'Test State 3',
+ 'command': 'set state', 'new state': True}
+test(): {'sender': 'Test State 3', '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}
+test(): {'sender': 'Test AndSet', 'target': 'Test State 3',
+ 'command': 'set state', 'new state': False}
+test(): {'sender': 'Test State 3', 'event': 'changed', 'state': False}
"""
from controlpi import BasePlugin, Message, MessageTemplate
'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'},
+ 'sends': [{'target': {'const': 'Test State'},
'command': {'const': 'get state'}},
{'target': {'const': 'Test State'},
'command': {'const': 'set state'},
- 'new state': {'type': 'boolean'}}],
+ 'new state': {'type': 'boolean'}},
+ {'event': {'const': 'changed'},
+ 'state': {'type': 'boolean'}},
+ {'state': {'type': 'boolean'}}],
'receives': [{'target': {'const': 'Test StateAlias'},
'command': {'const': 'get state'}},
{'target': {'const': 'Test StateAlias'},
class AndState(BasePlugin):
- """Implement conjunction of states.
+ """Define 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
class OrState(BasePlugin):
- """Implement disjunction of states.
+ """Define 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
async def run(self) -> None:
"""Run no code proactively."""
pass
+
+
+class AndSet(BasePlugin):
+ """Set state based on conjunction of other states.
+
+ The "input states" configuration key gets an array of states used to
+ determine the state in the "output state" configuration key:
+ >>> import asyncio
+ >>> import controlpi
+ >>> asyncio.run(controlpi.test(
+ ... {"Test State 1": {"plugin": "State"},
+ ... "Test State 2": {"plugin": "State"},
+ ... "Test State 3": {"plugin": "State"},
+ ... "Test AndSet": {"plugin": "AndSet",
+ ... "input states": ["Test State 1",
+ ... "Test State 2"],
+ ... "output state": "Test State 3"}},
+ ... [{"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}]))
+ ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+ test(): {'sender': '', 'event': 'registered', ...
+ 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 AndSet', 'target': 'Test State 3',
+ 'command': 'set state', 'new state': True}
+ test(): {'sender': 'Test State 3', '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 AndSet', 'target': 'Test State 3',
+ 'command': 'set state', 'new state': False}
+ test(): {'sender': 'Test State 3', 'event': 'changed', 'state': False}
+ """
+
+ CONF_SCHEMA = {'properties': {'input states': {'type': 'array',
+ 'items': {'type':
+ 'string'}},
+ 'output state': {'type': 'string'}},
+ 'required': ['input states', 'output state']}
+ """Schema for AndSet plugin configuration.
+
+ Required configuration keys:
+
+ - 'input states': list of names of combined states.
+ - 'output state': name of state to be set.
+ """
+
+ def process_conf(self) -> None:
+ """Register plugin as bus client."""
+ sends = [MessageTemplate({'target': {'const':
+ self.conf['output state']},
+ 'command': {'const': 'set state'},
+ 'new state': {'type': 'boolean'}})]
+ receives = []
+ self.states: Dict[str, bool] = {}
+ for state in self.conf['input states']:
+ receives.append(MessageTemplate({'sender': {'const': state},
+ 'state': {'type': 'boolean'}}))
+ self.states[state] = False
+ self.state: bool = all(self.states.values())
+ self.bus.register(self.name, 'AndSet',
+ sends, receives, self.receive)
+
+ async def receive(self, message: Message) -> None:
+ """Process messages of combined 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 = new_state
+ await self.bus.send(Message(self.name,
+ {'target': self.conf['output state'],
+ 'command': 'set state',
+ 'new state': self.state}))
+
+ async def run(self) -> None:
+ """Run no code proactively."""
+ pass
+
+
+class OrSet(BasePlugin):
+ """Set state based on disjunction of other states.
+
+ The "input states" configuration key gets an array of states used to
+ determine the state in the "output state" configuration key:
+ >>> import asyncio
+ >>> import controlpi
+ >>> asyncio.run(controlpi.test(
+ ... {"Test State 1": {"plugin": "State"},
+ ... "Test State 2": {"plugin": "State"},
+ ... "Test State 3": {"plugin": "State"},
+ ... "Test OrSet": {"plugin": "OrSet",
+ ... "input states": ["Test State 1",
+ ... "Test State 2"],
+ ... "output state": "Test State 3"}},
+ ... [{"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}]))
+ ... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
+ test(): {'sender': '', 'event': 'registered', ...
+ 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 OrSet', 'target': 'Test State 3',
+ 'command': 'set state', 'new state': True}
+ test(): {'sender': 'Test State 3', '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}
+ """
+
+ CONF_SCHEMA = {'properties': {'input states': {'type': 'array',
+ 'items': {'type':
+ 'string'}},
+ 'output state': {'type': 'string'}},
+ 'required': ['input states', 'output state']}
+ """Schema for OrSet plugin configuration.
+
+ Required configuration keys:
+
+ - 'input states': list of names of combined states.
+ - 'output state': name of state to be set.
+ """
+
+ def process_conf(self) -> None:
+ """Register plugin as bus client."""
+ sends = [MessageTemplate({'target': {'const':
+ self.conf['output state']},
+ 'command': {'const': 'set state'},
+ 'new state': {'type': 'boolean'}})]
+ receives = []
+ self.states: Dict[str, bool] = {}
+ for state in self.conf['input states']:
+ receives.append(MessageTemplate({'sender': {'const': state},
+ 'state': {'type': 'boolean'}}))
+ self.states[state] = False
+ self.state: bool = any(self.states.values())
+ self.bus.register(self.name, 'OrSet',
+ sends, receives, self.receive)
+
+ async def receive(self, message: Message) -> None:
+ """Process messages of combined 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 = new_state
+ await self.bus.send(Message(self.name,
+ {'target': self.conf['output state'],
+ 'command': 'set state',
+ 'new state': self.state}))
+
+ async def run(self) -> None:
+ """Run no code proactively."""
+ pass