Skip to content

Instantly share code, notes, and snippets.

@EvanBalster
Last active July 10, 2023 22:16
Show Gist options
  • Select an option

  • Save EvanBalster/4ea8642c582752d03ae654206851c580 to your computer and use it in GitHub Desktop.

Select an option

Save EvanBalster/4ea8642c582752d03ae654206851c580 to your computer and use it in GitHub Desktop.
Tea timer for circuit playground. RB cycles steeps & green/black, LB starts timer. When idle, lights auto-dim.
#include <Adafruit_CircuitPlayground.h>
struct Timer
{
long started;
long length;
Timer()
{
reset();
}
long remaining()
{
return length - (millis()-started);
}
bool done()
{
return started && remaining() <= 0;
}
void begin(long length)
{
started = millis();
this->length = length;
}
void reset()
{
started = 0;
length = 0;
}
};
Timer countdown;
Timer alarm;
long last_moved = 0;
enum TeaType
{
SENCHA = 0,
BLACK = 1,
MAX_TEA_TYPE = 2
};
TeaType teaType = SENCHA;
long resteeps = 0;
const long DT = 8;
struct Accel
{
float x, y, z;
long time;
void readSensor()
{
x = CircuitPlayground.motionX();
y = CircuitPlayground.motionY();
z = CircuitPlayground.motionZ();
time = millis();
}
};
Accel gravity;
// the setup function runs once when you press reset or power the board
void setup() {
// initialize digital pin 13 as an output.
//pinMode(13, OUTPUT);
// Buttons
//pinMode(A0, OUTPUT);
//pinMode(4, INPUT); pinMode(5, INPUT);
//pinMode(PA30, OUTPUT);
//digitalWrite(PA30, HIGH);
CircuitPlayground.begin();
teaType = MAX_TEA_TYPE;
resteeps = 3;
refreshPixels();
}
void setPixel(uint16_t n, uint16_t r, uint16_t g, uint16_t b, uint16_t w = 0)
{
float brightness = 1.f;
if (alarm.started)
{
float pulse = .8f + .8f * sin(TWO_PI * countdown.remaining()/500.f);
brightness = pulse;
}
else if (countdown.started)
{
float pulse = .5f + .5f * sin(TWO_PI * countdown.remaining()/5000.f);
pulse = pulse*pulse*pulse;
brightness = .7f + .3f * pulse;
}
else
{
brightness = 2.f - (millis() - last_moved) / 10000.f;
if (brightness > 1.f) brightness = 1.f;
if (brightness < 0.f) brightness = 0.f;
}
r = uint16_t(r*brightness);
g = uint16_t(g*brightness);
b = uint16_t(b*brightness);
w = uint16_t(w*brightness);
// Excess RGB overflow into white.
if (r > 255) {w += (r-253)/3; r = 255;}
if (g > 255) {w += (g-254)/2; g = 255;}
if (b > 255) {w += (g-252)/4; b = 255;}
if (w > 255) w = 255;
CircuitPlayground.setPixelColor(n, Adafruit_CPlay_NeoPixel::gamma32(Adafruit_CPlay_NeoPixel::Color(r, g, b, w)));
}
void setPixel(uint16_t n, uint32_t color)
{
setPixel(n,
((color>>16)&255),
((color>>8)&255),
(color&255),
(color>>24));
}
void refreshPixels()
{
switch (teaType)
{
case SENCHA:
setPixel(0, 0x007f5f);
setPixel(1, 0x2b9348);
setPixel(2, 0x55a630);
setPixel(3, 0xaacc00);
setPixel(4, 0x80b918);
break;
case BLACK:
setPixel(0, 0x03071e);
setPixel(1, 0x370617);
setPixel(2, 0xdc2f02);
setPixel(3, 0xf48c06);
setPixel(4, 0xe85d04);
break;
default:
setPixel(0, 0x7F007F);
setPixel(1, 0x550055);
setPixel(2, 0xBB00BB);
setPixel(3, 0x550055);
setPixel(4, 0x7F007F);
break;
}
if (countdown.started)
{
float progress = 1.0f - float(countdown.remaining()) / float(countdown.length);
for (long i = 0; i < 5; ++i)
{
float fill = (progress-.2f*float(i))/.2f;
if (fill < 0.f) fill = 0.f;
if (fill > 1.f) fill = 1.f;
setPixel(5+i, 192.f * fill, 127.f, 192.f - 192.f*fill);
}
}
else
{
for (long i = 0; i < 5; ++i)
{
setPixel(5+i, (resteeps > i) ? 0xfdd85d : 0x6798c0);
}
}
}
PinStatus b_right_prev = LOW, b_left_prev = LOW;
static const long ARP_PATTERN[8] = {440, 550, 660, 1600, 880, 1320, 1100, 880};
// the loop function runs over and over again forever
void loop() {
PinStatus b_left = digitalRead(CPLAY_LEFTBUTTON);
PinStatus b_right = digitalRead(CPLAY_RIGHTBUTTON);
bool p_left = (b_left && !b_left_prev);
bool p_right = (b_right && !b_right_prev);
if (!countdown.started)
{
if (teaType == MAX_TEA_TYPE)
{
// Just started loop
teaType = SENCHA;
resteeps = 0;
gravity.readSensor();
last_moved = millis();
}
if (p_right)
{
resteeps += 1;
if (resteeps > 5)
{
resteeps = 0;
teaType = TeaType((long(teaType)+1) % MAX_TEA_TYPE);
// Beep!
refreshPixels();
tone(A0, 440); delay(50);
tone(A0, 330); delay(50);
tone(A0, 220); delay(100);
}
else
{
// Beep!
refreshPixels();
tone(A0, 440); delay(50);
tone(A0, 550); delay(50);
}
}
refreshPixels();
if (p_left)
{
long baseTime = 60;
switch (teaType)
{
case SENCHA: baseTime = 60; break;
case BLACK: baseTime = 120; break;
}
long upscale = resteeps;
while (upscale > 1) {baseTime *= 2; upscale -= 2;}
if (upscale == 1) {baseTime = (17*baseTime) / 12;}
resteeps += 1;
countdown.begin(1000 * baseTime);
// Beep!
for (int i = 0; i < 10; ++i) setPixel(i, 0x7F00FF00);
tone(A0, 880); delay(50);
tone(A0, 1320); delay(50);
tone(A0, 1760); delay(100);
}
}
else
{
refreshPixels();
}
{
Accel accel;
accel.readSensor();
float dtf = (accel.time - gravity.time) / 1000.f;
float dx = accel.x-gravity.x, dy = accel.y-gravity.y, dz = accel.z-gravity.z;
if (sqrt(dx*dx+dy*dy+dz*dz)/dtf > 150.f)
{
last_moved = millis();
digitalWrite(CPLAY_REDLED, 1);
}
else
{
digitalWrite(CPLAY_REDLED, 0);
}
float a = pow(.95f, dtf);
gravity.x += a*dx;
gravity.y += a*dy;
gravity.z += a*dz;
gravity.time = accel.time;
}
if (countdown.done())
{
alarm.begin(5000);
countdown.reset();
}
if (alarm.started)
{
unsigned long arp = alarm.remaining() / 96;
tone(A0, ARP_PATTERN[7-(arp&7)]);
if (alarm.done()) alarm.reset();
}
else noTone(A0);
/*if (alarm.started)
{
digitalWrite(CPLAY_REDLED, alarm.remaining() % 250 < 125);
}
else if (countdown.started)
{
digitalWrite(CPLAY_REDLED, countdown.remaining() % 1000 < 100);
}*/
delay(DT);
b_right_prev = b_right;
b_left_prev = b_left;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment