From 363b1a69338a775ee8bd608bfba8c5e7bf3fed1e Mon Sep 17 00:00:00 2001
From: johannes walcher <johannes.walcher@stusta.de>
Date: Sat, 4 Aug 2018 18:24:59 +0200
Subject: [PATCH] Added remote-hbf-python

---
 haspa_web/__main__.py      | 10 +----
 haspa_web/haspa.py         | 47 ++++++++++++----------
 hauptbahnhof/__init__.py   |  5 +++
 hauptbahnhof/knechtmqtt.py | 82 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 115 insertions(+), 29 deletions(-)
 create mode 100644 hauptbahnhof/knechtmqtt.py

diff --git a/haspa_web/__main__.py b/haspa_web/__main__.py
index 6d7a99d..1772c1d 100644
--- a/haspa_web/__main__.py
+++ b/haspa_web/__main__.py
@@ -1,19 +1,11 @@
-import asyncio
 from haspa_web.haspa import HaspaWeb
 
 def main():
     """
     Actually start the shit
     """
-    loop = asyncio.get_event_loop()
     haspaweb = HaspaWeb()
-    loop.set_debug(True)
-    try:
-        loop.run_forever()
-    except KeyboardInterrupt:
-        pass
-
-    loop.run_until_complete(haspaweb.teardown())
+    haspaweb.hbf.start(spinoff=False)
 
 if __name__ == "__main__":
     main()
diff --git a/haspa_web/haspa.py b/haspa_web/haspa.py
index e2bf9cf..7e7488e 100644
--- a/haspa_web/haspa.py
+++ b/haspa_web/haspa.py
@@ -1,44 +1,50 @@
-import asyncio
+"""
+Display the haspa state as website
+"""
+
 import time
+import json
 from pathlib import Path
 
-from hauptbahnhof import Hauptbahnhof
+import libconf
+
+from hauptbahnhof import MQTTBahnhofClient
 
 class HaspaWeb:
     """
     Recreate the haspa website to represent the current haspa state
+    -- This will connect to a remote hauptbahnhof client!
     """
 
-    def __init__(self, loop=None):
-        if not loop:
-            loop = asyncio.get_event_loop()
+    def __init__(self):
+        with open('/etc/hauptbahnhof/hauptbahnhof.conf') as cfgfile:
+            self.config = libconf.load(cfgfile)
 
-        self.loop = loop
-        self.hbf = Hauptbahnhof(loop)
-        self.hbf.subscribe('/haspa/status', self.command_state)
+        self.hbf = MQTTBahnhofClient(self.config, {
+            '/haspa/status': self.command_state
+        })
 
         prism_path = Path(__file__).resolve().parent
         self.config = {}
         self.config['TEMPLATE_PATH'] = prism_path / "templates"
         self.config['OUTPUT_PATH'] = prism_path / "html"
 
-    async def teardown(self):
-        """ Clean it up """
-        await self.hbf.teardown()
-
-    async def command_state(self, client, message, _):
-        """ After the state has changed, do something! """
-        del client
-        print(message)
+    def command_state(self, client, userdata, mqttmsg):
+        """ /haspa/status change detected """
+        del client, userdata
+        message = json.loads(mqttmsg.payload.decode('utf-8'))
+        print("Received:", message)
         if 'haspa' in message:
             if message['haspa'] in ['open', 'offen', 'auf']:
-                self.set_state(message['haspa'], True)
+                self.set_state(True)
             elif message['haspa'] in ['close', 'zu', 'closed']:
-                self.set_state(message['haspa'], False)
+                self.set_state(False)
             else:
                 print("Haspa state undetermined: ", message['haspa'])
+        else:
+            print("Invalid Message received")
 
-    def set_state(self, state, is_open):
+    def set_state(self, is_open):
         """
         Export the current haspa state to the website
 
@@ -46,7 +52,6 @@ class HaspaWeb:
         Change any old and glorious routines!
         """
         for template in self.config['TEMPLATE_PATH'].glob('*.tpl'):
