-
Notifications
You must be signed in to change notification settings - Fork 10
/
webapp.py
executable file
·1906 lines (1625 loc) · 68.7 KB
/
webapp.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# webapp.py - v4, by Mark Harris. Web Based Configurator for LiveSectional - Using Flask and Python
# Updated to work with New FAA API: 10-2023. Thank you to user Marty for all the hardwork.
# Updated to work with Python 3
# 3 editors for Config.py, Airports, and Heat Map.
# This version includes color picker
# This version adds ability to TAB between airport textboxes.
# Added Logging capabilities which is stored in /NeoSectional/logfile.log
# Added routine to grab airport details, i.e. City and State to display.
# Added admin feature that will list IP address of running RPI to ftp server with drop down to pick from.
# Added ability to load a profile into Settings Editor/
# Added System Info Page Display
# Added internet check and recheck if not currently available
# Added User App to control map that doesn't have a Rotary Switch.
# Added QR Code generator to help user load app on phone.
# Fixed bug where the url was getting appended to rather than replaced.
# Added Web based software update checker and file updater
# Added import file ability for Airports and Heat Map Data
# Fixed bug when page is loaded directly from URL box rather than the loaded page.
# Added menu item to manually check for an update
# Added menu items to display logfile.log, and console output (requires modified version of seashells
# Added LiveSectional Web Map, which recreates the builders map online.
# Fixed bug where get_led_map_info() would not get lat/Lon from XML file.
# Thank you Daniel from pilotmap.co for the change the routine that handles maps with more than 300 airports.
# Added counter to quit the script if FAA data (internet) is not available. Will try 10 times before quitting.
# print test #force bug to cause webapp.py to error out
# import needed libraries
# To install, sudo pip3 install flask
from flask import Flask, render_template, request, flash, redirect, url_for, send_file, Response
from werkzeug.utils import secure_filename
# if URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] try;
# $ sudo update-ca-certificates --fresh
# $ export SSL_CERT_DIR=/etc/ssl/certs
import urllib.request, urllib.error, urllib.parse
import xml.etree.ElementTree as ET
from datetime import datetime
import time
import os
import sys
import subprocess
import shutil
import wget
import zipfile
import folium
import folium.plugins
from folium.features import CustomIcon
from folium.features import DivIcon
from folium.vector_layers import Circle, CircleMarker, PolyLine, Polygon, Rectangle
import requests
import json
# from neopixel import * # works with python 2.7
from rpi_ws281x import * # works with python 3.7. sudo pip3 install rpi_ws281x
import socket
import logging
import logzero
from logzero import logger
import config
import admin
import scan_network
###################
from itertools import islice # Thanks Daniel
###################
# Setup rotating logfile with 3 rotations, each with a maximum filesize of 1MB:
map_name = admin.map_name
version = admin.version # Software version
loglevel = config.loglevel
loglevels = [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR]
logzero.loglevel(loglevels[loglevel]) # Choices in order; DEBUG, INFO, WARNING, ERROR
logzero.logfile("/NeoSectional/logfile.log", maxBytes=1e6, backupCount=1)
logger.info("\n\nStartup of metar-v4.py Script, Version " + version)
logger.info("Log Level Set To: " + str(loglevels[loglevel]))
# setup variables
#useip2ftp = admin.use_ftp # OBSOLETE 0 = No, 1 = Yes. Use IP to FTP for multiple boards on local network admin.
airports_file = '/NeoSectional/airports'
airports_bkup = '/NeoSectional/airports-bkup'
settings_file = '/NeoSectional/config.py'
settings_bkup = '/NeoSectional/config-bkup.py'
heatmap_file = '/NeoSectional/hmdata'
local_ftp_file = '/NeoSectional/lsinfo.txt'
settings = {}
airports = []
hmdata = []
datalist = []
newlist = []
ipaddresses = []
current_timezone = ''
loc = {}
machines = []
lat_list = []
lon_list = []
max_lat = ''
min_lat = ''
max_lon = ''
min_lon = ''
################
# misc variables
################
max_api_airports = 300 # The max amount of airports from api with one request - Thanks Daniel pilotmap.co
# Settings for web based file updating
src = '/NeoSectional' # Main directory, /NeoSectional
dest = '../previousversion' # Directory to store currently run version of software
verfilename = 'version.py' # Version Filename
zipfilename = 'ls.zip' # File that holds the names of all the files that need to be updated
source_path = 'http://www.livesectional.com/liveupdate/neoupdate/'
target_path = '/NeoSectional/'
update_available = 0 # 0 = No update available, 1 = Yes update available
update_vers = "4.000" # initiate variable
# Used to capture staton information for airport id decode for tooltip display in web pages.
apinfo_dict = {}
#orig_apurl = "https://aviationweather-cprk.ncep.noaa.gov/adds/dataserver_current/httpparam?dataSource=stations&requestType=retrieve&format=xml&stationString="
orig_apurl = "https://aviationweather.gov/api/data/stationinfo?format=xml&ids="
logger.debug(orig_apurl)
#Used to display weather and airport locations on a map
led_map_dict = {}
#led_map_url = "https://aviationweather-cprk.ncep.noaa.gov/adds/dataserver_current/httpparam?dataSource=metars&requestType=retrieve&format=json&hoursBeforeNow=2.5&mostRecentForEachStation=constraint&stationString="
#led_map_url = "https://aviationweather-cprk.ncep.noaa.gov/adds/dataserver_current/httpparam?dataSource=metars&requestType=retrieve&format=xml&hoursBeforeNow=2.5&mostRecentForEachStation=constraint&stationString="
led_map_url = "https://aviationweather.gov/api/data/metar?format=xml&hours=2.5&ids="
logger.debug(led_map_url)
# LED strip configuration:
LED_COUNT = 500 # Max Number of LED pixels.
LED_PIN = 18 # GPIO pin connected to the pixels (18 uses PWM!).
LED_FREQ_HZ = 800000 # LED signal frequency in hertz (usually 800khz)
LED_DMA = 5 # DMA channel to use for generating signal (try 5)
LED_BRIGHTNESS = 255 # Set to 0 for darkest and 255 for brightest
LED_INVERT = False # True to invert the signal (when using NPN transistor level shift)
LED_CHANNEL = 0 # set to '1' for GPIOs 13, 19, 41, 45 or 53
LED_STRIP = ws.WS2811_STRIP_RGB # Strip type and colour ordering
# instantiate strip
strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS, LED_CHANNEL, LED_STRIP)
strip.begin()
# misc variables
color = Color(255,255,255) # Color to display when cycling through LED's. White is the default.
black_color = Color(0,0,0) # Color Black used to turn off the LED.
num = 0
now = datetime.now()
timestr = (now.strftime("%H:%M:%S - %b %d, %Y"))
logger.debug(timestr)
delay_time = 5 # Delay in seconds between checking for internet availablility.
num = 0 # initialize num for airports editor
ipadd = ''
# Initiate flash session
app = Flask(__name__)
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
logger.info("Settings and Flask Have Been Setup")
##########
# Routes #
##########
# Routes for Map Display - Testing
@app.route('/map1', methods=["GET", "POST"])
def map1():
start_coords = (35.1738, -111.6541)
folium_map = folium.Map(location=start_coords, \
zoom_start = 6, height='80%', width='100%', \
control_scale = True, \
zoom_control = True, \
tiles = 'OpenStreetMap')
folium_map.add_child(folium.LatLngPopup())
folium_map.add_child(folium.ClickForMarker(popup='Marker'))
folium.plugins.Geocoder().add_to(folium_map)
folium.TileLayer('http://wms.chartbundle.com/tms/1.0.0/sec/{z}/{x}/{y}.png?origin=nw', attr='chartbundle.com', name='ChartBundle Sectional').add_to(folium_map)
folium.TileLayer('Stamen Terrain', name='Stamen Terrain').add_to(folium_map)
folium.TileLayer('CartoDB positron', name='CartoDB Positron').add_to(folium_map)
# other mapping code (e.g. lines, markers etc.)
folium.LayerControl().add_to(folium_map)
folium_map.save('../../NeoSectional/templates/map.html')
return render_template('mapedit.html', title='Map', num = 5)
# return folium_map._repr_html_()
@app.route('/touchscr', methods=["GET", "POST"])
def touchscr():
return render_template('touchscr.html', title = 'Touch Screen', num = 5, machines = machines, ipadd = ipadd)
# Route to open live console display window using seashells dependency - TESTING
# Uses the dependency, 'seashells' to display console data to web page. Follow the steps at
# https://seashells.io/ to install. THEN MUST ADD THE FOLLOWING CODE to copy seashell url to file.
# AT FILE LOCATION; sudo nano -c /usr/local/lib/python3.7/dist-packages/seashells/__init__.py
# On line 50 add this to the parser routine
# parser.add_argument('-s', '--script', type=str, default='webapp',
# help='Capture console stream from livesectional')
#
# On line 96 add this routine
# # write ip address to file - Mark Harris
# ipadd = data.decode()
# ipadd = ipadd[11:]
# script_name = args.script
# if script_name == 'webapp':
# f = open("/NeoSectional/console_ip.txt", "w")
# f.write(script_name + " " + ipadd)
# f.close()
@app.route('/open_console', methods=["GET", "POST"])
def open_console():
console_ips = []
with open("/NeoSectional/console_ip.txt", "r") as file:
for line in (file.readlines() [-1:]):
line = line.rstrip()
console_ips.append(line)
logger.info("Opening open_console in separate window")
return render_template('open_console.html', urls = console_ips, title = 'Display Console Output-'+version, num = 5, machines = machines, ipadd = ipadd, timestr = timestr)
# Routes to display logfile live, and hopefully for a dashboard
@app.route('/stream_log', methods=["GET", "POST"])
def stream_log():
global ipadd
global timestr
logger.info("Opening stream_log in separate window")
return render_template('stream_log.html', title = 'Display Logfile-'+version, num = 5, machines = machines, ipadd = ipadd, timestr = timestr)
@app.route('/stream_log1', methods=["GET", "POST"]) # Alternate route. Not currently used
def stream_log1():
def generate():
with open('/NeoSectional/logfile.log') as f:
while True:
yield "{}\n".format(f.read())
time.sleep(1)
return app.response_class(generate(), mimetype='text/plain')
# Route to manually check for update using menu item
@app.route('/test_for_update', methods=["GET", "POST"])
def test_for_update():
global update_available
url = request.referrer
if url is None:
url = 'http://' + ipadd + ':5000/index' # Use index if called from URL and not page.
temp = url.split('/')
testupdate()
if update_available == 0:
flash('No Update Available')
elif update_available == 1:
flash('UPDATE AVAILABLE, Use Map Utilities to Update')
else:
flash('New Image Available - Use Map Utilities to Download')
logger.info('Checking to see if there is an update available')
return redirect(temp[3]) # temp[3] holds name of page that called this route.
# Route to update Software if one is available and user chooses to update
@app.route('/update_info', methods=["GET", "POST"])
def update_info():
global ipadd
global timestr
with open("/NeoSectional/update_info.txt","r") as file:
content = file.readlines()
logger.debug(content)
return render_template("update_info.html", content = content, title = 'Update Info-'+version, num = 5, machines = machines, ipadd = ipadd, timestr = timestr)
@app.route('/update', methods=["GET", "POST"])
def update():
url = request.referrer
if url is None:
url = 'http://' + ipadd + ':5000/index' # Use index if called from URL and not page.
temp = url.split('/')
updatefiles()
flash('Software has been updated to v' + update_vers)
logger.info('Updated Software to version ' + update_vers)
return redirect(temp[3]) # temp[3] holds name of page that called this route.
@app.route('/update_page', methods=["GET", "POST"])
def update_page():
global ipadd
global timestr
return render_template("update_page.html", title = 'Software Update Information-'+version, num = 5, machines = machines, ipadd = ipadd, timestr = timestr)
# Route to display map's airports on a digital map.
@app.route('/led_map', methods=["GET", "POST"])
def led_map():
global hmdata
global airports
global led_map_dict
global settings
global strip
global num
global ipadd
global strip
global ipaddresses
global timestr
global version
global map_name
global current_timezone
templateData = {
'title': 'LiveSectional Map-'+version,
'hmdata': hmdata,
'airports': airports,
'settings': settings,
'ipadd': ipadd,
'strip': strip,
'ipaddresses': ipaddresses,
'timestr': timestr,
'num': num,
'apinfo_dict': apinfo_dict,
'led_map_dict': led_map_dict,
'timestr': timestr,
'version': version,
'update_available': update_available,
'update_vers': update_vers,
'current_timezone': current_timezone,
'machines': machines,
'map_name':map_name,
'max_lat': max_lat,
'min_lat': min_lat,
'max_lon': max_lon,
'min_lon': min_lon,
}
# Update flight categories
get_led_map_info()
points = []
title_coords = (max_lat,(float(max_lon)+float(min_lon))/2)
start_coords = ((float(max_lat)+float(min_lat))/2, (float(max_lon)+float(min_lon))/2)
# Initialize Map
folium_map = folium.Map(location=start_coords, \
zoom_start = 5, height='100%', width='100%', \
control_scale = True, \
zoom_control = True, \
tiles = 'OpenStreetMap')
# Place map within bounds of screen
folium_map.fit_bounds([[min_lat, min_lon], [max_lat, max_lon]])
# Set Marker Color by Flight Category
for j,led_ap in enumerate(led_map_dict):
if led_map_dict[led_ap][2] == "VFR":
color = 'green'
elif led_map_dict[led_ap][2] == "MVFR":
color = 'blue'
elif led_map_dict[led_ap][2] == "IFR":
color = 'red'
elif led_map_dict[led_ap][2] == "LIFR":
color = 'violet'
else:
color = 'black'
# Get Pin Number to display in popup
if led_ap in airports:
pin_num = airports.index(led_ap)
else:
pin_num = None
try:
pop_url = '<a href="https://nfdc.faa.gov/nfdcApps/services/ajv5/airportDisplay.jsp?airportId='+led_ap+'"target="_blank">'
popup = pop_url+"<b>"+led_ap+"</b><br>"+apinfo_dict[led_ap][0]+', '+apinfo_dict[led_ap][1]\
+"</a><br>Pin Number = "+str(pin_num)+"<br><b><font size=+2 color="+color+">"+led_map_dict[led_ap][2]+"</font></b>"
except:
pop_url = ""
popup = ""
pass
# pop_url = '<a href="https://nfdc.faa.gov/nfdcApps/services/ajv5/airportDisplay.jsp?airportId='+led_ap+'"target="_blank">'
# popup = pop_url+"<b>"+led_ap+"</b><br>"+apinfo_dict[led_ap][0]+', '+apinfo_dict[led_ap][1]\
# +"</a><br>Pin Number = "+str(pin_num)+"<br><b><font size=+2 color="+color+">"+led_map_dict[led_ap][2]+"</font></b>"
# Add airport markers with proper color to denote flight category
folium.CircleMarker(
radius=7,
fill=True,
color=color,
location=[led_map_dict[led_ap][0], led_map_dict[led_ap][1]],
popup=popup,
tooltip=str(led_ap)+"<br>Pin "+str(pin_num),
weight=6,
).add_to(folium_map)
# Custom Icon Code - Here for possible future use
# url = "../../NeoSectional/static/{}".format
# icon_image = url("dot1.gif")
# icon = CustomIcon(
# icon_image,
# icon_size=(48, 48),
# )
# marker = folium.Marker(
# location=[45.3288, -121.6625], icon=icon, popup="Mt. Hood Meadows"
# )
# folium_map.add_child(marker)
# Add lines between airports. Must make lat/lons floats otherwise recursion error occurs.
for pin_ap in airports:
if pin_ap in led_map_dict:
pin_index = airports.index(pin_ap)
points.insert(pin_index, [float(led_map_dict[pin_ap][0]), float(led_map_dict[pin_ap][1])])
logger.debug(points)
folium.PolyLine(points, color='grey', weight=2.5, opacity=1, dash_array='10').add_to(folium_map)
# Add Title to the top of the map
folium.map.Marker(
title_coords,
icon=DivIcon(
icon_size=(500,36),
icon_anchor=(150,64),
html='<div style="font-size: 24pt"><b>LiveSectional Map Layout</b></div>',
)
).add_to(folium_map)
# Extra features to add if desired
folium_map.add_child(folium.LatLngPopup())
# folium.plugins.Terminator().add_to(folium_map)
# folium_map.add_child(folium.ClickForMarker(popup='Marker'))
folium.plugins.Geocoder().add_to(folium_map)
folium.plugins.Fullscreen(
position="topright",
title="Full Screen",
title_cancel="Exit Full Screen",
force_separate_button=True,
).add_to(folium_map)
folium.TileLayer('http://wms.chartbundle.com/tms/1.0.0/sec/{z}/{x}/{y}.png?origin=nw', attr='chartbundle.com', name='ChartBundle Sectional').add_to(folium_map)
folium.TileLayer('Stamen Terrain', name='Stamen Terrain').add_to(folium_map)
folium.TileLayer('CartoDB positron', name='CartoDB Positron').add_to(folium_map)
folium.LayerControl().add_to(folium_map)
folium_map.save('../../NeoSectional/templates/map.html')
logger.info("Opening led_map in separate window")
return render_template('led_map.html', **templateData)
# Route to expand RPI's file system.
@app.route('/expandfs', methods=["GET", "POST"])
def expandfs():
global hmdata
global airports
global settings
global strip
global num
global ipadd
global strip
global ipaddresses
global timestr
global version
global map_name
global current_timezone
if request.method == "POST":
os.system('sudo raspi-config --expand-rootfs')
flash('File System has been expanded')
flash('NOTE: Select "Reboot RPI" from "Map Functions" Menu for changes to take affect')
return redirect('expandfs')
else:
templateData = {
'title': 'Expand File System-'+version,
'hmdata': hmdata,
'airports': airports,
'settings': settings,
'ipadd': ipadd,
'strip': strip,
'ipaddresses': ipaddresses,
'timestr': timestr,
'num': num,
'apinfo_dict': apinfo_dict,
'timestr': timestr,
'version': version,
'map_name':map_name,
'update_available': update_available,
'update_vers': update_vers,
'current_timezone': current_timezone,
'machines': machines
}
logger.info("Opening expand file system page")
return render_template('expandfs.html', **templateData)
# Route to display and change Time Zone information.
@app.route('/tzset', methods=["GET", "POST"])
def tzset():
global hmdata
global airports
global settings
global strip
global num
global ipadd
global strip
global ipaddresses
global timestr
global version
global map_name
global current_timezone
now = datetime.now()
timestr = (now.strftime("%H:%M:%S - %b %d, %Y"))
currtzinfolist = []
if request.method == "POST":
timezone = request.form['tzselected']
flash('Timezone set to ' + timezone)
flash('NOTE: Select "Reboot RPI" from "Map Functions" Menu for changes to take affect')
os.system('sudo timedatectl set-timezone ' + timezone)
return redirect('tzset')
tzlist = subprocess.run(['timedatectl', 'list-timezones'], stdout=subprocess.PIPE).stdout.decode('utf-8')
tzoptionlist = tzlist.split()
currtzinfo = subprocess.run(['timedatectl', 'status'], stdout=subprocess.PIPE).stdout.decode('utf-8')
tztemp = currtzinfo.split('\n')
for j in range(len(tztemp)):
if j==0 or j==1 or j==3:
currtzinfolist.append(tztemp[j])
current_timezone = tztemp[3]
templateData = {
'title': 'Timezone Set-'+version,
'hmdata': hmdata,
'airports': airports,
'settings': settings,
'ipadd': ipadd,
'strip': strip,
'ipaddresses': ipaddresses,
'timestr': timestr,
'num': num,
'apinfo_dict': apinfo_dict,
'timestr': timestr,
'version': version,
'map_name':map_name,
'update_available': update_available,
'update_vers': update_vers,
'tzoptionlist': tzoptionlist,
'currtzinfolist': currtzinfolist,
'current_timezone': current_timezone,
'machines': machines
}
logger.info("Opening Time Zone Set page")
return render_template('tzset.html', **templateData)
# Route to display system information.
@app.route('/yield')
def yindex():
def inner():
proc = subprocess.Popen(
['/NeoSectional/info-v4.py'], # 'dmesg' call something with a lot of output so we can see it
shell=True,
stdout=subprocess.PIPE
)
for line in iter(proc.stdout.readline,b''):
time.sleep(.01) # Don't need this just shows the text streaming
line = line.decode("utf-8")
yield line.strip() + '<br/>\n'
logger.info("Opening yeild to display system info in separate window")
return Response(inner(), mimetype='text/html') # text/html is required for most browsers to show this info.
# Route to create QR Code to display next to map so user can use an app to control the map
@app.route('/qrcode', methods=["GET", "POST"])
def qrcode():
global ipadd
qraddress = 'http://' + ipadd.strip() + ':5000/lsremote'
logger.info("Opening qrcode in separate window")
return render_template('qrcode.html', qraddress = qraddress)
#Routes for homepage
@app.route('/', methods=["GET", "POST"])
@app.route('/index', methods=["GET", "POST"])
def index ():
global hmdata
global airports
global settings
global strip
global num
global ipadd
global strip
global ipaddresses
global timestr
global version
global map_name
now = datetime.now()
timestr = (now.strftime("%H:%M:%S - %b %d, %Y"))
templateData = {
'title': 'LiveSectional Home-'+version,
'hmdata': hmdata,
'airports': airports,
'settings': settings,
'ipadd': ipadd,
'strip': strip,
'ipaddresses': ipaddresses,
'timestr': timestr,
'num': num,
'apinfo_dict': apinfo_dict,
'timestr': timestr,
'current_timezone': current_timezone,
'update_available': update_available,
'update_vers': update_vers,
'version': version,
'machines': machines,
'map_name':map_name
}
# flash(machines) # Debug
logger.info("Opening Home Page/Index")
return render_template('index.html', **templateData)
# Routes to download airports, logfile.log and config.py to local computer
@app.route('/download_ap', methods=["GET", "POST"])
def downloadairports ():
logger.info("Downloaded Airport File")
path = "airports"
return send_file(path, as_attachment=True)
@app.route('/download_cf', methods=["GET", "POST"])
def downloadconfig ():
logger.info("Downloaded Config File")
path = "config.py"
return send_file(path, as_attachment=True)
@app.route('/download_log', methods=["GET", "POST"])
def downloadlog ():
logger.info("Downloaded Logfile")
path = "logfile.log"
return send_file(path, as_attachment=True)
@app.route('/download_hm', methods=["GET", "POST"])
def downloadhm ():
logger.info("Downloaded Heat Map data file")
path = "hmdata"
return send_file(path, as_attachment=True)
# Routes for Heat Map Editor
@app.route("/hmedit", methods=["GET", "POST"])
def hmedit():
logger.info("Opening hmedit.html")
global strip
global num
global ipadd
global strip
global ipaddresses
global timestr
global map_name
now = datetime.now()
timestr = (now.strftime("%H:%M:%S - %b %d, %Y"))
readhmdata(heatmap_file) # read Heat Map data file
logger.debug(ipadd) # debug to display ip address on console
templateData = {
'title': 'Heat Map Editor-'+version,
'hmdata': hmdata,
'ipadd': ipadd,
'strip': strip,
'ipaddresses': ipaddresses,
'timestr': timestr,
'num': num,
'current_timezone': current_timezone,
'update_available': update_available,
'update_vers': update_vers,
'apinfo_dict': apinfo_dict,
'machines': machines,
'map_name':map_name
}
return render_template('hmedit.html', **templateData)
@app.route("/hmpost", methods=["GET", "POST"])
def handle_hmpost_request():
logger.info("Saving Heat Map Data File")
global hmdata
global strip
global num
global ipadd
global ipaddresses
global timestr
newlist = []
if request.method == "POST":
data = request.form.to_dict()
logger.debug(data) # debug
j = 0
for key in data:
value = data.get(key)
if value == '':
value = '0'
newlist.append(airports[j] + " " + value)
j += 1
writehmdata(newlist, heatmap_file)
get_apinfo()
flash('Heat Map Data Successfully Saved')
return redirect("hmedit")
# Import a file to populate Heat Map Data. Must Save Airports to keep
@app.route("/importhm", methods=["GET", "POST"])
def importhm():
logger.info("Importing Heat Map File")
global ipaddresses
global airports
global timestr
global hmdata
hmdata = []
if 'file' not in request.files:
flash('No File Selected')
return redirect('./hmedit')
file = request.files['file']
if file.filename == '':
flash('No File Selected')
return redirect('./hmedit')
filedata = file.read()
tmphmdata = bytes.decode(filedata)
logger.debug(tmphmdata)
hmdata = tmphmdata.split('\n')
hmdata.pop()
logger.debug(hmdata)
templateData = {
'title': 'Heat Map Editor-'+version,
'hmdata': hmdata,
'ipadd': ipadd,
'strip': strip,
'ipaddresses': ipaddresses,
'timestr': timestr,
'num': num,
'current_timezone': current_timezone,
'update_available': update_available,
'update_vers': update_vers,
'apinfo_dict': apinfo_dict,
'machines': machines
}
flash('Heat Map Imported - Click "Save Heat Map File" to save')
return render_template("hmedit.html", **templateData)
# Routes for Airport Editor
@app.route("/apedit", methods=["GET", "POST"])
def apedit():
logger.info("Opening apedit.html")
global airports
global strip
global num
global ipadd
global strip
global ipaddresses
global timestr
global map_name
now = datetime.now()
timestr = (now.strftime("%H:%M:%S - %b %d, %Y"))
readairports(airports_file) # Read airports file.
logger.debug(ipadd) # debug to display ip address on console
templateData = {
'title': 'Airports Editor-'+version,
'airports': airports,
'ipadd': ipadd,
'strip': strip,
'ipaddresses': ipaddresses,
'timestr': timestr,
'num': num,
'current_timezone': current_timezone,
'update_available': update_available,
'update_vers': update_vers,
'apinfo_dict': apinfo_dict,
'machines': machines,
'map_name':map_name
}
return render_template('apedit.html', **templateData)
@app.route("/numap", methods=["GET", "POST"])
def numap():
logger.info("Updating Number of Airports in airport file")
global ipaddresses
global airports
global timestr
if request.method == "POST":
numap = int(request.form["numofap"])
print (numap)
readairports(airports_file)
newnum = numap - int(len(airports))
if newnum < 0:
airports = airports[:newnum]
else:
for n in range(len(airports), numap):
airports.append("NULL")
templateData = {
'title': 'Airports Editor-'+version,
'airports': airports,
'ipadd': ipadd,
'strip': strip,
'ipaddresses': ipaddresses,
'timestr': timestr,
'num': num,
'current_timezone': current_timezone,
'update_available': update_available,
'update_vers': update_vers,
'apinfo_dict': apinfo_dict,
'machines': machines
}
flash('Number of LEDs Updated - Click "Save Airports" to save.')
return render_template('apedit.html', **templateData)
@app.route("/appost", methods=["GET", "POST"])
def handle_appost_request():
logger.info("Saving Airport File")
global airports
global hmdata
global strip
global num
global ipadd
global ipaddresses
global timestr
if request.method == "POST":
data = request.form.to_dict()
logging.debug(data) # debug
writeairports(data, airports_file)
readairports(airports_file)
get_apinfo() # decode airports to get city and state to display
# update size and data of hmdata based on saved airports file.
readhmdata(heatmap_file) # get heat map data to update with newly edited airports file
if len(hmdata) > len(airports): # adjust size of hmdata list if length is larger than airports
num = len(hmdata) - len(airports)
hmdata = hmdata[:-num]
elif len(hmdata) < len(airports): # adjust size of hmdata list if length is smaller than airports
for n in range(len(hmdata), len(airports)):
hmdata.append('NULL 0')
for index, airport in enumerate(airports): # now that both lists are same length, be sure the data matches
ap, *_ = hmdata[index].split()
if ap != airport:
hmdata[index] = (airport + ' 0') # save changed airport and assign zero landings to it in hmdata
writehmdata(hmdata, heatmap_file)
flash('Airports Successfully Saved')
return redirect("apedit")
@app.route("/ledonoff", methods=["GET", "POST"])
def ledonoff():
logger.info("Controlling LED's on/off")
global airports
global strip
global num
global ipadd
global ipaddresses
global timestr
for i in range(strip.numPixels()):
strip.setPixelColor(i, Color(0,0,0))
strip.show()
if request.method == "POST":
readairports(airports_file)
if "buton" in request.form:
num = int(request.form['lednum'])
logger.info("LED " + str(num) + " On")
strip.setPixelColor(num, Color(155,155,155))
strip.show()
flash('LED ' + str(num) + ' On')
elif "butoff" in request.form:
num = int(request.form['lednum'])
logger.info("LED " + str(num) + " Off")
strip.setPixelColor(num, Color(0,0,0))
strip.show()
flash('LED ' + str(num) + ' Off')
elif "butup" in request.form:
logger.info("LED UP")
num = int(request.form['lednum'])
strip.setPixelColor(num, Color(0,0,0))
num = num + 1
if num > len(airports):
num = len(airports)
strip.setPixelColor(num, Color(155,155,155))
strip.show()
flash('LED ' + str(num) + ' should be On')
elif "butdown" in request.form:
logger.info("LED DOWN")
num = int(request.form['lednum'])
strip.setPixelColor(num, Color(0,0,0))
num = num - 1
if num < 0:
num = 0
strip.setPixelColor(num, Color(155,155,155))
strip.show()
flash('LED ' + str(num) + ' should be On')
elif "butall" in request.form:
logger.info("LED All ON")
num = int(request.form['lednum'])
for num in range(len(airports)):
strip.setPixelColor(num, Color(155,155,155))
strip.show()
flash('All LEDs should be On')
num=0
elif "butnone" in request.form:
logger.info("LED All OFF")
num = int(request.form['lednum'])
for num in range(len(airports)):
strip.setPixelColor(num, Color(0,0,0))
strip.show()
flash('All LEDs should be Off')
num=0
else: # if tab is pressed
logger.info("LED Edited")
num = int(request.form['lednum'])
flash('LED ' + str(num) + ' Edited')
templateData = {
'title': 'Airports File Editor-'+version,
'airports': airports,
'ipadd': ipadd,
'strip': strip,
'ipaddresses': ipaddresses,
'timestr': timestr,
'num': num,
'update_available': update_available,
'update_vers': update_vers,
'apinfo_dict': apinfo_dict,
'machines': machines
}
return render_template("apedit.html", **templateData)
# Import a file to populate airports. Must Save Airports to keep
@app.route("/importap", methods=["GET", "POST"])
def importap():
logger.info("Importing Airports File")
global ipaddresses
global airports
global timestr
if 'file' not in request.files:
flash('No File Selected')
return redirect('./apedit')
file = request.files['file']
if file.filename == '':
flash('No File Selected')
return redirect('./apedit')
filedata = file.read()
fdata = bytes.decode(filedata)
logger.debug(fdata)