From c9f159a81a4ad7de2e07ed8952a807f9c2a63c29 Mon Sep 17 00:00:00 2001 From: markyharris Date: Mon, 1 Jan 2024 17:43:12 -0700 Subject: [PATCH] Version 2.1 --- aviationweather.schema | 482 +++++++++++++++++++++++++ data.txt | 9 +- metar.html | 198 ----------- metar_display.py | 15 +- metar_layouts.py | 791 ++++++++++++++--------------------------- metar_main.py | 15 +- metar_poweroff.py | 4 +- metar_remarks.py | 3 + metar_routines.py | 245 ++++++++++++- metar_settings.py | 17 +- temp_pic.png | Bin 134290 -> 47961 bytes templates/metar.html | 181 +++++++--- webapp.py | 73 +++- 13 files changed, 1206 insertions(+), 827 deletions(-) create mode 100644 aviationweather.schema mode change 100644 => 100755 data.txt delete mode 100644 metar.html diff --git a/aviationweather.schema b/aviationweather.schema new file mode 100644 index 0000000..26618d0 --- /dev/null +++ b/aviationweather.schema @@ -0,0 +1,482 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://aviationweather.gov/data/schema/metar1_0.json", + "description": "Set of surface observations (METARs)", + "type": "array", + "default": [], + "title": "Observations", + "items": { + "type": "object", + "default": {}, + "title": "METARs", + "required": [ + "metar_id", + "icaoId", + "receiptTime", + "obsTime", + "reportTime", + "temp", + "dewp", + "wdir", + "wspd", + "wgst", + "visib", + "altim", + "slp", + "qcField", + "wxString", + "presTend", + "maxT", + "minT", + "maxT24", + "minT24", + "precip", + "pcp3hr", + "pcp6hr", + "pcp24hr", + "snow", + "vertVis", + "metarType", + "rawOb", + "mostRecent", + "lat", + "lon", + "elev", + "prior", + "name", + "clouds" + ], + "properties": { + "metar_id": { + "type": "integer", + "default": 0, + "title": "Unique id number (autoincrement)", + "examples": [ + 93352910 + ] + }, + "icaoId": { + "type": "string", + "default": "", + "title": "ICAO identifier", + "examples": [ + "KORD" + ] + }, + "receiptTime": { + "type": "string", + "default": "", + "title": "The time the observation was received (yyyy-mm-dd hh:mm:ss)", + "examples": [ + "2023-11-03 21:54:03" + ] + }, + "obsTime": { + "type": "integer", + "default": 0, + "title": "The observation time (unix timestamp)", + "examples": [ + 1699048260 + ] + }, + "reportTime": { + "type": "string", + "default": "", + "title": "The time of the report (yyyy-mm-dd hh:mm:ss)", + "examples": [ + "2023-11-03 22:00:00" + ] + }, + "temp": { + "type": "number", + "default": 0, + "title": "Temperature in Celcius", + "examples": [ + 14.4 + ] + }, + "dewp": { + "type": [ + "number", + "null" + ], + "default": null, + "title": "Dewpoint temperature in Celcius", + "examples": [ + 2.8 + ] + }, + "wdir": { + "type": [ + "integer", + "string" + ], + "default": 0, + "title": "Wind direction in degrees or VRB for variable winds", + "examples": [ + 230, + "VRB" + ] + }, + "wspd": { + "type": [ + "integer", + "null" + ], + "default": null, + "title": "Wind speed in knots", + "examples": [ + 6 + ] + }, + "wgst": { + "type": [ + "integer", + "null" + ], + "default": null, + "title": "Wind gusts in knots", + "examples": [ + 12 + ] + }, + "visib": { + "type": [ + "number", + "string", + "null" + ], + "default": null, + "title": "Visibility in statute miles, 10+ is greater than 10 sm", + "examples": [ + 3, + "10+", + null + ] + }, + "altim": { + "type": "number", + "default": null, + "title": "Altimeter setting in hectoPascals", + "examples": [ + 1016.3 + ] + }, + "slp": { + "type": [ + "number", + "null" + ], + "default": null, + "title": "Sea level pressure in hectoPascals", + "examples": [ + 1016.2, + null + ] + }, + "qcField": { + "type": "integer", + "default": 0, + "title": "Quality control bitfield - 0x1=corrected, 0x2=auto, 0x4=auto_station, 0x8=maintenance_indicator_on, 0x10=no_signal, 0x20=lightning_sensor_off, 0x40=freezing_rain_sensor_off, 0x80=present_weather_sensor_off", + "examples": [ + 4 + ] + }, + "wxString": { + "type": [ + "string", + "null" + ], + "default": null, + "title": "Encoded present weather string", + "examples": [ + "-RA", + "SN", + "TSRA" + ] + }, + "presTend": { + "type": [ + "number", + "null" + ], + "default": null, + "title": "Pressure tendency over last 3 hours in hectoPascals", + "examples": [ + null + ] + }, + "maxT": { + "type": [ + "number", + "null" + ], + "default": null, + "title": "Maximum temperature over last 6 hours in Celcius", + "examples": [ + 23.4, + null + ] + }, + "minT": { + "type": [ + "number", + "null" + ], + "default": null, + "title": "Minimum temperature over last 6 hours in Celcius", + "examples": [ + 12.3, + null + ] + }, + "maxT24": { + "type": [ + "number", + "null" + ], + "default": null, + "title": "Maximum temperature over last 24 hours in Celcius", + "examples": [ + 23.4 + ] + }, + "minT24": { + "type": [ + "number", + "null" + ], + "default": null, + "title": "Minimum temperature over last 24 hours in Celcius", + "examples": [ + 12.3 + ] + }, + "precip": { + "type": [ + "number", + "null" + ], + "default": null, + "title": "Precipitation over last hour in inches", + "examples": [ + 0.01, + null + ] + }, + "pcp3hr": { + "type": "null", + "default": null, + "title": "Precipitation over last 3 hours in inches", + "examples": [ + 0.1, + null + ] + }, + "pcp6hr": { + "type": "null", + "default": null, + "title": "Precipitation over last 6 hours in inches", + "examples": [ + 0.23, + null + ] + }, + "pcp24hr": { + "type": [ + "number", + "null" + ], + "default": null, + "title": "Precipitation over last 24 hours in inches", + "examples": [ + 0.53, + null + ] + }, + "snow": { + "type": [ + "number", + "null" + ], + "default": null, + "title": "Snow depth in inches", + "examples": [ + 1, + null + ] + }, + "vertVis": { + "type": [ + "integer", + "null" + ], + "default": null, + "title": "Vertical visibility in feet", + "examples": [ + 100, + null + ] + }, + "metarType": { + "type": "string", + "default": "", + "title": "Type of encoding", + "enum": [ + "METAR", + "SPECI", + "SYNOP", + "BUOY", + "CMAN" + ], + "examples": [ + "METAR" + ] + }, + "rawOb": { + "type": "string", + "default": "", + "title": "Raw text of observation", + "examples": [ + "KORD 032151Z 23006KT 10SM BKN110 OVC250 14/03 A3000 RMK AO2 SLP162 VIRGA OHD T01440028" + ] + }, + "mostRecent": { + "type": "integer", + "default": 0, + "title": "Most recent flag (1=most recent, 0=not)", + "examples": [ + 1 + ] + }, + "lat": { + "type": "number", + "default": 0, + "title": "Latitude of site in degrees", + "examples": [ + 41.9602 + ] + }, + "lon": { + "type": "number", + "default": 0, + "title": "Longitude of site in degrees", + "examples": [ + -87.9316 + ] + }, + "elev": { + "type": "integer", + "default": 0, + "title": "Elevation of site in meters", + "examples": [ + 202 + ] + }, + "prior": { + "type": "integer", + "default": 0, + "title": "Priority of site for proessive disclosure (0-9)", + "examples": [ + 0 + ] + }, + "name": { + "type": "string", + "default": "", + "title": "Full name of the site", + "examples": [ + "Chicago/O'Hare Intl, IL, US" + ] + }, + "clouds": { + "type": "array", + "default": [], + "title": "Clouds", + "items": { + "type": "object", + "title": "Cloud layer", + "required": [ + "cover", + "base" + ], + "properties": { + "cover": { + "type": "string", + "title": "Cover coverage", + "enum": [ + "CLR", + "CAVOK", + "FEW", + "SCT", + "BKN", + "OVC", + "OVX" + ], + "examples": [ + "BKN", + "OVC" + ] + }, + "base": { + "type": "integer", + "title": "Cloud base in feet", + "examples": [ + 11000, + 25000 + ] + } + } + } + } + }, + "examples": [ + { + "metar_id": 93352910, + "icaoId": "KORD", + "receiptTime": "2023-11-03 21:54:03", + "obsTime": 1699048260, + "reportTime": "2023-11-03 22:00:00", + "temp": 14.4, + "dewp": 2.8, + "wdir": 230, + "wspd": 6, + "wgst": null, + "visib": "10+", + "altim": 1016, + "slp": 1016.2, + "qcField": 4, + "wxString": null, + "presTend": null, + "maxT": null, + "minT": null, + "maxT24": null, + "minT24": null, + "precip": null, + "pcp3hr": null, + "pcp6hr": null, + "pcp24hr": null, + "snow": null, + "vertVis": null, + "metarType": "METAR", + "rawOb": "KORD 032151Z 23006KT 10SM BKN110 OVC250 14/03 A3000 RMK AO2 SLP162 VIRGA OHD T01440028", + "mostRecent": 1, + "lat": 41.9602, + "lon": -87.9316, + "elev": 202, + "prior": 0, + "name": "Chicago/O'Hare Intl, IL, US", + "clouds": [ + { + "cover": "BKN", + "base": 11000 + }, + { + "cover": "OVC", + "base": 25000 + } + ] + } + ] + } +} \ No newline at end of file diff --git a/data.txt b/data.txt old mode 100644 new mode 100755 index 5cd238f..700dfcc --- a/data.txt +++ b/data.txt @@ -1,4 +1,9 @@ -kdal --2 +KFLG +-1 +60 +1 +2 0 0 +1 +1 diff --git a/metar.html b/metar.html deleted file mode 100644 index 6c40735..0000000 --- a/metar.html +++ /dev/null @@ -1,198 +0,0 @@ - - - - METAR E-Paper Selector - - - - - - - - - - - - -

