Maker.io main logo

Star Fragment IoT Lamp

2023-05-16 | By Adafruit Industries

License: See Original Project 3D Printing Addressable LEDs Programmers

Courtesy of Adafruit

Guide by Ruiz Brothers

Overview

Inspired by Star Fragments from video games such as Zelda: Breath of the Wild and Animal Crossing: ‎New Horizons, this fully 3D printed star shaped lamp uses a QT Py ESP32-S2 and NeoPixel LEDs to turn ‎on when the sun rises and off when it sets.‎

 

The CircuitPython code uses the Open-Meteo weather API to get location data for requesting the ‎sunrise and sunset times. This IoT project knows when to light up and turn off, effectively automating ‎a nightstand lamp.‎

remotes_1

The core of the star fragment is a dodecahedron that has 12 hollow spikes that snap fit together and ‎are 3D printed without any support material.‎

A 3D printed mount houses two NeoPixel stick PCBs and snap fits into the bottom of the ‎dodecahedron.‎

outside_2

outside_3

Parts from Adafruit

Following parts are used in this project.‎

  • QT Py ESP32-S2
  • ‎NeoPixel Driver BFF
  • ‎2x NeoPixel Sticks
  • ‎‎3-pin JST cable

parts_4

This project requires two 8x NeoPixel LED sticks.‎

CircuitPython

CircuitPython is a derivative of MicroPython designed to simplify experimentation and education on ‎low-cost microcontrollers. It makes it easier than ever to get prototyping by requiring no upfront ‎desktop software downloads. Simply copy and edit files on the CIRCUITPY drive to iterate.‎

CircuitPython Quickstart

Follow this step-by-step to quickly get CircuitPython running on your board.‎

Download the latest version of CircuitPython for this board via circuitpython.org

Click the link above to download the latest CircuitPython UF2 file.‎

Save it wherever is convenient for you.‎

reset_5

assets_6

Plug your board into your computer, using a known-good data-sync cable, directly, or via an adapter if ‎needed.‎

Click the reset button once (highlighted in red above), and then click it again when you see the RGB ‎status LED(s) (highlighted in green above) turn red (approximately half a second later). Sometimes it ‎helps to think of it as a "slow double-click" of the reset button.‎

For this board, tap reset and wait for the LED to turn purple, and as soon as it turns purple, tap reset ‎again. The second tap needs to happen while the LED is still purple.‎

Once successful, you will see the RGB status LED(s) turn green (highlighted in green above). If you see ‎red, try another port, or if you're using an adapter or hub, try without the hub, or different adapter or ‎hub.‎

If double-clicking doesn't work the first time, try again. Sometimes it can take a few tries to get the ‎rhythm right!‎

A lot of people end up using charge-only USB cables and it is very frustrating! Make sure you have a ‎USB cable you know is good for data sync.‎

If after several tries, and verifying your USB cable is data-ready, you still cannot get to the bootloader, ‎it is possible that the bootloader is missing or damaged. Check out the Install UF2 Bootloader page for ‎details on resolving this issue.‎

You will see a new disk drive appear called QTPYS2BOOT.‎

Drag the adafruit_circuitpython_etc.uf2 file to QTPYS2BOOT.‎

uf2install_7

The BOOT drive will disappear, and a new disk drive called CIRCUITPY will appear.

That's it!‎

boot_8

Create Your settings.toml File

If you've worked on WiFi projects with CircuitPython before, you're probably familiar with the ‎secrets.py file. This file is a Python file that is stored on your CIRCUITPY drive that contains all of your ‎secret WiFi information, such as your SSID, SSID password and any API keys for IoT services. ‎

As of CircuitPython 8, there is support for a settings.toml file. Similar to secrets.py, the settings.toml ‎file separates your sensitive information from your main code.py file.

Your settings.toml file should be stored in the main directory of your CIRCUITPY drive. It should not be in a folder.

settings.toml File Example

Here is an example on how to format your settings.toml file.‎

Download File‎

Copy Code
# Comments are supported
CIRCUITPY_WIFI_SSID="guest wifi"
CIRCUITPY_WIFI_PASSWORD="guessable"
CIRCUITPY_WEB_API_PORT=80
CIRCUITPY_WEB_API_PASSWORD="passw0rd"
test_variable="this is a test"
thumbs_up="\U0001f44d"

