[{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eHow many matrix panels can you use with the Matrix Portal S3?\u0026nbsp; Theoretically, around 50, however the more panels you add the greater the travel distance and signal degradation. Degraded signals can have effects of pixel artifacts/glitching and visible scan lines that are very hard on the eyes.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eThe bit depth (amount of possible colors) is also very important. 12 panels cannot stream more than a bit depth of 4 without significant artifacts.\u0026nbsp; Normally with Circuit Python, images are 8-bit indexed BMP's but matrix panels can only display a maximum of 6-bit color. A TFT can support up to 24-bit so do not make the mistake of treating a matrix display like a TFT. It will still process 8-bit indexed BMP's with some image quality loss due to the nature of RGB LED's in a matrix panel.\u003c/p\u003e\n\u003cp\u003eFor this project I'm using 12x 5mm pitch matrix panels. The pitch denotes the physical distance between pixels. A 3mm pitch panel will be much smaller physically than a 6mm pitch for example.\u003c/p\u003e\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/248/original/12_Panels.jpg?1698168086","metadata":{"caption":"12-panel matrix with weather data"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch1\u003ePanel Arrangement\u003c/h1\u003e\n\u003cp\u003eThe serpentine arrangement of the panels matters.\u0026nbsp; If you get it wrong you'll end up re-arranging the entire matrix until you get it right. The RGB Matrix library expects the controller (Matrix Portal S3) to be in the top right of any arrangement regardless of post-processing rotation in the code. The library expects each 2nd row to have the panels flipped 180 degrees as shown below. You can have each row or column with as many panels as you want so long as the rules of the serpentine arrangement are adhered to.\u003c/p\u003e\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/249/original/12-Panel_Arrangement.jpg?1698168330","metadata":{"caption":"12-panel serpentine arrangement"}},{"element_type":"text","content":"\n  \n  \n  \n  \n        \u003ch1\u003eSupport Brackets\u003c/h1\u003e\n\u003cp\u003e3D printing is a very convenient way to assemble all of the panels to make 1 large structural display. There are many other ways you could create a bracket system including aluminum sheets or wood.\u0026nbsp; The 3D printed STL files for these 5mm pitch panels are on \u003ca title=\"Hub 75 P5 Matrix Panel Brackets\" href=\"https://www.printables.com/model/578204-hub75-5mm-pitch-4-panel-bracket\" target=\"_blank\"\u003emy printables page\u003c/a\u003e. The Quad \u0026amp; End brackets are designed to butt together to provide most of the vertical rigidity. \u003c/p\u003e\n      \n\n\n\n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/764/original/MatrixPanelBrackets.jpg?1711439412","metadata":{"caption":"3D Printed Brackets (available on Printables)"}},{"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        \u003ch1\u003ePower Supplies\u003c/h1\u003e\n\u003cp\u003eEach panel requires 5V 4A from a PSU and 15KB of RAM from your microcontroller.\u0026nbsp; For 12 panels that's 5V 42A!\u0026nbsp; I split the power requirements in 2 sections using \u003ca title=\"5V 18A Power Supply\" href=\"https://www.amazon.com/dp/B018TEAPRQ\" target=\"_blank\"\u003e2x 5V 18A power supplies\u003c/a\u003e. By running at lower bit depth you also reduce the amperage required.\u0026nbsp; It is possible that I might need to add a 3rd PSU to my arrangement as I'm maxing out both PSU's.\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","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/251/original/IMG_1163.jpg?1698169453","metadata":{"caption":"5V power distributed to 6 panels for each PSU."}},{"element_type":"text","content":"\n        \u003cp\u003eI chose to pipe the mains power through a \u003ca title=\"Safety Switches\" href=\"https://www.amazon.com/dp/B07RQV2NPN\" target=\"_blank\"\u003epower safety rocker switch for each PSU\u003c/a\u003e.\u0026nbsp; In case of emergency it will be much easier to cut power to sections instead of trying to run a single 48A rated safety switch from a single PSU. You can find 5V power supplies with 60A capability but then you can't use it with a safety switch of this type.\u003c/p\u003e\n\u003cp\u003eIt would be unwise to put a 60A fuse in one of these as the switch itself is only rated for 20A. More than 20A and you could melt the switch! It's not recommended to run a single large high amperage power supply without some type of fuse and safety switch. These panels pull a LOT of power and can produce a good amount of heat.\u0026nbsp; Please plan accordingly when designing your power setup.\u003c/p\u003e\n      ","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/252/original/SafetySwitches.JPG?1698170041","metadata":{"caption":"Mains power safety switches"}},{"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        \u003cp\u003eThe switches come with a 15A cylindrical fuse built-in which I've swapped out for a 20A fuse to meet the power requirements.\u0026nbsp; I have not blown a fuse or PSU yet so the amperage has stayed below the 18A maximum of the PSU.\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":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/253/original/SafetyFuse.JPG?1698170232","metadata":{"caption":"Swap 15A fuse for 20A fuse"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch1\u003eCircuit Python Code\u003c/h1\u003e\n\u003cp\u003e\u003ca title=\"12 Matrix Panel Weather Display Repo\" href=\"https://github.com/DJDevon3/My_Circuit_Python_Projects/tree/main/Boards/espressif/Adafruit%20MatrixPortal%20S3/192x128%20RGB%20Matrix\" target=\"_blank\"\u003eCode repository including fonts, icons, and images\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eThe code for this project incorporates a BME688 temp/humidity/pressure sensor. Also included is temperature biasing algorithm because the BME688 temp sensor in particular can be quite far off from the real temperature. I highly recommend using the BME280 instead as it's far more accurate out of the box and doesn't need bias adjustment in my experience. I used the BME688 here as an experiment to test its quality vs the BME280 and it comes up very short in comparison.\u003c/p\u003e\n\u003cp\u003eThe imports require some external libraries for Circuit Python 8.2.x such as display_text, display_shapes, bitmap_font, and \u003ca title=\"Palette Fader Playground Note\" href=\"https://adafruit-playground.com/u/CGrover/pages/palettefader\" target=\"_blank\"\u003ecedargrove_palettefader\u003c/a\u003e.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eBy using framebufferio we can translate the display into a displayio object so the total display can be used in the same manner as a TFT display with all the features of displayio.\u003c/p\u003e\n\u003cp\u003eFirst you always want to set displayio.release_displays() which will reset the total display and prepare it for new data.\u0026nbsp; We'll set some variables for the pixel dimensions, rotation, and bit depth.\u003c/p\u003e\n\u003cp\u003eBecause I only need the display to update periodically with new weather data I'm setting auto_refresh to false. This helps cut down on display artifacts by not continually streaming new data to the display.\u0026nbsp; In this example its only updating the display once every 60 seconds.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"import time\nimport board\nimport displayio\nimport rgbmatrix\nimport framebufferio\nimport adafruit_imageload\nimport ulab.numpy as np\nimport terminalio\nfrom adafruit_display_text import label\nfrom adafruit_display_shapes.roundrect import RoundRect\nfrom adafruit_bitmap_font import bitmap_font\nfrom cedargrove_palettefader.palettefader_ulab import PaletteFader\nimport adafruit_bme680\n\ndisplayio.release_displays()\nDISPLAY_WIDTH = 192\nDISPLAY_HEIGHT = 128\nDISPLAY_ROTATION = 0\nBIT_DEPTH = 3\nAUTO_REFRESH = 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  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \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\u003eNext we setup the RGBmatrix with physical pinouts of the Matrix Portal S3.\u0026nbsp; This is also where you define how many rows (tile) you have in your array.\u0026nbsp; There are other arrangements that are not serpentine. Please see Jepler's \u003ca href=\"https://learn.adafruit.com/rgb-led-matrices-matrix-panels-with-circuitpython/advanced-multiple-panels\" target=\"_blank\"\u003eAdvanced Matrix Panel Guide for Multiple Panels\u003c/a\u003e as a reference. Double Buffer helps with visual quality especially for scrolling projects.\u0026nbsp; If you can use doublebuffer in your project without running out of ram or crashes I highly recommend it.\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":"matrix = rgbmatrix.RGBMatrix(\n    width=DISPLAY_WIDTH, height=DISPLAY_HEIGHT, bit_depth=BIT_DEPTH,\n    rgb_pins=[\n        board.MTX_R1,\n        board.MTX_G1,\n        board.MTX_B1,\n        board.MTX_R2,\n        board.MTX_G2,\n        board.MTX_B2],\n    addr_pins=[board.MTX_ADDRA, board.MTX_ADDRB, board.MTX_ADDRC, board.MTX_ADDRD],\n    clock_pin=board.MTX_CLK,\n    latch_pin=board.MTX_LAT,\n    output_enable_pin=board.MTX_OE,\n    tile=4,\n    serpentine=True,\n    doublebuffer=True)","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        \u003cp\u003eNext we'll setup the RGBMatrix to use framebufferio and translate that into a displayio object.\u0026nbsp; We'll also setup the BME688 sensor and our display update rate. Polling time and display refresh are the same thing in this circumstance but they are normally separate things for a TFT project. Because we're using it to cut down on glitches \u0026amp; artifacting they can be set to the same variable later in the code.\u0026nbsp; \u003c/p\u003e\n\u003cp\u003eI typically include a set of quick hex color variables in most of my projects.\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","metadata":{}},{"element_type":"code","content":"# Associate the RGB matrix with a Display so we can use displayio\ndisplay = framebufferio.FramebufferDisplay(matrix, auto_refresh=AUTO_REFRESH, rotation=DISPLAY_ROTATION)\n\ni2c = board.I2C()\nsensor = adafruit_bme680.Adafruit_BME680_I2C(i2c)\n\n# Time in seconds between updates (polling)\n# 600 = 10 mins, 900 = 15 mins, 1800 = 30 mins, 3600 = 1 hour\nsleep_time = 60\n\n# Converts seconds in minutes/hours/days\ndef time_calc(input_time):\n    if input_time \u003c 60:\n        sleep_int = input_time\n        time_output = f\"{sleep_int:.0f} seconds\"\n    elif 60 \u003c= input_time \u003c 3600:\n        sleep_int = input_time / 60\n        time_output = f\"{sleep_int:.0f} minutes\"\n    elif 3600 \u003c= input_time \u003c 86400:\n        sleep_int = input_time / 60 / 60\n        time_output = f\"{sleep_int:.0f} hours\"\n    else:\n        sleep_int = input_time / 60 / 60 / 24\n        time_output = f\"{sleep_int:.1f} days\"\n    return time_output\n  \n# Quick Colors for Labels\nTEXT_BLACK = 0x000000\nTEXT_BLUE = 0x0000FF\nTEXT_CYAN = 0x00FFFF\nTEXT_GRAY = 0x8B8B8B\nTEXT_GREEN = 0x00FF00\nTEXT_LIGHTBLUE = 0x90C7FF\nTEXT_MAGENTA = 0xFF00FF\nTEXT_ORANGE = 0xFFA500\nTEXT_PURPLE = 0x800080\nTEXT_RED = 0xFF0000\nTEXT_WHITE = 0xFFFFFF\nTEXT_YELLOW = 0xFFFF00","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        \u003cp\u003eSetup custom fonts and a long list of custom labels that are updated each display refresh.\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":"# Fonts are optional\ntinyfont = bitmap_font.load_font(\"/fonts/tiny3x5.bdf\")\nmedium_font = bitmap_font.load_font(\"/fonts/Arial-16.bdf\")\nhuge_font = bitmap_font.load_font(\"/fonts/GoodTimesRg-Regular-16.bdf\")\nGoodTimes40 = bitmap_font.load_font(\"/fonts/GoodTimesRg-Regular-40.bdf\")\n\n# Individual customizable position labels\n# https://learn.adafruit.com/circuitpython-display-support-using-displayio/text\nhello_label = label.Label(tinyfont)\nhello_label.anchor_point = (0.5, 0.0)\nhello_label.anchored_position = (DISPLAY_WIDTH/2, 2)\nhello_label.scale = (1)\nhello_label.color = TEXT_WHITE\n\nhello_label_outline1 = label.Label(tinyfont)\nhello_label_outline1.anchor_point = (0.5, 0.0)\nhello_label_outline1.anchored_position = (DISPLAY_WIDTH/2, 2+1)\nhello_label_outline1.scale = (1)\nhello_label_outline1.color = TEXT_BLACK\n\nhello_label_outline2 = label.Label(tinyfont)\nhello_label_outline2.anchor_point = (0.5, 0.0)\nhello_label_outline2.anchored_position = (DISPLAY_WIDTH/2, 2-1)\nhello_label_outline2.scale = (1)\nhello_label_outline2.color = TEXT_BLACK\n\nhello_label_outline3 = label.Label(tinyfont)\nhello_label_outline3.anchor_point = (0.5, 0.0)\nhello_label_outline3.anchored_position = (DISPLAY_WIDTH/2+1, 2)\nhello_label_outline3.scale = (1)\nhello_label_outline3.color = TEXT_BLACK\n\nhello_label_outline4 = label.Label(tinyfont)\nhello_label_outline4.anchor_point = (0.5, 0.0)\nhello_label_outline4.anchored_position = (DISPLAY_WIDTH/2-1, 2)\nhello_label_outline4.scale = (1)\nhello_label_outline4.color = TEXT_BLACK\n\nwarning_text_label = label.Label(tinyfont)\nwarning_text_label.anchor_point = (0.5, 0.5)\nwarning_text_label.anchored_position = (DISPLAY_WIDTH/2, 13)\nwarning_text_label.scale = (1)\nwarning_text_label.color = TEXT_RED\n\ntemp_label = label.Label(medium_font)\ntemp_label.anchor_point = (1.0, 1.0)\ntemp_label.anchored_position = (DISPLAY_WIDTH-5, DISPLAY_HEIGHT/2+3)\ntemp_label.scale = 1\ntemp_label.color = TEXT_ORANGE\n\ntemp_data_label = label.Label(GoodTimes40)\ntemp_data_label.anchor_point = (0.5, 0.5)\ntemp_data_label.anchored_position = (DISPLAY_WIDTH/2, DISPLAY_HEIGHT/2)\ntemp_data_label.scale = 1\ntemp_data_label.color = TEXT_ORANGE\n\ntemp_data_shadow = label.Label(GoodTimes40)\ntemp_data_shadow.anchor_point = (0.5, 0.5)\ntemp_data_shadow.anchored_position = (DISPLAY_WIDTH/2+1, DISPLAY_HEIGHT/2+1)\ntemp_data_shadow.scale = 1\ntemp_data_shadow.color = TEXT_BLACK\n\nhumidity_label = label.Label(terminalio.FONT)\nhumidity_label.anchor_point = (0.0, 1.0)\nhumidity_label.anchored_position = (2, DISPLAY_HEIGHT - 14)\nhumidity_label.scale = 1\nhumidity_label.color = TEXT_GRAY\n\nhumidity_outline1 = label.Label(terminalio.FONT)\nhumidity_outline1.anchor_point = (0.0, 1.0)\nhumidity_outline1.anchored_position = (2, DISPLAY_HEIGHT - 14+1)\nhumidity_outline1.scale = 1\nhumidity_outline1.color = TEXT_BLACK\n\nhumidity_outline2 = label.Label(terminalio.FONT)\nhumidity_outline2.anchor_point = (0.0, 1.0)\nhumidity_outline2.anchored_position = (2, DISPLAY_HEIGHT - 14-1)\nhumidity_outline2.scale = 1\nhumidity_outline2.color = TEXT_BLACK\n\nhumidity_outline3 = label.Label(terminalio.FONT)\nhumidity_outline3.anchor_point = (0.0, 1.0)\nhumidity_outline3.anchored_position = (2+1, DISPLAY_HEIGHT - 14)\nhumidity_outline3.scale = 1\nhumidity_outline3.color = TEXT_BLACK\n\nhumidity_outline4 = label.Label(terminalio.FONT)\nhumidity_outline4.anchor_point = (0.0, 1.0)\nhumidity_outline4.anchored_position = (2-1, DISPLAY_HEIGHT - 14)\nhumidity_outline4.scale = 1\nhumidity_outline4.color = TEXT_BLACK\n\nhumidity_data_label = label.Label(huge_font)\nhumidity_data_label.anchor_point = (0.0, 1.0)\nhumidity_data_label.anchored_position = (1, DISPLAY_HEIGHT-2)\nhumidity_data_label.scale = 1\nhumidity_data_label.color = TEXT_ORANGE\n\nhumidity_shadow = label.Label(huge_font)\nhumidity_shadow.anchor_point = (0.0, 1.0)\nhumidity_shadow.anchored_position = (2, DISPLAY_HEIGHT-1)\nhumidity_shadow.scale = 1\nhumidity_shadow.color = TEXT_BLACK\n\nbarometric_label = label.Label(terminalio.FONT)\nbarometric_label.anchor_point = (1.0, 1.0)\nbarometric_label.anchored_position = (DISPLAY_WIDTH-2, DISPLAY_HEIGHT - 13)\nbarometric_label.scale = 1\nbarometric_label.color = TEXT_GRAY\n\nbarometric_label_shadow = label.Label(terminalio.FONT)\nbarometric_label_shadow.anchor_point = (1.0, 1.0)\nbarometric_label_shadow.anchored_position = (DISPLAY_WIDTH-1, DISPLAY_HEIGHT - 12)\nbarometric_label_shadow.scale = 1\nbarometric_label_shadow.color = TEXT_BLACK\n\nbarometric_data_label = label.Label(huge_font)\nbarometric_data_label.anchor_point = (1.0, 1.0)\nbarometric_data_label.anchored_position = (DISPLAY_WIDTH-2, DISPLAY_HEIGHT-2)\nbarometric_data_label.scale = 1\nbarometric_data_label.color = TEXT_ORANGE\n\nbarometric_shadow = label.Label(huge_font)\nbarometric_shadow.anchor_point = (1.0, 1.0)\nbarometric_shadow.anchored_position = (DISPLAY_WIDTH-1, DISPLAY_HEIGHT-1)\nbarometric_shadow.scale = 1\nbarometric_shadow.color = TEXT_BLACK","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        \u003cp\u003eNext we'll use CedarGrove's PaletteFader so we can dynamically change the brightness level of any displayio layer. This part is optional and a nice feature to have. Note that I'm using a large custom background image as the first displayio layer.\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","metadata":{}},{"element_type":"code","content":"# Define background graphic object parameters\nBKG_BRIGHTNESS = 0.5   # Initial brightness level\nBKG_GAMMA = 0.8  # Works nicely for brightness = 0.2\nBKG_IMAGE_FILE = \"images/Astral_Fruit_192x128.bmp\"\n\n# Load the background image and source color palette\nbkg_bitmap, bkg_palette_source = adafruit_imageload.load(\n    BKG_IMAGE_FILE, bitmap=displayio.Bitmap, palette=displayio.Palette\n)\n# Instantiate background PaletteFader object and display on-screen\nfaded_object = PaletteFader(bkg_palette_source,\n                            brightness=BKG_BRIGHTNESS,\n                            gamma=BKG_GAMMA,\n                            normalize=True)\nbkg_tile = displayio.TileGrid(bkg_bitmap, pixel_shader=faded_object.palette)","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        \u003cp\u003eNext is creating a rounded rectangle as the background layer for severe weather popup warnings.\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","metadata":{}},{"element_type":"code","content":"# Warning label RoundRect\nroundrect = RoundRect(int(0),  # x-position of the top left corner\n                      int(8),  # y-position of the top left corner\n                      DISPLAY_WIDTH,  # width of the rounded-corner rectangle\n                      11,  # height of the rounded-corner rectangle\n                      5,  # corner radius\n                      fill=0x0,  # fill color\n                      outline=0xFFFFFF,  # outline color\n                      stroke=1)  # stroke width","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        \u003cp\u003eGroup \u0026amp; Sub-Group creation. \u0026nbsp;Background layers are ordered from top to bottom in displayio groups.\u0026nbsp;\u003cbr\u003eTo layer elements properly you must put them in the correct order you want them displayed.\u003c/p\u003e\n\u003cp\u003eThe function for the warning popup depends on a boolean variable set later in the code.\u0026nbsp; If a weather parameter is over or under a certain threshold then a rounded rectangle popup warning will display alerting you.\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","metadata":{}},{"element_type":"code","content":"# Create subgroups\nprimary_group = displayio.Group()\nprimary_group.append(bkg_tile)\ntext_group = displayio.Group()\nhello_group = displayio.Group()\ntemp_group = displayio.Group()\nwarning_group = displayio.Group()\nmain_group = displayio.Group()\n\n# Add subgroups to main display group\nmain_group.append(primary_group)\nmain_group.append(text_group)\nmain_group.append(hello_group)\nmain_group.append(warning_group)\nmain_group.append(temp_group)\n\n# Add warning popup group\nwarning_group.append(roundrect)\nwarning_group.append(warning_text_label)\n\n# Label Display Group (foreground layer)\nhello_group.append(hello_label_outline1)\nhello_group.append(hello_label_outline2)\nhello_group.append(hello_label_outline3)\nhello_group.append(hello_label_outline4)\nhello_group.append(hello_label)\ntemp_group.append(temp_label)\ntemp_group.append(temp_data_shadow)\ntemp_group.append(temp_data_label)\ntext_group.append(humidity_outline1)\ntext_group.append(humidity_outline2)\ntext_group.append(humidity_outline3)\ntext_group.append(humidity_outline4)\ntext_group.append(humidity_label)\ntext_group.append(humidity_shadow)\ntext_group.append(humidity_data_label)\ntext_group.append(barometric_label_shadow)\ntext_group.append(barometric_label)\ntext_group.append(barometric_shadow)\ntext_group.append(barometric_data_label)\ndisplay.show(main_group)\n\ndef show_warning(text):\n    warning_text_label.text = text\n    warning_group.hidden = False\ndef hide_warning():\n    warning_group.hidden = True","metadata":{"language":"python","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eI typically set some final variables right before the main while loop that are less configuration and more like last minute variables that don't need customization.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eOnce you figure out the offset bias for your temp sensor you can plug values into the algorithm to make it much more accurate. For instance, at 120F I have to subtract 15F for an accurate reading.\u0026nbsp; The algorithm will automatically interpolate between each set of values you give it. Being within 2% of the real temperature is considered acceptable.\u003c/p\u003e\n\u003cp\u003eHow do you know what the real temperature is? Get a NIST traceable thermometer to compare it against. Do not rely on online weather as it can be drastically different from the temperature at your location.\u0026nbsp; Local sensors can be far more useful and more accurate than online temperature data alone. With the right algorithm you can make practically any temp sensor extremely accurate.\u003c/p\u003e\n\u003cp\u003e\u0026nbsp;\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"display_temperature = 0\n# Temperature offset adjustments\n# For correcting incorrect temp sensor readings\ninput_range = [50, 70, 80, 88, 90, 95, 120]\noutput_range = [50-0.1, 70-2.0, 80-2.0, 88-4.5, 90-7.0, 95-10.0, 120-15]\n\nhello_text = \"MATRIX PORTAL S3\"\nhumidity_text = \"HUMIDITY\"\npressure_text = \"PRESSURE\"","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        \u003cp\u003eFinally we're at the main while loop where everything comes together.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eBecause I live at sea level, I have the luxury of calibrating sea level to whatever my pressure sensor outputs. Normally this is where you would want to tie into an online weather service for that value as it will fluxuate constantly... feeding it a single default value like 1014 will only work for about 15 minutes and might throw off your altitude if you're interested in displaying altitude. I do not display altitude from pressure readings in any manner here but it's there if you want to use it.\u003c/p\u003e\n\u003cp\u003eIn order to make text legible against all background colors we can create an outline using 5 labels.\u0026nbsp; Every label you display slows down the display a little. Every label works the CPU a little harder until you have 50 labels and your project slows to a crawl.\u0026nbsp; A microcontroller CPU has a very limited amount of resources so choose what you want to display wisely. Thankfully the Matrix Portal S3 is a beast of a microcontroller and can more than adequately handle everything I'm throwing at it.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"while True:\n    sensor.sea_level_pressure = sensor.pressure\n    hello_label_outline1.text = f\"{hello_text}\"\n    hello_label_outline2.text = f\"{hello_text}\"\n    hello_label_outline3.text = f\"{hello_text}\"\n    hello_label_outline4.text = f\"{hello_text}\"\n    hello_label.text = f\"{hello_text}\"\n    print(\"===============================\")\n    debug_OWM = False  # Set to True for Serial Print Debugging\n\n    # Local sensor data display\n    temp_label.text = \"°F\"\n\n    # Board Uptime\n    print(\"Board Uptime: \", time_calc(time.monotonic()))\n\n    # Account for PCB heating bias, gets slightly hotter as ambient increases\n    temperature = sensor.temperature * 1.8 + 32\n    temp_round = round(temperature, 2)\n    print(\"Sensor Temp: \", temperature)  # biased reading\n    display_temperature = np.interp(temperature, input_range, output_range)\n    display_temperature = round(display_temperature[0], 2)\n    print(f\"Adjusted Temp: {display_temperature:.1f}\")\n    #mqtt_pressure = 1009  # Manually set to debug warning message\n    mqtt_humidity = round(sensor.relative_humidity, 1)\n    mqtt_pressure = round(sensor.pressure, 1)\n    mqtt_altitude = round(sensor.altitude, 2)\n\n    temp_data_shadow.text = f\"{display_temperature:.1f}\"\n    temp_data_label.text = f\"{display_temperature:.1f}\"\n    humidity_outline1.text = f\"{humidity_text}\"\n    humidity_outline2.text = f\"{humidity_text}\"\n    humidity_outline3.text = f\"{humidity_text}\"\n    humidity_outline4.text = f\"{humidity_text}\"\n    humidity_label.text = f\"{humidity_text}\"\n    humidity_shadow.text = f\"{mqtt_humidity:.1f} %\"\n    humidity_data_label.text = f\"{mqtt_humidity:.1f} %\"\n    barometric_label_shadow.text = f\"{pressure_text}\"\n    barometric_label.text = f\"{pressure_text}\"\n    barometric_shadow.text = f\"{mqtt_pressure:.1f}\"\n    barometric_data_label.text = f\"{mqtt_pressure:.1f}\"\n\n    # Warnings based on local sensors\n    if mqtt_pressure \u003c= 919:  # pray you never see this message\n        show_warning(\"HOLY COW: SEEK SHELTER!\")\n    elif 920 \u003c= mqtt_pressure \u003c= 979:\n        show_warning(\"DANGER: MAJOR HURRICANE!\")\n    elif 980 \u003c= mqtt_pressure \u003c= 989:\n        show_warning(\"DANGER: MINOR HURRICANE\")\n    elif 990 \u003c= mqtt_pressure \u003c= 1001:\n        show_warning(\"WARNING: TROPICAL STORM\")\n    elif 1002 \u003c= mqtt_pressure \u003c= 1009:  # sudden gusty downpours\n        show_warning(\"CAUTION: LOW PRESSURE SYSTEM\")\n    elif 1019 \u003c= mqtt_pressure \u003c= 1025:  # sudden light cold rain\n        show_warning(\"CAUTION: HIGH PRESSURE SYSTEM\")\n    elif mqtt_pressure \u003e= 1026:\n        show_warning(\"WARNING: HAIL \u0026 TORNADO WATCH\")\n    else:\n        hide_warning()  # Normal pressures: 1110-1018 (no message)\n\n    print(\"Next Update: \", time_calc(sleep_time))\n    print(\"===============================\")\n    display.refresh(target_frames_per_second=10, minimum_frames_per_second=0)\n    time.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        \u003cp\u003eThe \u003ca title=\"12 Matrix Panel Weather Display Repo\" href=\"https://github.com/DJDevon3/My_Circuit_Python_Projects/tree/main/Boards/espressif/Adafruit%20MatrixPortal%20S3/192x128%20RGB%20Matrix\" target=\"_blank\"\u003ecode repository for this project is located here\u003c/a\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","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/5778","metadata":{"name":"Adafruit Matrix Portal S3","url":"https://www.adafruit.com/product/5778","description":"Adafruit Matrix Portal S3 CircuitPython Powered Internet Display","quantity":"1","featured":false}}]