- - - - - - -

- - -

-
-
- - -
-

METAR E-Paper Display Selector

- Use 4 char Airport ICAO ID. If left blank - the default values in settings will be used.
- -
- -
- -

- -

-
- -
- https://www.world-airport-codes.com/ to lookup ICAO ID's -

- -
- -

- -
- -

- -
- -
- Note: Auto Interval changes the update based on Flight Category.
- VFR=1 hour, MVFR=30 mins, IFR=20 mins and LIFR=10 mins

- - -

-
-

- -


- -
- {% with messages = get_flashed_messages() %} - {% if messages %} -
- {% for message in messages %} -
{{ message }} - {% endfor %} - - {% endif %} - {% endwith %} -
-
- -
-

-

- -

-
-

-

- -

-
-

-

- -

-
-

-

- -

-
-

-

- -

-
-

-

- -

-
-

-

- -

-
-

-

- -

- - - - -
-
- - diff --git a/metar_display.py b/metar_display.py index 5fee53c..fa33240 100755 --- a/metar_display.py +++ b/metar_display.py @@ -1,5 +1,8 @@ # metar_display.py # Metar Display - Mark Harris +# Version 2.1 +# UPDATED FAA API 12-2023, https://aviationweather.gov/data/api/ +# # Altered from https://github.com/aerodynamics-py/WEATHER_STATION_PI # # Added a number of bold fonts @@ -60,17 +63,21 @@ class Metar: def __init__(self, airport): self.data = requests.get( - f"https://api.weather.gov/stations/"+airport+"/observations/latest", timeout=5).json() +# f"https://api.weather.gov/stations/"+airport+"/observations/latest", timeout=5).json() + f"https://aviationweather.gov/api/data/metar?ids="+airport+"&format=json&hours=2.5").json() self.data2 = requests.get( - f"https://api.weather.gov/stations/"+airport, timeout=5).json() +# f"https://api.weather.gov/stations/"+airport, timeout=5).json() + f"https://aviationweather.gov/api/data/metar?ids="+airport+"&format=json&hours=2.5").json() requests.session().close() pass def update(self, airport): self.data = requests.get( - f"https://api.weather.gov/stations/"+airport+"/observations/latest", timeout=5).json() +# f"https://api.weather.gov/stations/"+airport+"/observations/latest", timeout=5).json() + f"https://aviationweather.gov/api/data/metar?ids="+airport+"&format=json&hours=2.5").json() self.data2 = requests.get( - f"https://api.weather.gov/stations/"+airport, timeout=5).json() +# f"https://api.weather.gov/stations/"+airport, timeout=5).json() + f"https://aviationweather.gov/api/data/metar?ids="+airport+"&format=json&hours=2.5").json() requests.session().close() return self.data, self.data2 diff --git a/metar_layouts.py b/metar_layouts.py index 5ce3871..22d25a1 100755 --- a/metar_layouts.py +++ b/metar_layouts.py @@ -1,5 +1,7 @@ # metar_layouts.py # Layouts for Metar Display - Mark Harris +# Version 2.1 +# UPDATED FAA API 12-2023, https://aviationweather.gov/data/api/ # # Each Layout offers a different look and amount of information. # More can be created by starting at the bottom and pasting the following; @@ -11,7 +13,7 @@ # # Get metar data along with flightcategory and related icon # decoded_airport,decoded_time,decoded_wndir,decoded_wnspd,decoded_wngust,decoded_vis,\ # decoded_alt,decoded_temp,decoded_dew,decoded_cloudlayers,decoded_weather,decoded_rvr \ -# = decode_rawmessage(metar.data["properties"]["rawMessage"]) # pass airport name +# = decode_rawmessage(get_rawOb(metar)) # flightcategory, icon = flight_category(metar) # airport = decoded_airport # @@ -20,6 +22,7 @@ # metar_main.py to the variable "layout_list" # epaper.html to the ' - +