In a settings.toml file, it's important to keep these factors in mind:‎

  • Strings are wrapped in double quotes; ex: "your-string-here"
  • ‎Integers are not quoted and may be written in decimal with optional sign (+1, -1, 1000) or hexadecimal ‎‎(0xabcd).‎
    • Floats, octal (0o567) and binary (0b11011) are not supported.‎
  • Use \u escapes for weird characters, \x and \ooo escapes are not available in .toml files
    • Example: \U0001f44d for (thumbs up emoji) and \u20ac for € (EUR sign)
  • ‎Unicode emoji, and non-ASCII characters, stand for themselves as long as you're careful to save in ‎‎"UTF-8 without BOM" format‎‎

‎When your settings.toml file is ready, you can save it in your text editor with the .toml extension.‎

dot_9

Accessing Your settings.toml Information in code.py

In your code.py file, you'll need to import the os library to access the settings.toml file. Your settings ‎are accessed with the os.getenv() function. You'll pass your settings entry to the function to import it ‎into the code.py file.‎

Download File

Copy Code
import os

print(os.getenv("test_variable"))

output_10

In the upcoming CircuitPython WiFi examples, you'll see how the settings.toml file is used for ‎connecting to your SSID and accessing your API keys.‎

Code the Star Fragment Lamp

hero_11

Once you've finished setting up your QT Py ESP32-S2 with CircuitPython, you can access the code and ‎necessary libraries by downloading the Project Bundle.‎

To do this, click on the Download Project Bundle button in the window below. It will download to your ‎computer as a zipped folder.‎

Download Project Bundle

Copy Code
# SPDX-FileCopyrightText: 2023 Liz Clark for Adafruit Industries
# SPDX-License-Identifier: MIT

import os
import ssl
import time
import microcontroller
import board
import wifi
import socketpool
import adafruit_requests
import neopixel
import simpleio
from adafruit_ticks import ticks_ms, ticks_add, ticks_diff
from adafruit_io.adafruit_io import IO_HTTP

# latitude
lat = 42.36
# longitude
long = -71.06

# neopixel setup
NUMPIXELS = 30 # number of neopixels
BRIGHTNESS = 0.5 # A number between 0.0 and 1.0, where 0.0 is off, and 1.0 is max.
PIN = board.A3 # This is the default pin on the NeoPixel Driver BFF.

pixels = neopixel.NeoPixel(PIN, NUMPIXELS, brightness=BRIGHTNESS, auto_write=False)

# turn on NeoPixels on boot to check wiring
pixels.fill((255, 125, 0))
pixels.show()

# API request to open-meteo
weather_url = "https://api.open-meteo.com/v1/forecast?"
# pass latitude and longitude
# will return sunrise and sunset times
weather_url += "latitude=%d&longitude=%d&timezone=auto&daily=sunrise,sunset" % (lat, long)

# connect to SSID
wifi.radio.connect(os.getenv('CIRCUITPY_WIFI_SSID'), os.getenv('CIRCUITPY_WIFI_PASSWORD'))

pool = socketpool.SocketPool(wifi.radio)
requests = adafruit_requests.Session(pool, ssl.create_default_context())

pool = socketpool.SocketPool(wifi.radio)

# adafruit IO info
aio_username = os.getenv('aio_username')
aio_key = os.getenv('aio_key')
location = "America/New York"

# io HTTP for getting the time from the internet
io = IO_HTTP(aio_username, aio_key, requests)

def reset_on_error(delay, error):
print("Error:\n", str(error))
print("Resetting microcontroller in %d seconds" % delay)
time.sleep(delay)
microcontroller.reset()

# function for making http requests with try/except
def get_request(tries, ping):
for i in range(tries):
try:
n = ping
except Exception as error:
print(error)
time.sleep(10)
if i < tries - 1:
continue
raise
break
return n

# get the time on start-up
# pylint: disable=broad-except
try:
now = get_request(5, io.receive_time())
except Exception as e:
reset_on_error(10, e)
print(now)
today = now.tm_mday

# function to make a request to open-meteo
def sun_clock():
# make the API request
response = get_request(5, requests.get(weather_url))
# packs the response into a JSON
response_as_json = response.json()
# gets sunrise
_rise = response_as_json['daily']['sunrise'][0]
# gets sunset
_set = response_as_json['daily']['sunset'][0]
return _rise, _set

# initial API call
try:
sunrise, sunset = sun_clock()
except Exception as e:
reset_on_error(10, e)

print(sunrise)
print(sunset)

# the sunrise/sunset time is returned as a JSON aka a string
# this function chops up the string to get the hours and minutes as integers
def divide_time(z):
string_time = z.split("-")
clock_time = string_time[2].split("T")
int_time = clock_time[1].split(":")
event_time = time.struct_time(
(int(string_time[0]), int(string_time[1]), int(clock_time[0]), int(int_time[0]),
int(int_time[1]), 0, -1, -1, False)
)
# print(event_time)
return event_time

rise_time = divide_time(sunrise)
set_time = divide_time(sunset)

# function that tracks how many hours/minutes until sunrise or sunset
def sun_countdown(sun_event):
n = get_request(5, io.receive_time())
remaining = time.mktime(sun_event) - time.mktime(n)
r = remaining
# print(remaining)
# calculate the seconds remaining
secs_remaining = remaining % 60 # pylint: disable=unused-variable
remaining //= 60
# calculate the minutes remaining
minutes_until = remaining % 60
remaining //= 60
# calculate the hours remaining
hours_until = remaining % 24
remaining //= 24
return r, hours_until, minutes_until, n
try:
total_until_rise, hours_until_sunrise, mins_until_sunrise, now = sun_countdown(rise_time)
except Exception as e:
reset_on_error(10, e)
try:
total_until_set, hours_until_sunset, mins_until_sunset, now = sun_countdown(set_time)
except Exception as e:
reset_on_error(10, e)

# red and yellow color percentage for neopixels
percent_red = 0
percent_yellow = 0

print(total_until_set)
# check to see if the star fragment should be lit up on start-up
if total_until_set < 0:
print("star glow true")
star_glow = True
percent_red = 255
percent_yellow = 125
# turn neopixels on using RGB values
pixels.fill((percent_red, percent_yellow, 0))
pixels.show()
else:
print("star glow false")
star_glow = False
percent_red = 0
percent_yellow = 0
# turn neopixels on using RGB values
pixels.fill((percent_red, percent_yellow, 0))
pixels.show()

# ticks time tracker
clock = ticks_ms()

# tracker for initial start-up state
first_run = True

# 15 minutes in milliseconds
time_check = 900000
# state to tell if it's after midnight yet before sunrise
looking_for_sunrise = False

while True:
try:
# if it's daytime
if not star_glow:
# every 15 minutes...
if first_run or ticks_diff(ticks_ms(), clock) > time_check:
print("pinging Open-Meteo")
sunrise, sunset = sun_clock()
(total_until_set, hours_until_sunset,
mins_until_sunset, now) = sun_countdown(set_time)
print(now)
print("%d hour(s) until sunset" % hours_until_sunset)
print("%d minutes(s) until sunset" % mins_until_sunset)
print(sunset)
print(percent_red)
print()
# less than an hour until sunset...
if hours_until_sunset in (0, 23):
# check every minute
time_check = 300000
# map color to ramp up in brightness over the course of the final hour
percent_red = simpleio.map_range(mins_until_sunset, 59, 0, 0, 255)
percent_yellow = simpleio.map_range(mins_until_sunset, 59, 0, 0, 125)
# if the sun has set..
if total_until_set < 0:
percent_red = 255
percent_yellow = 125
time_check = 900000
star_glow = True
print("star is glowing")
# otherwise just keep checking every 15 minutes
else:
time_check = 900000
percent_red = 0
percent_yellow = 0
if first_run:
first_run = False
else:
# reset clock
clock = ticks_add(clock, time_check)
# if it's nighttime...
else:
if first_run or ticks_diff(ticks_ms(), clock) > time_check:
if today != now.tm_mday or (first_run and now.tm_hour < rise_time.tm_hour):
today = now.tm_mday
looking_for_sunrise = True
# begin tracking the incoming sunrise
if looking_for_sunrise:
print("pinging Open-Meteo")
sunrise, sunset = sun_clock()
(total_until_rise, hours_until_sunrise,
mins_until_sunrise, now) = sun_countdown(rise_time)
print(now)
print("%d hour(s) until sunrise" % hours_until_sunrise)
print("%d minutes(s) until sunrise" % mins_until_sunrise)
print(sunrise)
print(now)
print()
# less than an hour until sunset...
if hours_until_sunrise in (0, 23):
# check every minute
time_check = 300000
# map color to decrease brightness over the course of the final hour
percent_red = simpleio.map_range(mins_until_sunrise, 59, 0, 255, 0)
percent_yellow = simpleio.map_range(mins_until_sunrise, 59, 0, 125, 0)
# if the sun has risen..
if total_until_rise < 0:
percent_red = 0
percent_yellow = 0
time_check = 900000
star_glow = False
looking_for_sunrise = False
print("star is off")
# otherwise just keep checking every 15 minutes
# and keep neopixels on
else:
time_check = 900000
percent_red = 255
percent_yellow = 125
# otherwise just keep checking every 15 minutes
# and keep neopixels on
else:
now = get_request(5, io.receive_time())
print("not looking for sunrise")
print(now)
print()
time_check = 900000
percent_red = 255
percent_yellow = 125
if first_run:
first_run = False
else:
# reset clock
clock = ticks_add(clock, time_check)
# turn neopixels on using RGB values
pixels.fill((percent_red, percent_yellow, 0))
pixels.show()
except Exception as e:
reset_on_error(10, e)