-            print("Updating templates")
             outfile = self.config['OUTPUT_PATH'] / template.stem
 
             with open(str(template), 'r') as orig:
@@ -57,3 +62,5 @@ class HaspaWeb:
                                           time.strftime("%a, %d %b %Y %H:%M:%S"))
                 with open(str(outfile), 'w') as new:
                     new.write(content)
+    def run(self):
+        self.hbf.start()
diff --git a/hauptbahnhof/__init__.py b/hauptbahnhof/__init__.py
index 962b541..387c219 100644
--- a/hauptbahnhof/__init__.py
+++ b/hauptbahnhof/__init__.py
@@ -1 +1,6 @@
+"""
+Initialize magic
+"""
+
 from .hauptbahnhof import Hauptbahnhof
+from .knechtmqtt import MQTTBahnhofClient
diff --git a/hauptbahnhof/knechtmqtt.py b/hauptbahnhof/knechtmqtt.py
new file mode 100644
index 0000000..88be443
--- /dev/null
+++ b/hauptbahnhof/knechtmqtt.py
@@ -0,0 +1,82 @@
+import logging
+from paho.mqtt.client import Client as PahoMqttClient
+
+class MQTTBahnhofClient:
+    """
+    A simplified hauptbahnhof client that is able to connect to a remote mqtt
+    with username password and certificates
+    """
+    def __init__(self, config, subscriptions):
+        logformat = '%(asctime)s | %(name)s | %(levelname)5s | %(message)s'
+        logging.basicConfig(format=logformat)
+        self.log = logging.getLogger(__name__)
+        if config['hauptbahnhof']['debug']:
+            self.log.setLevel(logging.DEBUG)
+        else:
+            self.log.setLevel(logging.INFO)
+
+        self.host = config['hauptbahnhof']['host']
+        self.subscriptions = subscriptions
+
+        self.mqtt = PahoMqttClient()
+        self.mqtt.enable_logger(self.log)
+        self.mqtt.on_message = self.on_message
+        self.mqtt.on_connect = self.on_connect
+
+        ssl = {"ca_certs": config['hauptbahnhof']['ca_crt'],
+               "certfile": config['hauptbahnhof']['certfile'],
+               "keyfile":  config['hauptbahnhof']['keyfile']}
+        self.mqtt.tls_set(**ssl)
+
+        auth = {"username": config['hauptbahnhof']['username'],
+                "password": config['hauptbahnhof']['password']}
+        self.mqtt.username_pw_set(**auth)
+
+    def on_message(self, client, userdata, msg):
+        """
+        A message was received. push it back towards the async context
+        """
+        self.log("Unhandled message has arrived: %s %s %s", client, userdata, msg)
+
+    def on_connect(self, client, userdata, flags, returncode):
+        """ After a successfull connection the topics are set and subscribed """
+        del client, userdata
+        if returncode == 0:
+            print("Flags: ", flags)
+            self.mqtt.subscribe([(topic, 0) for topic in self.subscriptions])
+
+            if not 'session present' in flags or flags['session present'] == 0:
+                # If we have a new session
+                for topic, callback in self.subscriptions.items():
+                    if callback:
+                        self.mqtt.message_callback_add(topic, callback)
+
+        else:
+            try:
+                msg = {
+                    0: "Connection successful",
+                    1: "Incorrect Protocol Version",
+                    2: "Invalid client identifier",
+                    3: "Server unavailable",
+                    4: "Bad username or password",
+                    5: "Not authorized",
+                }[returncode]
+            except KeyError:
+                msg = "Unknown error occured: " + returncode
+            print("Connection refused: ", msg)
+
+
+    def publish(self, topic, data):
+        """ Publish a message """
+        self.mqtt.publish(topic, data)
+
+    def start(self, spinoff=True):
+        """ Connect and start the mqtt machine """
+        self.mqtt.connect(self.host, port=8883)
+        self.log.info("Successfully connected to %s", self.host)
+
+        # Spinning of a thread for the magic
+        if spinoff:
+            self.mqtt.loop_start()
+        else:
+            self.mqtt.loop_forever()
-- 
GitLab