+ + + + +

+ + + + + + + + + + + +
+
+ +
+ https://www.world-airport-codes.com/ to lookup ICAO ID's +
+
+ +
+ Note: There are options to randomly display or cyle through all layouts. +
+
+ +
+ Note: Airport Info provides basic info about the station.
+ Remarks will decode info from the Remarks field of METAR +
+
+ -
- https://www.world-airport-codes.com/ to lookup ICAO ID's -

- -
- -

- -
- -

- -
- -
- Note: Auto Interval changes the update based on Flight Category.
- VFR=1 hour, MVFR=30 mins, IFR=20 mins and LIFR=10 mins

+ +
+ Note: Auto Interval changes the update based on Flight Category.
+ VFR=1 hour, MVFR=30 mins, IFR=20 mins and LIFR=10 mins
+

+ + + + + + + + + + + + + + + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
-
- -

+ + +

+


@@ -190,9 +256,8 @@

METAR E-Paper Display Selector

- - - + +
Copyright 2024, 2025 - Mark Harris, https://github.com/markyharris/metar
diff --git a/webapp.py b/webapp.py index f67a41e..6263c90 100755 --- a/webapp.py +++ b/webapp.py @@ -1,5 +1,8 @@ # webapp.py - Mark Harris # for E-Paper display +# Version 2.1 +# UPDATED FAA API 12-2023, https://aviationweather.gov/data/api/ +# # This will provide a web interface to control the e-Paper display. from flask import Flask, render_template, request, flash, redirect, url_for, send_file, Response @@ -13,22 +16,37 @@ # variables PATH = '/home/pi/metar/' -rem_data = "0" +data_field4 = "0" # Routes for flask @app.route("/", methods=["GET", "POST"]) @app.route("/index", methods=["GET", "POST"]) @app.route("/metar", methods=["GET", "POST"]) def metar(): - data_field1, data_field2, data_field3, rem_data = airport, use_disp_format, interval, use_remarks - data_field1, data_field2, data_field3, rem_data = get_data() + # Grab default settings from 'metar_settings.py' used if web admin is not used. + data_field1,data_field2,data_field3,data_field4, \ + data_field5,data_field6,data_field7,data_field8,data_field9 \ + = airport,use_disp_format,interval,use_remarks, \ + wind_speed_units,cloud_layer_units,visibility_units,temperature_units,pressure_units + + # Now read 'data.txt' and populate these fields with the settings stored from web admin. + data_field1, data_field2, data_field3, data_field4, \ + data_field5,data_field6,data_field7,data_field8,data_field9 \ + = get_data() if request.method == "POST": display = request.form['display'] - data_field1 = request.form['data_field1'] - data_field2 = request.form['data_field2'] - data_field3 = request.form['data_field3'] - rem_data = request.form['rem_data'] + data_field1 = request.form['data_field1'] # airport + data_field2 = request.form['data_field2'] # use_disp_format + data_field3 = request.form['data_field3'] # interval + data_field4 = request.form['data_field4'] # use_remarks + + data_field5 = request.form['data_field5'] # wind_speed_units + data_field6 = request.form['data_field6'] # cloud_layer_units + data_field7 = request.form['data_field7'] # visibility_units + data_field8 = request.form['data_field8'] # temperature_units + data_field9 = request.form['data_field9'] # pressure_units + if display == "powerdown": shutdown() @@ -47,25 +65,33 @@ def metar(): else: os.system("ps -ef | grep 'metar_main.py' | awk '{print $2}' | xargs sudo kill") - os.system('sudo python3 ' + PATH + 'metar_main.py '+ data_field1+' '+data_field2+' '+data_field3+' '+rem_data+' &') - flash("Running E-Paper Metar Airport ID = " + data_field1) + os.system('sudo python3 ' + PATH + 'metar_main.py '+ data_field1+' '+data_field2+' '+data_field3+' '+data_field4+' &') + flash("Running E-Paper Metar Airport ID = " + data_field1.upper()) if data_field2 != "": - flash("Display Layout = " + data_field2) + flash("Display Layout = " + data_field2.upper()) print(data_field1) # debug - write_data(data_field1, data_field2, data_field3, rem_data) - return render_template("metar.html", data_field1=data_field1, data_field2=data_field2, data_field3=data_field3, rem_data=rem_data) + write_data(data_field1,data_field2,data_field3,data_field4,data_field5,data_field6,data_field7,data_field8,data_field9) + return render_template("metar.html",data_field1=data_field1,data_field2=data_field2,data_field3=data_field3,data_field4=data_field4, \ + data_field5=data_field5,data_field6=data_field6,data_field7=data_field7,data_field8=data_field8,data_field9=data_field9) else: - return render_template("metar.html", data_field1=data_field1, data_field2=data_field2, data_field3=data_field3, rem_data=rem_data) + return render_template("metar.html", data_field1=data_field1,data_field2=data_field2,data_field3=data_field3,data_field4=data_field4, \ + data_field5=data_field5,data_field6=data_field6,data_field7=data_field7,data_field8=data_field8,data_field9=data_field9) # Functions -def write_data(data_field1, data_field2, data_field3, rem_data): +def write_data(data_field1,data_field2,data_field3,data_field4,data_field5,data_field6,data_field7,data_field8,data_field9): f= open(PATH + "data.txt","w+") f.write(data_field1+"\n") f.write(data_field2+"\n") f.write(data_field3+"\n") - f.write(rem_data+"\n") + f.write(data_field4+"\n") + + f.write(data_field5+"\n") + f.write(data_field6+"\n") + f.write(data_field7+"\n") + f.write(data_field8+"\n") + f.write(data_field9+"\n") f.close() return (True) @@ -75,16 +101,25 @@ def get_data(): data_field1 = Lines[0].strip() data_field2 = Lines[1].strip() data_field3 = Lines[2].strip() - rem_data = Lines[3].strip() + data_field4 = Lines[3].strip() + + data_field5 = Lines[4].strip() + data_field6 = Lines[5].strip() + data_field7 = Lines[6].strip() + data_field8 = Lines[7].strip() + data_field9 = Lines[8].strip() f.close() - return (data_field1, data_field2, data_field3, rem_data) + return (data_field1,data_field2,data_field3,data_field4,data_field5,data_field6,data_field7,data_field8,data_field9) # Start of Flask if __name__ == '__main__': # error = 1/0 # Force webapp to stop executing for debug purposes - data_field1, data_field2, data_field3, rem_data = "KFLG","-3","60","0" #get_data() # read what is in data.txt to get last run + data_field1,data_field2,data_field3,data_field4, \ + data_field5,data_field6,data_field7,data_field8,data_field9 = get_data() # "KFLG","-3","60","0" # read what is in data.txt to get last run - os.system('sudo python3 ' + PATH + 'metar_main.py ' + data_field1 + ' ' + data_field2 + ' ' + data_field3 + " " + rem_data + ' &') + # create cmdline command to start the main program using the 'data.txt' variables to kick things off. + os.system('sudo python3 ' + PATH + 'metar_main.py ' + "metar" + data_field1 + ' ' + data_field2 + ' ' + data_field3 + " " + data_field4 + \ + data_field5 + ' ' + data_field6 + ' ' + data_field7 + " " + data_field8 + data_field9 + ' &') app.run(debug=True, use_reloader=False, host='0.0.0.0') # use use_reloader=False to stop double loading \ No newline at end of file