Last active
July 10, 2023 22:16
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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