mqttGeo / mqttGeo.py /
f612b86 3 years ago
1 contributor
384 lines | 14.43kb
#!/usr/bin/python3

# For Multithread
from threading import Thread

# For HTTP server
from http.server import BaseHTTPRequestHandler, HTTPServer
import os
from urllib.parse import urlparse
import socket
import requests
import configuration

# For MQTT and plot part
import time
import csv2geojs
import paho.mqtt.client as mqtt

# Common
import json
from userio import *

MAXLRR=4
filename="mqttGeo.csv"
filenameBase="mqttGeo"

server = None
broker = None
latestHtml = ""

def htmlLatest(deveui,subID,FCnt,timeLatest,lat,lon,err,rssi):
    global latestHtml 
    htmlOutput="<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n"

    htmlOutput+=csv2geojs.networkSurveyAddLeaflet()

    htmlOutput+="<script type=\"text/javascript\">\n"
    htmlOutput+="setTimeout(function(){\n"
    htmlOutput+="      location = ''\n"
    htmlOutput+="},1200000);\n"
    htmlOutput+="</script>\n"
    htmlOutput+="<body>\n"
    htmlOutput+="<h2>"+server['name']+" Latest</h2>\n"
    htmlOutput+="  <div id=\"mapid\" style=\"width: 95%; max-width: 400px; height: 200px; margin: auto\"></div>\n"

    if 0 != lat and 0 != lon:
        xmin=0.999999*lon
        xmax=1.000001*lon
        ymin=0.999999*lat
        ymax=1.000001*lat
        htmlOutput+="<script>\n"
        zoomValue=19
        circleOpacity=0.2
        color=csv2geojs.networkSurveyColor(rssi)
        if err < 3:
            err=3
        htmlOutput+="  var mymap = L.map('mapid').setView(["+str(lat)+", "+str(lon)+"], "+str(zoomValue)+");\n"
        htmlOutput+="\n"
        htmlOutput+="  L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token="+csv2geojs.networkSurveySetMapboxApiToken()+"', {\n"
        htmlOutput+="    maxZoom: 20,\n"
        htmlOutput+="    attribution: 'Map data &copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors, ' +\n"
        htmlOutput+="                 'Imagery © <a href=\"https://www.mapbox.com/\">Mapbox</a>',\n"
        htmlOutput+="    id: 'mapbox/streets-v11',\n"
        htmlOutput+="    tileSize: 512,\n"
        htmlOutput+="    zoomOffset: -1\n"
        htmlOutput+="  }).addTo(mymap);\n"

        htmlOutput+="  L.circle(["+str(lat)+","+str(lon)+"],{color: '"+color+"', fillcolor: '"+color+"', fillOpacity: "+str(circleOpacity)+", radius:"+str(err)+"}).addTo(mymap);\n"
        htmlOutput+="</script>\n"

    htmlOutput+="<p>\n"
    htmlOutput+="<div id=\"deveui\">"+deveui+"</div>\n"
    htmlOutput+="<div id=\"fcnt\">"+str(FCnt)+"</div>\n"
    htmlOutput+="<div id=\"time\">"+timeLatest+"</div>\n"
    htmlOutput+="<div id=\"position\">"+str(lat)+" "+str(lon)+" "+str(err)+"</div>\n"
    htmlOutput+="<div id=\"rssi\">"+str(rssi)+"</div>\n"
    htmlOutput+="</p>\n"
    htmlOutput+="<p>\n<div id=\"reload\"><button class=\"btn-green\" onClick=\"window.location.reload();\">Reload</div>\n</p>\n"
    htmlOutput+="<p>\n<div id=\"home\"><button class=\"btn-green-menu\" onClick=\"window.location.href='.';\">Home</div>\n</p>\n"

    htmlOutput+="</body>\n"
    htmlOutput+="</html>\n"
    latestHtml = htmlOutput

# The callback for when the client receives a CONNACK response from the server.
def on_connect(client, userdata, flags, rc):
    if 0 == rc:
        ok("MQTT Connected with result code "+str(rc))
        client.subscribe("geoloc/#")
    else:
        error("MQTT error code "+str(rc))