View on GitHub

Upload the Code and Libraries to the QT Py ESP32-S2‎

After downloading the Project Bundle, plug your QT Py ESP32-S2 into the computer's USB port with a ‎known good USB data+power cable. You should see a new flash drive appear in the computer's File ‎Explorer or Finder (depending on your operating system) called CIRCUITPY. Unzip the folder and copy ‎the following items to the QT Py ESP32-S2's CIRCUITPY drive. ‎

  • lib folder
  • code.py

Your QT Py ESP32-S2 CIRCUITPY drive should look like this after copying the lib folder and the code.py ‎file.‎

circuitpy_12

Add Your settings.toml File

As of CircuitPython 8, there is support for Environment Variables. These Environmental Variables are ‎stored in a settings.toml file. Similar to secrets.py, the settings.toml file separates your sensitive ‎information from your main code.py file. Add your settings.toml file as described in the Create Your ‎settings.toml File page earlier in this guide. You'll need to include your CIRCUITPY_WIFI_SSID, ‎CIRCUITPY_WIFI_PASSWORD, aio_username, and aio_key in the file.‎

Download File

Copy Code
CIRCUITPY_WIFI_SSID = "your-wifi-ssid-here"
CIRCUITPY_WIFI_PASSWORD = "your-wifi-password-here"

aio_username = "your-Adafruit-IO-username-here"
aio_key = "your-Adafruit-IO-key-here"

How the CircuitPython Code Works

The Open-Meteo weather API uses latitude and longitude to determine your location when creating ‎an API request. At the top of the code, you can add your latitude and longitude coordinates by editing ‎the lat and long variables.‎

Download File

Copy Code
# latitude
lat = 42.36
# longitude
long = -71.06

weather_url is a string that holds the Open-Meteo API request. It passes the latitude and longitude ‎variables and requests the sunrise and sunset times.‎

Download File

Copy Code
# API request to open-meteo
weather_url = "https://api.open-meteo.com/v1/forecast?"
# pass latitude and longitude
# will return sunrise and sunset times
weather_url += "latitude=%d&longitude=%d&timezone=auto&daily=sunrise,sunset" % (lat, long)

Internet Connect!‎

The QT Py ESP32-S2 connects to your network by passing your SSID name and SSID password ‎information from the settings.toml file. io is instantiated as an Adafruit IO HTTP object by passing your ‎IO username and password from the settings.toml file as well. Adafruit IO is used to get the current ‎time.‎

Download File

Copy Code
#  connect to SSID
wifi.radio.connect(os.getenv('CIRCUITPY_WIFI_SSID'), os.getenv('CIRCUITPY_WIFI_PASSWORD'))

pool = socketpool.SocketPool(wifi.radio)
requests = adafruit_requests.Session(pool, ssl.create_default_context())

pool = socketpool.SocketPool(wifi.radio)

