Skip to content

Commit f704199

Browse files
authored
Merge pull request #2990 from adafruit/moon_phase
adding moon phase clock & bluetooth speaker
2 parents a216a61 + a326077 commit f704199

File tree

3 files changed

+202
-0
lines changed

3 files changed

+202
-0
lines changed

Bluetooth_Lumon_Speaker/.esp32.test.only

Whitespace-only changes.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// SPDX-FileCopyrightText: 2020 Phil Schatzmann
2+
//
3+
// SPDX-License-Identifier: GPL-3.0-or-later
4+
5+
/*
6+
Streaming Music from Bluetooth
7+
8+
Copyright (C) 2020 Phil Schatzmann
9+
This program is free software: you can redistribute it and/or modify
10+
it under the terms of the GNU General Public License as published by
11+
the Free Software Foundation, either version 3 of the License, or
12+
(at your option) any later version.
13+
This program is distributed in the hope that it will be useful,
14+
but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
GNU General Public License for more details.
17+
You should have received a copy of the GNU General Public License
18+
along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*/
20+
21+
// ==> Example which shows how to use the built in ESP32 I2S >= 3.0.0
22+
23+
#include "ESP_I2S.h"
24+
#include "BluetoothA2DPSink.h"
25+
26+
const uint8_t I2S_SCK = 8; /* Audio data bit clock */
27+
const uint8_t I2S_WS = 7; /* Audio data left and right clock */
28+
const uint8_t I2S_SDOUT = 14; /* ESP32 audio data output (to speakers) */
29+
I2SClass i2s;
30+
31+
BluetoothA2DPSink a2dp_sink(i2s);
32+
33+
void setup() {
34+
i2s.setPins(I2S_SCK, I2S_WS, I2S_SDOUT);
35+
if (!i2s.begin(I2S_MODE_STD, 44100, I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO, I2S_STD_SLOT_BOTH)) {
36+
Serial.println("Failed to initialize I2S!");
37+
while (1); // do nothing
38+
}
39+
40+
a2dp_sink.start("Lumon Industries Speaker");
41+
}
42+
43+
void loop() {
44+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# SPDX-FileCopyrightText: 2025 Liz Clark for Adafruit Industries
2+
# SPDX-License-Identifier: MIT
3+
4+
import os
5+
import time
6+
import ssl
7+
import board
8+
import wifi
9+
import socketpool
10+
import microcontroller
11+
import neopixel
12+
import adafruit_requests
13+
from adafruit_ticks import ticks_ms, ticks_add, ticks_diff
14+
15+
# FarmSense API for moon phase
16+
# https://www.farmsense.net/api/astro-widgets/
17+
url = "https://api.farmsense.net/v1/moonphases/?d="
18+
# Adafruit IO time server for UNIX time, no API key needed
19+
time_url = "https://io.adafruit.com/api/v2/time/seconds"
20+
# connect to wifi
21+
try:
22+
wifi.radio.connect(os.getenv('CIRCUITPY_WIFI_SSID'), os.getenv('CIRCUITPY_WIFI_PASSWORD'))
23+
except TypeError:
24+
print("Could not find WiFi info. Check your settings.toml file!")
25+
raise
26+
pool = socketpool.SocketPool(wifi.radio)
27+
requests = adafruit_requests.Session(pool, ssl.create_default_context())
28+
29+
# neopixels, 49 total
30+
OFF = (0, 0, 0)
31+
ON = (255, 255, 255)
32+
pixel_pin = board.A3
33+
num_pixels = 49
34+
pixels = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.1, auto_write=False)
35+
pixels.fill(0)
36+
37+
# phases of the moon
38+
NEW_MOON = 0
39+
WAXING_CRESCENT = 1
40+
FIRST_QUARTER = 2
41+
WAXING_GIBBOUS = 3
42+
FULL_MOON = 4
43+
WANING_GIBBOUS = 5
44+
THIRD_QUARTER = 6
45+
WANING_CRESCENT = 7
46+
# strings that match return from API
47+
phase_names = ["New Moon", "Waxing Crescent", "First Quarter", "Waxing Gibbous",
48+
"Full Moon", "Waning Gibbous", "Third Quarter", "Waning Crescent"]
49+
50+
# functions for each moon phase to light up based on neopixel orientation
51+
def set_new_moon():
52+
pixels.fill(OFF)
53+
pixels.show()
54+
55+
def set_waxing_crescent():
56+
pixels.fill(OFF)
57+
for i in range(31, 44):
58+
pixels[i] = ON
59+
pixels.show()
60+
61+
def set_first_quarter():
62+
pixels.fill(OFF)
63+
for i in range(24, 49):
64+
pixels[i] = ON
65+
pixels.show()
66+
67+
def set_waxing_gibbous():
68+
pixels.fill(OFF)
69+
for i in range(0, 4):
70+
pixels[i] = ON
71+
for i in range(18, 49):
72+
pixels[i] = ON
73+
pixels.show()
74+
75+
def set_full_moon():
76+
pixels.fill(ON)
77+
pixels.show()
78+
79+
def set_waning_gibbous():
80+
pixels.fill(OFF)
81+
for i in range(0, 30):
82+
pixels[i] = ON
83+
for i in range(44, 49):
84+
pixels[i] = ON
85+
pixels.show()
86+
87+
def set_third_quarter():
88+
pixels.fill(OFF)
89+
for i in range(0, 24):
90+
pixels[i] = ON
91+
pixels.show()
92+
93+
def set_waning_crescent():
94+
pixels.fill(OFF)
95+
for i in range(5, 18):
96+
pixels[i] = ON
97+
pixels.show()
98+
99+
# match functions with phases
100+
phase_functions = {
101+
NEW_MOON: set_new_moon,
102+
WAXING_CRESCENT: set_waxing_crescent,
103+
FIRST_QUARTER: set_first_quarter,
104+
WAXING_GIBBOUS: set_waxing_gibbous,
105+
FULL_MOON: set_full_moon,
106+
WANING_GIBBOUS: set_waning_gibbous,
107+
THIRD_QUARTER: set_third_quarter,
108+
WANING_CRESCENT: set_waning_crescent
109+
}
110+
111+
# test function, runs through all 8 in order
112+
def demo_all_phases(delay=1):
113+
for phase in range(8):
114+
print(f"Setting phase: {phase_names[phase]}")
115+
phase_functions[phase]()
116+
time.sleep(delay)
117+
demo_all_phases()
118+
119+
# takes response from API, matches to function, runs function
120+
def set_moon_phase(phase):
121+
phase_lower = phase.lower()
122+
for i, name in enumerate(phase_names):
123+
if phase_lower == name.lower():
124+
phase_functions[i]()
125+
print(f"Moon phase set to: {name}")
126+
127+
# time keeping, fetches API every 6 hours
128+
timer_clock = ticks_ms()
129+
timer = (6 * 3600) * 1000
130+
first_run = True
131+
132+
while True:
133+
try:
134+
if first_run or ticks_diff(ticks_ms(), timer_clock) >= timer:
135+
# get unix time
136+
unix_time = requests.get(time_url)
137+
# update farmsense request with UNIX time
138+
url = f"https://api.farmsense.net/v1/moonphases/?d={unix_time.text}"
139+
# get the JSON response
140+
response = requests.get(url)
141+
json_response = response.json()
142+
# isolate phase info
143+
print("-" * 40)
144+
print(json_response[0]['Phase'])
145+
print("-" * 40)
146+
# run function to update neopixels with current phase
147+
set_moon_phase(json_response[0]['Phase'])
148+
response.close()
149+
time.sleep(1)
150+
first_run = False
151+
# reset clock
152+
timer_clock = ticks_add(timer_clock, timer)
153+
# pylint: disable=broad-except
154+
except Exception as e:
155+
print("Error:\n", str(e))
156+
print("Resetting microcontroller in 10 seconds")
157+
time.sleep(10)
158+
microcontroller.reset()

0 commit comments

Comments
 (0)