From 707b6f049a0c3467c684d9257f576354ad6e4dbf Mon Sep 17 00:00:00 2001 From: Benjamin Braatz Date: Tue, 23 Mar 2021 15:12:40 +0100 Subject: [PATCH] Adapt to changes in controlpi/controlpi-wsserver. --- conf.json | 11 ++- controlpi-plugins/statemachine.py | 96 ------------------------- controlpi_plugins/statemachine.py | 115 ++++++++++++++++++++++++++++++ setup.py | 2 +- 4 files changed, 120 insertions(+), 104 deletions(-) delete mode 100644 controlpi-plugins/statemachine.py create mode 100644 controlpi_plugins/statemachine.py diff --git a/conf.json b/conf.json index d94fb6f..7429043 100644 --- a/conf.json +++ b/conf.json @@ -2,7 +2,10 @@ "Example Server": { "plugin": "WSServer", "port": 8080, - "web root": "../controlpi-wsserver/web" + "web": { + "/": { "module": "controlpi_plugins.wsserver", + "location": "Debug" } + } }, "Lubrication Button": { "plugin": "Alias", @@ -181,11 +184,5 @@ ] } } - }, - "Debug Logger": { - "plugin": "Log", - "filter": [ - {} - ] } } diff --git a/controlpi-plugins/statemachine.py b/controlpi-plugins/statemachine.py deleted file mode 100644 index 1732398..0000000 --- a/controlpi-plugins/statemachine.py +++ /dev/null @@ -1,96 +0,0 @@ -"""Provide … - -TODO: documentation, doctests -""" -import jsonschema # type: ignore -from typing import Mapping, Any - -from controlpi import BasePlugin, Message, PluginConfiguration - - -def template_from_message(message: Message) -> Message: - template = {} - for key in message: - value = message[key] - if (isinstance(value, bool) or isinstance(value, int) or - isinstance(value, float) or isinstance(value, str)): - value = {'const': value} - elif (isinstance(value, dict)): - value = {'type': 'object', - 'properties': template_from_message(value)} - template[key] = value - return template - - -class StateMachine(BasePlugin): - async def _receive(self, message: Message) -> None: - if ('target' in message and message['target'] == self._name and - 'command' in message): - if message['command'] == 'get state': - answer = {'sender': self._name, - 'state': self._current_state} - await self._bus.send(answer) - if message['command'] == 'get machine': - answer = {'sender': self._name, - 'init': self._init, - 'states': self._states} - await self._bus.send(answer) - for transition in self._states[self._current_state]['transitions']: - for trigger in transition['triggers']: - matches = True - for key in trigger: - if key not in message: - matches = False - break - try: - jsonschema.validate(message[key], trigger[key]) - except jsonschema.exceptions.ValidationError: - matches = False - break - if matches: - new_state = transition['to'] - self._current_state: str = new_state - for message in self._states[new_state]['commands']: - complete_message = {'sender': self._name} - complete_message.update(message) - await self._bus.send(complete_message) - event = {'sender': self._name, 'event': 'changed', - 'state': new_state} - await self._bus.send(event) - break - - def _process_conf(self, conf: PluginConfiguration) -> None: - self._init = conf['init'] - self._current_state = '' - self._states: Mapping[str, Any] = conf['states'] - sends: list[Message] = [{'event': {'const': 'changed'}, - 'state': {'type': 'string'}}, - {'state': {'type': 'string'}}, - {'init': {'type': 'string'}, 'states': - {'type': 'object', 'patternProperties': - {'.+': {'type': 'object', 'properties': - {'commands': {'type': 'array'}, - 'transitions': {'type': 'array'}}, - 'additionalProperties': False}}, - 'additionalProperties': False}}] - receives: list[Message] = [{'target': {'const': self._name}, - 'command': {'const': 'get state'}}, - {'target': {'const': self._name}, - 'command': {'const': 'get machine'}}] - for state in self._states: - for message in self._states[state]['commands']: - sends.append(template_from_message(message)) - for transition in self._states[state]['transitions']: - for template in transition['triggers']: - receives.append(template) - self._bus.register(self._name, 'StateMachine', - sends, receives, self._receive) - super()._process_conf(conf) - - async def run(self) -> None: - await super().run() - self._current_state = self._init - for message in self._states[self._init]['commands']: - complete_message = {'sender': self._name} - complete_message.update(message) - await self._bus.send(complete_message) diff --git a/controlpi_plugins/statemachine.py b/controlpi_plugins/statemachine.py new file mode 100644 index 0000000..a68e04f --- /dev/null +++ b/controlpi_plugins/statemachine.py @@ -0,0 +1,115 @@ +"""Provide state machine plugin. + +… + +TODO: documentation, doctests +""" +from controlpi import BasePlugin, Message, MessageTemplate + + +class StateMachine(BasePlugin): + """State machine plugin. + + Reacts to messages matching message templates at transitions by + switching states and sending messages defined for these states. + """ + + CONF_SCHEMA = {'properties': + {'init': {'type': 'string'}, + 'states': + {'type': 'object', + 'patternProperties': + {'^[A-Za-z ]+$': + {'type': 'object', + 'properties': + {'commands': + {'type': 'array', 'items': {'type': 'object'}}, + 'transitions': + {'type': 'array', + 'items': + {'type': 'object', + 'properties': + {'triggers': + {'type': 'array', 'items': {'type': 'object'}}, + 'to': {'type': 'string'}}, + 'required': ['triggers', 'to']}}}, + 'required': ['commands', 'transitions']}}, + 'additionalProperties': False}}, + 'required': ['init', 'states']} + + async def _receive(self, message: Message) -> None: + if ('target' in message and message['target'] == self.name and + 'command' in message): + if message['command'] == 'get state': + await self.bus.send(Message(self.name, + {'state': self._current_state})) + if message['command'] == 'get machine': + await self.bus.send(Message(self.name, + {'init': self.conf['init'], + 'states': self.conf['states']})) + transitions = self.conf['states'][self._current_state]['transitions'] + for transition in transitions: + for trigger in transition['triggers']: + if MessageTemplate(trigger).check(message): + new_state = transition['to'] + self._current_state: str = new_state + await self.bus.send(Message(self.name, + {'event': 'changed', + 'state': new_state})) + commands = self.conf['states'][new_state]['commands'] + for command in commands: + await self.bus.send(Message(self.name, command)) + break + + def process_conf(self) -> None: + """Register plugin as bus client.""" + self._current_state = '' + sends = [] + sends.append(MessageTemplate({'event': {'const': 'changed'}, + 'state': {'type': 'string'}})) + sends.append(MessageTemplate({'state': {'type': 'string'}})) + sends.append(MessageTemplate({'init': {'type': 'string'}, + 'states': + {'type': 'object', + 'patternProperties': + {'^[A-Za-z ]+$': + {'type': 'object', + 'properties': + {'commands': + {'type': 'array', + 'items': {'type': 'object'}}, + 'transitions': + {'type': 'array', + 'items': + {'type': 'object', + 'properties': + {'triggers': + {'type': 'array', + 'items': {'type': 'object'}}, + 'to': {'type': 'string'}}, + 'required': ['triggers', 'to']}}}, + 'required': ['commands', + 'transitions']}}, + 'additionalProperties': False}})) + receives = [] + receives.append(MessageTemplate({'target': {'const': self.name}, + 'command': {'const': 'get state'}})) + receives.append(MessageTemplate({'target': {'const': self.name}, + 'command': {'const': 'get machine'}})) + for state in self.conf['states']: + commands = self.conf['states'][state]['commands'] + for command in commands: + sends.append(MessageTemplate.from_message(command)) + transitions = self.conf['states'][state]['transitions'] + for transition in transitions: + for template in transition['triggers']: + receives.append(MessageTemplate(template)) + self.bus.register(self.name, 'StateMachine', + sends, receives, self._receive) + + async def run(self) -> None: + """Go into initial state.""" + self._current_state = self.conf['init'] + commands = self.conf['states'][self.conf['init']]['commands'] + for command in commands: + await self.bus.send(Message(self.name, command)) diff --git a/setup.py b/setup.py index e43100d..c299750 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setuptools.setup( long_description=long_description, long_description_content_type="text/markdown", url="http://docs.graph-it.com/graphit/controlpi-statemachine", - packages=["controlpi-plugins"], + packages=["controlpi_plugins"], install_requires=[ "jsonschema", "controlpi @ git+git://git.graph-it.com/graphit/controlpi.git", -- 2.34.1