Showing 2 changed files with 301 additions and 94 deletions
+38 -21
csv2geojs.py
... ...
@@ -1,7 +1,5 @@
1 1
 #!/usr/bin/python3
2 2
 import time
3
-import json
4
-import paho.mqtt.client as mqtt
5 3
 
6 4
 circleOpacity=0.2
7 5
 useLeafletLocal=1
... ...
@@ -46,7 +44,7 @@ def networkSurveySetMapboxApiToken():
46 44
     return MAPBOXTOKEN
47 45
 
48 46
 
49
-def networkSurvey(filename, subId):
47
+def networkSurvey(filename, subId, devEUI = None, fileOutput= True):
50 48
     global circleOpacity
51 49
     start_time = time.time()
52 50
     htmlOutput="<!DOCTYPE html>\n"
... ...
@@ -106,23 +104,40 @@ def networkSurvey(filename, subId):
106 104
     with open(filename, "rt") as fin:
107 105
         lines=fin.readlines()
108 106
         for line in lines:
109
-            lastLine=line
110 107
             part=line.split(',')
111 108
             countRaw += 1
112 109
             if subId == part[0]:
113
-                if part[5] is not "0" and part[6] is not "0":
114
-                    count += 1
115
-                    color=networkSurveyColor(part[9])
116
-                    jsLine="  L.circle(["+part[5]+","+part[6]+"],{color: '"+color+"', fillcolor: '"+color+"', fillOpacity: "+str(circleOpacity)+", radius:XXXX}).addTo(mymap);\n"
117
-                    if float(part[5]) > latMax:
118
-                        latMax=float(part[5])
119
-                    if float(part[5]) < latMin:
120
-                        latMin=float(part[5])
121
-                    if float(part[6]) > lonMax:
122
-                        lonMax=float(part[6])
123
-                    if float(part[6]) < lonMin:
124
-                        lonMin=float(part[6])
125
-                    pointList+=jsLine
110
+                if devEUI is None:
111
+                    lastLine=line
112
+                    if part[5] is not "0" and part[6] is not "0":
113
+                        count += 1
114
+                        color=networkSurveyColor(part[9])
115
+                        jsLine="  L.circle(["+part[5]+","+part[6]+"],{color: '"+color+"', fillcolor: '"+color+"', fillOpacity: "+str(circleOpacity)+", radius:XXXX}).addTo(mymap);\n"
116
+                        if float(part[5]) > latMax:
117
+                            latMax=float(part[5])
118
+                        if float(part[5]) < latMin:
119
+                            latMin=float(part[5])
120
+                        if float(part[6]) > lonMax:
121
+                            lonMax=float(part[6])
122
+                        if float(part[6]) < lonMin:
123
+                            lonMin=float(part[6])
124
+                        pointList+=jsLine
125
+                elif part[1] == devEUI:
126
+                    lastLine=line
127
+                    if part[5] is not "0" and part[6] is not "0":
128
+                        count += 1
129
+                        color=networkSurveyColor(part[9])
130
+                        jsLine="  L.circle(["+part[5]+","+part[6]+"],{color: '"+color+"', fillcolor: '"+color+"', fillOpacity: "+str(circleOpacity)+", radius:XXXX}).addTo(mymap);\n"
131
+                        if float(part[5]) > latMax:
132
+                            latMax=float(part[5])
133
+                        if float(part[5]) < latMin:
134
+                            latMin=float(part[5])
135
+                        if float(part[6]) > lonMax:
136
+                            lonMax=float(part[6])
137
+                        if float(part[6]) < lonMin:
138
+                            lonMin=float(part[6])
139
+                        pointList+=jsLine
140
+
126 141
         fin.close()
127 142
 
128 143
     # Adapt Zoom to Max(DeltaLat, DeltaLon)
... ...
@@ -196,7 +211,9 @@ def networkSurvey(filename, subId):
196 211
     htmlOutput+="</body>\n"
197 212
     htmlOutput+="</html>\n"
198 213
 
