Initial commit
authorBenjamin Braatz <bb@bbraatz.eu>
Wed, 24 Feb 2021 05:16:00 +0000 (06:16 +0100)
committerBenjamin Braatz <bb@bbraatz.eu>
Wed, 24 Feb 2021 05:16:00 +0000 (06:16 +0100)
.gitignore [new file with mode: 0644]
LICENSE [new file with mode: 0644]
README.md [new file with mode: 0644]
conf.json [new file with mode: 0644]
controlpi/__init__.py [new file with mode: 0644]
controlpi/main.py [new file with mode: 0644]
controlpi/plugins/delay.py [new file with mode: 0644]
controlpi/plugins/init.py [new file with mode: 0644]
controlpi/plugins/log.py [new file with mode: 0644]
setup.py [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..6dcc565
--- /dev/null
@@ -0,0 +1,3 @@
+__pycache__/
+controlpi.egg-info/
+venv/
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
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 (file)
index 0000000..003e3d2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,2 @@
+# Control-Pi-Infrastruktur
+
diff --git a/conf.json b/conf.json
new file mode 100644 (file)
index 0000000..18eeee5
--- /dev/null
+++ b/conf.json
@@ -0,0 +1,27 @@
+{ "delay":
+  [ { "name": "D1",
+      "seconds": 1.0 },
+    { "name": "D2",
+      "seconds": 2.0 } ],
+  "alias":
+  [ { "name": "One-second delay",
+      "aliasfor":
+      { "name": "D1" } },
+    { "name": "Two-second delay finished",
+      "aliasfor":
+      { "name": "D2",
+        "event": "finished" } } ],
+  "log":
+  [ { "name": "Debug Logger",
+      "filter": {} },
+    { "name": "Test Logger",
+      "filter": { "name": "Test" } } ],
+  "init":
+  [ { "name": "Test Procedure",
+      "commands":
+      [ { "name": "Test", "event": "started" },
+        { "name": "D1", "command": "start" },
+        { "name": "D2", "command": "start" },
+        { "name": "D1", "command": "start" },
+        { "name": "D2", "command": "start" },
+        { "name": "Test", "event": "stopped" } ] } ] }
diff --git a/controlpi/__init__.py b/controlpi/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/controlpi/main.py b/controlpi/main.py
new file mode 100644 (file)
index 0000000..1c2d6bc
--- /dev/null
@@ -0,0 +1,74 @@
+import sys
+import json
+import asyncio
+import pkgutil
+import importlib
+
+import controlpi.plugins
+
+
+def get_triggered(message, triggers):
+    result = []
+    for key in triggers:
+        if key == '*':
+            result.extend(triggers[key])
+        elif key in message:
+            value = message[key]
+            if value in triggers[key]:
+                result.extend(get_triggered(message, triggers[key][value]))
+    return result
+
+
+class ControlPi:
+    def __init__(self):
+        self._queue = asyncio.Queue()
+        self._triggers = {}
+
+    def register(self, message_template, async_callback):
+        current = self._triggers
+        for key in message_template:
+            value = message_template[key]
+            if key not in current:
+                current[key] = {}
+            if value not in current[key]:
+                current[key][value] = {}
+            current = current[key][value]
+        if '*' not in current:
+            current['*'] = []
+        current['*'].append(async_callback)
+
+    async def send(self, message):
+        await self._queue.put(message)
+
+    async def run(self):
+        while True:
+            message = await self._queue.get()
+            for async_callback in get_triggered(message, self._triggers):
+                asyncio.create_task(async_callback(message))
+            self._queue.task_done()
+
+
+async def main():
+    plugins = {name: importlib.import_module(name)
+               for finder, name, ispkg
+               in pkgutil.iter_modules(controlpi.plugins.__path__,
+                                       controlpi.plugins.__name__ + ".")}
+    control_pi = ControlPi()
+    coros = [control_pi.run()]
+    with open(sys.argv[1]) as json_data:
+        conf = json.load(json_data)
+        for plugin_name in conf:
+            full_plugin_name = 'controlpi.plugins.' + plugin_name
+            if full_plugin_name in plugins:
+                module = plugins[full_plugin_name]
+                plugin_confs = conf[plugin_name]
+                for plugin_conf in plugin_confs:
+                    coros.append(module.init(control_pi, plugin_conf))
+    await asyncio.gather(*coros)
+
+
+if __name__ == '__main__':
+    try:
+        asyncio.run(main())
+    except KeyboardInterrupt:
+        pass
diff --git a/controlpi/plugins/delay.py b/controlpi/plugins/delay.py
new file mode 100644 (file)
index 0000000..95e02eb
--- /dev/null
@@ -0,0 +1,20 @@
+import asyncio
+import functools
+
+
+async def waiter(control_pi, name, seconds, message):
+    await asyncio.sleep(seconds)
+    await control_pi.send({"name": name, "event": "delay finished"})
+
+
+async def init_finished(name):
+    print(f"{name} initialised")
+
+
+def init(control_pi, conf):
+    control_pi.register({"name": conf['name'], "command": "start"},
+                        functools.partial(waiter,
+                                          control_pi,
+                                          conf['name'],
+                                          conf['seconds']))
+    return init_finished(conf['name'])
diff --git a/controlpi/plugins/init.py b/controlpi/plugins/init.py
new file mode 100644 (file)
index 0000000..df10a9b
--- /dev/null
@@ -0,0 +1,11 @@
+import asyncio
+
+
+async def run(control_pi, conf):
+    print(f"{conf['name']} initialised")
+    for message in conf['commands']:
+        await control_pi.send(message)
+
+
+def init(control_pi, conf):
+    return run(control_pi, conf)
diff --git a/controlpi/plugins/log.py b/controlpi/plugins/log.py
new file mode 100644 (file)
index 0000000..2b54318
--- /dev/null
@@ -0,0 +1,15 @@
+import functools
+
+
+async def logger(name, message):
+    print(f"{name}: {message}")
+
+
+async def init_finished(name):
+    print(f"{name} initialised")
+
+
+def init(control_pi, conf):
+    control_pi.register(conf['filter'],
+                        functools.partial(logger, conf['name']))
+    return init_finished(conf['name'])
diff --git a/setup.py b/setup.py
new file mode 100644 (file)
index 0000000..890d134
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,21 @@
+import setuptools
+
+with open("README.md", "r") as readme_file:
+    long_description = readme_file.read()
+
+setuptools.setup(
+    name="controlpi",
+    version="0.1.0",
+    author="Graph-IT GmbH",
+    author_email="info@graph-it.com",
+    description="Control-Pi-Infrastruktur",
+    long_description=long_description,
+    long_description_content_type="text/markdown",
+    url="http://docs.graph-it.com/graphit/controlpi",
+    packages=setuptools.find_namespace_packages(include=['controlpi_plugins.*']),
+    classifiers=[
+        "Programming Language :: Python",
+        "License :: OSI Approved :: MIT License",
+        "Operating System :: OS Independent",
+    ],
+)