{"id":2264,"date":"2020-12-31T08:19:00","date_gmt":"2020-12-31T08:19:00","guid":{"rendered":"http:\/\/haveblue.org\/?p=2264"},"modified":"2021-05-04T02:11:10","modified_gmt":"2021-05-04T02:11:10","slug":"metar-map-oled-add-on","status":"publish","type":"post","link":"https:\/\/haveblue.org\/?p=2264","title":{"rendered":"METAR Map OLED Add-on"},"content":{"rendered":"\n<p>My gift of a <a href=\"http:\/\/haveblue.org\/?p=2236\">METAR map<\/a> to my flight instructor last year resulted in said map quickly adorning the wall of the flight school hangar, followed by word spreading around the airfield of it being my handiwork, soon followed by an inquiry from another student asking if they could buy one from me.  I kind of brushed it off, saying that it wasn&#8217;t difficult to build for yourself, here&#8217;s the blog post explaining how I did it, etc.  Yet people continued asking if they could buy one from me, so I find myself seriously considering producing southeastern Wisconsin METAR maps for fellow flyers.  Especially since the transponder in my plane has died (apologies to the KUES tower &#8211; it <em>had<\/em> been working earlier in the year, honest), and what better way to raise funds for replacing the Carter-era radio stack than by unleashing my inner craft fair booth denizen?  &#8220;Please sir, spare a half shilling for an avionics upgrade?&#8221;<\/p>\n\n\n\n<p>I had come across a new project called <a href=\"https:\/\/www.livesectional.com\/\">LiveSectional<\/a> offering itself as a more turnkey approach to making a METAR map, and what really interested me was the <a href=\"https:\/\/www.livesectional.com\/oled-displays-add-on\/\">OLED add-on<\/a>.  I downloaded the image as well as the source, but had no luck in getting the software to function after many hours of messing with it.  So, what to do, but dive in and roll my own&#8230;<\/p>\n\n\n\n<p>I purchased a <a href=\"https:\/\/www.adafruit.com\/product\/3531\">128&#215;64 OLED bonnet<\/a> from Adafruit and dove into testing it out with a RasPi 0 W (as that&#8217;s the board I am using for the METAR maps).  After going through the <a href=\"https:\/\/learn.adafruit.com\/adafruit-128x64-oled-bonnet-for-raspberry-pi\">learning guide<\/a>, I started poking at the sample files and was tossed head-first into the world of Python.  After a few hours of poking around and hacking up the code with <a href=\"https:\/\/github.com\/prueker\/METARMap\">Philip Reuker&#8217;s code<\/a>, I was suitably impressed with Python as a language, especially when realizing &#8220;wait, that should not have actually worked&#8221; when passing floating point values to a function that drives a bitmapped display.  I would have LOVED to have had that sort of simplicity when doing graphics programming a quarter century ago.<\/p>\n\n\n\n<p>My favorite part was just coming up with a simple pointer to indicate wind direction.  I initially thought of doing static bitmaps or a lookup table or somesuch, but laziness won out and trigonometry saved the day (I hope my high school teachers are around to bask in my admission of trigonometry being &#8216;lazy&#8217;).  This is still a work-in-progress, but it actually works!<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"980\" height=\"517\" src=\"https:\/\/i0.wp.com\/haveblue.org\/wp-content\/uploads\/2021\/01\/thumbnail_IMG_6883.jpg?resize=980%2C517\" alt=\"\" class=\"wp-image-2281\" srcset=\"https:\/\/i0.wp.com\/haveblue.org\/wp-content\/uploads\/2021\/01\/thumbnail_IMG_6883.jpg?resize=1024%2C540&amp;ssl=1 1024w, https:\/\/i0.wp.com\/haveblue.org\/wp-content\/uploads\/2021\/01\/thumbnail_IMG_6883.jpg?resize=300%2C158&amp;ssl=1 300w, https:\/\/i0.wp.com\/haveblue.org\/wp-content\/uploads\/2021\/01\/thumbnail_IMG_6883.jpg?resize=768%2C405&amp;ssl=1 768w, https:\/\/i0.wp.com\/haveblue.org\/wp-content\/uploads\/2021\/01\/thumbnail_IMG_6883.jpg?resize=210%2C111&amp;ssl=1 210w, https:\/\/i0.wp.com\/haveblue.org\/wp-content\/uploads\/2021\/01\/thumbnail_IMG_6883.jpg?w=1280&amp;ssl=1 1280w\" sizes=\"auto, (max-width: 980px) 100vw, 980px\" \/><\/figure>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/usr\/bin\/env python3\n\nimport urllib.request\nimport xml.etree.ElementTree as ET\nimport datetime\nimport math\n\nairportcode = \"KOSH\"\n\n# That's the configurables, now for the code\n\nprint(\"Running windoled.py at \" + datetime.datetime.now().strftime('%d\/%m\/%Y %H:%M'))\n\n# Retrieve METAR from aviationweather.gov data server\n# Details about parameters can be found here: https:\/\/www.aviationweather.gov\/dataserver\/example?datatype=metar\nurl = \"https:\/\/www.aviationweather.gov\/adds\/dataserver_current\/httpparam?dataSource=metars&amp;requestType=retrieve&amp;format=xml&amp;hoursBeforeNow=5&amp;mostRecentForEachStation=true&amp;stationString=\" + airportcode\nprint(url)\nreq = urllib.request.Request(url, headers={'User-Agent': 'Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/86.0.4240.198 Safari\/537.36 Edg\/86.0.622.69'})\ncontent = urllib.request.urlopen(req).read()\n\n# Retrieve flying conditions for the airport noted in airportcode\nroot = ET.fromstring(content)\nfor metar in root.iter('METAR'):\n\tstationId = metar.find('station_id').text\n\tif metar.find('flight_category') is None:\n\t\tprint(\"Missing flight condition, skipping.\")\n\t\tcontinue\n\tflightCategory = metar.find('flight_category').text\n\twindGust = 0\n\twindSpeed = 0\n\twindDir = 0\n\tvisibility = 0\n\tpressure = 0\n\tlightning = False\n\tif metar.find('wind_gust_kt') is not None:\n\t\twindGust = int(metar.find('wind_gust_kt').text)\n\tif metar.find('wind_speed_kt') is not None:\n\t\twindSpeed = int(metar.find('wind_speed_kt').text)\n\tif metar.find('wind_dir_degrees') is not None:\n\t\twindDir = int(metar.find('wind_dir_degrees').text)\n\tif metar.find('visibility_statute_mi') is not None:\n                visibility = float(metar.find('visibility_statute_mi').text)\n\tif metar.find('altim_in_hg') is not None:\n\t\tpressure = float(metar.find('altim_in_hg').text)\n\t\tformatted_pressure = \"{:.2f}\".format(pressure)\n\tif metar.find('raw_text') is not None:\n\t\trawText = metar.find('raw_text').text\n\t\tlightning = False if rawText.find('LTG') == -1 else True\n\tprint(stationId + \":\" + flightCategory + \":\" + str(windSpeed) + \":\" + str(windGust) + \":\" + str(lightning))\nprint(\"WindDir:\")\nprint(windDir)\nprint(\"WindSpeed:\")\nprint(windSpeed)\nprint(\"WindGust:\")\nprint(windGust)\nprint(\"visibility:\")\nprint(visibility)\nprint(\"flightCategory:\")\nprint(flightCategory)\n\n# now for the display stuff\nfrom board import SCL, SDA\nimport busio\nfrom PIL import Image, ImageDraw, ImageFont\nimport adafruit_ssd1306\n\n# Create the I2C interface.\ni2c = busio.I2C(SCL, SDA)\n# Create the SSD1306 OLED class.\ndisp = adafruit_ssd1306.SSD1306_I2C(128, 64, i2c)\n\n# Clear display.\ndisp.fill(0)\ndisp.show()\n\n# Create blank image for drawing.\n# Make sure to create image with mode '1' for 1-bit color.\nwidth = disp.width\nheight = disp.height\nimage = Image.new(\"1\", (width, height))\n\n# Get drawing object to draw on image.\ndraw = ImageDraw.Draw(image)\n\n# Draw a black filled box to clear the image.\ndraw.rectangle((0, 0, width, height), outline=0, fill=0)\n\n# Load our font of choice\n# Pixellari is a great font, but others can be found at http:\/\/www.dafont.com\/bitmap.php\n#font = ImageFont.load_default()\n#font = ImageFont.truetype('Minecraftia-Regular.ttf', 8)\nfont = ImageFont.truetype('\/home\/pi\/Pixellari.ttf', 16)\n\n# Write our textual info on the right side of the display\nTextXStart = 66\nTextYStart = -1\nTextRowSpacing = 13\ndraw.text((TextXStart, TextYStart + TextRowSpacing * 0), airportcode, font=font, fill=255)\ndraw.text((TextXStart, TextYStart + TextRowSpacing * 1), str(windDir) + \" deg\", font=font, fill=255)\nif windGust: \n    draw.text((TextXStart, TextYStart + TextRowSpacing * 2), str(windSpeed) + \" G \" + str(windGust), font=font, fill=255)\nelse:\n    draw.text((TextXStart, TextYStart + TextRowSpacing * 2), str(windSpeed) + \" kts\", font=font, fill=255)\ndraw.text((TextXStart, TextYStart + TextRowSpacing * 3), str(visibility) + \" sm\", font=font, fill=255)\ndraw.text((TextXStart, TextYStart + TextRowSpacing * 4), str(formatted_pressure) + \" in\", font=font, fill=255)\n\n# Figure out how to draw the wind pointer\nPointerRadius = 32\nArrowTailSize = 25 # Degrees per side for the arrow tail (How chonky to make the pointer - 10=A Fine Boi; 50=OH LAWD HE COMIN)\nArrowTipX = PointerRadius * (math.sin(math.radians(windDir + 180)))\nArrowTipY = PointerRadius * (math.cos(math.radians(windDir + 180)))\nArrowTailX = PointerRadius * 0.5 * (math.sin(math.radians(windDir)))\nArrowTailY = PointerRadius * 0.5 * (math.cos(math.radians(windDir)))\nArrowTailLeftX = PointerRadius * (math.sin(math.radians(windDir + ArrowTailSize)))\nArrowTailLeftY = PointerRadius * (math.cos(math.radians(windDir + ArrowTailSize)))\nArrowTailRightX = PointerRadius * (math.sin(math.radians(windDir - ArrowTailSize)))\nArrowTailRightY = PointerRadius * (math.cos(math.radians(windDir - ArrowTailSize)))\n\n#translate wind pointer to quadrant IV and get absolute values for Y\nArrowTipX += PointerRadius\nArrowTipY = abs(ArrowTipY - PointerRadius)\nArrowTailLeftX += PointerRadius\nArrowTailLeftY = abs(ArrowTailLeftY - PointerRadius)\nArrowTailX += PointerRadius\nArrowTailY = abs(ArrowTailY - PointerRadius)\nArrowTailRightX += PointerRadius\nArrowTailRightY = abs(ArrowTailRightY - PointerRadius)\n\n#draw wind circle\ndraw.ellipse((0,0,(PointerRadius * 2), PointerRadius * 2 - 1), outline=255, fill=0)\n\n#draw wind pointer if there is wind and fill it if conditions are VFR\nif windSpeed:\n  if flightCategory == 'VFR':\n    draw.polygon(\n      &#91;(ArrowTipX, ArrowTipY), (ArrowTailLeftX, ArrowTailLeftY), (ArrowTailX, ArrowTailY), (ArrowTailRightX, ArrowTailRightY)],\n      outline=255,\n      fill=1,\n    )\n  else:\n    draw.polygon(\n      &#91;(ArrowTipX, ArrowTipY), (ArrowTailLeftX, ArrowTailLeftY), (ArrowTailX, ArrowTailY), (ArrowTailRightX, ArrowTailRightY)],\n      outline=255,\n      fill=0,\n    )\n\n# Display image.\ndisp.image(image)\ndisp.show()\n\nprint()\nprint(\"Done\")<\/code><\/pre>\n\n\n\n<p>The arrowhead gets filled if conditions are VFR, and the font can certainly be changed to add more rows of data if desired.  Yes, the code is ugly, but&#8230;<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/haveblue.org\/wp-content\/uploads\/2021\/01\/butitdoesrun.png?resize=306%2C512\" alt=\"\" class=\"wp-image-2276\" width=\"306\" height=\"512\" srcset=\"https:\/\/i0.wp.com\/haveblue.org\/wp-content\/uploads\/2021\/01\/butitdoesrun.png?resize=612%2C1024&amp;ssl=1 612w, https:\/\/i0.wp.com\/haveblue.org\/wp-content\/uploads\/2021\/01\/butitdoesrun.png?resize=179%2C300&amp;ssl=1 179w, https:\/\/i0.wp.com\/haveblue.org\/wp-content\/uploads\/2021\/01\/butitdoesrun.png?resize=126%2C210&amp;ssl=1 126w, https:\/\/i0.wp.com\/haveblue.org\/wp-content\/uploads\/2021\/01\/butitdoesrun.png?w=700&amp;ssl=1 700w\" sizes=\"auto, (max-width: 306px) 100vw, 306px\" \/><\/figure>\n\n\n\n<p>Naturally, after I wrote all this up, I found that Philip had just added his own OLED output capability to his code&#8230;  Welp, great minds think alike!  Er, at least, that&#8217;s what I keep telling myself&#8230;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>My gift of a METAR map to my flight instructor last year resulted in said map quickly adorning the wall of the flight school hangar, followed by word spreading around the airfield of it being my handiwork, soon followed by an inquiry from another student asking if they could buy one from me. I kind [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"nf_dc_page":"","_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[3],"tags":[],"class_list":["post-2264","post","type-post","status-publish","format-standard","hentry","category-aviation"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/haveblue.org\/index.php?rest_route=\/wp\/v2\/posts\/2264","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/haveblue.org\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/haveblue.org\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/haveblue.org\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/haveblue.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2264"}],"version-history":[{"count":5,"href":"https:\/\/haveblue.org\/index.php?rest_route=\/wp\/v2\/posts\/2264\/revisions"}],"predecessor-version":[{"id":2290,"href":"https:\/\/haveblue.org\/index.php?rest_route=\/wp\/v2\/posts\/2264\/revisions\/2290"}],"wp:attachment":[{"href":"https:\/\/haveblue.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2264"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/haveblue.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2264"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/haveblue.org\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2264"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}