From 4493ccb3831602bb7b80b0ca1de6533e75a6ce27 Mon Sep 17 00:00:00 2001 From: Benjamin Braatz Date: Wed, 5 May 2021 03:12:33 +0200 Subject: [PATCH] Initial version --- .gitignore | 4 +++ LICENSE | 19 +++++++++++ README.md | 14 ++++++++ conf-controller.json | 13 ++++++++ conf-machine.json | 29 ++++++++++++++++ controlpi_plugins/wsclient.py | 63 +++++++++++++++++++++++++++++++++++ doc/index.md | 28 ++++++++++++++++ setup.py | 25 ++++++++++++++ 8 files changed, 195 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 conf-controller.json create mode 100644 conf-machine.json create mode 100644 controlpi_plugins/wsclient.py create mode 100644 doc/index.md create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..63dbcc8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +__pycache__/ +dist/ +controlpi_wsclient.egg-info/ +venv/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ebb8ac1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2021 Graph-IT GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..fb01570 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# ControlPi Plugin for Websocket Clients +This distribution package contains a plugin for the +[ControlPi system](https://docs.graph-it.com/graphit/controlpi), that +<…> + +Documentation (in German) can be found at [doc/index.md](doc/index.md) in +the source repository and at +[https://docs.graph-it.com/graphit/controlpi-wsclient/](https://docs.graph-it.com/graphit/controlpi-wsclient/). +Code documentation (in English) including doctests is contained in the +source files. +An API documentation generated by pdoc3 can be found at +[doc/controlpi_plugins/index.html](doc/controlpi_plugins/index.html) in the source +repository and at +[https://docs.graph-it.com/graphit/controlpi-wsclient/controlpi_plugins/](https://docs.graph-it.com/graphit/controlpi-wsclient/controlpi_plugins/). diff --git a/conf-controller.json b/conf-controller.json new file mode 100644 index 0000000..d6d1cd8 --- /dev/null +++ b/conf-controller.json @@ -0,0 +1,13 @@ +{ + "Controller": { + "plugin": "WSServer", + "port": 8080, + "web": { + "/": { "module": "controlpi_plugins.wsserver", + "location": "Debug" } + } + }, + "Engine Clearance": { + "plugin": "State" + } +} diff --git a/conf-machine.json b/conf-machine.json new file mode 100644 index 0000000..92aca76 --- /dev/null +++ b/conf-machine.json @@ -0,0 +1,29 @@ +{ + "Machine": { + "plugin": "WSServer", + "port": 8081, + "web": { + "/": { "module": "controlpi_plugins.wsserver", + "location": "Debug" } + } + }, + "Controller": { + "plugin": "WSClient", + "url": "ws://localhost:8080", + "client": "Machine", + "filter": { "sender": { "const": "Engine" } } + }, + "Engine Clearance": { + "plugin": "Alias", + "from": { "sender": { "const": "Controller" }, + "original sender": { "const": "Engine Clearance" } }, + "to": { } + }, + "Engine Switch": { + "plugin": "State" + }, + "Engine": { + "plugin": "AndState", + "states": [ "Engine Clearance", "Engine Switch" ] + } +} diff --git a/controlpi_plugins/wsclient.py b/controlpi_plugins/wsclient.py new file mode 100644 index 0000000..45f5c13 --- /dev/null +++ b/controlpi_plugins/wsclient.py @@ -0,0 +1,63 @@ +"""Provide … + +… + +TODO: documentation, doctests +""" +import json +import websockets +from controlpi import BasePlugin, Message, MessageTemplate + + +class WSClient(BasePlugin): + """… plugin. + + Do this and that. + """ + + CONF_SCHEMA = {'properties': + {'url': {'type': 'string'}, + 'client': {'type': 'string'}, + 'filter': {'type': 'object'}}, + 'required': ['url', 'client', 'filter']} + + async def _receive(self, message: Message) -> None: + assert isinstance(message['sender'], str) + original_sender = message['sender'] + if 'original sender' in message: + original_sender += f"/{message['original sender']}" + message['original sender'] = original_sender + del message['sender'] + if 'target' in message: + assert isinstance(message['target'], str) + prefix = f"{self.name}/" + if message['target'].startswith(prefix): + message['target'] = message['target'][len(prefix):] + else: + message['target'] = f"{self.conf['client']}/{message['target']}" + json_message = json.dumps(message) + await self._websocket.send(json_message) + + def process_conf(self) -> None: + """Register plugin as bus client.""" + self.bus.register(self.name, 'WSClient', [MessageTemplate()], + [self.conf['filter']], self._receive) + + async def run(self) -> None: + """Send initial message.""" + async with websockets.connect(self.conf['url']) as websocket: + self._websocket = websocket + async for json_message in websocket: + message = json.loads(json_message) + original_sender = message['sender'] + if 'original sender' in message: + original_sender += f"/{message['original sender']}" + message['original sender'] = original_sender + message['sender'] = self.name + if 'target' in message: + prefix = f"{self.conf['client']}/" + if message['target'].startswith(prefix): + message['target'] = message['target'][len(prefix):] + else: + message['target'] = f"{self.name}/{message['target']}" + await self.bus.send(message) diff --git a/doc/index.md b/doc/index.md new file mode 100644 index 0000000..cded302 --- /dev/null +++ b/doc/index.md @@ -0,0 +1,28 @@ +# ControlPi-Plugin für Websocket-Client +Dieses Paket enthält ein Plugin für das ControlPi-System, mit dem <…> + +## Benutzung +… + +## Installation +Eine ausführliche Dokumentation ist in der Dokumentation der +[ControlPi-Infrastruktur](https://docs.graph-it.com/graphit/controlpi) zu +finden. + +Der Code dieses Plugins kann mit git geclonet werden: +```sh +$ git clone git://git.graph-it.com/graphit/controlpi-wsclient.git +``` +(Falls Zugang zu diesem Server per SSH besteht und Änderungen gepusht +werden sollen, sollte stattdessen die SSH-URL benutzt werden.) + +Dann kann es editierbar in ein virtuelles Environment installiert werden: +```sh +(venv)$ pip install --editable +``` + +Auf dem Raspberry Pi (oder wenn keine Code-Änderungen gewünscht sind) kann +es auch direkt, ohne einen git-Clone installiert werden: +```sh +(venv)$ pip install git+git://git.graph-it.com/graphit/controlpi-wsclient.git +``` diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..f2bf674 --- /dev/null +++ b/setup.py @@ -0,0 +1,25 @@ +import setuptools + +with open("README.md", "r") as readme_file: + long_description = readme_file.read() + +setuptools.setup( + name="controlpi-wsclient", + version="0.1.0", + author="Graph-IT GmbH", + author_email="info@graph-it.com", + description="ControlPi Plugin for Websocket Clients", + long_description=long_description, + long_description_content_type="text/markdown", + url="http://docs.graph-it.com/graphit/controlpi-wsclient", + packages=["controlpi_plugins"], + install_requires=[ + "websockets", + "controlpi @ git+git://git.graph-it.com/graphit/controlpi.git", + ], + classifiers=[ + "Programming Language :: Python", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], +) -- 2.34.1