199
-    filenameSurvey="networkSurvey-"+str(subId)+".html"
200
-    with open(filenameSurvey,"w") as f:
201
-        f.write(htmlOutput+"\n")
202
-        f.close()
214
+    if fileOutput is True:
215
+        filenameSurvey="networkSurvey-"+str(subId)+".html"
216
+        with open(filenameSurvey,"w") as f:
217
+            f.write(htmlOutput+"\n")
218
+            f.close()
219
+    return htmlOutput
+263 -73
mqttGeo.py
... ...
@@ -1,64 +1,46 @@
1 1
 #!/usr/bin/python3
2
+
3
+# For Multithread
4
+from threading import Thread
5
+
6
+# For HTTP server
7
+from http.server import BaseHTTPRequestHandler, HTTPServer
8
+import os
9
+from urllib.parse import urlparse
10
+import socket
11
+import requests
12
+import configuration
13
+
14
+# For MQTT and plot part
2 15
 import time
3 16
 import csv2geojs
4
-import json
5 17
 import paho.mqtt.client as mqtt
6 18
 
19
+# Common
20
+import json
21
+from userio import *
22
+
7 23
 MAXLRR=4
8 24
 filename="mqttGeo.csv"
9
-#filenameStatus="mqttGeo.html"
10 25
 filenameBase="mqttGeo"
11 26
 
27
+server = None
28
+broker = None
29
+latestHtml = ""
12 30
 
13 31
 def htmlLatest(deveui,subID,FCnt,timeLatest,lat,lon,err,rssi):
32
+    global latestHtml 
14 33
     htmlOutput="<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n"
15
-    htmlOutput+="<html>\n"
16
-    htmlOutput+="<head>\n"
17
-    htmlOutput+="  <title>mqttGeo Latest</title>\n"
18
-    htmlOutput+="  <meta charset=\"utf-8\" />\n"
19
-    htmlOutput+="  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"
20
-    htmlOutput+="  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"
21 34
 
22 35
     htmlOutput+=csv2geojs.networkSurveyAddLeaflet()
23 36
 
24
-    htmlOutput+="<style>\n"
25
-    htmlOutput+="html {\n"
26
-    htmlOutput+="    font-family: \"Lucida Sans\", sans-serif; text-align: center;\n"
27
-    htmlOutput+="}\n"
28
-    htmlOutput+="#deveui { font-size: 1.4em; }\n"
29
-    htmlOutput+="#fcnt { font-size: 1.8em; }\n"
30
-    htmlOutput+="#time { font-size: 1.4em; }\n"
31
-    htmlOutput+="#position { display: inline-block; font-size: 1.5em; }\n"
32
-    htmlOutput+="#rssi { font-size: 1.2em; }\n"
33
-    htmlOutput+="button {\n"
34
-    htmlOutput+="  position: relative;\n"
35
-    htmlOutput+="  width: 300px;\n"
36
-    htmlOutput+="  height: 40px;\n"
37
-    htmlOutput+="  margin: 10px auto;\n"
38
-    htmlOutput+="  padding: 0;\n"
39
-    htmlOutput+="  font-size: 22px;\n"
40
-    htmlOutput+="  text-align: center;\n"
41
-    htmlOutput+="  color: white;\n"
42
-    htmlOutput+="  border: none;\n"
43
-    htmlOutput+="  outline: none;\n"
44
-    htmlOutput+="  cursor: pointer;\n"
45
-    htmlOutput+="  overflow: hidden;\n"
46
-    htmlOutput+="  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);\n"
47
-    htmlOutput+="  transition: transform 0.4s ease-in-out;\n"
48
-    htmlOutput+="}\n"
49
-    htmlOutput+=".btn-green {\n"
50
-    htmlOutput+="  background: #1abc9c;\n"
51
-    htmlOutput+="  border-bottom: 2px solid #12ab8d;\n"
52
-    htmlOutput+="  box-shadow: inset 0 -2px #12ab8d;\n"
53
-    htmlOutput+="}\n"
54
-    htmlOutput+="</style>\n"
55 37
     htmlOutput+="<script type=\"text/javascript\">\n"