# adafruit IO info
aio_username = os.getenv('aio_username')
aio_key = os.getenv('aio_key')
location = "America/New York"

# io HTTP for getting the time from the internet
io = IO_HTTP(aio_username, aio_key, requests)

Error Checking

The reset_on_error() function takes an Exception error and resets the QT Py depending on the delay ‎time. ‎

The get_request() function uses a try/except loop to attempt an HTTP request. In the event of an ‎error, the request will be attempted again after a delay. Once the number of tries has been exceeded, ‎then the loop breaks.‎

Download File

Copy Code
def reset_on_error(delay, error):
print("Error:\n", str(error))
print("Resetting microcontroller in %d seconds" % delay)
time.sleep(delay)
microcontroller.reset()

# function for making http requests with try/except
def get_request(tries, ping):
for i in range(tries):
try:
n = ping
except Exception as error:
print(error)
time.sleep(10)
if i < tries - 1:
continue
raise
break
return n

These two functions are utilized together in a try/except loop for each HTTP request. If the try/except ‎loop exceeds the tries in get_request(), then reset_on_error() is called and the QT Py resets itself.‎

Download File

Copy Code
try:
now = get_request(5, io.receive_time())
except Exception as e:
reset_on_error(10, e)

When and Where Is The Sun?‎

The sun_clock() function makes the Open-Meteo API request and returns that day's sunrise and ‎sunset timestamp.‎

Download File

Copy Code
# function to make a request to open-meteo
def sun_clock():
# make the API request
response = get_request(5, requests.get(weather_url))
# packs the response into a JSON
response_as_json = response.json()
# gets sunrise
_rise = response_as_json['daily']['sunrise'][0]
# gets sunset
_set = response_as_json['daily']['sunset'][0]
return _rise, _set

However, the sunrise and sunset timestamps are returned as strings, which isn't very helpful for doing ‎math. The divide_time() function chops up the string and returns a struct_time object.‎

Download File

Copy Code
def divide_time(z):
string_time = z.split("-")
clock_time = string_time[2].split("T")
int_time = clock_time[1].split(":")
event_time = time.struct_time(
(int(string_time[0]), int(string_time[1]), int(clock_time[0]), int(int_time[0]),
int(int_time[1]), 0, -1, -1, False)
)
# print(event_time)
return event_time

Then, the sun_countdown() function calculates the time remaining until either sunrise or sunset.‎

Download File

Copy Code
# function that tracks how many hours/minutes until sunrise or sunset
def sun_countdown(sun_event):
n = get_request(5, io.receive_time())
remaining = time.mktime(sun_event) - time.mktime(n)
r = remaining
# print(remaining)
# calculate the seconds remaining
secs_remaining = remaining % 60 # pylint: disable=unused-variable
remaining //= 60
# calculate the minutes remaining
minutes_until = remaining % 60
remaining //= 60
# calculate the hours remaining
hours_until = remaining % 24
remaining //= 24
return r, hours_until, minutes_until, n

Before the loop, it's determined if the sun has already set. This sets the state for star_glow, which is ‎used in the loop to turn the NeoPixels on or off.‎

Download File

Copy Code
# check to see if the star fragment should be lit up on start-up
if total_until_set < 0:
print("star glow true")
star_glow = True
percent_red = 255
percent_yellow = 125
# turn neopixels on using RGB values
pixels.fill((percent_red, percent_yellow, 0))
pixels.show()
else:
print("star glow false")
star_glow = False
percent_red = 0
percent_yellow = 0
# turn neopixels on using RGB values
pixels.fill((percent_red, percent_yellow, 0))
pixels.show()

The Loop

In the loop, ticks_ms() is used to track time. Every 15 minutes, requests are sent to Open-Meteo and ‎Adafruit IO to retrieve the sunrise and sunset times and the current time. Depending on whether or ‎not star_glow is True determines if sunrise or sunset is tracked.‎

Download File

Copy Code
while True:
try:
# if it's daytime
if not star_glow:
# every 15 minutes...
if first_run or ticks_diff(ticks_ms(), clock) > time_check:
print("pinging Open-Meteo")
sunrise, sunset = sun_clock()
(total_until_set, hours_until_sunset,
mins_until_sunset, now) = sun_countdown(set_time)
print(now)
print("%d hour(s) until sunset" % hours_until_sunset)
print("%d minutes(s) until sunset" % mins_until_sunset)
print(sunset)
print(percent_red)
print()

