mqttWebServer.py 4.2 KB
Newer Older
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
1 2 3 4 5
#!/usr/bin/python3

import json
import time
import paho.mqtt.client as paho
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
6
import os
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
7 8
import parse
from http.server import HTTPServer, BaseHTTPRequestHandler
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
9
import logging, argparse
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
10
from urllib.parse import unquote # for converting url encoded string to unicode string
11
import traceback
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
12 13 14 15 16 17 18 19 20 21 22 23

logging.basicConfig(format="%(asctime)-15s %(levelname)-8s  %(message)s")
logger = logging.getLogger("MQTT-HTTP-Server")

parser = argparse.ArgumentParser(description='MQTT <-> HTTP-Server.')
parser.add_argument("-v", "--verbosity", help="increase output verbosity", default=0, action="count")
parser.add_argument("-p", "--http-port", help="TCP port of HTTP server", default=8000)
parser.add_argument("-n", "--http-hostname", help="DNS name or IP address, where HTTP server will be available on. Possible: (empty string), 'localhost', DNS name or IP address", default="") 
parser.add_argument("-b", "--mqtt-broker-host", help="MQTT broker hostname", default="localhost")
parser.add_argument("mqtt_client_name", help="MQTT client name. Needs to be unique in the MQTT namespace, eg enocean-http-server.", type=str)
args = parser.parse_args()
logger.setLevel(logging.WARNING-(args.verbosity*10 if args.verbosity <=2 else 20) )
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
24 25 26 27 28 29 30



mlast = {}

def on_connect(client, userdata, flags, rc):
  if rc==0:
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
31
    logger.info("MQTT connected OK. Return code"+str(rc))
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
32
    client.subscribe("#")
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
33
    logger.info("MQTT: Subscribed to all topics")
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
34
  else:
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
35
    logger.error("Bad connection. Return code="+str(rc))
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
36 37 38

def on_disconnect(client, userdata, rc):
  if rc != 0:
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
39
    logger.warning ("Unexpected MQTT disconnection. Will auto-reconnect")
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
40 41 42 43 44

def on_message(client, userdata, message):
    global mlast
    t = time.time()
    m = message.payload.decode("utf-8")
45
#    print("received:", message.topic, str(m))
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
46 47

    # the lambda in loads is for converting the object name into a int, if appropiate
48 49 50 51 52 53 54
    try:
      j = json.loads(m, object_hook=lambda d: {int(k) if k.lstrip('-').isdigit() else k: v for k, v in d.items()})
  #    print ("Topic:",message.topic,"JSON",j)
      if type(j) is not dict: #anpassen, falls es keine Zeitinfo hat
        j={'v':j,'time':t}
      if t-j['time'] > 10: # corrects the time of too much in the future
        j['time'] = t
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
55

56 57 58
      mlast[message.topic] = j
    except:
      print("The following MQTT message was not processed:", message.topic, str(m))
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
59

Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
60
client= paho.Client(args.mqtt_client_name)
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
61 62 63
client.on_message=on_message
client.on_connect = on_connect
client.on_disconnect = on_disconnect
64 65
client.enable_logger(logger)
logger.info("connecting to broker "+args.mqtt_broker_host)
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
66
client.connect(args.mqtt_broker_host)
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
67
client.loop_start() #start loop to process received messages in separate thread
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
68
logger.info ("MQTT Loop started.")
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
69 70 71 72 73


class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
74
        self.send_header("Content-type", "text/json;charset=UTF-8")
75
        self.send_header("Access-Control-Allow-Origin", "*")
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
76 77 78 79 80
        self.end_headers()
        # self.path equals GET Parameter: "/foo/bar/".
        erg = parse.parse("/{topic}/set?{value:d}", self.path)
        if not erg: erg = parse.parse("/{topic}", self.path)
        #print(erg)
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
81 82
        topic = unquote(erg['topic']) if erg else None
        if erg and topic in mlast:
83
          #print("topic:",topic)
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
84
          if 'value' in erg:
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
85 86 87
            logger.info("publish: "+str(topic)+" value: "+str(erg['value']) )
            client.publish(topic+"/set", erg['value'], qos=1, retain=False)  # send command to Relais
            self.wfile.write(str.encode('{"result":"sucess", "topic":"'+topic+'", "value":'+str(erg['value'])+'}'))
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
88
          else:
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
89 90
            self.wfile.write(str.encode("/*You accessed path: {}*/\n{}".format(topic,
                                        json.dumps(mlast[topic]) )) )
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
91
        else:
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
92
          self.wfile.write(json.dumps(mlast, ensure_ascii=False).encode('utf8') )
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
93 94

try:
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
95 96
  httpd = HTTPServer((args.http_hostname, args.http_port), SimpleHTTPRequestHandler)
  logger.info("Server started on port {} and host {}".format(args.http_port, args.http_hostname))
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
97 98 99
  httpd.serve_forever()

except KeyboardInterrupt:
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
100
    logger.info ('^C received, shutting down the web server')
Peter-Bernd Otte's avatar
Peter-Bernd Otte committed
101 102
    httpd.server_close()
    client.disconnect()
103
    client.loop_stop()