[{"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        \u003cp\u003eI was looking for a clock that would tell the time in words rather than just hands (analog) or numbers (digital). I found a few for sale on places like Amazon and eBay, but none of those particularly stood out, so I decided to create a customizable one myself.\u003c/p\u003e\n\u003cp\u003eI designed and built two variants. Both use the same RGB matrix panel. Both use CircuitPython on the ESP32-S3, but one uses the Matrix Portal S3, and the other uses a standalone Feather ESP32-S3 with an Adafruit RGB Matrix Featherwing Kit. The Matrix Portal version requires no soldering; the Feather version requires some simple soldering. I will show both here.\u003c/p\u003e\n\u003cp\u003eThe text is left justified, vertically centered on the display and each line is in a random color which changes every time the minute changes. Since you control the CircuitPython code, you can change this formatting any way you choose, e.g. same color lines, individually horizontally centered lines, etc.\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","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/660/original/IMG_0763.jpeg?1706475192","metadata":{"caption":""}},{"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        \u003ch3\u003e\u003cstrong\u003eCommon Hardware\u003c/strong\u003e\u003c/h3\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","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        \u003cul\u003e\n\u003cli\u003e\u003cspan\u003eAny of the 64x64 RGB LED matrix panels will work. The only differences in the result will be the panel dimensions and appearance. I chose the 2mm pitch version because I liked the smaller panel size and less space between LEDs. The 2.5 mm pitch also looks good. Just choose one that meets your requirements.\u003c/span\u003e\u003c/li\u003e\n\u003c/ul\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":"product","content":"https://www.adafruit.com/product/5362","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        \u003cul\u003e\n\u003cli\u003eOptional - magnetic feet for matrix panel\u003c/li\u003e\n\u003c/ul\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","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/4631","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        \u003ch3\u003e\u003cstrong\u003eThe Matrix Portal S3 Version\u003c/strong\u003e\u003c/h3\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","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        \u003cul\u003e\n\u003cli\u003eAdafruit Matrix Portal S3\u003c/li\u003e\n\u003c/ul\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","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/5778","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        \u003cul\u003e\n\u003cli\u003eAny 5V, 2A or higher USB power supply and cable with a USB type C connector. For example:\u003c/li\u003e\n\u003c/ul\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","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/5802","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        \u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eAssembly\u003c/strong\u003e\u003c/li\u003e\n\u003c/ul\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","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n        \u003cul\u003e\n\u003cli\u003ePrep the Matrix Portal. See: \u003ca href=\"https://learn.adafruit.com/adafruit-matrixportal-s3/prep-the-matrixportal\"\u003ehttps://learn.adafruit.com/adafruit-matrixportal-s3/prep-the-matrixportal\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003eUsing a USB cable, install CircuitPython on the Matrix Portal. See: \u003ca href=\"https://learn.adafruit.com/adafruit-matrixportal-s3/install-circuitpython\"\u003ehttps://learn.adafruit.com/adafruit-matrixportal-s3/install-circuitpython\u003c/a\u003e\u003cbr\u003eBe sure to update the settings.toml file with your WiFi SSID, WiFi password, and timezone information.\u003c/li\u003e\n\u003c/ul\u003e\n      \n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"CIRCUITPY_WIFI_SSID = \"xxxxxx\"\nCIRCUITPY_WIFI_PASSWORD = \"xxxxxx\"\nCIRCUITPY_WEB_API_PASSWORD = \"yyyyyy\"\nCIRCUITPY_WEB_API_PORT=80\n\n# UTC_OFFSET, if present, will override TZ, no worldtimeapi.org API query will be done, and no DST adjustment\n#UTC_OFFSET=-25200\nTZ=\"America/Phoenix\"","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        \u003cul\u003e\n\u003cli\u003e\n\u003cspan\u003eUpdate your\u0026nbsp;\u003c/span\u003e\u003cstrong\u003ecode.py\u003c/strong\u003e\u003cspan\u003e\u0026nbsp;to the following:\u003c/span\u003e\n\u003c/li\u003e\n\u003c/ul\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","metadata":{}},{"element_type":"code","content":"# SPDX-FileCopyrightText: 2023 Frederick M Meyer\n#\n# SPDX-License-Identifier: MIT\n\nimport ipaddress\nimport ssl\nimport wifi\nimport socketpool\nimport adafruit_requests\nimport random\nimport os\nimport time\nimport rtc\nimport adafruit_ntp\nimport adafruit_datetime\nimport displayio\nimport framebufferio\nimport rgbmatrix\nimport board\nimport digitalio\nimport terminalio\nimport adafruit_display_text as adt\nimport adafruit_display_text.label as adtl\n\nUTC_OFFSET = os.getenv('UTC_OFFSET')\nTZ = os.getenv('TZ')\n\nQUARTER = \"Quarter\"\nHALF = \"Half\"\nPAST = \"Past\"\nUNTIL = \"Until\"\nOCLOCK = \"O'clock\"\nNOON = \"Noon\"\nMIDNIGHT = \"Midnight\"\nMINUTE = \"Minute\"\nSINGLE_DIGITS = [\"One\", \"Two\", \"Three\", \"Four\", \"Five\", \"Six\", \"Seven\", \"Eight\", \"Nine\"]\nTEN_PLUS = [\"Ten\", \"Eleven\", \"Twelve\", \"Thirteen\", \"Fourteen\", \"Fifteen\", \"Sixteen\", \"Seventeen\", \"Eighteen\", \"Nineteen\"]\nTENS = [\"Twenty\", \"Thirty\", \"Fourty\", \"Fifty\"]\n\n#time.sleep(15)\n\nled = digitalio.DigitalInOut(board.LED)\nled.direction = digitalio.Direction.OUTPUT\nled.value = False\n\ndisplayio.release_displays()\n\n# Wifi details are in settings.toml file\n\nprint(\"My MAC addr:\", [hex(i) for i in wifi.radio.mac_address])\n\nprint(\"Connecting to %s\"%os.getenv(\"CIRCUITPY_WIFI_SSID\"))\nwifi.radio.connect(os.getenv(\"CIRCUITPY_WIFI_SSID\"), os.getenv(\"CIRCUITPY_WIFI_PASSWORD\"))\nprint(\"Connected to %s!\"%os.getenv(\"CIRCUITPY_WIFI_SSID\"))\nprint(\"My IP address is\", wifi.radio.ipv4_address)\n\npool = socketpool.SocketPool(wifi.radio)\n\nif UTC_OFFSET is None:\n    requests = adafruit_requests.Session(pool, ssl.create_default_context())\n    response = requests.get(\"http://worldtimeapi.org/api/timezone/\" + TZ)\n    response_as_json = response.json()\n    UTC_OFFSET = response_as_json[\"raw_offset\"] + response_as_json[\"dst_offset\"]\n\nntp = adafruit_ntp.NTP(pool, server=\"us.pool.ntp.org\", tz_offset=UTC_OFFSET // 3600)\n\nrtc.RTC().datetime = ntp.datetime\n\nlast_minute_displayed = -1\n\ndef format_hour(hour_num):\n    hour = hour_num % 24\n    if hour == 0:\n        o_hour = MIDNIGHT\n    elif hour == 12:\n        o_hour = NOON\n    else:\n        if hour \u003e 12:\n            hour -= 12\n        elif hour == 0:\n            hour = 12\n\n        if hour \u003c 10:\n            o_hour = SINGLE_DIGITS[hour - 1]\n        else:\n            o_hour = TEN_PLUS[hour - 10]\n    return hour, o_hour\n\nCOLOR_VALUES = [0, 128, 255]  # Brighter - Possible color values used for random 0..2 selection\n#COLOR_VALUES = [0, 80, 160]   # Dimmer   - Possible color values used for random 0..2 selection\n#COLOR_VALUES = [0, 64, 128]   # Dimmer   - Possible color values used for random 0..2 selection\n\ndef pick_random_color():\n    # Pick a random color for each line and add it to the display\n    r = COLOR_VALUES[random.randint(0, 2)]\n    g = COLOR_VALUES[random.randint(0, 2)]\n    b = COLOR_VALUES[random.randint(0, 2)]\n    if not (r | g | b): r = g = b = COLOR_VALUES[2] # Set to white if black result\n\n    return (r\u003c\u003c16|g\u003c\u003c8|b)\n\nmatrix = rgbmatrix.RGBMatrix(\n    width=64, height=64, bit_depth=2,\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    ],\n    addr_pins=[\n        board.MTX_ADDRA,\n        board.MTX_ADDRB,\n        board.MTX_ADDRC,\n        board.MTX_ADDRD,\n        board.MTX_ADDRE\n    ],\n    clock_pin=board.MTX_CLK,\n    latch_pin=board.MTX_LAT,\n    output_enable_pin=board.MTX_OE\n)\n\ndisplay = framebufferio.FramebufferDisplay(matrix, auto_refresh=True)\ndisplay.rotation = 0\n\nwhile True:\n    # time.struct_time(tm_year=2023, tm_mon=2, tm_mday=23, tm_hour=14, tm_min=6, tm_sec=29, tm_wday=3, tm_yday=54, tm_isdst=0)\n    lt = time.localtime()\n\n    hour = lt.tm_hour\n    min = lt.tm_min\n\n    #hour = 12  # For testing\n    #min = 00   # For testing\n\n    if last_minute_displayed != min:\n        last_minute_displayed = min\n\n        if min \u003c= 30:\n            o_min_half = \"Past\"\n            hour, o_hour = format_hour(hour)\n        else:\n            o_min_half = \"Until\"\n            min = 60 - min\n            hour, o_hour = format_hour(hour + 1)\n\n        if min == 15:\n            o_min = \"Quarter\"\n        elif min == 30:\n            o_min = \"Half\"\n        elif min == 0:\n            o_min = \"\"\n            o_min_half = \"\"\n        elif min \u003c 10:\n            o_min = SINGLE_DIGITS[min - 1]\n        elif min \u003c 20:\n            o_min = TEN_PLUS[min - 10]\n        else:\n            o_min = TENS[(min // 10) - 2]\n            if min % 10:\n                o_min += \" \" + SINGLE_DIGITS[(min % 10) - 1]\n\n        if o_min not in [QUARTER, HALF, \"\"]:\n            o_min += \" \" + MINUTE\n            if min != 1: o_min += \"s\"\n\n        if min \u003e 0 or o_hour in [MIDNIGHT, NOON]:\n            txt = (o_min + \" \" + o_min_half + \" \" + o_hour).strip()\n        else:\n            txt = (o_min + \" \" + o_min_half + \" \" + o_hour + \" \" + OCLOCK).strip()\n\n        text_list = adt.wrap_text_to_pixels(txt, 60, font=terminalio.FONT)\n        \n        total_height = 0\n        max_width = 0\n        line_list = []\n        for w in text_list:\n            line = adtl.Label(\n                terminalio.FONT,\n                color=pick_random_color(),\n                text=w,\n                scale=1)\n            line_list.append(line)\n            zx, zy, zwidth, zheight = line.bounding_box\n            total_height += zheight\n            max_width = max(max_width, zwidth)\n        xwork = ((60 - max_width) // 2) + 2\n        ywork = ((60 - total_height) // 2) + 2 + 6\n\n        current_y = ywork        \n        g = displayio.Group()\n        for l in line_list:\n            l.x = xwork\n            l.y = current_y\n            zx, zy, zwidth, zheight = l.bounding_box\n            current_y += zheight\n            g.append(l)\n        display.root_group=g\n\n    time.sleep(1)","metadata":{"language":"auto","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n        \u003cul\u003e\n\u003cli\u003eInstall the following modules into the lib folder:\n\u003cul\u003e\n\u003cli\u003e\u003ca\u003e\u003cspan class=\"filename\" title=\"adafruit_display_text\"\u003eadafruit_display_text\u003c/span\u003e\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\u003e\u003cspan class=\"filename\" title=\"adafruit_datetime.mpy\"\u003eadafruit_datetime\u003c/span\u003e\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\u003e\u003cspan class=\"filename\" title=\"adafruit_ntp.mpy\"\u003eadafruit_ntp\u003c/span\u003e\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\u003e\u003cspan class=\"filename\" title=\"adafruit_requests.mpy\"\u003eadafruit_requests\u003c/span\u003e\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eConnect the Matrix Portal and power connector to the RGB LED panel and then connect the power supply to the Matrix Portal. If there is a pin on the frame of the panel under the Matrix Portal that prevents it from connecting fully, use angle cutters to remove the pin.\u003c/li\u003e\n\u003c/ul\u003e\n      \n\n\n\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        \u003ch3\u003e\u003cstrong\u003eThe standalone ESP32-S3 Feather Version\u003c/strong\u003e\u003c/h3\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","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        \u003cul\u003e\n\u003cli\u003eAny of the Adafruit ESP32-S3 Feathers should work. I decided that the best fit would be one of these two. I chose the first for no specific reason.\u003c/li\u003e\n\u003c/ul\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","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/5477","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        \u003cp style=\"text-align: center;\"\u003e-or-\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","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/5323","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        \u003cul\u003e\n\u003cli\u003eBecause I chose an ESP32-S3 in the Feather form factor as a processor, I needed a compatible Featherwing format for the interface to the RGB matrix panel. Be careful, there are multiple versions of these kits based on the processor type it supports.\u003c/li\u003e\n\u003c/ul\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","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/3036","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        \u003cul\u003e\n\u003cli\u003eAny 5V, 2A or greater power supply with a 'standard' 5.5mm OD, 2.1mm ID positive tip connector to plug into the socket on the Featherwing adapter. For example, I used a 4-amp power supply in case I ever decide to put up a display that lights more pixels and has a higher power draw, but 2A should be enough to power this build.\u003c/li\u003e\n\u003c/ul\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","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/1466","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        \u003cul\u003e\n\u003cli\u003eThe ESP32-S3 stacks on top of the Featherwing, so we need some female headers.\u003c/li\u003e\n\u003c/ul\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","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/2886","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        \u003cul\u003e\n\u003cli\u003eA 6-inch piece of 18-22AWG solid-core hook-up wire\u003c/li\u003e\n\u003cli\u003eSoldering Iron and rosin-core electronics solder\u003c/li\u003e\n\u003cli\u003eAngle cutters\u003c/li\u003e\n\u003c/ul\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","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        \u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eAssembly\u003c/strong\u003e\u003c/li\u003e\n\u003c/ul\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","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        \u003cul\u003e\n\u003cli\u003ePrep RGB Matrix Featherwing. See: \u003ca href=\"https://learn.adafruit.com/rgb-matrix-featherwing\"\u003ehttps://learn.adafruit.com/rgb-matrix-featherwing\u003c/a\u003e\u003cbr\u003e\u003cbr\u003eBe sure you are soldering all the components on the proper side of the PCB and facing in the correct direction. Before soldering any other parts, you will need to install a jumper wire, as seen in the picture. The Featherwing comes from the factory with 4 address lines (\"A\"-\"D\"). This build uses a 64x64 pixel panel which requires 5 address lines, so you have to add an additional address line. This wire will provide the path for the \"E\" address line on Pin 8 of the HUB-75 connector. (If your panel uses Pin 16, move the wire to connect on that pin instead. Adafruit panels all use Pin 8.)\u003cbr\u003eYou may use either the male OR female HUB-75 connector, but only one can be soldered on the PCB, so choose the one that meets your needs. They mount on different sides of the board, so again, be sure you're soldering on the correct side and facing in the correct direction (male connector has a notch in it).\u003c/li\u003e\n\u003c/ul\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","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/668/original/IMG_1130.jpeg?1706770790","metadata":{"caption":""}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/667/original/IMG_1123%281%29.jpeg?1706770688","metadata":{"caption":""}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/671/original/IMG_1126.jpeg?1706774974","metadata":{"caption":""}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/670/original/IMG_1125.jpeg?1706774856","metadata":{"caption":""}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/669/original/IMG_1107.jpeg?1706774718","metadata":{"caption":""}},{"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        \u003cul\u003e\n\u003cli\u003eSolder the headers onto the Feather ESP32-S3 facing down and soldered on the top.\u003c/li\u003e\n\u003cli\u003eUsing a USB cable, install CircuitPython on the ESP32-S3. See: \u003ca href=\"https://learn.adafruit.com/adafruit-esp32-s3-feather/circuitpython\"\u003ehttps://learn.adafruit.com/adafruit-esp32-s3-feather/circuitpython\u003c/a\u003e\u003cbr\u003eBe sure to update the settings.toml file with your WiFi SSID, WiFi password, and timezone information.\u003c/li\u003e\n\u003c/ul\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","metadata":{}},{"element_type":"code","content":"CIRCUITPY_WIFI_SSID = \"xxxxxx\"\nCIRCUITPY_WIFI_PASSWORD = \"xxxxxx\"\nCIRCUITPY_WEB_API_PASSWORD = \"yyyyyy\"\nCIRCUITPY_WEB_API_PORT=80\n\n# UTC_OFFSET, if present, will override TZ, no worldtimeapi.org API query will be done, and no DST adjustment\n#UTC_OFFSET=-25200\nTZ=\"America/Phoenix\"","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        \u003cul\u003e\n\u003cli\u003e\n\u003cspan\u003eUpdate your\u0026nbsp;\u003c/span\u003e\u003cstrong\u003ecode.py\u003c/strong\u003e\u003cspan\u003e\u0026nbsp;to the following:\u003c/span\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\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":"# SPDX-FileCopyrightText: 2023 Frederick M Meyer\n#\n# SPDX-License-Identifier: MIT\n\nimport ipaddress\nimport ssl\nimport wifi\nimport socketpool\nimport adafruit_requests\nimport random\nimport os\nimport time\nimport rtc\nimport adafruit_ntp\nimport adafruit_datetime\nimport displayio\nimport framebufferio\nimport rgbmatrix\nimport board\nimport digitalio\nimport terminalio\nimport adafruit_display_text as adt\nimport adafruit_display_text.label as adtl\n\nUTC_OFFSET = os.getenv('UTC_OFFSET')\nTZ = os.getenv('TZ')\n\nQUARTER = \"Quarter\"\nHALF = \"Half\"\nPAST = \"Past\"\nUNTIL = \"Until\"\nOCLOCK = \"O'clock\"\nNOON = \"Noon\"\nMIDNIGHT = \"Midnight\"\nMINUTE = \"Minute\"\nSINGLE_DIGITS = [\"One\", \"Two\", \"Three\", \"Four\", \"Five\", \"Six\", \"Seven\", \"Eight\", \"Nine\"]\nTEN_PLUS = [\"Ten\", \"Eleven\", \"Twelve\", \"Thirteen\", \"Fourteen\", \"Fifteen\", \"Sixteen\", \"Seventeen\", \"Eighteen\", \"Nineteen\"]\nTENS = [\"Twenty\", \"Thirty\", \"Fourty\", \"Fifty\"]\n\n#time.sleep(15)\n\nneopix_pwr = digitalio.DigitalInOut(board.NEOPIXEL_POWER)\nneopix_pwr.direction = digitalio.Direction.OUTPUT\nneopix_pwr.value = False\n\ndisplayio.release_displays()\n\n# Wifi details are in settings.toml file\n\nprint(\"My MAC addr:\", [hex(i) for i in wifi.radio.mac_address])\n\nprint(\"Connecting to %s\"%os.getenv(\"CIRCUITPY_WIFI_SSID\"))\nwifi.radio.connect(os.getenv(\"CIRCUITPY_WIFI_SSID\"), os.getenv(\"CIRCUITPY_WIFI_PASSWORD\"))\nprint(\"Connected to %s!\"%os.getenv(\"CIRCUITPY_WIFI_SSID\"))\nprint(\"My IP address is\", wifi.radio.ipv4_address)\n\npool = socketpool.SocketPool(wifi.radio)\n\nif UTC_OFFSET is None:\n    requests = adafruit_requests.Session(pool, ssl.create_default_context())\n    response = requests.get(\"http://worldtimeapi.org/api/timezone/\" + TZ)\n    response_as_json = response.json()\n    UTC_OFFSET = response_as_json[\"raw_offset\"] + response_as_json[\"dst_offset\"]\n\nntp = adafruit_ntp.NTP(pool, server=\"us.pool.ntp.org\", tz_offset=UTC_OFFSET // 3600)\n\nrtc.RTC().datetime = ntp.datetime\n\nlast_minute_displayed = -1\n\ndef format_hour(hour_num):\n    hour = hour_num % 24\n    if hour == 0:\n        o_hour = MIDNIGHT\n    elif hour == 12:\n        o_hour = NOON\n    else:\n        if hour \u003e 12:\n            hour -= 12\n        elif hour == 0:\n            hour = 12\n\n        if hour \u003c 10:\n            o_hour = SINGLE_DIGITS[hour - 1]\n        else:\n            o_hour = TEN_PLUS[hour - 10]\n    return hour, o_hour\n\nCOLOR_VALUES = [0, 128, 255]  # Brighter - Possible color values used for random 0..2 selection\n#COLOR_VALUES = [0, 80, 160]   # Dimmer   - Possible color values used for random 0..2 selection\n#COLOR_VALUES = [0, 64, 128]   # Dimmer   - Possible color values used for random 0..2 selection\n\ndef pick_random_color():\n    # Pick a random color for each line and add it to the display\n    r = COLOR_VALUES[random.randint(0, 2)]\n    g = COLOR_VALUES[random.randint(0, 2)]\n    b = COLOR_VALUES[random.randint(0, 2)]\n    if not (r | g | b): r = g = b = COLOR_VALUES[2] # Set to white if black result\n\n    return (r\u003c\u003c16|g\u003c\u003c8|b)\n\nmatrix = rgbmatrix.RGBMatrix(\n    width=64, height=64, bit_depth=2,\n    rgb_pins=[board.D6, board.D5, board.D9, board.D11, board.D10, board.D12],\n    addr_pins=[board.A5, board.A4, board.A3, board.A2, board.A1],\n    clock_pin=board.D13, latch_pin=board.RX, output_enable_pin=board.TX)\ndisplay = framebufferio.FramebufferDisplay(matrix, auto_refresh=True)\ndisplay.rotation = 0\n\nwhile True:\n    lt = time.localtime()\n\n    hour = lt.tm_hour\n    min = lt.tm_min\n\n    #hour = 12  # For testing\n    #min = 00   # For testing\n\n    if last_minute_displayed != min:\n        last_minute_displayed = min\n\n        if min \u003c= 30:\n            o_min_half = \"Past\"\n            hour, o_hour = format_hour(hour)\n        else:\n            o_min_half = \"Until\"\n            min = 60 - min\n            hour, o_hour = format_hour(hour + 1)\n\n        if min == 15:\n            o_min = \"Quarter\"\n        elif min == 30:\n            o_min = \"Half\"\n        elif min == 0:\n            o_min = \"\"\n            o_min_half = \"\"\n        elif min \u003c 10:\n            o_min = SINGLE_DIGITS[min - 1]\n        elif min \u003c 20:\n            o_min = TEN_PLUS[min - 10]\n        else:\n            o_min = TENS[(min // 10) - 2]\n            if min % 10:\n                o_min += \" \" + SINGLE_DIGITS[(min % 10) - 1]\n\n        if o_min not in [QUARTER, HALF, \"\"]:\n            o_min += \" \" + MINUTE\n            if min != 1: o_min += \"s\"\n\n        if min \u003e 0 or o_hour in [MIDNIGHT, NOON]:\n            txt = (o_min + \" \" + o_min_half + \" \" + o_hour).strip()\n        else:\n            txt = (o_min + \" \" + o_min_half + \" \" + o_hour + \" \" + OCLOCK).strip()\n\n        text_list = adt.wrap_text_to_pixels(txt, 60, font=terminalio.FONT)\n        \n        total_height = 0\n        max_width = 0\n        line_list = []\n        for w in text_list:\n            line = adtl.Label(\n                terminalio.FONT,\n                color=pick_random_color(),\n                text=w,\n                scale=1)\n            line_list.append(line)\n            zx, zy, zwidth, zheight = line.bounding_box\n            total_height += zheight\n            max_width = max(max_width, zwidth)\n        xwork = ((60 - max_width) // 2) + 2\n        ywork = ((60 - total_height) // 2) + 2 + 6\n\n        current_y = ywork        \n        g = displayio.Group()\n        for l in line_list:\n            l.x = xwork\n            l.y = current_y\n            zx, zy, zwidth, zheight = l.bounding_box\n            current_y += zheight\n            g.append(l)\n        display.root_group=g\n\n    time.sleep(1)","metadata":{"language":"auto","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n        \u003cul\u003e\n\u003cli\u003eInstall the following modules into the lib folder:\n\u003cul\u003e\n\u003cli\u003e\u003ca\u003e\u003cspan class=\"filename\" title=\"adafruit_display_text\"\u003eadafruit_display_text\u003c/span\u003e\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\u003e\u003cspan class=\"filename\" title=\"adafruit_datetime.mpy\"\u003eadafruit_datetime\u003c/span\u003e\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\u003e\u003cspan class=\"filename\" title=\"adafruit_ntp.mpy\"\u003eadafruit_ntp\u003c/span\u003e\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca\u003e\u003cspan class=\"filename\" title=\"adafruit_requests.mpy\"\u003eadafruit_requests\u003c/span\u003e\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eConnect the Featherwing and power connector to the RGB LED panel and then connect the power supply to the Featherwing. A USB cable is not needed. If you plug the Featherwing directly into the panel, the HUB-75 connector has no tab on it to show the proper direction, be sure it's facing in the correct direction. See picture.\u003c/li\u003e\n\u003c/ul\u003e\n      \n\n\n\n\n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/672/original/IMG_1116.jpeg?1706776888","metadata":{"caption":""}}]