56 38
     htmlOutput+="setTimeout(function(){\n"
57 39
     htmlOutput+="      location = ''\n"
58 40
     htmlOutput+="},1200000);\n"
59 41
     htmlOutput+="</script>\n"
60 42
     htmlOutput+="<body>\n"
61
-    htmlOutput+="<h2>mqttGeo Latest</h2>\n"
43
+    htmlOutput+="<h2>"+server['name']+" Latest</h2>\n"
62 44
     htmlOutput+="  <div id=\"mapid\" style=\"width: 95%; max-width: 400px; height: 200px; margin: auto\"></div>\n"
63 45
 
64 46
     if 0 != lat and 0 != lon:
... ...
@@ -93,21 +75,20 @@ def htmlLatest(deveui,subID,FCnt,timeLatest,lat,lon,err,rssi):
93 75
     htmlOutput+="<div id=\"position\">"+str(lat)+" "+str(lon)+" "+str(err)+"</div>\n"
94 76
     htmlOutput+="<div id=\"rssi\">"+str(rssi)+"</div>\n"
95 77
     htmlOutput+="</p>\n"
96
-    htmlOutput+="<p>\n"
97
-    htmlOutput+="<div id=\"reload\"><button class=\"btn-green\" onClick=\"window.location.reload();\">Reload</div>\n"
98
-    htmlOutput+="</p>\n"
78
+    htmlOutput+="<p>\n<div id=\"reload\"><button class=\"btn-green\" onClick=\"window.location.reload();\">Reload</div>\n</p>\n"
79
+    htmlOutput+="<p>\n<div id=\"home\"><button class=\"btn-green-menu\" onClick=\"window.location.href='.';\">Home</div>\n</p>\n"
99 80
 
100 81
     htmlOutput+="</body>\n"
101 82
     htmlOutput+="</html>\n"
102
-    filenameStatus=filenameBase+"-"+subID+".html"
103
-    with open(filenameStatus,"w") as f2:
104
-        f2.write(htmlOutput+"\n")
105
-        f2.close
83
+    latestHtml = htmlOutput
106 84
 
107 85
 # The callback for when the client receives a CONNACK response from the server.
108 86
 def on_connect(client, userdata, flags, rc):
109
-    print("Connected with result code "+str(rc))
110
-    client.subscribe("geoloc/#")
87
+    if 0 == rc:
88
+        ok("MQTT Connected with result code "+str(rc))
89
+        client.subscribe("geoloc/#")
90
+    else:
91
+        error("MQTT error code "+str(rc))
111 92
 
112 93
 # The callback for when a PUBLISH message is received from the server.
113 94
 def on_message(client, userdata, msg):
... ...
@@ -122,13 +103,23 @@ def on_message(client, userdata, msg):
122 103
             epoch = int(time.mktime(time.strptime(parsed['DevEUI_uplink']['Time'], pattern)))
123 104
             FCnt=parsed['DevEUI_uplink']['FCntUp']
124 105
             csvLine+=parseTopic[1]+","+parseTopic[3]+","+parsed['DevEUI_uplink']['Time']+","+str(epoch)+","+str(FCnt)
125
-            print("Uplink received for SubID: "+parseTopic[1]+" and DevEUI: "+parseTopic[3]+" Fcnt: "+str(FCnt))
106
+            say("UL SubID: "+parseTopic[1]+" DevEUI: "+parseTopic[3]+" Fcnt: "+str(FCnt))
126 107
         except:
127
-            print("Uh ho")
108
+            try:
109
+                dlFPort=parsed['DevEUI_downlink_Sent']['FPort']
110
+                dlFCntDn=parsed['DevEUI_downlink_Sent']['FCntDn']
111
+                say("DL SubID: "+parseTopic[1]+" DevEUI: "+parseTopic[3]+" Fcnt: "+str(dlFCntDn)+" Fport: "+str(dlFPort))
112
+            except:
113
+                with open(server['logfile'],'a+') as f:
114
+                    f.write("------------------------------\nMQTT Error: topic: "+msg.topic+" content: "+msg.payload.decode('ASCII')+"\n------------------------------\n")
115
+                    f.close()
116
+                warn("Uh ho")
117
+            return
128 118
 
