[{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/002/117/original/output.gif?1767142987","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eA Newxie display is a modern take on the classic Nixie tube aesthetic. It is a 135 wide x 240 tall TFT display with double pins on the bottom for mechanical stability - see the product page link below.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eWhat's a good use for a Newxie display? How about an analog thermometer - you know, the old-school type with a glass tube with red alcohol inside that rises as the temperature rises. Let's build it!\u0026nbsp;\u003c/p\u003e\n\u003ch1\u003eOriginal Idea\u003c/h1\u003e\n\u003cp\u003eI've been working of building a weather information center using a Fruit Jam and the AdafruitIO Weather Power-up. AdafruitIO provides over 300 hyper local weather data elements updated every 20 minutes. Using it is like drinking from a fire hose, and most boards can only handle a few data points due to memory constraints. The Fruit Jam changed all that - with a 640x480 display and tons of memory, I can now display pages and pages of weather data.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eOnce I had the horsepower to show more than just text, I started thinking about more expressive ways to visualize the data. I thought that a classic thermometer would be an ideal way to display temperature data. As an added bonus, by adding a narrow stripe inside the main body, I can show a second temperature, such as the \"feels like\" temperature. Here is how it came out:\u003c/p\u003e\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/002/118/original/Sample0.jpg?1767145815","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\u003eOn page 1 of the Fruit Jam Weather Center (I'm up to 10 pages so far) it shows current weather data. The thermometer sits vertically along the right edge, with bold tick marks and a clean red column that rises with the temperature. It shows degrees Fahrenheit for the left scale, degrees Celsius on the right side, and freezing is marked in red.\u003c/p\u003e\n\u003cp\u003eThe graphic was built using rounded rectangles from the adafruit_display_shapes library for the outlines and indicators, lines from display_shapes for the tic marks, and bitmap_fonts for the numbers. When all was said and done, there ended up being a lot of display elements to create this seemingly simple graphic.\u0026nbsp;\u003c/p\u003e\n\u003ch1\u003eAfter all that work let's make it reusable...\u003c/h1\u003e\n\u003cp\u003eWith a little effort I converted the text into a separate helper library. The first hurdle was that not all displays are as large as the Fruit Jam. The original was 440 px high but to be reusable it needs to be scalable. Likewise if it isn't going to be as tall I'd also want to be able to customize the min/max values. Ultimately the goal was to make most of the elements customizable. After several iterations I with AI's help, got it working well enough to try it on a different display.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eI had a \"multi-board\" where I have a Feather RP2350 connected to a 240x320 display, an RTC module, a TLV320 DAC, etc., that I've been using to try things out. So, I added an SPA06-003 temperature breakout and added the thermometer graphic:\u003c/p\u003e\n\u003cp\u003e\u0026nbsp;\u003c/p\u003e\n\u003cp\u003e\u0026nbsp;\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":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/002/119/original/Sample1.jpg?1767145247","metadata":{}},{"element_type":"text","content":"\n  \n  \n        \u003cp\u003eFor the most part it worked quite well, and by trying it on a completely different display I was able to tweak a couple of issues that came up. For the multi-board I changed the colors to match the existing design, narrowed the temperature scale, and resized the thermometer to fit on the display. One interesting thing is that since I didn't have two different temperatures to display, I just set the color of the second temperature scale to red and update both temperatures to the same value.\u0026nbsp; You'll also note that in this use the minimum temperature is above freezing, so the freezing temperature isn't shown. Proof of concept - check! Ready to try a new display.\u0026nbsp;\u003c/p\u003e\n\u003ch2\u003eHow About a Round Display\u003c/h2\u003e\n\u003cp\u003eHere's a 240 round TFT with the thermometer graphic - this rotates between two pages of a watch face and the thermometer.\u003c/p\u003e\n      \n\n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/002/124/original/output.gif?1767334110","metadata":{}},{"element_type":"text","content":"\n  \n        \u003ch1\u003eOn to the Newxie Display\u003c/h1\u003e\n\u003ch2\u003eGather the parts\u003c/h2\u003e\n\u003cp\u003eThere are few components for this project, and the assembly is straightforward. There is some soldering adding pins to the board, assembling the board and connecting it up. The star of the show is the Newxie display:\u003c/p\u003e\n      \n","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/6113","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        \u003cp\u003eFor the board I used a Feather ESP32 v2 as I wanted to use wifi:\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","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/5400","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        \u003cp\u003eFor getting the local temperature I used a SPA06-003 breakout along with a short STEMMA cable:\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","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/6420","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/4399","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        \u003cp\u003eFor a build plate I used a Feather Tripler.\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","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/3417","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eAssembly\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eSolder the header pins on to your Feather board\u003c/li\u003e\n\u003cli\u003eSolder on one set of female headers to plug the Feather board in to the tripler\u003c/li\u003e\n\u003cli\u003eSolder on a 2x header for the Newxie (I used a 2x14 but any 2x at least 7 will work) in the prototyping area in the empty space.\u003c/li\u003e\n\u003cli\u003eWire up the Newxie pins to the Feather pins as follows:\n\u003cul\u003e\n\u003cli\u003eV+ to 3.3v on the board (labeled \"3V\")\u003c/li\u003e\n\u003cli\u003eG to Ground on the board (labeled \"GND\")\u003c/li\u003e\n\u003cli\u003eCL to spi clock on the board (labeled \"SCK\")\u003c/li\u003e\n\u003cli\u003eDA to spi data on the board (labeled \"MO\")\u003c/li\u003e\n\u003cli\u003eCS to D15 on the board (labeled \"15\")\u003c/li\u003e\n\u003cli\u003eDC to D32 on the board (labeled \"32\")\u003c/li\u003e\n\u003cli\u003eyou can leave BL unconnected\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003ePlug in the SPA06-003 breakout board STEMMA connector to the STEMMA port on the board with the STEMMA cable.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eRefer to the Learn Guides for the Newxie (\u003ca href=\"https://learn.adafruit.com/adafruit-1-14-240x135-color-newxie-tft-display/pinouts\"\u003ePinouts | Adafruit 1.14\" 240x135 Color Newxie TFT Display | Adafruit Learning System\u003c/a\u003e) and the Feather board (\u003ca href=\"https://learn.adafruit.com/adafruit-esp32-feather-v2\"\u003eOverview | Adafruit ESP32 Feather V2 | Adafruit Learning System\u003c/a\u003e) for the main components.\u003c/p\u003e\n      \n\n\n\n\n\n\n","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/002/120/original/Overview.jpg?1767149858","metadata":{}},{"element_type":"alert","content":"\u003cp\u003eThe Adafruit Learn Guides are the key sources for understanding and using the components - be sure to take a few minutes to review before working on the project.\u003c/p\u003e","metadata":{"markdown":"The Adafruit Learn Guides are the key sources for understanding and using the components - be sure to take a few minutes to review before working on the project.","alert_type":"primary","icon":"star"}},{"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        \u003ch1\u003eGet Ready to Code\u003c/h1\u003e\n\u003cp\u003eYou will first need to install CircuitPython on the board. The particular Feather I chose (ESP32 v2) does not show up as a USB drive like on some boards. Follow the instructions in the \u003ca title=\"Installing CP on ESP32\" href=\"https://learn.adafruit.com/circuitpython-with-esp32-quick-start\" target=\"_blank\"\u003eLearn Guide\u003c/a\u003e to install CircuitPython and understand editing programs using \u003ca title=\"Editing on ESP32\" href=\"https://learn.adafruit.com/getting-started-with-web-workflow-using-the-code-editor\"\u003eWeb Workflow\u003c/a\u003e. I always use the most recent beta version of CircuitPython (I like living on the bleeding edge...)\u003c/p\u003e\n\u003cp\u003eOnce you have installed CircuitPython you will need to install several libraries from the bundle to your board. Specifically, you will need:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eadafruit_requests\u0026nbsp;\u003cem\u003erequired by other libraries\u003c/em\u003e\n\u003c/li\u003e\n\u003cli\u003eadafruit_connection_manager\u0026nbsp;\u003cem\u003eused for wifi connectivity\u003c/em\u003e\n\u003c/li\u003e\n\u003cli\u003eadafruit_io\u0026nbsp;\u003cem\u003eused to set the time\u003c/em\u003e\n\u003c/li\u003e\n\u003cli\u003eadafruit_st7789\u0026nbsp;\u003cem\u003eused to set up the Newxie display\u003c/em\u003e\n\u003c/li\u003e\n\u003cli\u003eadafruit_bitmap_font\u0026nbsp;\u003cem\u003eused for fonts used on the display\u003c/em\u003e\n\u003c/li\u003e\n\u003cli\u003eadafruit_display_text\u0026nbsp;\u003cem\u003eused to create text labels\u003c/em\u003e\n\u003c/li\u003e\n\u003cli\u003eadafruit_display_shapes\u0026nbsp;\u003cem\u003eused to create the graphical components\u003c/em\u003e\n\u003c/li\u003e\n\u003cli\u003eadafruit_spa06_003\u0026nbsp;\u003cem\u003eused for the temperature breakout board\u003c/em\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eYou will also need to create a /fonts/ folder and add some fonts of different sizes. I created a number of pcf font files from the Calibri font on my PC following the instructions in this Learn Guide: \u003ca href=\"https://learn.adafruit.com/custom-fonts-for-pyportal-circuitpython-display\"\u003eOverview | Custom Fonts for CircuitPython Displays | Adafruit Learning System\u003c/a\u003e\u003c/p\u003e\n\u003ch2\u003eCreate the thermometer.py Helper Library\u003c/h2\u003e\n\u003cp\u003eYou will need a file named\u0026nbsp;\u003cstrong\u003ethermometer.py\u003c/strong\u003e in your \\lib\\ folder which is the helper library for this thermometer display element. In your editor create a new file named 'thermometer.py' and save it in your /lib/ folder. Copy the code from the following code file into this file and save:\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","metadata":{}},{"element_type":"code","content":"# SPDX-FileCopyrightText: Copyright (c) 2025 Dana Kamp\n#\n# SPDX-License-Identifier: MIT\n\nimport math\nimport displayio\nfrom adafruit_display_shapes.rect import Rect\nfrom adafruit_display_shapes.roundrect import RoundRect\nfrom adafruit_display_shapes.circle import Circle\nfrom adafruit_display_shapes.line import Line\nfrom adafruit_display_text.bitmap_label import Label\n\n\nclass Thermometer:\n    \"\"\"Reusable thermometer graphic with update method for actual/apparent temps.\n\n    Builds a displayio.Group containing the frame, outline, bulb, tick marks,\n    labels, and two fills (actual in red, apparent in white). Use ``update`` to\n    redraw the fills at new temperatures.\n\n    \u003e Copy thermometer.py into the lib folder.\n\n    \u003e anchor_point is the top-left corner of the thermometer graphic. Default is\n    (550, 0). Adjust as needed to fit your display.\n\n    The default graphic is sizeed 100 px wide by 440 px high. The scale runs\n    from temp_min (default 0F) to temp_max (default 100F). Each degree is\n    represented by px_per_degree pixels (default 4), so the scale height is\n    px_per_degree * (temp_max - temp_min) pixels high.\n\n    Default min/max temperature is 0-100°F. You can adjust temp_min and temp_max\n    to fit your expected temperature range. If you set px_per_degree to None,\n    the scale will stretch to fill the full 400px height between the top and\n    bottom of the thermometer graphic.\n\n    Colors can be customized by passing a dictionary of color values (0xRRGGBB)\n    for any of these keys:\n        \"frame\", \"bg\", \"outline\", \"bulb_outline\", \"bulb_fill\",\n        \"tick_major\", \"tick_minor\", \"tick_c\", \"tick_c_zero\", \"freezing_tick\",\n        \"fill_actual\", \"fill_apparent\", \"fill_placeholder\",\n        \"label_f\", \"label_c\"\n    If omitted, defaults will be used.\n\n   \n    \n    from thermometer import Thermometer\n    therm = Thermometer(\n        anchor=(550, 0),\n        display=display,\n        tick_font=font15,\n        therm = Thermometer(\n        temp_min=20,\n        temp_max=80,    # scale stretches to fill\n                        # px_per_degree=None  # implied; leave it out to auto-fill\n        frame_height=320,      # optional shrink total height from detault 440 if needed\n        frame_width=80,       # optional\n        scale_margin_top=8,    # optional\n        scale_margin_bottom=22, # optional\n        colors={\n            \"frame\": 0x001122,\n            \"outline\": 0xFFFFFF,\n            \"bulb_fill\": 0x00FF00,\n            \"fill_actual\": 0x00CCFF,\n            \"fill_apparent\": 0xFFFF00,\n            \"tick_major\": 0xEEEEEE,\n            \"label_f\": 0xFFAA00,\n        },\n    )\n\n    group.append(therm.group) where you want it layered.\n\n    \u003e Call \n    therm.update(current_temp, apparent_temp) whenever new data arrives.\n\n    \"\"\"\n\n    def __init__(\n        self,\n        anchor=(550, 0),\n        *,\n        display=None,\n        colors=None,\n        tick_font=None,\n        px_per_degree=None,\n        temp_min=0,\n        temp_max=100,\n        frame_height=440,\n        frame_width=100,\n        scale_margin_top=10,\n        scale_margin_bottom=30,\n        log_fn=None,\n        initial_temp=50,\n    ):\n        self.anchor = anchor\n        self.display = display\n        self.tick_font = tick_font\n        self.temp_min = temp_min\n        self.temp_max = temp_max\n        self.frame_height = frame_height\n        self.frame_width = frame_width\n        self.scale_margin_top = scale_margin_top\n        self.scale_margin_bottom = scale_margin_bottom\n        self.log = log_fn or (lambda msg: None)\n\n        # Core geometry: frame is 100x440 by default with a scale that fills the\n        # available vertical space between the top and bottom margins. If\n        # px_per_degree is not provided, it is derived so the given temp range\n        # fills the entire scale height (e.g., 20-80°F will stretch to the full\n        # height). Reduce frame_height if your display is shorter than 440 px.\n        self.scale_height_px = max(\n            1, self.frame_height - self.scale_margin_top - self.scale_margin_bottom\n        )\n        self.px_per_degree = (\n            px_per_degree\n            if px_per_degree is not None\n            else self.scale_height_px / max(1, (self.temp_max - self.temp_min))\n        )\n\n        self.colors = {\n            \"frame\": 0x000066,\n            \"bg\": 0x000066,\n            \"outline\": 0xFFFFFF,\n            \"bulb_outline\": 0xFFFFFF,\n            \"bulb_fill\": 0xFF0000,\n            \"tick_major\": 0xFFFFFF,\n            \"tick_minor\": 0xFFFFFF,\n            \"tick_c\": 0xAAAAFF,\n            \"tick_c_zero\": 0xFF0000,\n            \"freezing_tick\": 0xFF0000,\n            \"fill_actual\": 0xFF0000,\n            \"fill_apparent\": 0xFFFFFF,\n            \"fill_placeholder\": 0x000066,\n            \"label_f\": 0xFFFFFF,\n            \"label_c\": 0xAAAAFF,\n        }\n        if colors:\n            self.colors.update(colors)\n\n        self.group = displayio.Group()\n        self._build_static_parts()\n        self._append_fills(initial_temp, initial_temp, placeholder=True)\n\n    # --- public API -----------------------------------------------------\n    def update(self, temperature, apparent_temperature):\n        \"\"\"Update the thermometer fills based on current and apparent temps.\"\"\"\n        temp = self._parse_temp(temperature, default=50)\n        app = self._parse_temp(apparent_temperature, default=temp)\n\n        if temp \u003c self.temp_min:\n            temp = self.temp_min\n        if temp \u003e self.temp_max:\n            temp = self.temp_max\n        if app \u003c self.temp_min:\n            app = self.temp_min\n        if app \u003e self.temp_max:\n            app = self.temp_max\n\n        if self.display:\n            self.display.auto_refresh = False\n        try:\n            self._pop_fills()\n            self._append_fills(temp, app, placeholder=False)\n            self.log(\n                f\"Thermometer updated - actual: {temp}F, apparent: {app}F\"\n            )\n        finally:\n            if self.display:\n                self.display.auto_refresh = True\n\n    # --- internals ------------------------------------------------------\n    def _build_static_parts(self):\n        x, y = self.anchor\n        self.top_y = y + self.scale_margin_top\n        self.bottom_y = self.top_y + self.scale_height_px\n\n        # Background frame and outline\n        frame_fill = self.colors.get(\"bg\", self.colors[\"frame\"])\n        frame = Rect(x, y, self.frame_width, self.frame_height, fill=frame_fill)\n        outline = RoundRect(\n            x=x + 40,\n            y=self.top_y,\n            width=20,\n            height=self.scale_height_px,\n            r=10,\n            outline=self.colors[\"outline\"],\n            stroke=1,\n        )\n        bulb = Circle(\n            x + 50,\n            y + self.frame_height - (self.scale_margin_bottom + 10),\n            15,\n            outline=self.colors[\"bulb_outline\"],\n            fill=self.colors[\"bulb_fill\"],\n            stroke=1,\n        )\n\n        self.group.append(frame)\n        self.group.append(outline)\n        self.group.append(bulb)\n\n        self._build_ticks(x, y)\n        self._build_labels(x)\n\n    def _build_ticks(self, x, y):\n        # Fahrenheit ticks (respect the configured min/max range)\n        start_major = int(math.ceil(self.temp_min / 10.0) * 10)\n        for temp in range(start_major, self.temp_max + 1, 10):\n            y_pos = self._temp_to_y(temp)\n            self.group.append(\n                Line(x + 30, y_pos, x + 40, y_pos, color=self.colors[\"tick_major\"])\n            )\n\n        start_minor = int(math.ceil((self.temp_min - 5) / 10.0) * 10 + 5)\n        for temp in range(start_minor, self.temp_max, 10):\n            if temp \u003c self.temp_min:\n                continue\n            y_pos = self._temp_to_y(temp)\n            self.group.append(\n                Line(x + 35, y_pos, x + 40, y_pos, color=self.colors[\"tick_minor\"])\n            )\n\n        # Freezing tick (32F) only if in range\n        if self.temp_min \u003c= 32 \u003c= self.temp_max:\n            freezing_y = self._temp_to_y(32)\n            self.group.append(\n                Line(x + 30, freezing_y, x + 40, freezing_y, color=self.colors[\"freezing_tick\"])\n            )\n\n        # Celsius ticks (-10 to 35) filtered to range\n        for c_temp in (-10, 0, 10, 20, 30):\n            f_temp = self._c_to_f(c_temp)\n            if not (self.temp_min \u003c= f_temp \u003c= self.temp_max):\n                continue\n            y_pos = self._temp_to_y(f_temp)\n            color = self.colors[\"tick_c_zero\"] if c_temp == 0 else self.colors[\"tick_c\"]\n            self.group.append(Line(x + 60, y_pos, x + 65, y_pos, color=color))\n        for c_temp in (-5, 5, 15, 25, 35):\n            f_temp = self._c_to_f(c_temp)\n            if not (self.temp_min \u003c= f_temp \u003c= self.temp_max):\n                continue\n            y_pos = self._temp_to_y(f_temp)\n            self.group.append(Line(x + 60, y_pos, x + 65, y_pos, color=self.colors[\"tick_c\"]))\n\n    def _build_labels(self, x):\n        if not self.tick_font:\n            return\n\n        # Fahrenheit labels (right-justified on the left side of the graphic)\n        start_major = int(math.ceil(self.temp_min / 10.0) * 10)\n        for temp in range(start_major, self.temp_max + 1, 10):\n            y_pos = self._temp_to_y(temp) - 4\n            self.group.append(\n                Label(\n                    self.tick_font,\n                    text=str(temp),\n                    anchor_point=(1.0, 0.5),\n                    anchored_position=(x + 20, y_pos),\n                    color=self.colors[\"label_f\"],\n                )\n            )\n\n        # Celsius labels (left-justified on the right side)\n        for c_temp in (-10, -5, 0, 5, 10, 15, 20, 25, 30, 35):\n            f_temp = self._c_to_f(c_temp)\n            if not (self.temp_min \u003c= f_temp \u003c= self.temp_max):\n                continue\n            y_pos = self._temp_to_y(f_temp) - 3\n            color = self.colors[\"tick_c_zero\"] if c_temp == 0 else self.colors[\"label_c\"]\n            self.group.append(\n                Label(\n                    self.tick_font,\n                    text=str(c_temp),\n                    anchor_point=(0.0, 0.5),\n                    anchored_position=(x + 70, y_pos),\n                    color=color,\n                )\n            )\n\n    def _append_fills(self, temp, app_temp, *, placeholder):\n        actual_fill_x = self.anchor[0] + 42\n        apparent_fill_x = self.anchor[0] + 48\n\n        actual_height, actual_y = self._height_and_y(temp)\n        apparent_height, apparent_y = self._height_and_y(app_temp)\n\n        actual_radius = min(7, actual_height // 2) if actual_height \u003e 0 else 0\n        app_radius = min(2, apparent_height // 2) if apparent_height \u003e 0 else 0\n\n        actual_color = (\n            self.colors[\"fill_placeholder\"] if placeholder else self.colors[\"fill_actual\"]\n        )\n        app_color = (\n            self.colors[\"fill_placeholder\"] if placeholder else self.colors[\"fill_apparent\"]\n        )\n\n        actual_rect = RoundRect(\n            x=actual_fill_x,\n            y=actual_y,\n            width=15,\n            height=max(1, actual_height),\n            r=actual_radius,\n            fill=actual_color,\n        )\n        apparent_rect = RoundRect(\n            x=apparent_fill_x,\n            y=apparent_y,\n            width=4,\n            height=max(1, apparent_height),\n            r=app_radius,\n            fill=app_color,\n        )\n\n        self.group.append(actual_rect)\n        self.group.append(apparent_rect)\n\n    def _pop_fills(self):\n        if len(self.group) \u003e= 2:\n            self.group.pop()\n            self.group.pop()\n\n    def _parse_temp(self, value, default):\n        try:\n            txt = str(value).replace(\"°F\", \"\").replace(\"°\", \"\").strip()\n            return float(txt)\n        except Exception:\n            return float(default)\n\n    def _height_and_y(self, temp):\n        delta = temp - self.temp_min\n        height = int(delta * self.px_per_degree)\n        y_pos = self.bottom_y - height\n        return height, y_pos\n\n    def _temp_to_y(self, temp_f):\n        delta = temp_f - self.temp_min\n        return int(self.bottom_y - delta * self.px_per_degree)\n\n    @staticmethod\n    def _c_to_f(temp_c):\n        return temp_c * 9 / 5 + 32","metadata":{"language":"python","linenums":false,"filename":""}},{"element_type":"text","content":"\n  \n  \n  \n  \n        \u003ch2\u003eCreate your settings.toml file\u003c/h2\u003e\n\u003cp\u003eYou will need to update the settings.toml file on your board (by default the CircuitPython installation adds an empty settings.toml file. The set-up process also gives you a chance to add in your wifi data so that may already be in your file.). You will need your WiFi credentials as well as an AdafruitIO userID and key. These are available free from the Adafruit website:\u0026nbsp;\u003ca href=\"https://learn.adafruit.com/welcome-to-adafruit-io/getting-started-with-adafruit-io\"\u003eGet Started with Adafruit IO | Welcome to Adafruit IO | Adafruit Learning System\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eThe items needed in your settings.toml file include:\u003c/p\u003e\n      \n\n\n\n","metadata":{}},{"element_type":"code","content":"CIRCUITPY_WIFI_SSID = \"***Enter your wifi SSD here***\"\nCIRCUITPY_WIFI_PASSWORD = \"***Enter your wifi pawword here***\"\nCIRCUITPY_WEB_API_PASSWORD=\"***Enter your password used to access your board on Web Workflow***\"\nCIRCUITPY_WEB_API_PORT=80\nAIO_USERNAME = \"***Enter your AdafruitIO username***\"\nAIO_KEY = \"***Enter your AdafruitIO key***\"","metadata":{"language":"python","linenums":false,"filename":""}},{"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        \u003cp\u003eNote that in the above code you need to replace the starred placeholder information between the quotes with your own information. The settings.toml file needs to be saved in the root directory of your board.\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","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch1\u003eOn to code.py\u003c/h1\u003e\n\u003cp\u003eI am going to go through code snippets to explain the process flow. The complete code for this project in included at the end of this article.\u0026nbsp;\u003c/p\u003e\n\u003ch2\u003eSet up the display\u003c/h2\u003e\n\u003cp\u003eTo set up the display we use:\u003c/p\u003e\n      \n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"displayio.release_displays()\nspi = board.SPI()\ncs = board.D15\ndc = board.D32\ndisplay_bus = fourwire.FourWire(spi, command=dc, chip_select=cs, reset=None)\ndisplay = ST7789(display_bus, rotation=180, width=135, height=240, rowstart=40, colstart=53)","metadata":{"language":"python","linenums":false,"filename":""}},{"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        \u003ch2\u003eConnect to WiFi and AdafruitIO\u003c/h2\u003e\n\u003cp\u003eNext we run code to connect to WiFi and prepare to get the time from AdafruitIO to set the RTC.\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","metadata":{}},{"element_type":"code","content":"aio_username = os.getenv('AIO_USERNAME')\naio_key = os.getenv('AIO_KEY')\nwifi_ssid = os.getenv('CIRCUITPY_WIFI_SSID')\nwifi_pw = os.getenv('CIRCUITPY_WIFI_PASSWORD')\ntimezone = os.getenv('TIMEZONE')\n\nwifi.radio.connect(wifi_ssid, wifi_pw)\nprint(\"Connected as: \", wifi.radio.ipv4_address)\npool = adafruit_connection_manager.get_radio_socketpool(wifi.radio)\nssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio)\nrequests = adafruit_requests.Session(pool, ssl_context)\nio = IO_HTTP(aio_username, aio_key, requests)","metadata":{"language":"python","linenums":false,"filename":""}},{"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\u003eWe start by getting our credentials from the settings.toml file and putting them into variables for easy access. Next we run commands to establish the WiFi connection and establish a connection to AdafruitIO.\u003c/p\u003e\n\u003ch2\u003eSet the time\u003c/h2\u003e\n\u003cp\u003eWe call 'set_clock()' function to set the time, then call the 'get_time()' and 'get_date()' to confirm our settings. These are functions defined at the top of code.py after the\u0026nbsp;\u003cstrong\u003eimport\u003c/strong\u003e statements:\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":"def set_clock():\n    try:\n        now = time.struct_time(io.receive_time(timezone))\n        r = rtc.RTC()\n        r.datetime = time.struct_time((now.tm_year, now.tm_mon, now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec, 1, -1, -1))\n    except Exception as e:\n        print(\"Failed to set RTC: \", e)\n\ndef get_time():\n    r = rtc.RTC()\n    now = r.datetime\n    hour = now.tm_hour\n    period = \"am\"\n    if hour \u003e 11:\n        period = \"pm\"\n    if hour \u003e 12:\n        hour = hour - 12\n    if hour == 0:\n        hour = 12\n    return f\"{hour:01d}:{now.tm_min:02d} {period}\"\n\ndef get_date():\n    r = rtc.RTC()\n    now = r.datetime\n    return f\"{now.tm_mon:1}/{now.tm_mday:2}/{now.tm_year}\"","metadata":{"language":"python","linenums":false,"filename":""}},{"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        \u003ch2\u003eSet up the Temperature Breakout Board\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\n","metadata":{}},{"element_type":"code","content":"i2c = board.STEMMA_I2C()\nspa = SPA06_003.over_i2c(i2c)\ntime.sleep(2)\n\nprint(spa.temperature, c2f(spa.temperature), spa.pressure + ELE_CORR)","metadata":{"language":"python","linenums":false,"filename":""}},{"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\u003eNote that we first have to set up the i2c bus which is simple with the STEMMA port. We then use the library to establish the device. I added a 2 second sleep to give the breakout time to stabilize. We then print out the current device readings. Since the SPA06-00 reports temperature in Celsius we need a conversion to Fahrenheit, hence another function at the top of the file:\u0026nbsp;\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":"code","content":"def c2f(c):\n    return (c * 9/5) + 32","metadata":{"language":"python","linenums":false,"filename":""}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eNote too that the SPA06-003 reports local atmospheric pressure whereas the common usage needs to convert to standard sea level. I have a constant at the top of the file 'ELE_CORR' = 11.2 which is the correction needed for my elevation above sea level. I found this factor with an internet search. Realistically there is a fairly complex calculation, but you can get a simple approximation which is what I am using. Be sure you are using the correct units (you want hPa).\u0026nbsp;\u003c/p\u003e\n\u003ch2\u003eAdd the Display Elements:\u003c/h2\u003e\n\u003cp\u003eBasically, there are three display elements - the background color (rectangle), the thermometer graphic and the rotating text at the bottom for time and readings.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eFirst, we set up the display group then add the elements and define our fonts (you will need your own fonts and change the code to name the appropriately). Then we add a rectangle the display width and height. Next, we add the thermometer at the top and in the center of the display, and then the bottom text label centered. Once then element is created then they need to be appended to the splash group.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eFinally, we use therm.update() to set the initial readings for the thermometer graphic. therm.update() is looking for two arguments - temperatures in °F. Since we only have one temperature (the output of the SPA06-003), we use it twice. Since the colors of both are the same (red) it appears on the graphic as a single reading.\u0026nbsp;\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"alert","content":"\u003cp\u003eThe only required arguments that need to be passed are the anchor point (upper left-hand corner), display and font. The remaining defaults are designed for a Fruit Jam 640x480 display, so in this case we need to scale the thermometer, placement, etc. We are also changing the colors for the background (\"bg\") and the secondary temperature (\"fill_apparent\") to match the primary temperature (as we are combining them to the single value). Details on all of the colors and all of the arguments that can be set are in the comments at the top if the library code.\u003c/p\u003e","metadata":{"markdown":"The only required arguments that need to be passed are the anchor point (upper left-hand corner), display and font. The remaining defaults are designed for a Fruit Jam 640x480 display, so in this case we need to scale the thermometer, placement, etc. We are also changing the colors for the background (\"bg\") and the secondary temperature (\"fill_apparent\") to match the primary temperature (as we are combining them to the single value). Details on all of the colors and all of the arguments that can be set are in the comments at the top if the library code.","alert_type":"purple","icon":"thinking"}},{"element_type":"code","content":"splash = displayio.Group()\ndisplay.root_group = splash\n\ndisplay_background = Rect(0, 0, display.width, display.height, fill=0x000088)\nsplash.append(display_background)\n\nfont17 = bitmap_font.load_font(\"/fonts/Calibri-17.pcf\")\nfont23 = bitmap_font.load_font(\"/fonts/Calibri-23.pcf\")\n\ndh = display.height\ntw=display.width - 40\n\ntherm = Thermometer(\n    anchor=(20,5),\n    display=display,\n    tick_font=font17,\n    frame_height=display.height - 20,\n    frame_width=tw,\n    temp_min=50,\n    temp_max=90,\n    scale_margin_top = 10,\n    colors={\n        \"bg\": 0x000088,\n        \"fill_apparent\": 0xFF0000,\n    }\n)\n\nsplash.append(therm.group)\n\nbottom_label = label.Label(font23, text=f\"{c2f(spa.temperature):.1f} °F\", anchored_position = (display.width//2, 220), anchor_point = (0.5, 0.0))\nsplash.append(bottom_label)\n\ntherm.update(int(c2f(spa.temperature)),int(c2f(spa.temperature)))","metadata":{"language":"python","linenums":false,"filename":""}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eThe while loop\u003c/h2\u003e\n\u003cp\u003eFor the while loop we use a non-blocking approach with time.monotonic(). In the loop we do a couple of things:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eEvery ten seconds (or whatever the UPDATE_INTERVAL is set to) we cycle through the rotating text at the bottom of the display\n\u003cul\u003e\n\u003cli\u003eIf displaying the time or date, we get the current time and date from the RTC and update the display with the appropriate function.\u003c/li\u003e\n\u003cli\u003eIf displaying a temperature, we request the current temperature from the SPA06-003, update the text at the bottom as well as update the graphic value\u003c/li\u003e\n\u003cli\u003eIf displaying the pressure, we request the current pressure, adjust to sea level and update the text\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eIf we have gone through 400 cycles, we go out and reset the clock. At 10 second interval this is about an hour - there is no need to do this more often than that.\u0026nbsp;\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1\u003eFurther Refinements\u003c/h1\u003e\n\u003cp\u003eSince we can show two different temperatures, one addition would be to get the outside temperature from the AdafruitIO weather API. Another improvement would be to redesign the layout and create a 3D printed case to make this a more finished project. What other ideas do you have?\u003c/p\u003e\n\u003ch1\u003eFull Code\u003c/h1\u003e\n      \n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"code","content":"import time\nimport board\nimport rtc\nimport displayio\nimport os\nimport ssl\nimport fourwire\nimport wifi\nimport adafruit_requests\nimport adafruit_connection_manager\nfrom adafruit_io.adafruit_io import IO_HTTP\nfrom adafruit_st7789 import ST7789\nfrom adafruit_bitmap_font import bitmap_font\nfrom adafruit_display_text import label\nfrom adafruit_display_shapes.rect import Rect\nfrom thermometer import Thermometer\nfrom adafruit_spa06_003 import SPA06_003\n\nELE_CORR = 11.2\nUPDATE_INTERVAL = 10\n\ndef c2f(c):\n    return (c * 9/5) + 32\n\ndef set_clock():\n    try:\n        now = time.struct_time(io.receive_time(timezone))\n        r = rtc.RTC()\n        r.datetime = time.struct_time((now.tm_year, now.tm_mon, now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec, 1, -1, -1))\n    except Exception as e:\n        print(\"Failed to set RTC: \", e)\n\ndef get_time():\n    r = rtc.RTC()\n    now = r.datetime\n    hour = now.tm_hour\n    period = \"am\"\n    if hour \u003e 11:\n        period = \"pm\"\n    if hour \u003e 12:\n        hour = hour - 12\n    if hour == 0:\n        hour = 12\n    return f\"{hour:01d}:{now.tm_min:02d} {period}\"\n\ndef get_date():\n    r = rtc.RTC()\n    now = r.datetime\n    return f\"{now.tm_mon:1}/{now.tm_mday:2}/{now.tm_year}\"\n    \ndisplayio.release_displays()\nspi = board.SPI()\ncs = board.D15\ndc = board.D32\ndisplay_bus = fourwire.FourWire(spi, command=dc, chip_select=cs, reset=None)\ndisplay = ST7789(display_bus, rotation=180, width=135, height=240, rowstart=40, colstart=53)\n\naio_username = os.getenv('AIO_USERNAME')\naio_key = os.getenv('AIO_KEY')\nwifi_ssid = os.getenv('CIRCUITPY_WIFI_SSID')\nwifi_pw = os.getenv('CIRCUITPY_WIFI_PASSWORD')\ntimezone = os.getenv('TIMEZONE')\n\nwifi.radio.connect(wifi_ssid, wifi_pw)\nprint(\"Connected as: \", wifi.radio.ipv4_address)\npool = adafruit_connection_manager.get_radio_socketpool(wifi.radio)\nssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio)\nrequests = adafruit_requests.Session(pool, ssl_context)\nio = IO_HTTP(aio_username, aio_key, requests)\n\nset_clock()\nprint(get_time(), \" \", get_date())\n\ni2c = board.STEMMA_I2C()\nspa = SPA06_003.over_i2c(i2c)\ntime.sleep(2)\n\nprint(spa.temperature, c2f(spa.temperature), spa.pressure + ELE_CORR)\n\nsplash = displayio.Group()\ndisplay.root_group = splash\n\ndisplay_background = Rect(0, 0, display.width, display.height, fill=0x000088)\nsplash.append(display_background)\n\nfont17 = bitmap_font.load_font(\"/fonts/Calibri-17.pcf\")\nfont23 = bitmap_font.load_font(\"/fonts/Calibri-23.pcf\")\n\ndh = display.height\ntw=display.width - 40\n\ntherm = Thermometer(\n    anchor=(20,5),\n    display=display,\n    tick_font=font17,\n    frame_height=display.height - 20,\n    frame_width=tw,\n    temp_min=50,\n    temp_max=90,\n    scale_margin_top = 10,\n    colors={\n        \"bg\": 0x000088,\n        \"fill_apparent\": 0xFF0000,\n    }\n)\n\nsplash.append(therm.group)\n\nbottom_label = label.Label(font23, text=f\"{c2f(spa.temperature):.1f} °F\", anchored_position = (display.width//2, 220), anchor_point = (0.5, 0.0))\nsplash.append(bottom_label)\n\ntherm.update(int(c2f(spa.temperature)),int(c2f(spa.temperature)))\nnext_cycle = time.monotonic() + UPDATE_INTERVAL\ntime_cycle = time.monotonic() + UPDATE_INTERVAL * 400\n\ncycle = 1\nwhile True:\n    if time.monotonic() \u003e time_cycle:\n        time_cycle = time.monotonic() + UPDATE_INTERVAL * 400\n        try:\n            set_clock()\n        except Exception as e:\n            print(\"Error setting time: \", e)\n    if cycle == 5:\n        cycle = 0\n    if time.monotonic() \u003e next_cycle:\n        next_cycle = time.monotonic() + UPDATE_INTERVAL\n        cycle = cycle + 1\n        temp_c = None\n        pressure_hpa = None\n        if cycle in (3, 4, 5):\n            temp_c = spa.temperature\n            pressure_hpa = spa.pressure + ELE_CORR\n        if cycle == 1:\n            bottom_label.color = 0xFF88FF\n            bottom_label.text = get_time()\n        elif cycle == 2:\n            bottom_label.color = 0x55FF55\n            bottom_label.text = get_date()\n        elif cycle == 3:\n            bottom_label.color = 0xFFFFFF\n            t = c2f(temp_c)\n            ft = f\"{t:.1f} °F\"\n            therm.update(t,t)\n            bottom_label.text = ft\n        elif cycle == 4:\n            bottom_label.color = 0xFFFF00\n            ft = f\"{temp_c:.1f} °C\"\n            bottom_label.text = ft\n        elif cycle == 5:\n            bottom_label.color = 0x00FF00\n            bottom_label.text = f\"{pressure_hpa:.1f} hPa\"\n        else:\n            pass\n\n    time.sleep(.1)","metadata":{"language":"python","linenums":false,"filename":""}}]