Mapping Color to Time

When there is less than an hour until sunrise or sunset, Open-Meteo and Adafruit IO begin to be ‎pinged every 5 minutes. The NeoPixels begin to either dim or brighten during that hour countdown. ‎Their red and green values are mapped to the minutes remaining.‎

Once the sun event has been reached, the NeoPixels are set to fully yellow or fully off, the ‎time_check is reset to 15 minutes, and the state of star_glow is updated.‎

Download File

Copy Code
# less than an hour until sunset...
if hours_until_sunset in (0, 23):
# check every minute
time_check = 300000
# map color to ramp up in brightness over the course of the final hour
percent_red = simpleio.map_range(mins_until_sunset, 59, 0, 0, 255)
percent_yellow = simpleio.map_range(mins_until_sunset, 59, 0, 0, 125)
# if the sun has set..
if total_until_set < 0:
percent_red = 255
percent_yellow = 125
time_check = 900000
star_glow = True
print("star is glowing")

What Day Is It?‎

There is some additional logic in place to prevent errors while the star is glowing. Once the day changes ‎‎(aka once it's past midnight), the looking_for_sunrise state is set to True to begin checking Open-‎Meteo for the sunrise and sunset times for the new day.‎

Download File

Copy Code
if first_run or ticks_diff(ticks_ms(), clock) > time_check:
if today != now.tm_mday or (first_run and now.tm_hour < rise_time.tm_hour):
today = now.tm_mday
looking_for_sunrise = True
# begin tracking the incoming sunrise
if looking_for_sunrise:
print("pinging Open-Meteo")
sunrise, sunset = sun_clock()
(total_until_rise, hours_until_sunrise,
mins_until_sunrise, now) = sun_countdown(rise_time)

While the code waits for the new day, the time is checked every 15 minutes and the NeoPixels are ‎fully yellow.‎

Download File

Copy Code
# otherwise just keep checking every 15 minutes
# and keep neopixels on
else:
now = get_request(5, io.receive_time())
print("not looking for sunrise")
print(now)
print()
time_check = 900000
percent_red = 255
percent_yellow = 125

Circuit Diagram

The diagram below provides a general visual reference for wiring of the components once you get to ‎the Assembly page. This diagram was created using the software package Fritzing.‎

Adafruit Library for Fritzing

Use Adafruit's Fritzing parts library to create circuit diagrams for your projects. Download the library or ‎just grab individual parts. Get the library and parts from GitHub - Adafruit Fritzing Parts.‎

You'll forgo socket headers in the final assembly and connect the QT Py and BFF directly with header ‎pins.‎

diagram_13

Wired Connections

The QT Py is powered by a 5V 1A power supply wall adapter. ‎

NeoPixel Stick to NeoPixel Stick

  • DATA OUT to DATA In
  • GND to GND pin
  • ‎5V to 5V pin‎

NeoPixel Stick to BFF NeoPixel Drive

The power, data, and ground connections from the first NeoPixel stick connect directly to the 3-pin JST ‎port on the NeoPixel BFF board.‎

CAD Files

CAD Assembly

The two NeoPixel sticks are secured to the NeoPixel Stick Mount with hardware screws and nuts. The ‎NeoPixel mount snap fits into the bottom side of the star core. Eleven spikes snap fit into the star core. ‎The star bottom piece snap fits into the NeoPixel stick mount. The star fragment assembly rests onto ‎the stand. The stand is secured to the star case top cover with machine screws. The star case top and ‎bottom covers snap fit onto the star case frame. ‎

star_14

CAD Parts List

STL files for 3D printing are oriented to print "as-is" on FDM style machines. Parts are designed to 3D ‎print without any support material using PLA filament. Original design source may be downloaded ‎using the links below.‎

  • NeoPixel Stick Mount.stl
  • simple-case-bottom.stl
  • simple-case-frame.stl
  • simple-case-top.stl
  • Star-Bottom.stl
  • Star-Case-Bottom.stl
  • Star-Case-Frame.stl
  • Star-Case-Top.stl
  • Star-Core.stl
  • Star-Spike.stl
  • Star-Stand.stl

case_15

slide_16

Download STLs.zip

Download CAD Source

Transparent Filament

For best illumination, we suggest printing the Star-Core and NeoPixel Stick Mount parts in translucent / ‎transparent PLA filament.‎

The star spikes were 3D printed in a yellow-colored PLA filament.‎

bedsize_17

Build Volume

The parts require a 3D printer with a minimum build volume.‎

  • ‎138mm (X) x 132mm (Y) x 42mm (Z)‎

brim_18

Bed Adhesion

Applying brim to the star spikes can help improve bed adhesion. A minimum of 6 perimeters is ‎suggested to keep the part from coming off the bed of the 3D printer.‎

bed_19

Headers Assembly

Headers for QT Py and BFF

Trim the included strip of headers to create two 1x7 headers.‎

headers_20

Install Headers to QT Py

Insert the two strips of header pins to the bottom of the QT Py PCB.‎

install_21

Install NeoPixel Driver BFF

Place the NeoPixel Driver BFF board under the QT Py to get a sense of the correct orientation. ‎

install_22

Soldering Headers

Use a breadboard to help keep the two strips of header pins straight when soldering them to the QT ‎Py.‎

headers_23

Solder NeoPixel Driver BFF to QT Py

Carefully solder the header pins from the QT Py to the NeoPixel Driver BFF.‎

soldered_24

NeoPixel Wiring

Short Cable for NeoPixels

The two NeoPixel sticks will be daisy chained using a short 3-wire ribbon cable.‎

Peel three wires from the 10-wire ribbon cable to create a short 3-pin cable for connecting the two ‎NeoPixel sticks together.‎

short_25

JST Cable for NeoPixels

Separate three wires from the 10-wire ribbon cable to create a long cable extension for the 3-pin JST ‎cable. ‎

Cut the 3-wire ribbon cable to the desired length. Make sure it's a has a minimum length of 6in (15cm). ‎

A 3-pin JST cable will connect the NeoPixel sticks to the NeoPixel Driver BFF board.‎

cables_26

Solder Short Cable

Connect the short 3-wire ribbon cable to one of the NeoPixel sticks by soldering to the GND, 5VDC and ‎DOUT pads.‎

solder_27

Solder Short Cable (Continued)‎

Connect the other end of the short ribbon cable to the second NeoPixel stick by soldering to the GND, ‎‎5VDC and DIN pads.‎

The connections between the two boards are:‎

  • GND to GND
  • 5VDC to 5VDC‎D
  • OUT to DIN

solder_28

Soldered NeoPixel Sticks

Double check the short cable has been soldered to the correct pads.‎

soldered_29

Solder 3-pin JST Extension

Connect the long ribbon cable to the 3-pin JST cable by soldering the individual wires to the red, black ‎and white colored wires. ‎

Use pieces of heat shrink tubing to insulate the exposed wire.‎

soldering_30

‎3-pin JST Cable

Double check the soldered wires have a good and strong connection.‎

ext_31

Connect JST Cable to NeoPixels

Solder the three wires from the JST cable/extension to the NeoPixel stick with the DIN pad. Connect ‎red wire extension to 5VDC, black wire extension to GND and white wire extension to the DIN pad.‎

solder_32

Wired NeoPixel Sticks

Take a moment to double check the wired connections have been properly soldered.‎

soldered_33

Assembly

Hardware for NeoPixel Mount

You'll use the following hardware to secure the NeoPixel sticks to the 3D printed mount.‎

  • ‎2x M2 x 12mm long screws
  • ‎‎2x M2 hex nuts
  • ‎‎4x M2 washers‎

screws_34

Install Cable to NeoPixel Mount

Insert the 3-pin JST through the center opening of the 3D printed mount and pull it all the way through.‎

install_35

Mounting NeoPixel

Place both NeoPixel sticks onto the 3D printed mount and line up the mounting holes.‎

installing_36

Insert the M2 screws through the NeoPixel sticks and 3D printed mount with the washers fitted in ‎between. The washer will allow airflow to help with heat dissipation to prevent the 3D print from ‎warping. ‎

setup_37

Secure NeoPixels

Fasten M2 hex nuts onto the threads of the screws and use a screwdriver to secure them together.‎

secure_38

Assembled NeoPixel Holder

Take a moment to inspect the NeoPixel holder assembly and ensure it has been properly secured.‎

secured_39

Install NeoPixel Holder

Locate the bottom side of the 3D printed star core. It's the surface that touched the 3D printer's bed.‎

Insert the NeoPixel holder through the bottom side of the star core and firmly press it through the ‎edges.‎

install_40

Install Bottom Spike

Insert the JST cable through the hole on the side of the 3D printed bottom spike and pull it all the way ‎through.‎

Orient the bottom spike so the protruding lips are lined up with the NeoPixel holder.‎

Firmly press the bottom spike into the star core until the surfaces sit flush.‎

install_41

Install Top Spike

Insert one of the eleven spikes to the top side of the star core. Firmly press the spike into the star core ‎until the surfaces are flush with each other.‎

top_42

Install Another Spike

Proceed to install one of remaining spikes into another side of the star core ensuring the surfaces are ‎flush.‎

spike-2_43

Install Spikes (Continued)

Continue to install spikes by press fitting them into the remaining sides of the star core.‎

spike-5_44

 

Installed Spikes

Double check all of the spikes have been properly installed onto the star core.‎

installed_45

Connect JST to BFF

Grab the 3-pin JST plug from the NeoPixels and connect it to the NeoPixel Driver BFF.‎

connect_46

 

Connect USB to QT Py

Plug in a USB-C cable to the USB port on QT Py board.‎

connect_47

Power Test

Plug the other end of the USB cable into a 5V 1A (minimum, higher current rating is fine to use) USB ‎power supply to turn on the circuit. ‎

star-power_48

Simple Case Setup

Use the simple snap fit case if you prefer to use the star fragment off the stand.‎

Insert the JST plug from the NeoPixel through the smaller hole on the side of the case.‎

Insert the USB cable through the bigger hole on the side of the case.‎

case_49

case-set_50

 

Lamp Stand Setup

Insert the USB-C extension cable through the hole on the side of the lamp stand base. Use the ‎included hex nut to secure the cable.‎

lamp_51

lamp_52

Lamp Base Bottom Install

Line up the snap fit edges of the bottom cover with the lamp base. Firmly press bottom cover to snap ‎fit them together. ‎

bottom_53

bottom_54

Hardware for Lamp Stand

Use three M3 x 6mm long machine screws to secure the stand to the lamp base top cover.‎

stand_55

Secure Lamp Stand to Top Cover

Place the lamp stand over the base top cover and line up the three mounting holes.‎

Insert and fasten the M3 screws through the bottom of the top cover to secure the stand to the top ‎cover.‎

secure_56

Install NeoPixel Cable to Lamp Stand

Insert the NeoPixel cable through the lamp stand and pull it through the other end.‎

install_57

Connect NeoPixel and QT Py

Proceed to connect the 3-pin JST cable to the NeoPixel Driver BFF and the USB-C extension cable to ‎the QT Py.‎

connect_58

Install PCBs into Lamp Base

Place the QT Py and NeoPixel Driver BFF into the lamp case with the extension cable neatly coiled.‎

fit_59

Install Top Cover to Lamp Base

Line up the snap fit edges of the top cover with the lamp base and firmly press to snap fit them ‎together.‎

snap_60

Install Star Fragment to Lamp Stand

Pull on the cable from the NeoPixels through the openings on the bottom cover until the star ‎fragment can rest on the lamp stand.‎

Orient the star fragment so it can rest over the tapered edges of the lamp stand.‎

Optionally use glue to permanently adhere the star fragment to the lamp stand.‎

test_61

制造商零件编号 5325
STEMMA QT QT PY ESP32-S2 WIFI
Adafruit Industries LLC
制造商零件编号 5645
ADAFRUIT NEOPIXEL DRIVER BFF ADD
Adafruit Industries LLC
制造商零件编号 4336
JST PH 3-PIN PLUG-PLUG CABLE
Adafruit Industries LLC
制造商零件编号 3890
CBL RIBN 10COND FLAT BLACK 3.28'
Adafruit Industries LLC
制造商零件编号 1426
ADDRESS LED MODULE SERIAL RGB
Adafruit Industries LLC
Add all DigiKey Parts to Cart
TechForum

Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.

Visit TechForum