# The callback for when a PUBLISH message is received from the server.
def on_message(client, userdata, msg):
    json_data=str(msg.payload.decode('ASCII'))
    parseTopic=msg.topic.split('/')
    if "uplink" in msg.topic:
        csvLine=""
        #print("Uplink received for SubID: "+parseTopic[1]+" and DevEUI: "+parseTopic[3])
        try:
            parsed=json.loads(json_data)
            pattern = '%Y-%m-%dT%H:%M:%S.%f%z'
            epoch = int(time.mktime(time.strptime(parsed['DevEUI_uplink']['Time'], pattern)))
            FCnt=parsed['DevEUI_uplink']['FCntUp']
            csvLine+=parseTopic[1]+","+parseTopic[3]+","+parsed['DevEUI_uplink']['Time']+","+str(epoch)+","+str(FCnt)
            say("UL SubID: "+parseTopic[1]+" DevEUI: "+parseTopic[3]+" Fcnt: "+str(FCnt))
        except:
            try:
                dlFPort=parsed['DevEUI_downlink_Sent']['FPort']
                dlFCntDn=parsed['DevEUI_downlink_Sent']['FCntDn']
                say("DL SubID: "+parseTopic[1]+" DevEUI: "+parseTopic[3]+" Fcnt: "+str(dlFCntDn)+" Fport: "+str(dlFPort))
            except:
                with open(server['logfile'],'a+') as f:
                    f.write("------------------------------\nMQTT Error: topic: "+msg.topic+" content: "+msg.payload.decode('ASCII')+"\n------------------------------\n")
                    f.close()
                warn("Uh ho")
            return

        lat=0
        lon=0
        err=0
        isPosition=True
        if parsed['DevEUI_uplink']['payload'] is not None:
            if parsed['DevEUI_uplink']['payload']['messageType'] == "POSITION_MESSAGE":
                try:
                    lat=parsed['DevEUI_uplink']['payload']['gpsLatitude']
                    lon=parsed['DevEUI_uplink']['payload']['gpsLongitude']
                    err=parsed['DevEUI_uplink']['payload']['horizontalAccuracy']
                except:
                    warn("[WARN] GPS Timeout")
                    return

                
            else:
                isPosition=False
                say("[WARN] Not a position message:"+parsed['DevEUI_uplink']['payload']['messageType'])
                return
        else:
            isPosition=False
            warn("[WARN] No decoded payload")
            with open(server['logfile'],'a+') as f:
                f.write("------------------------------\nMQTT Not decoded: topic: "+msg.topic+" content: "+msg.payload.decode('ASCII')+"\n------------------------------\n")
                f.close()
            return
        csvLine+=","+str(lat)+","+str(lon)+","+str(err)

        # Append GW details
        #parsed['DevEUI_uplink']['Lrrs']['Lrr']
        #Lrrid
        #LrrRSSI
        #LrrSNR
        count=0
        rssi=100
        for lrr in parsed['DevEUI_uplink']['Lrrs']['Lrr']:
            count+=1
            #print(lrr['Lrrid'])
            if rssi == 100:
                rssi=lrr['LrrRSSI']
            csvLine+=","+lrr['Lrrid']+","+str(lrr['LrrRSSI'])+","+str(lrr['LrrSNR'])
        i = 0
        for i in range(count,MAXLRR):
            csvLine+=",,,"

        # Appending only if position is different from 0,0
        if 0 != lat and 0 != lon:
            with open(filename,'a+') as f:
                f.write(csvLine+"\n")
                f.close()
        if True == isPosition:
            htmlLatest(parseTopic[3],parseTopic[1],FCnt,parsed['DevEUI_uplink']['Time'],lat,lon,err,rssi)

def httpFromParse(obj):
    if ".css" not in obj.path and "favicon" not in obj.path and ".png" not in obj.path and ".js" not in obj.path:
        if obj.headers['X-Real-IP'] is None:
            say("From: "+obj.client_address[0]+" GET Received : "+obj.path)
        else:
            say("From: "+obj.headers['X-Real-IP']+" GET Received : "+obj.path)

def httpSubIdList():
    global server
    subIdList = []
    with open(server['rawCsv'],'rt') as f:
        lines=f.readlines()
        for line in lines:
            part = line.split(',')
            subId=part[0]
            if subId not in subIdList:
                subIdList.append(subId)
        f.close()
    subIdList.sort()

    content=""
    content += "<ul class=\"subIdList\">"
    for subId in subIdList:
        content += "<li><a href=\"?subId="+subId+"\">"+subId+"</a></li>"


    content += "</ul>"
    return content 

def httpDevEuiList(subId):
    global server
    devEuiList = []
    with open(server['rawCsv'],'rt') as f:
        lines=f.readlines()
        for line in lines:
            part = line.split(',')
            if subId == part[0]:
                if part[1] not in devEuiList:
                    devEuiList.append(part[1])
        f.close()
    devEuiList.sort()

    content=""
    content += "<ul class=\"devEuiList\">\n"
    content += "<li><a href=\"?subId="+subId+"\">All</a></li>\n"

    for devEui in devEuiList:
        content += "<li><a href=\"?subId="+subId+"&devEUI="+devEui+"\">"+devEui+"</a></li>\n"


    content += "</ul>"
    return content 


