adding moon phase clock & bluetooth speaker

CircuitPython code for moon phase clock and arduino code for bluetooth lumon speaker
This commit is contained in:
Liz 2025-03-11 15:53:43 -04:00
parent a216a61a40
commit 1e54149f1a
3 changed files with 202 additions and 0 deletions

View file

View file

@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: 2020 Phil Schatzmann
//
// SPDX-License-Identifier: GNU General Public License
/*
Streaming Music from Bluetooth
Copyright (C) 2020 Phil Schatzmann
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// ==> Example which shows how to use the built in ESP32 I2S >= 3.0.0
#include "ESP_I2S.h"
#include "BluetoothA2DPSink.h"
const uint8_t I2S_SCK = 8; /* Audio data bit clock */
const uint8_t I2S_WS = 7; /* Audio data left and right clock */
const uint8_t I2S_SDOUT = 14; /* ESP32 audio data output (to speakers) */
I2SClass i2s;
BluetoothA2DPSink a2dp_sink(i2s);
void setup() {
i2s.setPins(I2S_SCK, I2S_WS, I2S_SDOUT);
if (!i2s.begin(I2S_MODE_STD, 44100, I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO, I2S_STD_SLOT_BOTH)) {
Serial.println("Failed to initialize I2S!");
while (1); // do nothing
}
a2dp_sink.start("Lumon Industries Speaker");
}
void loop() {
}

View file

@ -0,0 +1,158 @@
# SPDX-FileCopyrightText: 2025 Liz Clark for Adafruit Industries
# SPDX-License-Identifier: MIT
import os
import time
import ssl
import board
import wifi
import socketpool
import microcontroller
import neopixel
import adafruit_requests
from adafruit_ticks import ticks_ms, ticks_add, ticks_diff
# FarmSense API for moon phase
# https://www.farmsense.net/api/astro-widgets/
url = "https://api.farmsense.net/v1/moonphases/?d="
# Adafruit IO time server for UNIX time, no API key needed
time_url = "https://io.adafruit.com/api/v2/time/seconds"
# connect to wifi
try:
wifi.radio.connect(os.getenv('CIRCUITPY_WIFI_SSID'), os.getenv('CIRCUITPY_WIFI_PASSWORD'))
except TypeError:
print("Could not find WiFi info. Check your settings.toml file!")
raise
pool = socketpool.SocketPool(wifi.radio)
requests = adafruit_requests.Session(pool, ssl.create_default_context())
# neopixels, 49 total
OFF = (0, 0, 0)
ON = (255, 255, 255)
pixel_pin = board.A3
num_pixels = 49
pixels = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.1, auto_write=False)
pixels.fill(0)
# phases of the moon
NEW_MOON = 0
WAXING_CRESCENT = 1
FIRST_QUARTER = 2
WAXING_GIBBOUS = 3
FULL_MOON = 4
WANING_GIBBOUS = 5
THIRD_QUARTER = 6
WANING_CRESCENT = 7
# strings that match return from API
phase_names = ["New Moon", "Waxing Crescent", "First Quarter", "Waxing Gibbous",
"Full Moon", "Waning Gibbous", "Third Quarter", "Waning Crescent"]
# functions for each moon phase to light up based on neopixel orientation
def set_new_moon():
pixels.fill(OFF)
pixels.show()
def set_waxing_crescent():
pixels.fill(OFF)
for i in range(31, 44):
pixels[i] = ON
pixels.show()
def set_first_quarter():
pixels.fill(OFF)
for i in range(24, 49):
pixels[i] = ON
pixels.show()
def set_waxing_gibbous():
pixels.fill(OFF)
for i in range(0, 4):
pixels[i] = ON
for i in range(18, 49):
pixels[i] = ON
pixels.show()
def set_full_moon():
pixels.fill(ON)
pixels.show()
def set_waning_gibbous():
pixels.fill(OFF)
for i in range(0, 30):
pixels[i] = ON
for i in range(44, 49):
pixels[i] = ON
pixels.show()
def set_third_quarter():
pixels.fill(OFF)
for i in range(0, 24):
pixels[i] = ON
pixels.show()
def set_waning_crescent():
pixels.fill(OFF)
for i in range(5, 18):
pixels[i] = ON
pixels.show()
# match functions with phases
phase_functions = {
NEW_MOON: set_new_moon,
WAXING_CRESCENT: set_waxing_crescent,
FIRST_QUARTER: set_first_quarter,
WAXING_GIBBOUS: set_waxing_gibbous,
FULL_MOON: set_full_moon,
WANING_GIBBOUS: set_waning_gibbous,
THIRD_QUARTER: set_third_quarter,
WANING_CRESCENT: set_waning_crescent
}
# test function, runs through all 8 in order
def demo_all_phases(delay=1):
for phase in range(8):
print(f"Setting phase: {phase_names[phase]}")
phase_functions[phase]()
time.sleep(delay)
demo_all_phases()
# takes response from API, matches to function, runs function
def set_moon_phase(phase):
phase_lower = phase.lower()
for i, name in enumerate(phase_names):
if phase_lower == name.lower():
phase_functions[i]()
print(f"Moon phase set to: {name}")
# time keeping, fetches API every 6 hours
timer_clock = ticks_ms()
timer = (6 * 3600) * 1000
first_run = True
while True:
try:
if first_run or ticks_diff(ticks_ms(), timer_clock) >= timer:
# get unix time
unix_time = requests.get(time_url)
# update farmsense request with UNIX time
url = f"https://api.farmsense.net/v1/moonphases/?d={unix_time.text}"
# get the JSON response
response = requests.get(url)
json_response = response.json()
# isolate phase info
print("-" * 40)
print(json_response[0]['Phase'])
print("-" * 40)
# run function to update neopixels with current phase
set_moon_phase(json_response[0]['Phase'])
response.close()
time.sleep(1)
first_run = False
# reset clock
timer_clock = ticks_add(timer_clock, timer)
# pylint: disable=broad-except
except Exception as e:
print("Error:\n", str(e))
print("Resetting microcontroller in 10 seconds")
time.sleep(10)
microcontroller.reset()