From 620292d8ba4d38717d01207b0b40bd63fab657ea Mon Sep 17 00:00:00 2001 From: Martin Hassman Date: Sun, 29 Sep 2024 15:37:12 +0200 Subject: [PATCH 1/2] Golemio example --- .../network/golemio_odjezdy_mhd/code.py | 132 ++++++++++++++++++ .../lib/adafruit_connection_manager.mpy | Bin 0 -> 3541 bytes .../lib/adafruit_requests.mpy | Bin 0 -> 6890 bytes .../network/golemio_odjezdy_mhd/settings.toml | 9 ++ 4 files changed, 141 insertions(+) create mode 100644 circuitpython/network/golemio_odjezdy_mhd/code.py create mode 100644 circuitpython/network/golemio_odjezdy_mhd/lib/adafruit_connection_manager.mpy create mode 100644 circuitpython/network/golemio_odjezdy_mhd/lib/adafruit_requests.mpy create mode 100644 circuitpython/network/golemio_odjezdy_mhd/settings.toml diff --git a/circuitpython/network/golemio_odjezdy_mhd/code.py b/circuitpython/network/golemio_odjezdy_mhd/code.py new file mode 100644 index 00000000..16e6b2d6 --- /dev/null +++ b/circuitpython/network/golemio_odjezdy_mhd/code.py @@ -0,0 +1,132 @@ +""" +What about using some open data that are provided by your government or city? +In this example we will connect to Prague data platform Golemio.cz and display departure times at some public transport stop. + +1. To get started, let's set up an API Golemio account, and generate your API key for free at https://api.golemio.cz/api-keys/auth/sign-up Don't forget to set the api key as an environment variable GOLEMIO_API_KEY in the settings.toml file. +2. Next, set the WiFi SSID and password as environment variables CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD in the settings.toml file. +3. Finally set stop STOP_NAME or STOP_GTFS_ID in settings.toml file for stop that you want you inspect. + - STOP_NAME is case-sensitive fullname of stop, MUST BE written with diacritics, no acronyms are accepted. + - GTFS ID (General Transit Feed Specification ID) is unique id for every stop, where you can be more selective, + eg. U400Z101P is GTFS ID for Muzeum metro A direction Depo Hostivar. + List of GTFS ID can be found at https://data.pid.cz/stops/json/stops.json + +If you are interested than you might check departure times at https://mapa.pid.cz/ they should be exatly same like in this script. + +You can find the required libraries in the CircuitPython library bundle (https://circuitpython.org/libraries). +""" +import os +import wifi +import ssl +import socketpool +import time +import zlib +import json +import adafruit_requests + + +# If you have font with Czech diacritics than you will not need this +def removeCzechDiacritics(text): + """ + Convert Czech characters to plain ASII. + """ + diacritics = "ÁÉĚÍÓÚŮÝČĎŇŘŠŤŽáéěíóúůýčďňřšťž" + no_diacritics = "AEEIOUUYCDNRSTZaeeiouuycdnrstz" + + result = "" + + for char in text: + if char in diacritics: + index = diacritics.index(char) + result += no_diacritics[index] + else: + result += char + + return result + + +def PrintStopInfo(stop): + print(f"Departures from {removeCzechDiacritics(stop["stop_name"]).upper()}") + +def PrintDepartures(departures): + for depart in departures: + if depart["trip"]["is_air_conditioned"]: + aircondition = "*" + else: + aircondition = " " + + print(f"{depart["route"]["short_name"]:>4} {aircondition} {removeCzechDiacritics(depart["trip"]["headsign"]):25} {depart["departure_timestamp"]["minutes"]:>3} min") + +def PrintInfotexts(infotexts): + for inf in infotexts: + print(inf["text"]) + + +def ConnectWifi(): + wifi.radio.connect(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) + print("Connected to WiFi", os.getenv("CIRCUITPY_WIFI_SSID")) + + pool = socketpool.SocketPool(wifi.radio) + request = adafruit_requests.Session(pool, ssl.create_default_context()) + return request + + +def AskGolem(request): + + if os.getenv("GOLEMIO_API_KEY") is None or len(os.getenv("GOLEMIO_API_KEY")) < 80: + print("You need to set GOLEMIO_API_KEY in settings.toml.") + return + + if os.getenv("STOP_NAME") is not None and len(os.getenv("STOP_NAME")) > 0: + query = f"names={os.getenv("STOP_NAME")}" + elif os.getenv("STOP_GTFS_ID") is not None and len(os.getenv("STOP_GTFS_ID")) > 0: + query = f"ids={os.getenv("STOP_GTFS_ID")}" + else: + print("You need to set either STOP_GTFS_ID or STOP_NAME in settings.toml.") + return + + # You might wanna to tweak query parameters in request url, you can do a lot of magic here + # see docs at https://api.golemio.cz/pid/docs/openapi/#/%F0%9F%9A%8F%20PID%20Departure%20Boards%20(v2)/get_v2_pid_departureboards + url = f"https://api.golemio.cz/v2/pid/departureboards?{query}&preferredTimezone=Europe%2FPrague&total=10" + + headers = { + "X-Access-Token": os.getenv("GOLEMIO_API_KEY"), # Use secret of Shem HaMephorash + "Accept": "application/json" + } + # Uncomment if there is a problem with gzip compression of the response + # headers["Accept-Encoding"] = "identity"; + + + print("Golemio... 8-)") + response = request.get(url, headers=headers) + status_code = response.status_code + + if "content-encoding" in response.headers and response.headers["content-encoding"] == "gzip": + print("Decompressing response...") + decompressed = zlib.decompress(response.content, 31) # 31 for gzip, see https://docs.python.org/3/library/zlib.html#zlib.decompress + json_response = json.loads(decompressed) + else: + json_response = json.loads(response.text) + + if status_code != 200: + print("- E - R - R - O - R - ") # May all your teeth fall out, except one to give you a toothache. (Yiddish Curse) + if "error_status" in json_response and "error_message" in json_response: + print(json_response["error_status"], json_response["error_message"]) + + if "error_info" in json_response: + print(json_response["error_info"]) + else: + print(f"Status code: {status_code}") + + if "stops" in json_response and "departures" in json_response and "infotexts" in json_response: + print() + PrintStopInfo(json_response["stops"][0]) + print("-" * 40) + PrintDepartures(json_response["departures"]) + PrintInfotexts(json_response["infotexts"]) + print("Powered by PID.cz and Golemio.cz") + else: + print("Wrong response :("); # You should be transformed into a chandelier, to hang by day and to burn by night. (Yiddish Curse) + + +request = ConnectWifi() +AskGolem(request) diff --git a/circuitpython/network/golemio_odjezdy_mhd/lib/adafruit_connection_manager.mpy b/circuitpython/network/golemio_odjezdy_mhd/lib/adafruit_connection_manager.mpy new file mode 100644 index 0000000000000000000000000000000000000000..9d0c75d96e3fef7f7fc890cbc5de4da9f81ddb4c GIT binary patch literal 3541 zcmaJ?-%}gc6}}P(gbaAuU2glSstxjS)ndwufozhO?_fG$Z_Ux`?u;VE++P!=m6+K>${17Y+;?zA}8JDg$*tnOcrqUqIbDFH{@xoT|&IqV`IqO^)A^P|D#wX?YTR-4Tu@>I{Q&CQI~)ZQjO>W(ZfEkxk7Oi5KXqV5X0YAX+x znolwdOLPPcF}XXrQaQI)J|XN?Wy7p(H>WV)+U0%CpoXXdv+BohmUSoUX9TVYqsR%Z z({e8{JeoqABu~s2L{5NaG3&f2X|wEoofbfCRV3urZ-y3L)4F4zPL&*WRy+msyKxxuz^n~RSG2Qo7`OB zv8upVIYHV+LjqUka-cqeD{ul=VL1_TI|L?`MO{p_@m}@FoG7B)&iGD$En7?Css%Iz zRWPe1N9HOoNGMxdu>i0tFcIR_i@20WrWw6@OE%ajU?ktG9Ck=NCMnLrt|5FxK&kIa(TN} z2s>NOiQ<&~<4=&e?ef?q6P45kPQAD66<)G8m4Zyesr8cB+?3}zQS&y@>@W%AGpSTO ziA=Z-@aks&^@^Rl2Wp0YA$Gklc0kLO;YQAH+g~c#F7ZHReEj22rht)Y+uD<&4D`Oy@D%8S0F!o9edpP(8L@s@HawI-5=T5Z326Qhh0(0mET| z`;64Nl&=jV;DhiYDg(b2_^rY(>uaap@|masUk5dk@^xY)1d)lj$C#WjQx|<*)I@@{ z)aH?;Z_m5i)7MYk!1|~UeTee=4Aiv*Yuv3)*!)G;x_ohH$O(Ui(Yj)tY$(=1op?vF zzEdZzDAp@=;$6l1Zk?D?tW$O3J;nMSUaDj7C;V-S^?h}DRa;(7u%E5|V8pH!e=Seh z2h%g%3NcAut1CnxU}RVD!V5b4Bz6|xz}tuq@N&a%@EaS2#1psi)~w-Hg01DvW_&M{ z*LdPO&xFU2oG?(=f$RE!#b3N7hk;=*!S2lb(STnsx@P4W`{*40D}@MRA-|EH!}}m% zkbpIdvktT0!0ng^@pCtC$;+}O7KKPh8^lEwlBx#`SsbtX4eUMCLG0;?3F<>YN0Z%e zF>+;aVWtDWtw6*Io_j$@vkGA)`dJ|R`%7Dy0Y=`E%}%$|8Hu=J&ZukF6)gq_T`s%J zIXxKR!^1J>aAbXccsPQ;1t5Q}_nrlJ zEqj)HaPPu;a6P!&yW9DBr`B*%Z+Kj9_$AQ&jMn@%D%O|xwfha!9K22No(H*4`#Pyb z@OzP?1M{rKvC*{z=3N8X#}jPVj(Gs{7jMdcppum{hj$pSC`8zAc+z0zEZ->P^gd`K54G6Ah66_bj|C&6pV)Y}zGwPN9v*;M53(>)gv(Au0TqRv~IbE-%1IA~E z^wLA}`VltU2<#Z1HJ*p2>z}6O^z$R~odaMn(|tre@w|n0Nqed?2kzd~a(^q?P zFdi>{By8P*8a(jGJq~Bl<#dgN2FuPtXVmQq50>4I^02e)4js_bc=-TITyN|jc-o&o z0uDdY1$aJ6fB#q`GN%!F3`8EYz}rH)_v?*78_?g!6329C`RPM?_9z?$g`NSUTIc0@ zLxwlu47AJXA7&_pjD1~i96me>bbPOnGmjnzi~y#U6)$+&UeGKI_#AyxAumC7-yEHX z;s4$Gzh#5x;!W;BK{h+4qr)S^(OE~x?HGwE^t3W+coK@@8FDOuR6Q+z1MBhqM8Np% zfi`0WM<)IVRl-J3^-LwNcj|GSMBee bxcT6V+pZA>&p;nzI0>VMXOs;5((L~L*;;Yq literal 0 HcmV?d00001 diff --git a/circuitpython/network/golemio_odjezdy_mhd/lib/adafruit_requests.mpy b/circuitpython/network/golemio_odjezdy_mhd/lib/adafruit_requests.mpy new file mode 100644 index 0000000000000000000000000000000000000000..3279156a41427715245ec5d26cdc7119ff202315 GIT binary patch literal 6890 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 Date: Sat, 5 Oct 2024 23:07:12 +0200 Subject: [PATCH 2/2] variables passed as arguments, widened check for characters with diacritics --- .../network/golemio_odjezdy_mhd/code.py | 52 +++++++++++-------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/circuitpython/network/golemio_odjezdy_mhd/code.py b/circuitpython/network/golemio_odjezdy_mhd/code.py index 16e6b2d6..1be01a72 100644 --- a/circuitpython/network/golemio_odjezdy_mhd/code.py +++ b/circuitpython/network/golemio_odjezdy_mhd/code.py @@ -17,6 +17,7 @@ import os import wifi import ssl +import sys import socketpool import time import zlib @@ -29,9 +30,8 @@ def removeCzechDiacritics(text): """ Convert Czech characters to plain ASII. """ - diacritics = "ÁÉĚÍÓÚŮÝČĎŇŘŠŤŽáéěíóúůýčďňřšťž" - no_diacritics = "AEEIOUUYCDNRSTZaeeiouuycdnrstz" - + diacritics = "ÁÉĚÍÓÖÚŮÝČĎŇŘŠŤŽáéěíóöúůüýčďňřšťž✈" + no_diacritics = "AEEIOOUUYCDNRSTZaeeioouuuycdnrstz " result = "" for char in text: @@ -61,27 +61,19 @@ def PrintInfotexts(infotexts): print(inf["text"]) -def ConnectWifi(): - wifi.radio.connect(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) - print("Connected to WiFi", os.getenv("CIRCUITPY_WIFI_SSID")) +def ConnectWifi(wifi_ssid, wifi_password): + wifi.radio.connect(wifi_ssid, wifi_password) + print("Connected to WiFi", wifi_ssid) pool = socketpool.SocketPool(wifi.radio) request = adafruit_requests.Session(pool, ssl.create_default_context()) return request -def AskGolem(request): +def AskGolem(request, api_key, query): - if os.getenv("GOLEMIO_API_KEY") is None or len(os.getenv("GOLEMIO_API_KEY")) < 80: - print("You need to set GOLEMIO_API_KEY in settings.toml.") - return - - if os.getenv("STOP_NAME") is not None and len(os.getenv("STOP_NAME")) > 0: - query = f"names={os.getenv("STOP_NAME")}" - elif os.getenv("STOP_GTFS_ID") is not None and len(os.getenv("STOP_GTFS_ID")) > 0: - query = f"ids={os.getenv("STOP_GTFS_ID")}" - else: - print("You need to set either STOP_GTFS_ID or STOP_NAME in settings.toml.") + if (request is None or api_key is None or query is None): + print("Not enough parameters to bring Golem to live. Try harder.") return # You might wanna to tweak query parameters in request url, you can do a lot of magic here @@ -89,14 +81,12 @@ def AskGolem(request): url = f"https://api.golemio.cz/v2/pid/departureboards?{query}&preferredTimezone=Europe%2FPrague&total=10" headers = { - "X-Access-Token": os.getenv("GOLEMIO_API_KEY"), # Use secret of Shem HaMephorash + "X-Access-Token": api_key, # Use secret of Shem HaMephorash "Accept": "application/json" } # Uncomment if there is a problem with gzip compression of the response # headers["Accept-Encoding"] = "identity"; - - print("Golemio... 8-)") response = request.get(url, headers=headers) status_code = response.status_code @@ -128,5 +118,23 @@ def AskGolem(request): print("Wrong response :("); # You should be transformed into a chandelier, to hang by day and to burn by night. (Yiddish Curse) -request = ConnectWifi() -AskGolem(request) + +api_key = None +query = None + +if os.getenv("GOLEMIO_API_KEY") is None or len(os.getenv("GOLEMIO_API_KEY")) < 80: + print("You need to set GOLEMIO_API_KEY in settings.toml.") + sys.exit(1) +else: + api_key = os.getenv("GOLEMIO_API_KEY") + +if os.getenv("STOP_NAME") is not None and len(os.getenv("STOP_NAME")) > 0: + query = f"names={os.getenv("STOP_NAME")}" +elif os.getenv("STOP_GTFS_ID") is not None and len(os.getenv("STOP_GTFS_ID")) > 0: + query = f"ids={os.getenv("STOP_GTFS_ID")}" +else: + print("You need to set either STOP_GTFS_ID or STOP_NAME in settings.toml.") + sys.exit(1) + +request = ConnectWifi(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) +AskGolem(request, api_key, query)