129 119
         lat=0
130 120
         lon=0
131 121
         err=0
122
+        isPosition=True
132 123
         if parsed['DevEUI_uplink']['payload'] is not None:
133 124
             if parsed['DevEUI_uplink']['payload']['messageType'] == "POSITION_MESSAGE":
134 125
                 try:
... ...
@@ -136,15 +127,22 @@ def on_message(client, userdata, msg):
136 127
                     lon=parsed['DevEUI_uplink']['payload']['gpsLongitude']
137 128
                     err=parsed['DevEUI_uplink']['payload']['horizontalAccuracy']
138 129
                 except:
139
-                    print("[WARN] GPS Timeout")
130
+                    warn("[WARN] GPS Timeout")
131
+                    return
140 132
 
141 133
                 
142 134
             else:
143
-                print("[WARN] Not a position message:"+parsed['DevEUI_uplink']['payload']['messageType'])
135
+                isPosition=False
136
+                say("[WARN] Not a position message:"+parsed['DevEUI_uplink']['payload']['messageType'])
137
+                return
144 138
         else:
145
-            print("[WARN] No decoded payload")
139
+            isPosition=False
140
+            warn("[WARN] No decoded payload")
141
+            with open(server['logfile'],'a+') as f:
142
+                f.write("------------------------------\nMQTT Not decoded: topic: "+msg.topic+" content: "+msg.payload.decode('ASCII')+"\n------------------------------\n")
143
+                f.close()
144
+            return
146 145
         csvLine+=","+str(lat)+","+str(lon)+","+str(err)
147
-        #print(csvLine)
148 146
 
149 147
         # Append GW details
150 148
         #parsed['DevEUI_uplink']['Lrrs']['Lrr']
... ...
@@ -168,27 +166,219 @@ def on_message(client, userdata, msg):
168 166
             with open(filename,'a+') as f:
169 167
                 f.write(csvLine+"\n")
170 168
                 f.close()
