[{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/891/original/WebAPI_and_you.jpg?1716070972","metadata":{"caption":""}},{"element_type":"text","content":"\n        \u003cul\u003e\n\u003cli\u003eAn uncomprehensive \u0026amp; comprehensible guide to using Adafruit Requests with web API's.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThe amount of \u003ca href=\"https://github.com/adafruit/Adafruit_CircuitPython_Requests/tree/main/examples/wifi/expanded\" target=\"_blank\"\u003eonline API examples for the Adafruit_Requests library\u003c/a\u003e is growing. If you're interested in using an API but an example does not exist and you're unsure how to start I will help walk you through the process.\u003c/p\u003e\n\u003cp\u003eI wrote the majority of the web API examples currently in the examples directory. I'm a pretty good source on how to approach a new unknown web API and wrangle out some basic data.\u0026nbsp;\u003c/p\u003e\n\u003ch2\u003eJSON\u003c/h2\u003e\n\u003cp\u003eData is malleable, it can be shaped and formed in a variety of ways.\u0026nbsp; The most common format currently used with REST API's is JSON. The Adafruit_Requests library is particularly good with JSON data.\u0026nbsp; If you don't know how to read or write in JSON don't worry, you won't need to.\u0026nbsp; The library handles all the format conversions for you.\u0026nbsp; All you need to know is how to get at the data you want and I will walk you through how to do that.\u003c/p\u003e\n\u003cp\u003eBefore we begin there are some glossary terms that you'll need to know in order to make sense of JSON for the web.\u003c/p\u003e\n\u003ch2\u003eREST\u003c/h2\u003e\n\u003cp\u003e\u003cspan class=\"NA6bn BxUVEf ILfuVd\"\u003e\u003cspan class=\"hgKElc\"\u003eRepresentational State Transfer (REST) is a software architecture that imposes conditions on how an API should work. Most web API's use the REST architecture.\u0026nbsp; All a beginner needs to know about REST is it's the way a website allows you to retrieve data from an API.\u003cbr\u003e\u003c/span\u003e\u003c/span\u003e\u003c/p\u003e\n\u003ch2\u003e\n\u003cspan class=\"NA6bn BxUVEf ILfuVd\"\u003e\u003cspan class=\"hgKElc\"\u003eEndpoint\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"NA6bn BxUVEf ILfuVd\"\u003e\u003cspan class=\"hgKElc\"\u003e\u003c/span\u003e\u003c/span\u003e\n\u003c/h2\u003e\n\u003cp\u003e\u003cspan class=\"NA6bn BxUVEf ILfuVd\"\u003e\u003cspan class=\"hgKElc\"\u003eAn endpoint refers to the data to retrieve from an API. This can be part of the JSON heirarchy path or a key:value pair. For web API's typically this refers to the JSON Key:Value pair.\u003cbr\u003e\u003c/span\u003e\u003c/span\u003e\u003c/p\u003e\n\u003ch2\u003e\u003cspan class=\"NA6bn BxUVEf ILfuVd\"\u003e\u003cspan class=\"hgKElc\"\u003eKey:Value\u003c/span\u003e\u003c/span\u003e\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cspan class=\"NA6bn BxUVEf ILfuVd\"\u003e\u003cspan class=\"hgKElc\"\u003eKey:Value (always a pair separated by a colon)\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"NA6bn BxUVEf ILfuVd\"\u003e\u003cspan class=\"hgKElc\"\u003e\u003c/span\u003e\u003c/span\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cspan class=\"NA6bn BxUVEf ILfuVd\"\u003e\u003cspan class=\"hgKElc\"\u003eA key is a unique identifier associated with data and the value is the actual data.\u0026nbsp; An example would be if you're working with a weather API and might want to return current:temperature for a geographic area to display freedom units on a TFT. It would return as `current:70.0` (in Fahrenheit) or if you're living in a sensible country using the metric system it would return `current:21.1` (in Celsius).\u0026nbsp; \u003c/span\u003e\u003c/span\u003e\u003c/p\u003e\n\u003ch2\u003e\u003cspan class=\"NA6bn BxUVEf ILfuVd\"\u003e\u003cspan class=\"hgKElc\"\u003eKey Error\u003c/span\u003e\u003c/span\u003e\u003c/h2\u003e\n\u003cp\u003e\u003cspan class=\"NA6bn BxUVEf ILfuVd\"\u003e\u003cspan class=\"hgKElc\"\u003eIt's useful to know the above terms because an error that a website or Circuit Python might return is `invalid key:value pair` or simply `key error`. That error means you requested a key:value that does not exist, you spelled it incorrectly, or the request was otherwise incorrectly formatted.\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"NA6bn BxUVEf ILfuVd\"\u003e\u003cspan class=\"hgKElc\"\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"NA6bn BxUVEf ILfuVd\"\u003e\u003cspan class=\"hgKElc\"\u003e\u003c/span\u003e\u003c/span\u003e\u003c/p\u003e\n      ","metadata":{}},{"element_type":"text","content":"\n  \n  \n        \u003ch2\u003eRequired Libraries\u003c/h2\u003e\n\u003cp\u003eAll libraries used here are either built into Circuit Python or can be downloaded from the latest stable release bundle from \u003ca href=\"https://circuitpython.org/libraries\" target=\"_blank\"\u003eCircuitPython.org/libraries\u003c/a\u003e. Place external libraries downloaded from the bundle into your CIRCUITPY device's /lib directory.\u0026nbsp; Only ever place the external libraries you are actively using into your /lib directory.\u003c/p\u003e\n\u003cp\u003eBuilt-in Libraries\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eos\u003c/li\u003e\n\u003cli\u003etime\u003c/li\u003e\n\u003cli\u003ewifi\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eExternal Libraries (from the bundle)\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eadafruit_requests\u003c/li\u003e\n\u003cli\u003eadafruit_connection_manager\u003c/li\u003e\n\u003c/ul\u003e\n      \n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003e\u003cspan class=\"NA6bn BxUVEf ILfuVd\"\u003e\u003cspan class=\"hgKElc\"\u003eRocketLaunch.Live API example\u003c/span\u003e\u003c/span\u003e\u003c/h2\u003e\n\u003cp\u003e\u003cspan class=\"NA6bn BxUVEf ILfuVd\"\u003e\u003cspan class=\"hgKElc\"\u003eFor the walk through I will be using the RocketLaunch.Live API example because it's a relatively easy to use and a publicly accessible API. \u0026nbsp;A public API means you don't need any special credentials to pass with the request. \u0026nbsp;A private example means you must pass some type of credentials such as an oauth bearer token or login with the request.\u003cbr\u003e\u0026nbsp;\u003c/span\u003e\u003c/span\u003e\u003c/p\u003e\n\u003ch2\u003e\u003cspan class=\"NA6bn BxUVEf ILfuVd\"\u003e\u003cspan class=\"hgKElc\"\u003eLibrary Imports\u003c/span\u003e\u003c/span\u003e\u003c/h2\u003e\n\u003cp\u003e\u003cspan class=\"NA6bn BxUVEf ILfuVd\"\u003e\u003cspan class=\"hgKElc\"\u003eFirst we'll need to import the libraries at the top of your code.py file to be used with this example. As of Circuit Python 9.0 imports are grouped according to Python PEP-8. \u0026nbsp;That's just a way of saying imports are now grouped by:\u003cbr\u003e\u003c/span\u003e\u003c/span\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cspan class=\"NA6bn BxUVEf ILfuVd\"\u003e\u003cspan class=\"hgKElc\"\u003estandard library\u003c/span\u003e\u003c/span\u003e\u003c/li\u003e\n\u003cli\u003e\u003cspan class=\"NA6bn BxUVEf ILfuVd\"\u003e\u003cspan class=\"hgKElc\"\u003e3rd party library\u003c/span\u003e\u003c/span\u003e\u003c/li\u003e\n\u003cli\u003e\u003cspan class=\"NA6bn BxUVEf ILfuVd\"\u003e\u003cspan class=\"hgKElc\"\u003elocal library\u003c/span\u003e\u003c/span\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cspan class=\"NA6bn BxUVEf ILfuVd\"\u003e\u003cspan class=\"hgKElc\"\u003eIf you're an absolute beginner you don't need to worry about import order groupings unless you intend to submit your API example to the adafruit_requests repository. You can import them in any order you want if you're just beginning.\u003c/span\u003e\u003c/span\u003e\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"import os\nimport time\n\nimport adafruit_connection_manager\nimport wifi\n\nimport adafruit_requests","metadata":{"language":"python","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eSleep Timer (API Polling Rate)\u003c/h3\u003e\n\u003cp\u003eNext we setup a customizable poll timer. SLEEP_TIME is widely used in our API examples for the delay between API fetches. It's not an official variable just a name that is commonly used.\u0026nbsp; You can configure the poll time in seconds to whatever you like.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eSome API's will allow you to make a request at maximum of once per minute.\u0026nbsp; Other API's it might be once per 5 minutes or 15 minutes.\u003c/p\u003e\n\u003cp\u003eJust be aware that most API's have request limits and if you exceed too many requests in a certain time period you could be temporarily banned.\u0026nbsp; A temporary ban can range from 1 hour to 24 hours and is a way a website protects itself from being flooded with too many requests.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"# Time between API refreshes\n# 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour\nSLEEP_TIME = 43200","metadata":{"language":"python","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eWiFi Credential Setup\u003c/h3\u003e\n\u003cp\u003eNow we will configure the WiFi settings. Your WiFi credentials should exist in your settings.toml file on your CIRCUITPY board (device).\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"# Get WiFi details, ensure these are setup in settings.toml\nssid = os.getenv(\"CIRCUITPY_WIFI_SSID\")\npassword = os.getenv(\"CIRCUITPY_WIFI_PASSWORD\")","metadata":{"language":"auto","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eTime Calc\u003c/h3\u003e\n\u003cp\u003eThe time_calc function is a custom function I insert into most of my examples. \u0026nbsp;It converts seconds into human readable minutes, hours, or days. \u0026nbsp;This is typically used to return the time until the next update so it will say something like \u003cstrong\u003enext update: 12 hours\u003c/strong\u003e instead of \u003cstrong\u003enext update: 43200\u003c/strong\u003e.\u0026nbsp; Converting to human readable time is much easier to understand; otherwise you will be converting seconds in your head or using a calculator. \u0026nbsp;The time calc function does it for you.\u003c/p\u003e\n\u003cp\u003eYou can test the function anywhere in the script by calling \u003cstrong\u003etime_calc(500)\u003c/strong\u003e for example and it will convert 500 seconds into human readable format.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"def time_calc(input_time):\n    \"\"\"Converts seconds to minutes/hours/days\"\"\"\n    if input_time \u003c 60:\n        return f\"{input_time:.0f} seconds\"\n    if input_time \u003c 3600:\n        return f\"{input_time / 60:.0f} minutes\"\n    if input_time \u003c 86400:\n        return f\"{input_time / 60 / 60:.0f} hours\"\n    return f\"{input_time / 60 / 60 / 24:.1f} days\"","metadata":{"language":"python","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eJSON GET Source\u003c/h3\u003e\n\u003cp\u003eNow we define our GET source which should be a valid JSON formatted page. \u0026nbsp;If you visit the URL in your web browser:\u003c/p\u003e\n\u003cp\u003e\u0026nbsp;\u003ca href=\"https://fdo.rocketlaunch.live/json/launches/next/1\"\u003ehttps://fdo.rocketlaunch.live/json/launches/next/1\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eyou should see a JSON formatted hierarchy structure. \u0026nbsp;This is the JSON url that the adafruit_requests library will perform a GET request on. You can name the source variable whatever you like as long as it is specified with the same variable name. Using the GET source is covered later in the script.\u003cbr\u003e\u003cbr\u003e\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/892/original/RocketLaunch_Live_JSON_screenshot.JPG?1716074948","metadata":{"caption":"RocketLaunch.Live JSON Web Browser (Firefox) Screenshot"}},{"element_type":"code","content":"# Publicly available data no header required\n# The number at the end is the amount of launches (max 5 free api)\nROCKETLAUNCH_SOURCE = \"https://fdo.rocketlaunch.live/json/launches/next/1\"","metadata":{"language":"auto","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n        \u003ch3\u003eSocketPool \u0026amp; Connection_Manager\u003c/h3\u003e\n\u003cp\u003eNow we initialize the most important variables of the entire script which is the socket pool, ssl context, and session. \u0026nbsp;This is what will initiate a socket connection between your device and the URL using Adafruit_Requests (this can also be a localhost url or IP address). It also initializes the wifi radio and creates an active session via the Adafruit Connection Manager library. \u0026nbsp;The Connection Manager is what automatically manages all of your socket connections.\u003c/p\u003e\n      \n\n\n\n\n","metadata":{}},{"element_type":"code","content":"# Initalize Wifi, Socket Pool, Request Session\npool = adafruit_connection_manager.get_radio_socketpool(wifi.radio)\nssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio)\nrequests = adafruit_requests.Session(pool, ssl_context)","metadata":{"language":"auto","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003ewhile True: (infinite loop)\u003c/h3\u003e\n\u003cp\u003eIf you only want a script to run once then do not include \u003cstrong\u003ewhile True:\u003c/strong\u003e in your code.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eEverything inside the while True loop will loop infinitely.\u0026nbsp; With an infinite online loop the first thing is to ensure the wifi radio is capable of connecting to the host URL. If wifi is ever disconnected it will automatically reconnect prior to attempting to contact the host again for the next poll update.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"while True:\n    # Connect to Wi-Fi\n    print(\"\\n===============================\")\n    print(\"Connecting to WiFi...\")\n    while not wifi.radio.ipv4_address:\n        try:\n            wifi.radio.connect(ssid, password)\n        except ConnectionError as e:\n            print(\"❌ Connection Error:\", e)\n            print(\"Retrying in 10 seconds\")\n    print(\"✅ Wifi!\")","metadata":{"language":"python","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eError Handling\u003c/h3\u003e\n\u003cp\u003e\u003cbr\u003eAll code to be run on the API should be enclosed within a try/except error handler. \u0026nbsp;In the event an error occurs it can process the error in a way you specify and potentially recover so the script doesn't crash. Without a try/except error handler for a 24/7 online script the entire script will eventually crash.\u003c/p\u003e\n\u003cp\u003eConnection errors to the host could fail for a variety of reasons including; host timeout, unauthorized, wrong key:value pair, and the list goes on. \u0026nbsp;By using an error handler you can catch and ignore the most common errors allowing the script to continue. \u0026nbsp;At worst you will miss a poll update but your script will continue to function instead of crashing. \u0026nbsp;It's very important to learn how to use error handlers with Circuit Python for a 24/7 online infinite looping script.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"try:\n    # Print Request to Serial\n    print(\" | Attempting to GET RocketLaunch.Live JSON!\")\n    time.sleep(2)\n    debug_rocketlaunch_full_response = False\n\n    try:\n        with requests.get(url=ROCKETLAUNCH_SOURCE) as rocketlaunch_response:\n            rocketlaunch_json = rocketlaunch_response.json()\n    except ConnectionError as e:\n        print(\"Connection Error:\", e)\n        print(\"Retrying in 10 seconds\")\n    print(\" | ✅ RocketLaunch.Live JSON!\")\n\n    if debug_rocketlaunch_full_response:\n        print(\"Full API GET URL: \", ROCKETLAUNCH_SOURCE)\n        print(rocketlaunch_json)\n\n    # JSON Endpoints\n    RLFN = str(rocketlaunch_json[\"result\"][0][\"name\"])\n    RLWO = str(rocketlaunch_json[\"result\"][0][\"win_open\"])\n    TZERO = str(rocketlaunch_json[\"result\"][0][\"t0\"])\n    RLWC = str(rocketlaunch_json[\"result\"][0][\"win_close\"])\n    RLP = str(rocketlaunch_json[\"result\"][0][\"provider\"][\"name\"])\n    RLVN = str(rocketlaunch_json[\"result\"][0][\"vehicle\"][\"name\"])\n    RLPN = str(rocketlaunch_json[\"result\"][0][\"pad\"][\"name\"])\n    RLLS = str(rocketlaunch_json[\"result\"][0][\"pad\"][\"location\"][\"name\"])\n    RLLD = str(rocketlaunch_json[\"result\"][0][\"launch_description\"])\n    RLM = str(rocketlaunch_json[\"result\"][0][\"mission_description\"])\n    RLDATE = str(rocketlaunch_json[\"result\"][0][\"date_str\"])\n\n    # Print to serial \u0026 display label if endpoint not \"None\"\n    if RLDATE != \"None\":\n        print(f\" |  | Date: {RLDATE}\")\n    if RLFN != \"None\":\n        print(f\" |  | Flight: {RLFN}\")\n    if RLP != \"None\":\n        print(f\" |  | Provider: {RLP}\")\n    if RLVN != \"None\":\n        print(f\" |  | Vehicle: {RLVN}\")\n\n    # Launch time can sometimes be Window Open to Close, T-Zero, or weird combination.\n    # Should obviously be standardized but they're not input that way.\n    # Have to account for every combination of 3 conditions.\n    # T-Zero Launch Time Conditionals\n    if RLWO == \"None\" and TZERO != \"None\" and RLWC != \"None\":\n        print(f\" |  | Window: {TZERO} | {RLWC}\")\n    elif RLWO != \"None\" and TZERO != \"None\" and RLWC == \"None\":\n        print(f\" |  | Window: {RLWO} | {TZERO}\")\n    elif RLWO != \"None\" and TZERO == \"None\" and RLWC != \"None\":\n        print(f\" |  | Window: {RLWO} | {RLWC}\")\n    elif RLWO != \"None\" and TZERO != \"None\" and RLWC != \"None\":\n        print(f\" |  | Window: {RLWO} | {TZERO} | {RLWC}\")\n    elif RLWO == \"None\" and TZERO != \"None\" and RLWC == \"None\":\n        print(f\" |  | Window: {TZERO}\")\n    elif RLWO != \"None\" and TZERO == \"None\" and RLWC == \"None\":\n        print(f\" |  | Window: {RLWO}\")\n    elif RLWO == \"None\" and TZERO == \"None\" and RLWC != \"None\":\n        print(f\" |  | Window: {RLWC}\")\n\n    if RLLS != \"None\":\n        print(f\" |  | Site: {RLLS}\")\n    if RLPN != \"None\":\n        print(f\" |  | Pad: {RLPN}\")\n    if RLLD != \"None\":\n        print(f\" |  | Description: {RLLD}\")\n    if RLM != \"None\":\n        print(f\" |  | Mission: {RLM}\")\n\n    print(\"\\nFinished!\")\n    print(f\"Board Uptime: {time_calc(time.monotonic())}\")\n    print(f\"Next Update: {time_calc(SLEEP_TIME)}\")\n    print(\"===============================\")\n\nexcept (ValueError, RuntimeError) as e:\n    print(\"Failed to get data, retrying\\n\", e)\n    time.sleep(60)\n    break\ntime.sleep(SLEEP_TIME)","metadata":{"language":"python","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eSerial Debug Setup\u003c/h3\u003e\n\u003cp\u003eMore experienced coders won't need a line by line replay of what every line means.\u0026nbsp; As a beginner you might not even be interested either but if you want to learn how everything works it's good to know what every line does especially how to pull endpoints.\u003c/p\u003e\n\u003cp\u003eWe'll start with serial debugging (which simply means adding print statements to see what the code is doing at a particular point in the code). If you are using Mu you can click on the serial button to display the REPL and any serial debugging will be displayed there.\u003c/p\u003e\n\u003cp\u003eThe first print statement notifies you in your serial terminal/console/REPL that an attempt to connect to the API is about to occur.\u0026nbsp; This is useful so that if the script fails prior to this point you'll know that a connection attempt wasn't even made.\u0026nbsp; The variable for debugging can be named anything.\u0026nbsp; I intentionally name most variables in every script something different so you can copy and paste a variety of examples together with minimal manual code changes.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eA short time.sleep(2) is a 2 second time delay. It isn't completely necessary but in the event something goes wrong it can help prevent spamming connection attempts every x milliseconds which might get your temp banned by the API (all API's have different rules about connection timings).\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"# Print Request to Serial\nprint(\" | Attempting to GET RocketLaunch.Live JSON!\")\ntime.sleep(2)\ndebug_rocketlaunch_full_response = False","metadata":{"language":"python","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eGET Request\u003c/h3\u003e\n\u003cp\u003eThis is where the actual get request for the source (variable you specified earlier) takes place.\u0026nbsp; It should be within a try/except block (error handler).\u0026nbsp; This is the most prone to any wifi connections or routing disconnections that will throw an error.\u0026nbsp; It will TRY to connect and if it cannot it will throw an exception error. You can name the error variable anything you want.\u0026nbsp; e is commonly used as short-hand for error.\u0026nbsp; e is a variable that holds the error code to be printed.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eIt will continue to try to connect infinitely until it succeeds.\u0026nbsp; Once it does succeed it will print out the message \"RocketLaunch.Live JSON!\"\u0026nbsp; This lets you know that it has at the very least connected to the API and attempted to retrieve data.\u0026nbsp; This doesn't mean it's successful in pulling data only that the connection to the host JSON was successful.\u003c/p\u003e\n\u003cp\u003eEven though adafruit does have a separate importable JSON library it's built into adafruit_requests so you don't need a separate json import at the beginning of the script for this type of connection method. Using .json() will automatically parse the JSON data for you.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eBy using a with statement it will automatically close a socket when you're done with it. This is a recent development in Circuit Python 9 so you don't have to manually close a socket anymore, it's all handled automatically. Socket management is much more robust in Circuit Python 9 than it was in Circuit Python 6, 7, or 8.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"try:\n    with requests.get(url=ROCKETLAUNCH_SOURCE) as rocketlaunch_response:\n        rocketlaunch_json = rocketlaunch_response.json()\nexcept ConnectionError as e:\n    print(\"Connection Error:\", e)\n    print(\"Retrying in 10 seconds\")\nprint(\" | ✅ RocketLaunch.Live JSON!\")","metadata":{"language":"python","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eSerial Debugging\u003c/h3\u003e\n\u003cp\u003eThis is where we can print out debugging if you set debug_rocketlaunch_full_response to \u003cstrong\u003e= True\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eThis type of debugging is the shotgun approach when you can't figure out why a value isn't returning correctly.\u0026nbsp; It will print everything from the requested URL not just the data you want but EVERYTHING.\u0026nbsp; As you can see from the JSON browser screenshot above that can be a lot of data.\u0026nbsp; If it's for a very data heavy API like youtube, github, or steam it will return so much data that it can crash your microcontroller.\u0026nbsp; \u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"alert","content":"\u003cp\u003eOnly enable debugging if you need a better idea of the data structure and honestly don't mind if your microcontroller crashes. Fair warning.\u003c/p\u003e","metadata":{"class":"element row-fluid alert-element build-alert alert-warning","markdown":"Only enable debugging if you need a better idea of the data structure and honestly don't mind if your microcontroller crashes. Fair warning.","alert_type":"warning"}},{"element_type":"code","content":"if debug_rocketlaunch_full_response:\n    print(\"Full API GET URL: \", ROCKETLAUNCH_SOURCE)\n    print(rocketlaunch_json)","metadata":{"language":"python","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eReturn Parsed Endpoint Data\u003c/h3\u003e\n\u003cp\u003e\u003cstrong\u003erocketlaunch_json\u003c/strong\u003e represents the top level directory of the returned JSON hierarchy structure. List item keys are setup like a path to the value you want to pull.\u0026nbsp; Let's use the rocket launch vehicle name as an example for data to retrieve.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eHere is an easy way to correlate the JSON parsed path to the JSON web browser data structure.\u0026nbsp; We want to put it into a variable name for use later on. \u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"RLFN = str(rocketlaunch_json[\"result\"][0][\"name\"])","metadata":{"language":"auto","linenums":false}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/893/original/RocketLaunch_Live_JSON_structure.JPG?1716080123","metadata":{"caption":"JSON Heirarchy Path Structure"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eIn the above example \u003cstrong\u003eresult\u003c/strong\u003e is a key, \u003cstrong\u003e0\u003c/strong\u003e is a key, and \u003cstrong\u003ename\u003c/strong\u003e is a key.\u0026nbsp; We will retrieve the value for the key \u003cstrong\u003ename\u003c/strong\u003e.\u0026nbsp; If you receive an error for \"invalid key\" or \"invalid key:value\" now you can see how that error correlates to a key or key:value pair.\u003c/p\u003e\n\u003cp\u003eSo what happens if you just retrieve ['result'] or ['result][0]?\u0026nbsp; It will return everything that is listed under the requested key as a parsed list.\u0026nbsp; This can be useful for navigating through JSON to drill down to the data you do want.\u003c/p\u003e\n\u003cp\u003eNow simply repeat this process with the correct paths to any values you want to return. You can use either single or double quotes for keys or values, both work fine with adafruit_requests json parsing.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"# JSON Endpoints\nRLFN = str(rocketlaunch_json[\"result\"][0][\"name\"])\nRLWO = str(rocketlaunch_json[\"result\"][0][\"win_open\"])\nTZERO = str(rocketlaunch_json[\"result\"][0][\"t0\"])\nRLWC = str(rocketlaunch_json[\"result\"][0][\"win_close\"])\nRLP = str(rocketlaunch_json[\"result\"][0][\"provider\"][\"name\"])\nRLVN = str(rocketlaunch_json[\"result\"][0][\"vehicle\"][\"name\"])\nRLPN = str(rocketlaunch_json[\"result\"][0][\"pad\"][\"name\"])\nRLLS = str(rocketlaunch_json[\"result\"][0][\"pad\"][\"location\"][\"name\"])\nRLLD = str(rocketlaunch_json[\"result\"][0][\"launch_description\"])\nRLM = str(rocketlaunch_json[\"result\"][0][\"mission_description\"])\nRLDATE = str(rocketlaunch_json[\"result\"][0][\"date_str\"])","metadata":{"language":"python","linenums":false}},{"element_type":"alert","content":"\u003cp\u003e[0] is not the same as ['0'].  [0] is a list and ['0'] is a value.  Getting that wrong will lead to an exception error for invalid key.\u003c/p\u003e","metadata":{"class":"element row-fluid alert-element build-alert alert-info","markdown":"[0] is not the same as ['0'].  [0] is a list and ['0'] is a value.  Getting that wrong will lead to an exception error for invalid key.","alert_type":"info"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003ePrinting Values to Serial Console (REPL)\u003c/h3\u003e\n\u003cp\u003eAll API's are slightly different.\u0026nbsp; This API will actually return a literal value of 'None' if an endpoint is blank.\u0026nbsp; Not all API's do that.\u0026nbsp; This is quite convenient as any valid endpoint will never produce an error for not existing and makes it an excellent API to start with as a beginner.\u003c/p\u003e\n\u003cp\u003eWe can setup some checks using if/else statements to see if a value is 'None' and if it is then don't return it.\u0026nbsp; You can use the if statements to print something else such as \"Currently No Value\" but in this case I've decided simply not to return it at all if it's blank.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eif RLDATE != \"None\":\u003c/p\u003e\n\u003cp\u003eTranslates to: if RLDATE does not equal \"None\".\u0026nbsp; RLDATE endpoint variable was defined earlier above and is the rocket launch date.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"# Print to serial \u0026 display label if endpoint not \"None\"\nif RLDATE != \"None\":\n    print(f\" |  | Date: {RLDATE}\")\nif RLFN != \"None\":\n    print(f\" |  | Flight: {RLFN}\")\nif RLP != \"None\":\n    print(f\" |  | Provider: {RLP}\")\nif RLVN != \"None\":\n    print(f\" |  | Vehicle: {RLVN}\")","metadata":{"language":"auto","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eLaunch Window Conditionals\u003c/h3\u003e\n\u003cp\u003eThere are a combination of possible values for the launch window open, launch window close, and t-minus zero expected launch time.\u0026nbsp; These types of conditions can be accounted for with if/else statements.\u0026nbsp; Using the variable names we set above for all the different endpoints we'll now use those variables to print out a list of expected launch times for a rocket launch.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/894/original/launch_window_json.JPG?1716081931","metadata":{"caption":"Launch Window Endpoints"}},{"element_type":"code","content":"# Launch time can sometimes be Window Open to Close, T-Zero, or weird combination.\n# Should obviously be standardized but they're not input that way.\n# Have to account for every combination of 3 conditions.\n# T-Zero Launch Time Conditionals\n\nif RLWO == \"None\" and TZERO != \"None\" and RLWC != \"None\":\n    print(f\" |  | Window: {TZERO} | {RLWC}\")\nelif RLWO != \"None\" and TZERO != \"None\" and RLWC == \"None\":\n    print(f\" |  | Window: {RLWO} | {TZERO}\")\nelif RLWO != \"None\" and TZERO == \"None\" and RLWC != \"None\":\n    print(f\" |  | Window: {RLWO} | {RLWC}\")\nelif RLWO != \"None\" and TZERO != \"None\" and RLWC != \"None\":\n    print(f\" |  | Window: {RLWO} | {TZERO} | {RLWC}\")\nelif RLWO == \"None\" and TZERO != \"None\" and RLWC == \"None\":\n   print(f\" |  | Window: {TZERO}\")\nelif RLWO != \"None\" and TZERO == \"None\" and RLWC == \"None\":\n    print(f\" |  | Window: {RLWO}\")\nelif RLWO == \"None\" and TZERO == \"None\" and RLWC != \"None\":\n    print(f\" |  | Window: {RLWC}\")\n\nif RLLS != \"None\":\n    print(f\" |  | Site: {RLLS}\")\nif RLPN != \"None\":\n    print(f\" |  | Pad: {RLPN}\")\nif RLLD != \"None\":\n    print(f\" |  | Description: {RLLD}\")\nif RLM != \"None\":\n    print(f\" |  | Mission: {RLM}\")","metadata":{"language":"auto","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eAs you can see in the screenshot the values are 'null' and not 'None' as they once were.\u0026nbsp; API's can and do change frequently as their web developers work on them.\u0026nbsp; An API endpoint might suddenly stop working one day and you'll have to go digging around in the JSON to figure out why.\u0026nbsp; This is a good example of a sudden API change.\u0026nbsp; Now I have to go rewrite the example to account for values of 'null' and/or 'None'.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eDisconnection \u0026amp; Uptime\u003c/h3\u003e\n\u003cp\u003eAs the script finishes it prints to serial that it is finished processing all requests, shows how long the board has been running since last hard reset (Uptime), and the amount of time until it polls the server again.\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://docs.circuitpython.org/en/latest/shared-bindings/time/index.html#time.monotonic\" target=\"_blank\"\u003etime.monotonic()\u003c/a\u003e is a built-in time function to Circuit Python that counts upwards from 0 seconds when the board is first powered on.\u0026nbsp; It's a nice way to keep track of time, loosely, as it will lose precision after about 3 days. As long as we only need a rough estimate of time and not precision to the millisecond or second then time.monotonic() serves its purpose well for keeping track of approximate Uptime.\u003c/p\u003e\n      \n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"print(\"\\nFinished!\")\nprint(f\"Board Uptime: {time_calc(time.monotonic())}\")\nprint(f\"Next Update: {time_calc(SLEEP_TIME)}\")\nprint(\"===============================\")","metadata":{"language":"auto","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eFinal Exception\u003c/h3\u003e\n\u003cp\u003eThe last exception from the very first try/except block at the top of the while True loop ends here.\u0026nbsp; It's a general exception catcher.\u0026nbsp; There are a few choices you can make with exceptions.\u0026nbsp;\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003econtinue\u003c/li\u003e\n\u003cli\u003ebreak\u003c/li\u003e\n\u003cli\u003epass\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eContinue will attempt to retry at the point of the try statement it spawned from. Break will break out of the try loop and go to the top of the while True loop.\u0026nbsp; This might happen if WiFi goes down in the middle of the script for example. By using break it will put it back into an infinite reconnect loop which was the very first thing we specified in the while True loop.\u0026nbsp; pass is basically just an ignore and does nothing.\u003c/p\u003e\n\u003cp\u003etime.sleep(SLEEP_TIME) will delay the script from running again until the amount of seconds specified in the variable have passed.\u0026nbsp; In this example that means it will delay running again for 12 hours.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"except (ValueError, RuntimeError) as e:\n    print(\"Failed to get data, retrying\\n\", e)\n    time.sleep(60)\n    break\ntime.sleep(SLEEP_TIME)","metadata":{"language":"auto","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eBelow is a test of the Playground Note code embed directly from Github (must be .md or .py file).\u0026nbsp; If you see the full code below it is working as intended.\u0026nbsp; If the example in the library gets updated this Playground Note might not be kept up to date with future changes. The code below will always be the most up to date as it is pulled directly from the adafruit_requests examples library.\u003c/p\u003e\n\u003ch1\u003eFull Example Code\u003c/h1\u003e\n      \n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"embed","content":"https://github.com/adafruit/Adafruit_CircuitPython_Requests/blob/main/examples/wifi/expanded/requests_wifi_api_rocketlaunch_live.py"},{"element_type":"text","content":"\n  \n        \u003ch1\u003eMore Web API Examples\u003c/h1\u003e\n\u003cp\u003eIf you would like to see more web API examples for Circuit Python visit the\u0026nbsp;\u003ca href=\"https://github.com/adafruit/Adafruit_CircuitPython_Requests/tree/main/examples/wifi/expanded\" target=\"_blank\"\u003eAdafruit_Requests example directory here\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eThere are very basic examples for:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eDiscord\u003c/li\u003e\n\u003cli\u003eFitbit\u003c/li\u003e\n\u003cli\u003eGithub\u003c/li\u003e\n\u003cli\u003eMastodon\u003c/li\u003e\n\u003cli\u003eSteam\u003c/li\u003e\n\u003cli\u003eTwitch\u003c/li\u003e\n\u003cli\u003eYoutube\u003c/li\u003e\n\u003cli\u003eand more....\u003c/li\u003e\n\u003c/ul\u003e\n      \n","metadata":{}}]