electro/townhouse_lights/townhouse_lights.ino
2026-08-24 14:09:36 -04:00

88 lines
3.6 KiB
C++

// Townhouse dollhouse lighting, Arduino Uno R3, discrete zone outputs (no addressable data line).
// Each zone = one PWM pin -> 1k -> PN2222 base; transistor sinks that zone's LEDs to ground.
// Each LED has its own ~200-220 ohm series resistor: +5V -> LED -> R -> transistor collector.
// FIRE zone uses a RED LED; all other zones warm-white/white. (Color is hardware only, no code change.)
// No library needed.
#define PIN_GF 3 // ground-floor interior (living + kitchen ceilings) -> Q1 base via 1k
#define PIN_UP 5 // upstairs interior (bedrooms + attic) -> Q2 base via 1k
#define PIN_EXT 6 // exterior (porch, entry, deck, balcony) -> Q3 base via 1k
#define PIN_FIRE 9 // fireplace / hearth (red) -> Q4 base via 1k
#define BTN_PIN 2 // momentary button to GND: cycles scenes / wakes
#define BTN_SECT 4 // momentary button to GND: spotlight one zone at a time (GF->UP->EXT->FIRE->off)
#define IDLE_OFF_MS (20UL * 60UL * 1000UL) // cordless: fade dark after 20 min idle
// Per-zone brightness 0-255. Dollhouse rooms read best DIM, this is your knob
// (or raise the LED series resistors to knock brightness back in hardware).
uint8_t gGF, gUP, gEXT;
bool fireOn;
// ---- Scenes ---------------------------------------------------------------
void sceneEvening(){ gGF = 60; gUP = 40; gEXT = 140; fireOn = true; } // cozy, fire lit
void sceneAllOn() { gGF = 90; gUP = 90; gEXT = 120; fireOn = true; }
void sceneNight() { gGF = 0; gUP = 0; gEXT = 30; fireOn = true; } // porch + embers
void sceneOff() { gGF = 0; gUP = 0; gEXT = 0; fireOn = false; }
typedef void (*Scene)();
Scene scenes[] = {sceneEvening, sceneAllOn, sceneNight, sceneOff};
const uint8_t N_SCENES = sizeof(scenes) / sizeof(scenes[0]);
uint8_t sceneIdx = 0;
unsigned long lastActive = 0;
// ---- Section spotlight ----------------------------------------------------
// BTN_SECT lights exactly ONE zone at a time, then all off, then repeats.
uint8_t sectIdx = 0; // 0=GF 1=UP 2=EXT 3=FIRE 4=all off
const uint8_t N_SECT = 5;
void applySpotlight(uint8_t i){
gGF = (i == 0) ? 90 : 0;
gUP = (i == 1) ? 90 : 0;
gEXT = (i == 2) ? 120 : 0;
fireOn = (i == 3);
}
void setup() {
pinMode(PIN_GF, OUTPUT); pinMode(PIN_UP, OUTPUT);
pinMode(PIN_EXT, OUTPUT); pinMode(PIN_FIRE, OUTPUT);
pinMode(BTN_PIN, INPUT_PULLUP);
pinMode(BTN_SECT, INPUT_PULLUP);
// Power-on self-check: pulse each output in turn, so a dead zone shows itself immediately.
const uint8_t pins[] = {PIN_GF, PIN_UP, PIN_EXT, PIN_FIRE};
for (uint8_t i = 0; i < 4; i++) { analogWrite(pins[i], 120); delay(220); analogWrite(pins[i], 0); }
scenes[sceneIdx]();
lastActive = millis();
}
void loop() {
// Button: cycle scene + reset idle timer.
static bool prev = HIGH;
bool now = digitalRead(BTN_PIN);
if (prev == HIGH && now == LOW) {
sceneIdx = (sceneIdx + 1) % N_SCENES;
scenes[sceneIdx]();
lastActive = millis();
delay(30); // debounce
}
prev = now;
// Section button: spotlight one zone at a time (GF -> UP -> EXT -> FIRE -> off).
static bool prevS = HIGH;
bool nowS = digitalRead(BTN_SECT);
if (prevS == HIGH && nowS == LOW) {
sectIdx = (sectIdx + 1) % N_SECT;
applySpotlight(sectIdx);
lastActive = millis();
delay(30); // debounce
}
prevS = nowS;
// Cordless battery saver.
if (millis() - lastActive > IDLE_OFF_MS) sceneOff();
analogWrite(PIN_GF, gGF);
analogWrite(PIN_UP, gUP);
analogWrite(PIN_EXT, gEXT);
// Fireplace: amber-ish flicker around a low base when on.
analogWrite(PIN_FIRE, fireOn ? (55 + random(0, 130)) : 0);
delay(30); // ~33 Hz, smooth flicker, snappy button
}