171
-        htmlLatest(parseTopic[3],parseTopic[1],FCnt,parsed['DevEUI_uplink']['Time'],lat,lon,err,rssi)
169
+        if True == isPosition:
170
+            htmlLatest(parseTopic[3],parseTopic[1],FCnt,parsed['DevEUI_uplink']['Time'],lat,lon,err,rssi)
171
+
172
+def httpFromParse(obj):
173
+    if ".css" not in obj.path and "favicon" not in obj.path and ".png" not in obj.path and ".js" not in obj.path:
174
+        if obj.headers['X-Real-IP'] is None:
175
+            say("From: "+obj.client_address[0]+" GET Received : "+obj.path)
176
+        else:
177
+            say("From: "+obj.headers['X-Real-IP']+" GET Received : "+obj.path)
178
+
179
+def httpSubIdList():
180
+    global server
181
+    subIdList = []
182
+    with open(server['rawCsv'],'rt') as f:
183
+        lines=f.readlines()
184
+        for line in lines:
185
+            part = line.split(',')
186
+            subId=part[0]
187
+            if subId not in subIdList:
188
+                subIdList.append(subId)
189
+        f.close()
190
+    subIdList.sort()
191
+
192
+    content=""
193
+    content += "<ul class=\"subIdList\">"
194
+    for subId in subIdList:
195
+        content += "<li><a href=\"?subId="+subId+"\">"+subId+"</a></li>"
196
+
197
+
198
+    content += "</ul>"
199
+    return content 
200
+
201
+def httpDevEuiList(subId):
202
+    global server
203
+    devEuiList = []
204
+    with open(server['rawCsv'],'rt') as f:
205
+        lines=f.readlines()
206
+        for line in lines:
207
+            part = line.split(',')
208
+            if subId == part[0]:
209
+                if part[1] not in devEuiList:
210
+                    devEuiList.append(part[1])
211
+        f.close()
212
+    devEuiList.sort()
213
+
214
+    content=""
215
+    content += "<ul class=\"devEuiList\">\n"
216
+    content += "<li><a href=\"?subId="+subId+"\">All</a></li>\n"
217
+
218
+    for devEui in devEuiList:
219
+        content += "<li><a href=\"?subId="+subId+"&devEUI="+devEui+"\">"+devEui+"</a></li>\n"
220
+
221
+
222
+    content += "</ul>"
223
+    return content 
224
+
225
+
226
+class MyServer(BaseHTTPRequestHandler):
227
+    def log_message(self, format, *args):
228
+        # To silence the default output of server (too verbose)
229
+        return
230
+    def do_GET(self):
231
+        global server
232
+        global latestHtml
233
+        rootdir = server['root']
234
+        httpFromParse(self)
235
+
236
+        # Load skeletons
237
+        page = []
238
+        data_page = ""
239
+        data_begin = ""
240
+        with open(configuration.get_pageBegin(),'r') as fStart:
241
+            data_begin += fStart.read().replace( 'CSTAPPNAME', server['name'] )
242
+            fStart.close()
243
+
244
+        data_end = ""
245
+        with open(configuration.get_pageEnd(),'r') as fEnd:
246
+            data_end += fEnd.read().replace( 'CSTAPPNAME', server['name'] ).replace( 'CSTAPPVERSION', server['version'] )
247
+            fEnd.close()
248
+
249
+        if self.path == "/" or self.path == "/index.html":
250
+            # Send list of SubID
251
+            data_page ="<h2>"+server['name']+" Latest</h2>\n"
252
+            data_page += httpSubIdList()
253
+            data_page += "<p>\n<div id=\"latest\"><button class=\"btn-green-menu\" onClick=\"window.location.href='./last';\">Latest</div>\n</p>\n"
254
+
255
+            page.append(data_begin)
256
+            page.append(data_page)
257
+            page.append(data_end)
258
+            content = ''.join(page)
259
+            self.send_response(200)
260
+            self.send_header("Content-type", "text/html")
261
+            self.send_header('Server',server['name']+" v"+server['version'])
262
+            self.end_headers()
263
+            self.wfile.write(content.encode('utf-8'))
264
+
265
+        elif self.path == "/last" or self.path == "/last/index.html":
266
+            page.append(data_begin)
267
+            page.append(latestHtml)
268
+            page.append(data_end)
269
+            content = ''.join(page)
270
+            self.send_response(200)
271
+            self.send_header("Content-type", "text/html")
272
+            self.send_header('Server',server['name']+" v"+server['version'])
273
+            self.end_headers()
274
+            self.wfile.write(content.encode('utf-8'))
275
+            
276
+
277
+        elif "/?" in self.path:
278
+            uriObject = urlparse(self.path)
279
+            queries = uriObject.query.split('&')
280
+            uriParams = {} 
281
+
282
+            data_page = "" 
283
+            for query in queries:
284
+                element=query.split("=")
285
+                uriParams[element[0]]=element[1]
286
+
287
+            subId=uriParams['subId']
288
+            try:
289
+                devEUI=uriParams['devEUI']
290
+            except:
291
+                devEUI=None
292
+
293
+            if subId is not None and devEUI is None:
294
+                data_page = csv2geojs.networkSurvey(filename, subId, fileOutput=False)
295
+            elif subId is not None and devEUI is not None:
296
+                data_page = csv2geojs.networkSurvey(filename, subId, devEUI, fileOutput=False)
297
+            data_page += httpDevEuiList(subId)
298
+            data_page += "<p>\n<div id=\"latest\"><button class=\"btn-green-menu\" onClick=\"window.location.href='./last';\">Latest</div>\n"
299
+            data_page += "<div id=\"home\"><button class=\"btn-green-menu\" onClick=\"window.location.href='.';\">Home</div>\n</p>\n"
300
+
301
+            page.append(data_begin)
302
+            page.append(data_page)
303
+            page.append(data_end)
304
+            content = ''.join(page)
305
+            self.send_response(200)
306
+            self.send_header("Content-type", "text/html")
307
+            self.send_header('Server',server['name']+" v"+server['version'])
308
+            self.end_headers()
309
+            self.wfile.write(content.encode('utf-8'))
310
+
311
+
312
+        else:
313
+            try:
314
+                f = open(rootdir + self.path,'rb') #open requested file
315
+                self.send_response(200)
316
+                if self.path.endswith('.css'):
317
+                    self.send_header('Content-type','text/css')
318
+                elif self.path.endswith('.bmp'):
319
+                    self.send_header('Content-type','image/x-ms-bmp')
320
+                elif self.path.endswith('.png'):
321
+                    self.send_header('Content-type','image/png')
322
+                elif self.path.endswith('.jpg'):
323
+                    self.send_header('Content-type','image/jpeg')
324
+                else:
325
+                    self.send_header('Content-type','text/html')
326
+                self.send_header('Server',server['name']+" v"+server['version'])
327
+                self.end_headers()
328
+                self.wfile.write(f.read())
329
+                f.close()
330
+                return
331
+            except IOError:
332
+                self.send_header('Server',server['name']+" v"+server['version'])
333
+                self.send_error(404, 'file not found')
334
+                with open(server['logfile'],'a+') as f:
335
+                    f.write("------------------------------\nHTTP 404 "+self.path+"\n------------------------------\n")
336
+                    f.close()
337
+
338
+
339
+def mqttInitRun():
340
+    global broker
341
+    broker = configuration.get_broker()
342
+    if broker is not None:
343
+        ok("MQTT configuration read successfully")
344
+    # MQTT
345
+    client = mqtt.Client()
346
+    client.on_connect = on_connect
347
+    client.on_message = on_message
348
+
349
+    try:
350
+        client.username_pw_set(broker['mqttUser'], password=broker['mqttPassword'])
351
+    except:
352
+        error("MQTT Set")
353
+    try:
354
+        say("MQTT connecting "+broker['mqttUser']+"@"+broker['address']+":"+str(broker['port']))
355
+        client.connect(broker['address'], broker['port'], 60)
356
+    except:
357
+        error("MQTT connect")
358
+    client.loop_forever()
359
+
360
+def httpInitRun():
361
+    global server
362
+    server = configuration.get_server()
363
+    if server is not None:
364
+        ok("HTTP configuration read successfully")
365
+
366
+    say("HTTP "+server['name']+" v"+server['version'])
367
+    webServer = HTTPServer((server['address'], server['port']), MyServer)
172 368
 
173
-        csv2geojs.networkSurvey(filename,parseTopic[1])
174 369
 
370
+    say("HTTP started http://%s:%s" % (server['address'], server['port']))
371
+    htmlLatest(str(0),str(0),0,str(0),0,0,0,0)
372
+    try:
373
+        webServer.serve_forever()
374
+    except KeyboardInterrupt:
375
+        pass
175 376
 
377
+    webServer.server_close()
378
+    print("HTTP Server stopped.")
176 379
 
177 380
 
178
-client = mqtt.Client()
179
-client.on_connect = on_connect
180
-client.on_message = on_message
381
+if __name__ == "__main__":
382
+    Thread(target = mqttInitRun).start()
383
+    Thread(target = httpInitRun).start()
181 384
 
182
-broker_address="192.168.1.33"
183
-user="esp8266"
184
-password="NjWxXrEMnOeaNtv8b40u"
185
-port=1883
186
-try:
187
-    client.username_pw_set(user, password=password)
188
-except:
189
-    print("[FAILED]MQTT Set")
190
-try:
191
-    client.connect(broker_address, port, 60)
192
-except:
193
-    print("[FAILED]MQTT connect")
194
-client.loop_forever()