class MyServer(BaseHTTPRequestHandler):
    def log_message(self, format, *args):
        # To silence the default output of server (too verbose)
        return
    def do_GET(self):
        global server
        global latestHtml
        rootdir = server['root']
        httpFromParse(self)

        # Load skeletons
        page = []
        data_page = ""
        data_begin = ""
        with open(configuration.get_pageBegin(),'r') as fStart:
            data_begin += fStart.read().replace( 'CSTAPPNAME', server['name'] )
            fStart.close()

        data_end = ""
        with open(configuration.get_pageEnd(),'r') as fEnd:
            data_end += fEnd.read().replace( 'CSTAPPNAME', server['name'] ).replace( 'CSTAPPVERSION', server['version'] )
            fEnd.close()

        if self.path == "/" or self.path == "/index.html":
            # Send list of SubID
            data_page ="<h2>"+server['name']+" Latest</h2>\n"
            data_page += httpSubIdList()
            data_page += "<p>\n<div id=\"latest\"><button class=\"btn-green-menu\" onClick=\"window.location.href='./last';\">Latest</div>\n</p>\n"

            page.append(data_begin)
            page.append(data_page)
            page.append(data_end)
            content = ''.join(page)
            self.send_response(200)
            self.send_header("Content-type", "text/html")
            self.send_header('Server',server['name']+" v"+server['version'])
            self.end_headers()
            self.wfile.write(content.encode('utf-8'))

        elif self.path == "/last" or self.path == "/last/index.html":
            page.append(data_begin)
            page.append(latestHtml)
            page.append(data_end)
            content = ''.join(page)
            self.send_response(200)
            self.send_header("Content-type", "text/html")
            self.send_header('Server',server['name']+" v"+server['version'])
            self.end_headers()
            self.wfile.write(content.encode('utf-8'))
            

        elif "/?" in self.path:
            uriObject = urlparse(self.path)
            queries = uriObject.query.split('&')
            uriParams = {} 

            data_page = "" 
            for query in queries:
                element=query.split("=")
                uriParams[element[0]]=element[1]

            subId=uriParams['subId']
            try:
                devEUI=uriParams['devEUI']
            except:
                devEUI=None

            if subId is not None and devEUI is None:
                data_page = csv2geojs.networkSurvey(filename, subId, fileOutput=False)
            elif subId is not None and devEUI is not None:
                data_page = csv2geojs.networkSurvey(filename, subId, devEUI, fileOutput=False)
            data_page += httpDevEuiList(subId)
            data_page += "<p>\n<div id=\"latest\"><button class=\"btn-green-menu\" onClick=\"window.location.href='./last';\">Latest</div>\n"
            data_page += "<div id=\"home\"><button class=\"btn-green-menu\" onClick=\"window.location.href='.';\">Home</div>\n</p>\n"

            page.append(data_begin)
            page.append(data_page)
            page.append(data_end)
            content = ''.join(page)
            self.send_response(200)
            self.send_header("Content-type", "text/html")
            self.send_header('Server',server['name']+" v"+server['version'])
            self.end_headers()
            self.wfile.write(content.encode('utf-8'))


        else:
            try:
                f = open(rootdir + self.path,'rb') #open requested file
                self.send_response(200)
                if self.path.endswith('.css'):
                    self.send_header('Content-type','text/css')
                elif self.path.endswith('.bmp'):
                    self.send_header('Content-type','image/x-ms-bmp')
                elif self.path.endswith('.png'):
                    self.send_header('Content-type','image/png')
                elif self.path.endswith('.jpg'):
                    self.send_header('Content-type','image/jpeg')
                else:
                    self.send_header('Content-type','text/html')
                self.send_header('Server',server['name']+" v"+server['version'])
                self.end_headers()
                self.wfile.write(f.read())
                f.close()
                return
            except IOError:
                self.send_header('Server',server['name']+" v"+server['version'])
                self.send_error(404, 'file not found')
                with open(server['logfile'],'a+') as f:
                    f.write("------------------------------\nHTTP 404 "+self.path+"\n------------------------------\n")
                    f.close()


def mqttInitRun():
    global broker
    broker = configuration.get_broker()
    if broker is not None:
        ok("MQTT configuration read successfully")
    # MQTT
    client = mqtt.Client()
    client.on_connect = on_connect
    client.on_message = on_message

    try:
        client.username_pw_set(broker['mqttUser'], password=broker['mqttPassword'])
    except:
        error("MQTT Set")
    try:
        say("MQTT connecting "+broker['mqttUser']+"@"+broker['address']+":"+str(broker['port']))
        client.connect(broker['address'], broker['port'], 60)
    except:
        error("MQTT connect")
    client.loop_forever()

def httpInitRun():
    global server
    server = configuration.get_server()
    if server is not None:
        ok("HTTP configuration read successfully")

    say("HTTP "+server['name']+" v"+server['version'])
    webServer = HTTPServer((server['address'], server['port']), MyServer)


    say("HTTP started http://%s:%s" % (server['address'], server['port']))
    htmlLatest(str(0),str(0),0,str(0),0,0,0,0)
    try:
        webServer.serve_forever()
    except KeyboardInterrupt:
        pass

    webServer.server_close()
    print("HTTP Server stopped.")


if __name__ == "__main__":
    Thread(target = mqttInitRun).start()
    Thread(target = httpInitRun).start()