[{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/002/299/original/cover.png?1774412757","metadata":{}},{"element_type":"markdown","content":"\u003ch2\u003eIntroduction\u003c/h2\u003e\n\n\u003cp\u003eWe wanted an easy way to control a few aspects of our Bluesound Node box. While we could control the volume with the television remote, we could not easily switch inputs. Accessing favorites was limited to the set-top box and is restricted. For example we could not easily select a favorite saved in the Tune-In service.\u003c/p\u003e\n\n\u003cp\u003eThis controller is simple on purpose. However, the way it's written it can easily be modified and gain expanded functionality. Pretty much if you can craft the URL to the Bluesound API, the sky is the limit!\u003c/p\u003e\n","metadata":{"markdown":"## Introduction\n\nWe wanted an easy way to control a few aspects of our Bluesound Node box. While we could control the volume with the television remote, we could not easily switch inputs. Accessing favorites was limited to the set-top box and is restricted. For example we could not easily select a favorite saved in the Tune-In service.\n\nThis controller is simple on purpose. However, the way it's written it can easily be modified and gain expanded functionality. Pretty much if you can craft the URL to the Bluesound API, the sky is the limit!"}},{"element_type":"markdown","content":"\u003ch2\u003eBefore You Begin\u003c/h2\u003e\n\n\u003cp\u003eYou'll need to familiarize yourself with the \u003ca href=\"https://bluos.io/wp-content/uploads/2025/06/BluOS-Custom-Integration-API_v1.7.pdf\"\u003eBluesound API\u003c/a\u003e.\u003c/p\u003e\n\n\u003cp\u003eFull disclosure I found this document later in my efforts and only referenced it a little bit.\u003c/p\u003e\n\n\u003cp\u003eWhat I did find are some wonderful libraries with extremely helpful README documents.  This is the \u003ca href=\"https://github.com/albertony/blushell\"\u003eone\u003c/a\u003e I referenced the most.\u003c/p\u003e\n\n\u003cp\u003eOnce you get the gist of the URL format, it becomes finding exactly what you're looking for. The toughest for me was locating the specific URLs for the Tune-In favorites. Here's where to find them:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003ehttp://\u0026lt;IP of your Bluesound Node\u0026gt;:11000/RadioBrowse?service=TuneIn\u0026amp;url=presets\n\u003c/pre\u003e\u003c/code\u003e","metadata":{"markdown":"## Before You Begin\n\nYou'll need to familiarize yourself with the [Bluesound API](https://bluos.io/wp-content/uploads/2025/06/BluOS-Custom-Integration-API_v1.7.pdf).\n\nFull disclosure I found this document later in my efforts and only referenced it a little bit.\n\nWhat I did find are some wonderful libraries with extremely helpful README documents.  This is the [one](https://github.com/albertony/blushell) I referenced the most.\n\nOnce you get the gist of the URL format, it becomes finding exactly what you're looking for. The toughest for me was locating the specific URLs for the Tune-In favorites. Here's where to find them:\n\n```\nhttp://\u0026lt;IP of your Bluesound Node\u0026gt;:11000/RadioBrowse?service=TuneIn\u0026amp;url=presets\n```"}},{"element_type":"markdown","content":"\u003ch2\u003eDesign\u003c/h2\u003e\n\n\u003cul\u003e\n\u003cli\u003eThis project is coded in CircuitPython.\u003c/li\u003e\n\u003cli\u003eThe case is coded in OpenSCAD\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003ch3\u003eHardware\u003c/h3\u003e\n","metadata":{"markdown":"## Design\n\n* This project is coded in CircuitPython.\n* The case is coded in OpenSCAD\n\n### Hardware"}},{"element_type":"product","content":"https://www.adafruit.com/product/5477","metadata":{"name":"Microcontroller","url":"https://www.adafruit.com/product/5477","description":"Adafruit Feather ESP32-S3","quantity":"1","featured":false}},{"element_type":"product","content":"https://www.adafruit.com/product/5880","metadata":{"name":"Volume Control","url":"https://www.adafruit.com/product/5880","description":"Adafruit I2C Stemma QT Rotary Encoder Breakout With Encoder","quantity":"1","featured":false}},{"element_type":"product","content":"https://www.adafruit.com/product/5533","metadata":{"name":"Volume Control Knob","url":"https://www.adafruit.com/product/5533","description":"Orange Micro Potentiometer Knob - Pick the one you like!","quantity":"1","featured":false}},{"element_type":"product","content":"https://www.adafruit.com/product/4980","metadata":{"name":"Keyboard","url":"https://www.adafruit.com/product/4980","description":"Neokey 1x4 QT I2C ","quantity":"1","featured":false}},{"element_type":"product","content":"https://www.adafruit.com/product/4955","metadata":{"name":"Mechanical Keys ","url":"https://www.adafruit.com/product/4955","description":"Kailh Mechanical Key Switches - 10 pack","quantity":"1","featured":false}},{"element_type":"product","content":"https://www.adafruit.com/product/4956","metadata":{"name":"Key Caps","url":"https://www.adafruit.com/product/4956","description":"Translucent Keycaps for MX Compatible Switches - 10 pack - Pick the variety you like!","quantity":"1","featured":false}},{"element_type":"product","content":"https://www.adafruit.com/product/258","metadata":{"name":"Battery","url":"https://www.adafruit.com/product/258","description":"Lithium Ion Polymer Battery - 3.7v 1200mAh","quantity":"1","featured":false}},{"element_type":"product","content":"https://www.adafruit.com/product/4399","metadata":{"name":"Stemma QT Cables","url":"https://www.adafruit.com/product/4399","description":"STEMMA QT / Qwiic JST SH 4-Pin Cable - 50mm Long","quantity":"2","featured":false}},{"element_type":"parts","content":"\n  \u003cdiv class=\"parts-details\"\u003e\n    \u003cdiv\u003e\n      \u003cspan class=\"parts-quantity\"\u003e1\u003c/span\u003e\u003cspan\u003e x \u003c/span\u003e\n        \u003ca href=\"https://www.amazon.com/ProTapes-306UGLU600-UGlu-Dash-Sheets/dp/B06XCCRPRY/ref=sr_1_1_pp?crid=1WHVUK7FG33ZM\u0026amp;dib=eyJ2IjoiMSJ9.YO9rK1k2JX7wQnsA07Ip8PD5mKNPBGGuwxze2QJlFd33N6k8XsFg-rogqg3W7DfxBRK6YCNWH_x6HjdVNH7CdNHNZFJY71wOHElBmMNFNq7bksfYV3HUlkQ8VhzUXDUYxjCaAVAv8Ah9R3S6m-rWEyIA5RFBCEHe-GhT4KgWR5YuZx8GsBkUEYixfddG4NVjOrF15eO4EhTvtgO5pP3jF64i_qBHy2_uCN2QB7TFAMAeYXWU6ZgXDJY4dWaevvWas4TKPU6uzU_GJEphECfKSP2JqVzjUwmc4kkcuQvB6hI.Vt9X77DudyiMod924zgQALtRtj2At7nywblRXMdbeF8\u0026amp;dib_tag=se\u0026amp;keywords=uglu+dots\u0026amp;qid=1773801877\u0026amp;sprefix=uglu+dot%2Caps%2C274\u0026amp;sr=8-1\" class=\"parts-name\" target=\"_blank\"\u003eUGlu Dashes\u003c/a\u003e\n      \u003cdiv class=\"parts-description\"\u003eSecuring the battery to the housing\u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"parts-action\"\u003e\n  \u003cdiv\u003e\n      \u003ca href=\"https://www.amazon.com/ProTapes-306UGLU600-UGlu-Dash-Sheets/dp/B06XCCRPRY/ref=sr_1_1_pp?crid=1WHVUK7FG33ZM\u0026amp;dib=eyJ2IjoiMSJ9.YO9rK1k2JX7wQnsA07Ip8PD5mKNPBGGuwxze2QJlFd33N6k8XsFg-rogqg3W7DfxBRK6YCNWH_x6HjdVNH7CdNHNZFJY71wOHElBmMNFNq7bksfYV3HUlkQ8VhzUXDUYxjCaAVAv8Ah9R3S6m-rWEyIA5RFBCEHe-GhT4KgWR5YuZx8GsBkUEYixfddG4NVjOrF15eO4EhTvtgO5pP3jF64i_qBHy2_uCN2QB7TFAMAeYXWU6ZgXDJY4dWaevvWas4TKPU6uzU_GJEphECfKSP2JqVzjUwmc4kkcuQvB6hI.Vt9X77DudyiMod924zgQALtRtj2At7nywblRXMdbeF8\u0026amp;dib_tag=se\u0026amp;keywords=uglu+dots\u0026amp;qid=1773801877\u0026amp;sprefix=uglu+dot%2Caps%2C274\u0026amp;sr=8-1\" class=\"parts-url btn-primary\" target=\"_blank\"\u003eBuy Now\u003c/a\u003e\n\n  \u003c/div\u003e\n\u003c/div\u003e\n\n  \u003cdiv class=\"clearfix\"\u003e\u003c/div\u003e\n","metadata":{"name":"UGlu Dashes","url":"https://www.amazon.com/ProTapes-306UGLU600-UGlu-Dash-Sheets/dp/B06XCCRPRY/ref=sr_1_1_pp?crid=1WHVUK7FG33ZM\u0026dib=eyJ2IjoiMSJ9.YO9rK1k2JX7wQnsA07Ip8PD5mKNPBGGuwxze2QJlFd33N6k8XsFg-rogqg3W7DfxBRK6YCNWH_x6HjdVNH7CdNHNZFJY71wOHElBmMNFNq7bksfYV3HUlkQ8VhzUXDUYxjCaAVAv8Ah9R3S6m-rWEyIA5RFBCEHe-GhT4KgWR5YuZx8GsBkUEYixfddG4NVjOrF15eO4EhTvtgO5pP3jF64i_qBHy2_uCN2QB7TFAMAeYXWU6ZgXDJY4dWaevvWas4TKPU6uzU_GJEphECfKSP2JqVzjUwmc4kkcuQvB6hI.Vt9X77DudyiMod924zgQALtRtj2At7nywblRXMdbeF8\u0026dib_tag=se\u0026keywords=uglu+dots\u0026qid=1773801877\u0026sprefix=uglu+dot%2Caps%2C274\u0026sr=8-1","description":"Securing the battery to the housing","quantity":"1"}},{"element_type":"parts","content":"\n  \u003cdiv class=\"parts-details\"\u003e\n    \u003cdiv\u003e\n      \u003cspan class=\"parts-quantity\"\u003e1\u003c/span\u003e\u003cspan\u003e x \u003c/span\u003e\n        \u003ca href=\"https://tinyurl.com/y5p42r96\" class=\"parts-name\" target=\"_blank\"\u003eMomentary Switch\u003c/a\u003e\n      \u003cdiv class=\"parts-description\"\u003eAny switch of your choosing will do\u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"parts-action\"\u003e\n  \u003cdiv\u003e\n      \u003ca href=\"https://tinyurl.com/y5p42r96\" class=\"parts-url btn-primary\" target=\"_blank\"\u003eBuy Now\u003c/a\u003e\n\n  \u003c/div\u003e\n\u003c/div\u003e\n\n  \u003cdiv class=\"clearfix\"\u003e\u003c/div\u003e\n","metadata":{"name":"Momentary Switch","url":"https://tinyurl.com/y5p42r96","description":"Any switch of your choosing will do","quantity":"1"}},{"element_type":"product","content":"https://www.adafruit.com/product/3174","metadata":{"name":"22 AWG Wire","url":"https://www.adafruit.com/product/3174","description":"Solid Core Hook-Up Wire","quantity":"1","featured":false}},{"element_type":"markdown","content":"\u003ch2\u003eBuilding the Controller\u003c/h2\u003e\n\n\u003ch3\u003eNotifications and API Infrastructure\u003c/h3\u003e\n\n\u003cp\u003eIn order to be notified when you need to charge the battery, you'll need MQTT.\u003c/p\u003e\n\n\u003cp\u003e\u003ca href=\"https://adafruit.io\"\u003eAdafruitIO\u003c/a\u003e would be perfect for this as you would get your MQTT server, dashboard, and notifications handled in one place.\u003c/p\u003e\n\n\u003cp\u003eFor my project I use a local MQTT server, \u003ca href=\"https://mosquitto.org/\"\u003eMosquitto MQTT\u003c/a\u003e that I have running on a Raspberry Pi Zero W.\u003c/p\u003e\n\n\u003cp\u003eFor handling notifications I'm using a local \u003ca href=\"https://adafruit-playground.com/u/ntynen/pages/home-hub-project-with-node-red-dashboard\"\u003eNode-Red\u003c/a\u003e server running on a Raspberry Pi Zero 2 W.\u003c/p\u003e\n\n\u003ch3\u003eHardware\u003c/h3\u003e\n\n\u003cp\u003eYou don't need stacking headers for your project. You will see them on my board in the pictures, however, the headers were soldered on for testing out a different project. I did end up using them for the wake from sleep switch, however you could choose to direct solder to the board.\u003c/p\u003e\n\n\u003cp\u003eIn order to use the Keyboard, the Rotary Encoder, and do battery voltage monitoring you will need to solder either the A0, A1, or A2 pin on the bottom of the Rotary Encoder Breakout Board. The reason is because the default I2C address for battery monitoring on the Feather is 0x36 which also happens to be the default I2C address for the Rotary Encoder Board. It's all explained very well in the \u003ca href=\"https://learn.adafruit.com/adafruit-esp32-s3-feather/power-management\"\u003ePrimary Guide\u003c/a\u003e.\u003c/p\u003e\n\n\u003ch3\u003eThe Code\u003c/h3\u003e\n\n\u003cp\u003eThe project is available on \u003ca\u003eGithub\u003c/a\u003e.\u003c/p\u003e\n\n\u003ch4\u003eCircuitPython Libraries Needed\u003c/h4\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003eos\nadafruit_connection_manager\nadafruit_minimqtt.adafruit_minimqtt\nboard\nsocketpool\nwifi\ntime\nadafruit_neokey.neokey1x4\nadafruit_logging\nadafruit_requests\nadafruit_requests import OutOfRetries\nadafruit_seesaw import digitalio, rotaryio, seesaw\nElementTree\nadafruit_led_animation.color\nadafruit_max1704x\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/Neradoc/Circuitpython-ElementTree.git\"\u003eElementTree\u003c/a\u003e is available on Github. It is provided by Nearadoc.\u003c/p\u003e\n\n\u003ch4\u003eCode Highlights\u003c/h4\u003e\n\n\u003cp\u003eI use the testing flag to change logging level to debug and to print the requests and MQTT calls.\u003c/p\u003e\n\n\u003cp\u003eProject will publish messages to the MQTT service and will send API requests to the Bluesound Node device.\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003e# Handle all MQTT publish requests\ndef do_publish(feed, msg):\n    if testing:\n        logger.debug(f\"Testing: would publish {msg} to {feed}\")\n    else:\n        try:\n            my_mqtt.publish(feed, msg)\n        except MMQTTException:\n            logger.error(\"unable to connect to remote MQTT broker, message not sent\")\n            my_mqtt.disconnect()\n            pass\n\n# Handle all API requests\ndef send_request(url):\n    if testing:\n        logger.debug(f\"Would send {url}\")\n    else:\n        try:\n            requests.post(url)\n        except OutOfRetries as e:\n            logger.error(f\"unable to send message: {e}\")\n            pass\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eBluesound API calls look like this:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003e# ---- Bluesound Node ---- #\nbaseURL = os.getenv(\"bluesound_baseUrl\")\nlogger.debug(f\"base url is {baseURL}\")\nopticalInput = \"Play?url=Capture%3Ahw%3Aimxspdif%2C0%2F1%2F25%2F2%3Fid%3Dinput1\u0026amp;preset_id\u0026amp;image=/images/capture/ic_opticalinput.png\"\nhdmiInput = \"Play?url=Capture%3Ahw%3Aimxspdif%2C0%2F1%2F25%2F2%3Fid%3Dinput2\u0026amp;preset_id\u0026amp;image=/images/capture/ic_hdmi.png\"\nvolumeQuery = \"Volume\"\nvolumeChange = \"Volume?level=\"\naloha_joe_play = \"Play?url=TuneIn%3As49372\u0026amp;preset_id\u0026amp;image=http://cdn-radiotime-logos.tunein.com/s49372g.png\"\naloha_joe_pause = \"Pause?url=TuneIn%3As49372\u0026amp;preset_id\u0026amp;image=http://cdn-radiotime-logos.tunein.com/s49372g.png\"\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eWhen the battery gets below 3.7V a message will be sent to an MQTT feed. Node-Red will subscribe to this feed and when a message is received will send an email alerting me that it's time to put the device on the charger. I set a flag so that this message is only sent once. When the battery reaches a voltage of 4.0 or higher, a battery charged message will be sent and the flag reset.\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003e  # Check the voltage of the battery, send a message to MQTT if it's below 3.7V\n    if batteryCheck is None or time.monotonic() \u0026gt; batteryCheck + batteryCheckWait:\n        logger.debug(\"battery check hello\")\n        batteryVoltage, batteryPercentage = monitor_battery()\n        if batteryVoltage \u0026lt; 3.7 and not batteryWarn:\n            logger.debug(\"We need to warn\")\n            batteryWarn = True\n            do_publish(battery_feed, batteryVoltage)\n        elif batteryVoltage \u0026gt;= 4.0 and batteryWarn:\n            logger.debug(\"We need to reset the warn flag\")\n            do_publish(battery_feed, batteryVoltage)\n            batteryWarn = False\n        else:\n            logger.debug(f\"battery warning {batteryVoltage:.2f} Volts, and battery warn is {batteryWarn}\")\n\n        batteryCheck = time.monotonic()\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eI added in logic for the companion to exit and sleep until awakened by pressing a momentary switch installed on the side. I need to do this since when the companion is powered up it's kind of lit up like Vegas and the battery just doesn't last like that.\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003e    if time.monotonic() \u0026gt; sleeping + wait_to_sleep:\n        hibernate_alarm = alarm.pin.PinAlarm(pin=nightNight_pin, value=False, edge=False, pull=True)\n        alarm.exit_and_deep_sleep_until_alarms(hibernate_alarm)\n\u003c/pre\u003e\u003c/code\u003e\n\u003ch3\u003eThe Housing\u003c/h3\u003e\n\n\u003cp\u003eThe housing is print ready, load the STL file in your slicer of choice and pick a filament.\u003c/p\u003e\n\n\u003cp\u003eIf you'd like to modify the case you'll need \u003ca href=\"https://openscad.org\"\u003eOpenSCAD\u003c/a\u003e and this amazing library, \u003ca href=\"https://github.com/mrWheel/YAPP_Box\"\u003eYAPP Box\u003c/a\u003e.\u003c/p\u003e\n\n\u003cp\u003eThere is support in the YAPP_Box library to put labels directly on the case. This works really well if you want etched into the case style labels. For ease of reading I wanted raised labels that I could paint. Using the library to do this made printing the case a lot more complicated than it should be. Therefore I opted to create two plates that I will glue to the top of the case. The code to include the etched labels is still in the code, just commented out.\u003c/p\u003e\n\n\u003cp\u003eI painted the letters with acrylic paint and seal with Mod Podge Acrylic Sealer.\u003c/p\u003e\n\n\u003cp\u003eThere are snap joins, one each, on the front, back, left, and right, of the case allowing for a secure fit.\u003c/p\u003e\n\n\u003cul\u003e\n\u003cli\u003eFilament Used: FlashForge PLA - color: Marsala (base) and Hatch PETG - color: Transparent White\u003c/li\u003e\n\u003cli\u003eBed Temp: 55 and 80\u003c/li\u003e\n\u003cli\u003eNozzle Temp: 220 and 245\u003c/li\u003e\n\u003cli\u003eRaft: No\u003c/li\u003e\n\u003cli\u003eBrim: No (PLA), Yes (PETG) but only perimeter\u003c/li\u003e\n\u003cli\u003eSupports: Yes, only touching plate, should be 4; one under each standoff for the keyboard and maybe manually add one in the cutout for the momentary switch\u003c/li\u003e\n\u003c/ul\u003e\n","metadata":{"markdown":"## Building the Controller\n\n### Notifications and API Infrastructure\n\nIn order to be notified when you need to charge the battery, you'll need MQTT.\n\n[AdafruitIO](https://adafruit.io) would be perfect for this as you would get your MQTT server, dashboard, and notifications handled in one place.\n\nFor my project I use a local MQTT server, [Mosquitto MQTT](https://mosquitto.org/) that I have running on a Raspberry Pi Zero W.\n\nFor handling notifications I'm using a local [Node-Red](https://adafruit-playground.com/u/ntynen/pages/home-hub-project-with-node-red-dashboard) server running on a Raspberry Pi Zero 2 W.\n\n### Hardware\n\nYou don't need stacking headers for your project. You will see them on my board in the pictures, however, the headers were soldered on for testing out a different project. I did end up using them for the wake from sleep switch, however you could choose to direct solder to the board.\n\nIn order to use the Keyboard, the Rotary Encoder, and do battery voltage monitoring you will need to solder either the A0, A1, or A2 pin on the bottom of the Rotary Encoder Breakout Board. The reason is because the default I2C address for battery monitoring on the Feather is 0x36 which also happens to be the default I2C address for the Rotary Encoder Board. It's all explained very well in the [Primary Guide](https://learn.adafruit.com/adafruit-esp32-s3-feather/power-management).\n\n### The Code\n\nThe project is available on [Github](git@github.com:niccit/bluesound_companion.git).\n\n#### CircuitPython Libraries Needed\n```\nos\nadafruit_connection_manager\nadafruit_minimqtt.adafruit_minimqtt\nboard\nsocketpool\nwifi\ntime\nadafruit_neokey.neokey1x4\nadafruit_logging\nadafruit_requests\nadafruit_requests import OutOfRetries\nadafruit_seesaw import digitalio, rotaryio, seesaw\nElementTree\nadafruit_led_animation.color\nadafruit_max1704x\n```\n\n[ElementTree](https://github.com/Neradoc/Circuitpython-ElementTree.git) is available on Github. It is provided by Nearadoc.\n\n#### Code Highlights\n\nI use the testing flag to change logging level to debug and to print the requests and MQTT calls.\n\nProject will publish messages to the MQTT service and will send API requests to the Bluesound Node device.\n\n``` \n# Handle all MQTT publish requests\ndef do_publish(feed, msg):\n    if testing:\n        logger.debug(f\"Testing: would publish {msg} to {feed}\")\n    else:\n        try:\n            my_mqtt.publish(feed, msg)\n        except MMQTTException:\n            logger.error(\"unable to connect to remote MQTT broker, message not sent\")\n            my_mqtt.disconnect()\n            pass\n\n# Handle all API requests\ndef send_request(url):\n    if testing:\n        logger.debug(f\"Would send {url}\")\n    else:\n        try:\n            requests.post(url)\n        except OutOfRetries as e:\n            logger.error(f\"unable to send message: {e}\")\n            pass\n```\n\nBluesound API calls look like this:\n```\n# ---- Bluesound Node ---- #\nbaseURL = os.getenv(\"bluesound_baseUrl\")\nlogger.debug(f\"base url is {baseURL}\")\nopticalInput = \"Play?url=Capture%3Ahw%3Aimxspdif%2C0%2F1%2F25%2F2%3Fid%3Dinput1\u0026amp;preset_id\u0026amp;image=/images/capture/ic_opticalinput.png\"\nhdmiInput = \"Play?url=Capture%3Ahw%3Aimxspdif%2C0%2F1%2F25%2F2%3Fid%3Dinput2\u0026amp;preset_id\u0026amp;image=/images/capture/ic_hdmi.png\"\nvolumeQuery = \"Volume\"\nvolumeChange = \"Volume?level=\"\naloha_joe_play = \"Play?url=TuneIn%3As49372\u0026amp;preset_id\u0026amp;image=http://cdn-radiotime-logos.tunein.com/s49372g.png\"\naloha_joe_pause = \"Pause?url=TuneIn%3As49372\u0026amp;preset_id\u0026amp;image=http://cdn-radiotime-logos.tunein.com/s49372g.png\"\n```\n\n\n\nWhen the battery gets below 3.7V a message will be sent to an MQTT feed. Node-Red will subscribe to this feed and when a message is received will send an email alerting me that it's time to put the device on the charger. I set a flag so that this message is only sent once. When the battery reaches a voltage of 4.0 or higher, a battery charged message will be sent and the flag reset.\n\n```\n  # Check the voltage of the battery, send a message to MQTT if it's below 3.7V\n    if batteryCheck is None or time.monotonic() \u0026gt; batteryCheck + batteryCheckWait:\n        logger.debug(\"battery check hello\")\n        batteryVoltage, batteryPercentage = monitor_battery()\n        if batteryVoltage \u0026lt; 3.7 and not batteryWarn:\n            logger.debug(\"We need to warn\")\n            batteryWarn = True\n            do_publish(battery_feed, batteryVoltage)\n        elif batteryVoltage \u0026gt;= 4.0 and batteryWarn:\n            logger.debug(\"We need to reset the warn flag\")\n            do_publish(battery_feed, batteryVoltage)\n            batteryWarn = False\n        else:\n            logger.debug(f\"battery warning {batteryVoltage:.2f} Volts, and battery warn is {batteryWarn}\")\n\n        batteryCheck = time.monotonic()\n```\n\nI added in logic for the companion to exit and sleep until awakened by pressing a momentary switch installed on the side. I need to do this since when the companion is powered up it's kind of lit up like Vegas and the battery just doesn't last like that.\n```\n    if time.monotonic() \u0026gt; sleeping + wait_to_sleep:\n        hibernate_alarm = alarm.pin.PinAlarm(pin=nightNight_pin, value=False, edge=False, pull=True)\n        alarm.exit_and_deep_sleep_until_alarms(hibernate_alarm)\n```\n\n### The Housing\n    \nThe housing is print ready, load the STL file in your slicer of choice and pick a filament.\n    \nIf you'd like to modify the case you'll need [OpenSCAD](https://openscad.org) and this amazing library, [YAPP Box](https://github.com/mrWheel/YAPP_Box).\n\nThere is support in the YAPP_Box library to put labels directly on the case. This works really well if you want etched into the case style labels. For ease of reading I wanted raised labels that I could paint. Using the library to do this made printing the case a lot more complicated than it should be. Therefore I opted to create two plates that I will glue to the top of the case. The code to include the etched labels is still in the code, just commented out.\n\nI painted the letters with acrylic paint and seal with Mod Podge Acrylic Sealer.\n\nThere are snap joins, one each, on the front, back, left, and right, of the case allowing for a secure fit.\n\n* Filament Used: FlashForge PLA - color: Marsala (base) and Hatch PETG - color: Transparent White\n* Bed Temp: 55 and 80\n* Nozzle Temp: 220 and 245\n* Raft: No\n* Brim: No (PLA), Yes (PETG) but only perimeter\n* Supports: Yes, only touching plate, should be 4; one under each standoff for the keyboard and maybe manually add one in the cutout for the momentary switch"}},{"element_type":"markdown","content":"\u003ch2\u003ePutting It All Together\u003c/h2\u003e\n\n\u003ch4\u003eThe Base\u003c/h4\u003e\n\n\u003cul\u003e\n\u003cli\u003ePut two UGlu Dashes on the battery, top and bottom. \u003c/li\u003e\n\u003cli\u003eConnect the battery to the Feather ESP32-S3. \u003c/li\u003e\n\u003cli\u003eRun the battery wire under the standoffs for a cleaner look. \u003c/li\u003e\n\u003cli\u003eUsing four (4) M2x5 metal screws, secure the Feather to the standoffs.\u003cbr\u003e\n\u003c/li\u003e\n\u003cli\u003ePress the battery, with the UGlu Dashes exposed, to the base of the case, over the venting.\u003c/li\u003e\n\u003c/ul\u003e\n","metadata":{"markdown":"## Putting It All Together\n\n#### The Base\n* Put two UGlu Dashes on the battery, top and bottom. \n* Connect the battery to the Feather ESP32-S3. \n* Run the battery wire under the standoffs for a cleaner look. \n* Using four (4) M2x5 metal screws, secure the Feather to the standoffs.  \n* Press the battery, with the UGlu Dashes exposed, to the base of the case, over the venting."}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/002/300/original/base_assembly_1.png?1774412843","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/002/301/original/base_assembly_2.png?1774412880","metadata":{}},{"element_type":"markdown","content":"\u003ch4\u003eThe Lid\u003c/h4\u003e\n\n\u003cul\u003e\n\u003cli\u003eSolder one wire for ground and one for data to the momentary switch. Use solid core if you're using a header on the Feather board or stranded if you're soldering directly to the board\u003c/li\u003e\n\u003cli\u003eStrip the exposed end of the wires to either to insert into the headers on the board or solder directly to the Feather board\u003c/li\u003e\n\u003cli\u003eSecure the momentary switch to the lid using the supplied hardware\u003c/li\u003e\n\u003cli\u003eSecure the rotary encoder board to the lid using four (4) M2.5x5 metal screws. \u003c/li\u003e\n\u003cli\u003eSecure the keyboard board to the lid using four (4) M2.5x4 metal screws.\u003c/li\u003e\n\u003cli\u003eInstall the MX keys to the board and the key covers to the MX keys.\u003c/li\u003e\n\u003cli\u003eConnect the keyboard to the rotary encoder board using one of the Stemma QT cables.\u003c/li\u003e\n\u003cli\u003eConnect the rotary encoder or keyboard to the Feather board using the other Stemma QT cable.\u003c/li\u003e\n\u003cli\u003eConnect one lead from the switch to D9 and the other to ground\u003c/li\u003e\n\u003cli\u003ePress the reset key on the Feather board once everything is fully assembled\u003c/li\u003e\n\u003cli\u003e\n\u003cem\u003e(Optional)\u003c/em\u003e After painting the raised lettering on the label boards and letting it dry thoroughly, secure the boards to the lid with super glue.\u003cbr\u003e\n\u003c/li\u003e\n\u003cli\u003eConnect to the lid to the base and you're good to go.\u003c/li\u003e\n\u003c/ul\u003e\n","metadata":{"markdown":"#### The Lid\n\n* Solder one wire for ground and one for data to the momentary switch. Use solid core if you're using a header on the Feather board or stranded if you're soldering directly to the board\n* Strip the exposed end of the wires to either to insert into the headers on the board or solder directly to the Feather board\n* Secure the momentary switch to the lid using the supplied hardware\n* Secure the rotary encoder board to the lid using four (4) M2.5x5 metal screws. \n* Secure the keyboard board to the lid using four (4) M2.5x4 metal screws.\n* Install the MX keys to the board and the key covers to the MX keys.\n* Connect the keyboard to the rotary encoder board using one of the Stemma QT cables.\n* Connect the rotary encoder or keyboard to the Feather board using the other Stemma QT cable.\n* Connect one lead from the switch to D9 and the other to ground\n* Press the reset key on the Feather board once everything is fully assembled\n* *(Optional)* After painting the raised lettering on the label boards and letting it dry thoroughly, secure the boards to the lid with super glue.  \n* Connect to the lid to the base and you're good to go."}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/002/302/original/lid_assembly_1b.png?1774412933","metadata":{}},{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/002/303/original/lid_assembly_2.png?1774412965","metadata":{}}]