[{"element_type":"text","content":"\n  \n        \u003cp\u003eThere are plenty of countdown projects on Learn but I wanted my own custom, minimalist one. It simply shows the remaining time until a target moment, in the form of weeks, days, and hours, updated about once per hour.\u003c/p\u003e\n      \n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/209/original/PXL_20241002_150346648_2.jpg?1727884480","metadata":{"caption":""}},{"element_type":"text","content":"\n  \n  \n        \u003cp\u003eAlong the way I learned a few things:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eHow to take an Adafruit PCB design in Eagle format and import the board outline all the way into FreeCAD for locating dimensions and features\u003c/li\u003e\n\u003cli\u003eHow to make my code robust against transient errors like network errors\u003c/li\u003e\n\u003cli\u003eHow to make my code deep sleep for the right length of time\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThere are also a few things this code demonstrates that are less common knowledge:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eUsing fonts from the font bundle\u003c/li\u003e\n\u003cli\u003eUsing the datetime module for arithmetic on dates \u0026amp; times\u003c/li\u003e\n\u003cli\u003eIncreasing reliability by re-trying operations that can fail\u003c/li\u003e\n\u003cli\u003eReducing battery usage by deep sleeping and avoiding connecting to WiFi\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThis project uses \u003ca href=\"https://www.freecad.org/\"\u003eFreeCAD\u003c/a\u003e \u0026amp; \u003ca href=\"https://www.kicad.org/\"\u003eKiCAD\u003c/a\u003e, both of which are Open Source software that are free to download \u0026amp; use.\u003c/p\u003e\n\u003ch2\u003eParts Needed\u003c/h2\u003e\n      \n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eThe code should adapt to other CircuitPython boards that support WiFi, but this is the one I used.\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","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        \u003cp\u003eThe code \u0026amp; 3D printed stand are specific to this eInk display\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","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/4195","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\u003eThe stand design requires a right angle USB \"C\" connector at the Feather end. A regular cable won't fit.\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/5031","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        \u003cp\u003eI don't yet have a sense of how long this battery lasts between charges, but it does fit in the space between the Feather \u0026amp; eInk display.\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","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/1570","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        \u003cp\u003eM2.5 screws are used for mounting the electronics to the stand. Adafruit has a choice of black or white, pick whatever will go best with your color scheme (or use fasteners you already have!)\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","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/3299","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        \u003cp\u003eStacking these between the feather \u0026amp; display leaves room for the battery\u003c/p\u003e\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/2940","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        \u003ch2\u003eCode \u0026amp; Installation\u003c/h2\u003e\n\u003cp\u003eI recommend using\u0026nbsp;\u003ca href=\"https://learn.adafruit.com/keep-your-circuitpython-libraries-on-devices-up-to-date-with-circup/overview\"\u003eAdafruit circup\u003c/a\u003e to install the needed libraries for projects. Here's what you need to do:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eInstall circup on your desktop computer\u003c/li\u003e\n\u003cli\u003eEnable the \"fonts bundle\" by running this command (just once, circup remembers this setting):\n\u003cpre\u003ecircup bundle-add adafruit/circuitpython-fonts\u003c/pre\u003e\n\u003c/li\u003e\n\u003cli\u003eCopy code.py to your CIRCUITPY drive (Download it via the \"raw\" link at \u003ca href=\"https://gist.github.com/jepler/b2c020a6caa65a31297053b7216fcc15\"\u003ehttps://gist.github.com/jepler/b2c020a6caa65a31297053b7216fcc15\u003c/a\u003e)\u003c/li\u003e\n\u003cli\u003eRun the following command to auto-install required libraries:\n\u003cpre\u003ecircup install -a\u003c/pre\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eYou'll also want to \u003ca href=\"https://learn.adafruit.com/pico-w-wifi-with-circuitpython/create-your-settings-toml-file\"\u003econfigure wifi on your device using settings.toml\u003c/a\u003e. For lower power usage, configure \u003cstrong\u003eWIFI_SSID\u003c/strong\u003e and \u003cstrong\u003eWIFI_PASSWORD\u003c/strong\u003e options. For web workflow but higher power usage, configure \u003cstrong\u003eCIRCUITPY_WIFI_SSID\u003c/strong\u003e and \u003cstrong\u003eCIRCUITPY_WIFI_PASSWORD\u003c/strong\u003e options.\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","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eFeather ESP32-S3 model differences\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"alert","content":"\u003cp\u003eMy Feather ESP32-S3 is an older model which uses the LC709203 battery monitor IC. Newly purchased boards will have the MAX17048 chip instead. If you have a newer Feather you'll need to change the code to use the right battery monitor library.\u003c/p\u003e","metadata":{"class":"element row-fluid alert-element build-alert alert-warning ui-sortable-handle","markdown":"My Feather ESP32-S3 is an older model which uses the LC709203 battery monitor IC. Newly purchased boards will have the MAX17048 chip instead. If you have a newer Feather you'll need to change the code to use the right battery monitor library.","alert_type":"warning"}},{"element_type":"embed","content":"https://gist.github.com/jepler/b2c020a6caa65a31297053b7216fcc15"},{"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        \u003ch2\u003eStand Printing \u0026amp; Assembly\u003c/h2\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":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/211/original/PXL_20241002_190521800.jpg?1727896108","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        \u003cp\u003eIf you're using a battery, solder normal male headers on your Feather. If you're not planning to use a battery, you can use\u0026nbsp;\u003ca href=\"https://www.adafruit.com/product/3002\"\u003eshort male feather headers\u003c/a\u003e for a slightly slimmer result.\u003c/p\u003e\n\u003cp\u003eGrab the \u003ca href=\"https://www.printables.com/model/1025991-portrait-stand-for-adafruit-eink-featherwing\"\u003e3D printing files from printables\u003c/a\u003e (the design files are also included!) and print with generic slicer settings in a filament of your choice. I used one of those \"hologram\" print surfaces, which gives a neat effect—at least until it gets smudged with fingerprints. I also set the first layer infill pattern to \"hilbert curve\" to give a more organic look to the imperfections of the first layer.\u003c/p\u003e\n\u003cp\u003ePlace the display behind the stand with the ribbon cable at the top, then secure it with the M2.5 screws and nuts. Then, insert the feather in the correct orientation in the display.\u003c/p\u003e\n\u003cp\u003eIf you're using the 100mAh battery, plug it into the feather and tuck it under the pins before plugging the feather into the display. If you do this,\u0026nbsp;\u003cstrong\u003ethe pins will not insert fully into the other board\u003c/strong\u003e. Make sure you don't push too hard, or you might damage the battery pack. Adding \u003ca href=\"https://www.adafruit.com/product/2940\"\u003ea set of short female headers to the stack\u003c/a\u003e (as shown above) ensures clearance for the batteries and allows the connection to be made secure.\u003c/p\u003e\n\u003cp\u003eIt's a bit fiddly but you can power or charge the project with a right angle USB C connector.\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","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/213/original/ksnip_20241002-172209.png?1727907768","metadata":{"caption":""}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eImporting PCB Outline in FreeCAD\u0026nbsp;\u003c/h2\u003e\n\u003cp\u003eThere's at least \u003ca href=\"https://learn.adafruit.com/eink-featherwing-display-stand\"\u003eone display stand for this display\u003c/a\u003e but I had some specific wants:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003ePortrait display\u003c/li\u003e\n\u003cli\u003e\"upside down 7\" shape\u003c/li\u003e\n\u003cli\u003ePCBs hidden as much as possible behind the stand\u003c/li\u003e\n\u003cli\u003eEasy printing with no support required\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eI went through a couple of iterations of my stand design. Along the way, I learned a process for importing board outlines from Eagle files via KiCAD into FreeCAD, which is something I'll be doing constantly from now on.\u003c/p\u003e\n\u003ch3\u003ePCB Outlines in FreeCAD\u003c/h3\u003e\n\u003cp\u003eSince the eInk Featherwing is an open source design, I started by \u003ca href=\"https://github.com/adafruit/Adafruit-E-Paper-Display-Breakout-PCBs\"\u003egrabbing the design files from GitHub\u003c/a\u003e. Next, I opened KiCAD and imported the Eagle format design file called \"Adafruit 2.13in Tri-Color eInk FeatherWing.brd\". (yeah, I am using the monochrome version, but this board file seems to be exactly the matching shape). Especially since all I cared about was the board outline, this was a simple process:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eStart KiCAD\u003c/li\u003e\n\u003cli\u003eFile \u0026gt; Import \u0026gt; Eagle Project...\u003c/li\u003e\n\u003cli\u003eClick through the import process, remembering to choose \"Auto-Match Layers\" when that step arrives\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e(There are plenty of resources if you want to learn more about importing Eagle boards to KiCAD and then use or modify the board design. \u003ca href=\"https://hackaday.com/2023/07/01/importing-eagle-projects-into-kicad-7-and-how-to-fix-them/\"\u003eThis one looks pretty good\u003c/a\u003e.)\u003c/p\u003e\n\u003cp\u003eNow, with the board design in KiCAD, I exported it as a STEP file:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eExport \u0026gt; STEP...\u003c/li\u003e\n\u003cli\u003eSelect \"\u003cstrong\u003eBoard center origin\u003c/strong\u003e\"\u003c/li\u003e\n\u003cli\u003eFinish the export process, noting where you saved the STEP file.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eSave \u0026amp; close everything in KiCAD, then open up FreeCAD.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eCreate a new project\u003c/li\u003e\n\u003cli\u003eUsing the Import menu, select the STEP file you created in KiCAD. This will become visible in the 3D view in FreeCAD\u003c/li\u003e\n\u003cli\u003eSwitch to the Part Design workbench\u003c/li\u003e\n\u003cli\u003eCreate Body and select it\u003c/li\u003e\n\u003cli\u003eSelect the PCB outline's\u0026nbsp;\u003cstrong\u003ebottom\u003c/strong\u003e face\u003c/li\u003e\n\u003cli\u003eCreate a \"\u003cstrong\u003eSub-Object Shape(s) Binder\u003c/strong\u003e\" (command in the Part Design pull down menu)\u003c/li\u003e\n\u003cli\u003eHide the PCB file: In the tree view, click the STEP file and then Edit \u0026gt; Toggle Visibility (keyboard shortcut: Space bar)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eAt this point, you should be left with just the ghostly outline of the PCB sitting on the XY plane. You can reposition it by changing the Placement of the Binder object \u0026amp; you can create as many Binder objects as you like.\u003c/p\u003e\n\u003cp\u003eAs you design the rest of the part, you can use features such as the board edge, mounting holes \u0026amp; thru holes. When you are editing a sketch, add these as external geometry (keyboard shortcut: gx)\u003c/p\u003e\n\u003cp\u003eIn my design, I built the main shape of the bracket in the XZ plane, made a symmetric pad from it big enough to fit the display, and then used the geometry of the PCB to locate the holes \u0026amp; other features which I subtracted using a Pocket. This was highly preferable to (and much less error prone than) taking measurements in KiCAD with the dimension tool \u0026amp; manually entering them in FreeCAD.\u003c/p\u003e\n\u003cp\u003eMy design actually uses a \"shape binder\", not a \"sub-object(s) shape binder\". While preparing this tutorial I \u003ca href=\"https://wiki.freecad.org/PartDesign_ShapeBinder\"\u003elearned that the latter is preferable\u003c/a\u003e, in part because of how you have more control over the placement of the bound shape.\u003c/p\u003e\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        \u003ch2\u003eCode Highlights\u003c/h2\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","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eUsing fonts from the \u003ca href=\"https://github.com/adafruit/circuitpython-fonts\"\u003eCircuitPython Font Bundle\u003c/a\u003e\n\u003c/h3\u003e\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/001/212/original/ksnip_20241002-142934.png?1727897410","metadata":{"caption":""}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003e\u003ca href=\"https://github.com/adafruit/circuitpython-fonts\"\u003eThis bundle\u003c/a\u003e contains hundreds of open source fonts in bitmap format ready to use in CircuitPython with an import statement; since it's a bundle, they can be quickly installed by circup.\u003c/p\u003e\n\u003cp\u003eI wanted a futuristic look so I picked the \"\u003ca href=\"https://www.theleagueofmoveabletype.com/orbitron\"\u003eOrbitron\u003c/a\u003e\" in default weight. Some experimentation determined that the 54 pixel version was right for the display. I didn't need any special characters, just digits, so the \"latin1\" version of the font (which covers Unicode code points up to 255) was suitable:\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"from font_orbitron_medium_webfont_54_latin1 import FONT","metadata":{"language":"auto","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eLater in the program, just use the FONT as an argument to the Label constructor. Here, I create several labels distributed vertically on the screen:\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","metadata":{}},{"element_type":"code","content":"#\n    labels = [\n        Label(\n            FONT,\n            text=\"\",\n            anchor_point=(0.5, 0.5),\n            anchored_position=(display.width // 2, display.height * (i + 0.5) // 3),\n        )\n        for i in range(4)\n    ]","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        \u003ch3\u003eRetry \u003cstrong\u003eEverything!\u003c/strong\u003e\n\u003c/h3\u003e\n\u003cp\u003eEverything that communicates with another device, from connecting to WiFi, to getting the battery percentage(!) can fail.\u003c/p\u003e\n\u003cp\u003eAnything that I identified as failing sometimes I put in a small retry loop. The number of retries is somewhat arbitrary in each case. For instance, I retry checking the battery 5 times and treat it as 100% full if I can't get a reading:\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","metadata":{}},{"element_type":"code","content":"#\n    for _ in range(5):\n        try:\n            percent = battery_monitor.cell_percent\n            break\n        except Exception as e:\n            print(e)\n    else:\n        print(\"too much battery polling\")\n        percent = 100","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        \u003ch3\u003eEnsure code always sleeps\u003c/h3\u003e\n\u003cp\u003eBy using a \"finally\" clause, the code will always sleep even if it encounters an error. During development I have to comment out the \"exit_and_deep_sleep_until_alarms\" call so that I see errors in my code, but then I put that line back when I'm done:\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","metadata":{}},{"element_type":"code","content":"if __name__ == \"__main__\":\n    to_sleep = 300\n    try:\n        setup_display()\n        to_sleep = main()\n    finally:\n        displayio.release_displays()\n        print(f\"sleeping for {to_sleep} seconds\")\n        alarm.exit_and_deep_sleep_until_alarms(\n            alarm.time.TimeAlarm(monotonic_time=time.monotonic() + to_sleep)\n        )","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        \u003ch3\u003eUsing \u003ccode\u003eadafruit_datetime\u003c/code\u003e\n\u003c/h3\u003e\n\u003cp\u003e\u003ccode\u003eadafruit_datetime\u003c/code\u003e is a compatible subset of the standard Python datetime module. It has a useful feature for this project: subtracting two \u003ccode\u003edatetime\u003c/code\u003e objects results in a \u003ccode\u003etimedelta\u003c/code\u003e object.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003etarget\u003c/code\u003e is the time we're counting down to. I converted it to universal time (UTC, also sometimes called GMT) on my computer, because NTP naturally works in UTC time, and this frees me from thinking about daylight saving time rules.\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","metadata":{}},{"element_type":"code","content":"target = adafruit_datetime.datetime(2025, 3, 28, 21)  # UTC, 4PM local time","metadata":{"language":"python","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eOnce I've established the time using the network time protocol (NTP) I can just use subtraction to find out the time remaining until the target. Because my display is in hours, I then round it UP to the next full hour, and then convert it to weeks, days, and hours. With this info at hand, I can format the display.\u003c/p\u003e\n\u003cp\u003eThere are special cases for when it's after the target time, and when the battery is low (the hours display is replaced by \"bat!\"\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","metadata":{}},{"element_type":"code","content":"#\n    delta = target - now\n    delta = adafruit_datetime.timedelta(\n        seconds=math.ceil(delta.total_seconds() / 3600) * 3600\n    )\n    weeks = delta.days // 7\n    days = delta.days % 7\n    hours = delta.seconds // 3600\n\n    low_bat = percent \u003c 25\n\n    if delta.total_seconds() \u003c 0:\n        labels[0].text = \"GO\"\n        labels[1].text = \"GO\"\n        labels[2].text = \"GO\"\n    else:\n        labels[0].text = f\"{weeks}\"\n        labels[1].text = f\"{days}\"\n        labels[2].text = \"bat!\" if low_bat else f\"{hours}\"","metadata":{"language":"python","linenums":false}},{"element_type":"text","content":"\n  \n  \n  \n  \n        \u003ch3\u003eDon't connect to WiFi everytime\u003c/h3\u003e\n\u003cp\u003eConnecting to WiFi takes time, and the wifi radio also drains the battery faster. So, I track how long it's been since the device time was updated, using \u003ccode\u003esleep_memory\u003c/code\u003e. When successfully set, a counter is initialized to 24. When it's non-zero, the code can use the device time instead of connecting to the network (\u0026amp; subtracts 1 from the counter)\u003c/p\u003e\n\u003cp\u003eBecause sleep memory starts as all zeros when the device powers on, it'll always update the time after being reset or powered on.\u003c/p\u003e\n\u003cp\u003eIncidentally, this is why I recommend not to use the WiFi workflow: when it is enabled, wifi will connect everytime.\u003c/p\u003e\n      \n\n\n\n","metadata":{}},{"element_type":"code","content":"#\n    sm0 = alarm.sleep_memory[0]\n    if sm0 == 0:\n        # Assumes wifi connected via settings.toml before code.py started!\n        connect_wifi()\n        pool = socketpool.SocketPool(wifi.radio)\n        ntp = adafruit_ntp.NTP(pool, tz_offset=0, cache_seconds=3600, socket_timeout=1)\n        for _ in range(10):\n            try:\n                rtc.RTC().datetime = ntp.datetime\n                alarm.sleep_memory[0] = 24 # Don't set again for about 24h\n                break\n            except Exception as e:\n                print(e)\n        else:\n            print(\"NTP too much\")\n            # Hopefully it's better in a few hundred seconds\n            return 300\n    else:\n        print(f\"Don't need to set for {sm0} hours\")\n        alarm.sleep_memory[0] = sm0-1","metadata":{"language":"python","linenums":false}}]