[{"element_type":"text","content":"\n  \n        \u003cp\u003eI wanted a clock that would display the time in large yellow characters and display the week and day.\u0026nbsp; For fun added a 60 RGB neo-pixel ring to tick off the seconds.\u003c/p\u003e\n\u003cp\u003eThe last part was to display the phase of the moon in a fun way and found these to be entertaining.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eI cut a sheet of plex-glass to 8x11 sheet.\u0026nbsp; I used miniature self-tapping screws to mount the RGB neo-pixel ring.\u0026nbsp; I used this same kind of screws to mount the 4-digit display and two of the Quad Alphanumerica Displays.\u003c/p\u003e\n\u003cp\u003eI drill large enough holes to plug the displays up to the Adafruit Qualia ESP32-S3 and daisy chained the rest.\u003c/p\u003e\n\u003cp\u003eI did solder 3 wires to the RGB neo-pixel ring a crimped a 3-pin connector on the other and plugged it in socket A0.\u003c/p\u003e\n\u003cp\u003eI used Photopea to create a 3x3 of the moon phases I found on the internet.\u0026nbsp; For the new moon phase I copied the full moon phase and changed its color from yellow to blue.\u0026nbsp; and its a seperate .bmp file.\u003c/p\u003e\n\u003cp\u003eI am not a very good coder anymore and new to python.\u0026nbsp; I am sure there are better coders out there than me for sure.\u0026nbsp; I hacked most of the code from other projects.\u003c/p\u003e\n\u003cp\u003e\u0026nbsp;\u003c/p\u003e\n\u003cp\u003e\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eParts Required for this project:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eAdafruit Qualia ESP32-S3 for TTL RGB-666 Displays (PI - 5800\u003c/li\u003e\n\u003cli\u003eRound RGB TTL TFT Display -2.1\" (PI - 5806)\u003c/li\u003e\n\u003cli\u003eNeoPixel 1/4 60 Ring 5050 RGB LED w/Integrated Drivers (PI -1768)\u003c/li\u003e\n\u003cli\u003eQuad Alphanumerica Display - Blue 0.54\" Digits w/ I2C Backpack - Stemma QT / Qwiic (PI 1912)\u003c/li\u003e\n\u003cli\u003eAssembled Adafruit 0.56: 4-Digit 7 Segment Display -w/ I2c Backpack QT - Yellow (PI 5602)\u003c/li\u003e\n\u003cli\u003eStemma QT JST SH 4-Pin Cable (50mm \u0026amp; 100mm)\u003c/li\u003e\n\u003c/ol\u003e\n      \n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/742/original/20240313_110529_1_.jpg?1710352912","metadata":{"caption":""}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/743/original/20240313_110701_1_.jpg?1710352955","metadata":{"caption":""}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/000/744/original/20240313_110904_1_.jpg?1710353009","metadata":{"caption":""}},{"element_type":"code","content":"# SPDX-FileCopyrightText: 2020 Brent Rubell for Adafruit Industries\n# \n#  08-17-2023 DWE\n#\n# SPDX-License-Identifier: MIT\nimport time\nimport gc\nimport ipaddress\nimport displayio\nimport ssl\nimport wifi\nimport busio\nimport socketpool\nimport adafruit_requests\nimport board\nimport adafruit_imageload\nimport dotclockframebuffer\nimport neopixel\nimport pwmio\nfrom rtc import RTC\nfrom adafruit_ht16k33 import segments\nfrom framebufferio import FramebufferDisplay\nfrom displayio import release_displays\n\nrelease_displays()\ni2c = busio.I2C(board.SCL, board.SDA)\n\nnum_pixels = 60\n\npixels = neopixel.NeoPixel(board.A0, num_pixels, auto_write=False)\npixels.brightness = 0.01\n\nTWELVE_HOUR = True  # If set, use 12-hour time vs 24-hour (e.g. 3:00 vs 15:00)\n\nGREEN = (0, 255, 0)\nRED = (255, 0, 0)\nBLACK = (0, 0, 0)\nBLUE = (0, 0, 255)\n\nrefresh_time_update = 60\n\nweekname = {\n    0: \"Sun  \",\n    1: \"Mon  \",\n    2: \"Tue  \",\n    3: \"Wed  \",\n    4: \"Thurs \",\n    5: \"Fri  \",\n    6: \"Sat  \",\n}\n\ninit_sequence_tl021wvc02 = bytes((\n    0xff, 0x05, 0x77, 0x01, 0x00, 0x00, 0x10,\n    0xc0, 0x02, 0x3b, 0x00,\n    0xc1, 0x02, 0x0b, 0x02,\n    0xc2, 0x02, 0x00, 0x02,\n    0xcc, 0x01, 0x10,\n    0xcd, 0x01, 0x08,\n    0xb0, 0x10, 0x02, 0x13, 0x1b, 0x0d, 0x10, 0x05, 0x08, 0x07, 0x07, 0x24, 0x04, 0x11, 0x0e, 0x2c, 0x33, 0x1d,\n    0xb1, 0x10, 0x05, 0x13, 0x1b, 0x0d, 0x11, 0x05, 0x08, 0x07, 0x07, 0x24, 0x04, 0x11, 0x0e, 0x2c, 0x33, 0x1d,\n    0xff, 0x05, 0x77, 0x01, 0x00, 0x00, 0x11,\n    0xb0, 0x01, 0x5d,\n    0xb1, 0x01, 0x43,\n    0xb2, 0x01, 0x81,\n    0xb3, 0x01, 0x80,\n    0xb5, 0x01, 0x43,\n    0xb7, 0x01, 0x85,\n    0xb8, 0x01, 0x20,\n    0xc1, 0x01, 0x78,\n    0xc2, 0x01, 0x78,\n    0xd0, 0x01, 0x88,\n    0xe0, 0x03, 0x00, 0x00, 0x02,\n    0xe1, 0x0b, 0x03, 0xa0, 0x00, 0x00, 0x04, 0xa0, 0x00, 0x00, 0x00, 0x20, 0x20,\n    0xe2, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0xe3, 0x04, 0x00, 0x00, 0x11, 0x00,\n    0xe4, 0x02, 0x22, 0x00,\n    0xe5, 0x10, 0x05, 0xec, 0xa0, 0xa0, 0x07, 0xee, 0xa0, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0xe6, 0x04, 0x00, 0x00, 0x11, 0x00,\n    0xe7, 0x02, 0x22, 0x00,\n    0xe8, 0x10, 0x06, 0xed, 0xa0, 0xa0, 0x08, 0xef, 0xa0, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0xeb, 0x07, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00,\n    0xed, 0x10, 0xff, 0xff, 0xff, 0xba, 0x0a, 0xbf, 0x45, 0xff, 0xff, 0x54, 0xfb, 0xa0, 0xab, 0xff, 0xff, 0xff,\n    0xef, 0x06, 0x10, 0x0d, 0x04, 0x08, 0x3f, 0x1f,\n    0xff, 0x05, 0x77, 0x01, 0x00, 0x00, 0x13,\n    0xef, 0x01, 0x08,\n    0xff, 0x05, 0x77, 0x01, 0x00, 0x00, 0x00,\n    0x36, 0x01, 0x00,\n    0x3a, 0x01, 0x60,\n    0x11, 0x80, 0x64,\n    0x29, 0x80, 0x32,\n))\n\ntft_io_expander = dict(board.TFT_IO_EXPANDER)\n#tft_io_expander['i2c_address'] = 0x38 # uncomment for rev B\ndotclockframebuffer.ioexpander_send_init_sequence(i2c, init_sequence_tl021wvc02, **tft_io_expander)\ni2c.deinit()\n\ntft_pins = dict(board.TFT_PINS)\n\ntft_timings = {\n    \"frequency\": 16_000_000,\n    \"width\": 480,\n    \"height\": 480,\n    \"hsync_pulse_width\": 20,\n    \"hsync_front_porch\": 40,\n    \"hsync_back_porch\": 40,\n    \"vsync_pulse_width\": 10,\n    \"vsync_front_porch\": 40,\n    \"vsync_back_porch\": 40,\n    \"hsync_idle_low\": False,\n    \"vsync_idle_low\": False,\n    \"de_idle_high\": False,\n    \"pclk_active_high\": True,\n    \"pclk_idle_high\": False,\n}\n\n# Loop through each sprite in the sprite sheet\nsource_index = 0\ndisplayBL = pwmio.PWMOut(board.A1)\n\ni2c = busio.I2C(board.SCL, board.SDA)\n\n# Create the LED segment class.\n# This creates a 7 segment 4 character display:\ndisplay = segments.Seg7x4(i2c, address=0x74)\ndisplay14 = segments.Seg14x4(i2c, address=(0x71, 0x72))\n#  alphanumeric segment displpay setup\n#  using two displays together\n\n# Reset Display\ndisplay.fill(0)\ndisplay14.fill(0)\npixels.full = BLACK\npixels.show()\n\n# Get wifi details and more from a secrets.py file\ntry:\n    from secrets import secrets\nexcept ImportError:\n    print(\"WiFi secrets are kept in secrets.py, please add them there!\")\n    raise\n\nprint(\"Connecting to %s\" % secrets[\"ssid\"])\nwifi.radio.connect(secrets[\"ssid\"], secrets[\"password\"])\n# print(\"Connected to %s!\" % secrets[\"ssid\"])\n# print(\"My IP address is\", wifi.radio.ipv4_address)\n\npool = socketpool.SocketPool(wifi.radio)\nrequests = adafruit_requests.Session(pool, ssl.create_default_context())\n\ntry:\n    TIMEZONE = secrets[\"timezone\"]  # e.g. 'America/New_York'\nexcept:\n    TIMEZONE = None  # IP geolocation\n\nprint(\"TIME ZONE IS\")\nprint(TIMEZONE)\n\ntry:\n    LATITUDE = secrets['latitude']\n    LONGITUDE = secrets['longitude']\n    print('Using stored geolocation: ', LATITUDE, LONGITUDE)\nexcept:\n    LATITUDE = None\n    LONGITUDE = None\n\n# Set initial clock time, also fetch initial UTC offset while\n# here (NOT stored in secrets.py as it may change with DST).\n# pylint: disable=bare-except\n\n\ndef MoonData(year, month, day, lat, log):\n    \"\"\" Class holding lunar data for a given 24-hour period. App uses two\n        of these -- one for the current day, and one for the following day,\n        then some interpolations and such can be made. Elements include:\n        age               : Moon phase 'age' at start of period, expressed\n                            from 0.0 (new moon) through 0.5 (full moon) to\n                            1.0 (next new moon).\n        start_utc_seconds : Epoch time at start of period, UTC\n        end_utc_seconds   : Epoch time at end of period, \"\n        rise_utc_seconds  : Epoch time of moon rise within this 24-hour period\n        set_utc_seconds   : Epoch time of moon set within this 24-hour period\n    \"\"\"\n\n    # URL does not contain local or UTC time, only date. strftime() is\n    # not available in CircuitPython, manual conversion to time string\n    # is needed. Response is moon data for a 24-hour period, based on\n    # longitude and requested date. Some values within are UTC time,\n    # others are local. Anything we parse out of this will be converted\n    # to UTC epoch seconds, period.\n    utc_offset_string = \"-07:00\"\n    moon_url = ('https://api.met.no/weatherapi/sunrise/3.0/moon?lat=' +\n           str(lat) + '\u0026lon=' + str(log) +\n           '\u0026date=' + str(year) + '-' +\n           '{0:0\u003e2}'.format(month) + '-' +\n           '{0:0\u003e2}'.format(day) +\n           '\u0026offset=' + utc_offset_string)\n\n    # pylint: disable=bare-except\n\n\n    for _ in range(2): # Retries\n        print('Fetching moon data via', moon_url)\n        try:\n            moon_data = requests.get(moon_url)\n\n            properties = moon_data.json()['properties']\n            # 0 = new moon, 90 = Q1, 180 = full moon, 270 = LQ\n            age = float(properties['moonphase'])\n            # age = 169\n            print(\"AGE :\", age)\n            return int(age)\n        except:\n            # Moon server error (maybe), try again after 15 seconds.\n            # (Might be a memory error, that should be handled different)\n            time.sleep(5.5)\n    age = 0\n    return age\n\n\ndef moonPhase(age):\n    '''\n    0 = Waxing cresent\n    1 = First Quarter\n    2 = Waxing gibbous\n    3 = Waxing gibbous\n    4 = Full Moon\n    5 = Waning gibbous\n    6 = Waning gibbous\n    7 = Last Quarter\n    8 = Waning cresent\n    9 = New Moon\n    '''\n    print(\"THE MOON AGE IS: \", age)\n    if (age \u003e= 345) and (age \u003c= 15): # New Moon #0\n        moon_sprite = 9\n    elif (age \u003e= 16) and (age \u003c= 52): # Waxing gibbous #9\n        moon_sprite = 8\n    elif (age \u003e= 53) and (age \u003c= 77): # Waxing gibbous #8\n        moon_sprite = 7\n    elif (age \u003e= 78) and (age \u003c= 91): # First Quarter # 7\n        moon_sprite = 6\n    elif (age \u003e= 92) and (age \u003c= 163): # Waxing gibbous #6\n        moon_sprite = 5\n    elif (age \u003e= 163) and (age \u003c= 197): # Full Moon #5\n        moon_sprite = 4\n    elif (age \u003e= 188) and (age \u003c= 251): # Waning gibbous #4\n        moon_sprite = 3\n    elif (age \u003e= 252) and (age \u003c= 273): # Last Quarter #3\n        moon_sprite = 2\n    elif (age \u003e= 274) and (age \u003c= 309): # Waning gibbous #2\n        moon_sprite = 1\n    elif (age \u003e= 310) and (age \u003c= 344): # Waning gibbous #1\n        moon_sprite = 0\n    else:\n        moon_sprite = 9 # New Moon\n    print(\"THE MOON PHASE IS: \", moon_sprite)\n\n    return moon_sprite\n\n\ndef update_time(timezone=None):\n    \"\"\"Update system date/time from WorldTimeAPI public server;\n    no account required. Pass in time zone string\n    (http://worldtimeapi.org/api/timezone for list)\n    or None to use IP geolocation. Returns current local time as a\n    time.struct_time and UTC offset as string. This may throw an\n    exception on fetch_data() - it is NOT CAUGHT HERE, should be\n    handled in the calling code because different behaviors may be\n    needed in different situations (e.g. reschedule for later).\n    {\n    \"abbreviation\": \"MST\",\n    \"client_ip\": \"174.24.76.33\",\n    \"datetime\": \"2023-11-20T14:34:44.464927-07:00\",\n    \"day_of_week\": 1,\n    \"day_of_year\": 324,\n    \"dst\": false,\n    \"dst_from\": null,\n    \"dst_offset\": 0,\n    \"dst_until\": null,\n    \"raw_offset\": -25200,\n    \"timezone\": \"America/Denver\",\n    \"unixtime\": 1700516084,\n    \"utc_datetime\": \"2023-11-20T21:34:44.464927+00:00\",\n    \"utc_offset\": \"-07:00\",\n    \"week_number\": 47\n    }\n    \"\"\"\n    if timezone:  # Use timezone api\n        time_url = \"http://worldtimeapi.org/api/timezone/\" + timezone\n        print(time_url)\n    else:  # Use IP geolocation\n        time_url = \"http://worldtimeapi.org/api/ip\"\n    local_time_string = requests.get(time_url)\n    print(\"-\" * 40)\n    time_data = (local_time_string.json()[\"datetime\"], [\"dst\"], [\"utc_offset\"])\n    dayofweek = local_time_string.json()[\"day_of_week\"]\n    print(\"UPDATE_TIME_DATA\")\n    print(time_data)\n    time_struct = parse_time(time_data[0], time_data[1])\n    print(time_struct)\n    return time_struct, time_data[2], dayofweek\n\n\ndef parse_time(timestring, is_dst=-1):\n    \"\"\"Given a string of the format YYYY-MM-DDTHH:MM:SS.SS-HH:MM (and\n    optionally a DST flag), convert to and return an equivalent\n    time.struct_time (strptime() isn't available here). Calling function\n    can use time.mktime() on result if epoch seconds is needed instead.\n    Time string is assumed local time; UTC offset is ignored. If seconds\n    value includes a decimal fraction it's ignored.\n    \"\"\"\n    date_time = timestring.split(\"T\")  # Separate into date and time\n    year_month_day = date_time[0].split(\"-\")  # Separate time into Y/M/D\n    hour_minute_second = date_time[1].split(\"+\")[0].split(\"-\")[0].split(\":\")\n\n    return time.struct_time(\n        (\n            int(year_month_day[0]),\n            int(year_month_day[1]),\n            int(year_month_day[2]),\n            int(hour_minute_second[0]),\n            int(hour_minute_second[1]),\n            int(hour_minute_second[2].split(\".\")[0]),\n            -1,\n            -1,\n            is_dst,\n        )\n    )\n\n\ndef dim_display(status):\n\n    if status == True:\n        display.brightness = 0.01\n        display14.brightness = 0.01\n        pixels.brightness = 0.01\n        displayBL.duty_cycle = 30000\n    else:\n        display.brightness = 0.6\n        display14.brightness = 0.6\n        pixels.brightness = 0.1\n        displayBL.duty_cycle = 50000\n\ndef display_date(day, dayname):\n    dayname = weekname[dayname]\n    display14.marquee(dayname, 0.3, False)\n    display14.print(f\"{day:02d}\")\n\ndef display_time(hours, mins):\n    display.fill(0)\n    if hours \u003c 10:\n        display.print(\"0\" + str(hours))\n    else:\n        if hours \u003c 13:\n            display.print(hours)\n        else:\n            display.print(hours - 12)\n    display.print(\":\")\n    if mins \u003c 10:\n        display.print(\"0\" + str(mins))\n    else:\n        display.print(mins)\n\n\ndef display_sec(secs):\n    neo_seconds = secs\n    neo_seconds += 30\n    neo_seconds = neo_seconds % 60\n    if secs == 60:\n        pixels[neo_seconds] = RED\n    else:\n        pixels[neo_seconds] = GREEN\n    pixels[neo_seconds - 1] = BLACK\n    for x in range(20):\n        x=x+1\n        pixels[neo_seconds - x] = BLACK\n  # account for Date Display update delay\n    pixels.show()\n\n\ndef refesh_time():\n    datetime, UTC_OFFSET, dayofweek = update_time(TIMEZONE)\n    hours = datetime.tm_hour\n    mins = datetime.tm_min\n    secs = datetime.tm_sec\n    month = datetime.tm_mon\n    year= datetime.tm_year\n    day = datetime.tm_mday\n    dayofWeek = dayofweek\n\n    return year, month, day, dayofWeek, hours, mins, secs\n\ndef displaySprite(spriteNumber=0):\n    fb = dotclockframebuffer.DotClockFramebuffer(**tft_pins, **tft_timings)\n    displaysprite = FramebufferDisplay(fb, auto_refresh=False, rotation = 90)\n\n    if spriteNumber \u003c= 8:\n        sprite_sheet, palette = adafruit_imageload.load(\"/bmps/TheMoons720.bmp\",\n                                                    bitmap=displayio.Bitmap,\n                                                    palette=displayio.Palette)\n    else:\n        sprite_sheet, palette = adafruit_imageload.load(\"/bmps/NEWBLUEMOON.bmp\",\n                                                    bitmap=displayio.Bitmap,\n                                                    palette=displayio.Palette)\n\n    # Create a sprite (tilegrid)\n    sprite = displayio.TileGrid(sprite_sheet, pixel_shader=palette,\n                                width = 1,\n                                height = 1,\n                                tile_width = 240,\n                                tile_height = 240)\n\n    # Create a Group to hold the sprite\n    group = displayio.Group(scale=2)\n    sprite[0] = spriteNumber % 9\n    # Add the sprite to the Group\n    group.append(sprite)\n\n    # Add the Group to the Display\n    displaysprite.root_group = group\n\n    displaysprite.auto_refresh = True\n\n    # Set sprite location\n    group.x = 0\n    group.y = 0\n\n    # Loop through each sprite in the sprite sheet\n    source_index = 0\n\n# Initialize Clock and get it started\n\nyear, month, day, dayofWeek, hours, mins, secs = refesh_time()\ndisplay_sec(secs)\ndisplay_time(hours, mins)\ndisplay_date(day, dayofWeek)\nprint(year, month, day, dayofWeek, hours, mins, secs)\nage = MoonData(year, month, day, LATITUDE, LONGITUDE)\n\nprint(\"MOONPHASE IS: \", moonPhase(age))\n\ndisplaySprite(moonPhase(age))\n\n# Start running the clock\nwhile True:\n    if hours \u003e= 19 or hours \u003c= 6:\n        dim_display(True)\n    else:\n        dim_display(False)\n    display_sec(secs)\n    if secs == 60:\n        mins = mins + 1\n        if mins \u003e 59:\n            hours = hours + 1\n            mins = 0\n        if hours \u003e 23:\n            hours = 0\n        secs = 0\n        display_time(hours, mins)\n    if refresh_time_update == 0:\n        gc.collect()\n        release_displays()\n        year, month, day, dayofWeek, hours, mins, secs = refesh_time()\n        display_time(hours, mins)\n        display_date(day, dayofWeek)\n        refresh_time_update = 7200 # Update every 2 hour\n        age = MoonData(year, month, day, LATITUDE, LONGITUDE)\n        displaySprite(moonPhase(age))\n\n    #time.sleep(0.995)\n    time.sleep(0.987)  # Adjust seconds to account display delays\n    secs = secs + 1\n    refresh_time_update = refresh_time_update - 1","metadata":{"language":"auto","linenums":false}}]