#include #include #include #include #include #include // TODO: needs more OOP //#define DEBUG //#define TESTWEATHER #include "portal.h" #include "util.h" #include "weather.h" #define SNOWMANSSID "Snowman" #if defined(DEBUG) || defined(TESTWEATHER) #define UPDATEFREQ 30 // s while testing #else #define UPDATEFREQ 60*60 // s => hourly #endif #define LEDFREQ 0.25 // s #define FADESTEP PWMRANGE / (5 / LEDFREQ) #define BLINKTHRESHOLD 1 / LEDFREQ #define BLINKDARKMULTIPLIER 2 // 2 * longer dark than light /* * Layout: * GPIO0: red LED [bottom] * TX: blue builtin LED [bottom] * GPIO2: green LED [bottom] | NOT USED YET * RX: blue LED [top] */ #define GPIO0 0 #define TX 1 #define GPIO2 2 #define RX 3 #define LRED GPIO0 #define LGREEN GPIO2 #define LBLUET RX #define LBLUEB TX // builtin // when changing this, c&p to portal.cpp // TODO use enums instead of defines? #define OFF 0 #define ON 1 #define NONE -1 #define BLINK -2 #define FADEIN -3 #define FADEOUT -4 int states[4] = {OFF, OFF, OFF, OFF}; int values[4] = {0, 0, 0, 0}; int overrides[4] = {NONE, NONE, NONE, NONE}; Ticker updaterData; Ticker updaterLed; boolean needsUpdate = true; boolean needUpdateLeds = false; void setup() { #ifdef DEBUG Serial.begin(115200); #endif portalSetup(SNOWMANSSID); updaterData.attach(UPDATEFREQ, toggleUpdaterFlag); updaterLed.attach(LEDFREQ, toggleLedFlag); ArduinoOTA.setHostname(SNOWMANSSID); ArduinoOTA.begin(); #ifndef DEBUG pinMode(TX, OUTPUT); #endif pinMode(RX, OUTPUT); pinMode(GPIO0, OUTPUT); pinMode(GPIO2, OUTPUT); states[LGREEN] = BLINK; resetLeds(); } void toggleUpdaterFlag() { #ifdef DEBUG Serial.println(F("Scheduled update.")); #endif needsUpdate = true; } void toggleLedFlag() { needUpdateLeds = true; } void ledWrite(int pin, int state) { // TX has high-low reversed if (pin == TX) { #ifdef DEBUG return; #endif analogWrite(pin, PWMRANGE - state); } else { analogWrite(pin, state); } } #define THROTTLE_SECS 30 unsigned int throttleWait = 5000; // wait 5s after bootup void loop() { portalLoop(); ArduinoOTA.handle(); if (throttleWait > 0) { // do not send more than 1 request per 30s throttleWait--; } if (needsUpdate && throttleWait <= 0) { if (isConnected()) { needsUpdate = false; String statusCode = getWeatherStatus(); throttleWait = THROTTLE_SECS * 1000; if (statusCode == "-1") { needsUpdate = true; #ifdef DEBUG Serial.println(F("Did not get weather data.")); #endif } else { interpretWeatherCode(statusCode); } } } if (apiNeedLedUpdate()) { for (int n = 0; n < 4; n++) { // TODO create a getAPIOverrides() function that returns an array // TODO overriding has a delay overrides[n] = getAPIOverride(n); } resetLeds(); apiUpdatedLeds(); } // updateLeds: regularely // values[] holds a timer counter (BLINK) or the current brightness (other) if (needUpdateLeds) { updateLeds(); needUpdateLeds = false; } delay(1); } void updateLeds() { for (int n = 0; n < 4; n++) { if (overrides[n] == NONE) { ledWrite(n, ledUpdate(states[n], values[n])); } else if (overrides[n] < 0) { // update effects ledWrite(n, ledUpdate(overrides[n], values[n])); } } } // resetLeds: used when states[] was changed, resets values[] void resetLeds() { for (int n = 0; n < 4; n++) { if (overrides[n] == NONE) { ledWrite(n, ledInitValue(states[n], values[n])); } else { ledWrite(n, ledInitValue(overrides[n], values[n])); } } } int ledInitValue(int state, int &value) { if (state == OFF || state == BLINK || state == FADEIN) { value = 0; } if (state == ON || state == FADEOUT) { value = PWMRANGE; } return value; } // takes and modifies LED state, value; returns new brightness int ledUpdate(int &state, int &value) { if (state == BLINK) { // value < 0: off; value >= 0: on value++; if (value >= BLINKTHRESHOLD) { // turn off thresh*freq s value = -BLINKTHRESHOLD * BLINKDARKMULTIPLIER; // "overflow" and wait until turnon return 0; } if (value == 0) { // turn on again return PWMRANGE; } } // fade in until max brightness if (state == FADEIN) { value += FADESTEP; if (value > PWMRANGE) { value = PWMRANGE; state = FADEOUT; } return value; } // …and then fade out again if (state == FADEOUT) { value -= FADESTEP; if (value < 0) { value = 0; state = FADEIN; } return value; } return value; } void interpretWeatherCode(String code) { #ifdef TESTWEATHER switch (random(5)) { case 0: code = "01"; break; case 1: code = "09"; break; case 2: code = "11"; break; case 3: code = "13"; break; case 4: code = "50"; break; } code += (random(1) == 0 ? "d" : "n"); #ifdef DEBUG Serial.println(F("Faking weather data:")); #endif #endif // http://openweathermap.org/weather-conditions boolean isDay = (code.substring(2) == "d"); int id = code.substring(0, 2).toInt(); #ifdef DEBUG if (isDay) { Serial.print(F("day ")); } else { Serial.print(F("night ")); } Serial.print(F("icon: ")); Serial.println(id); #endif states[LGREEN] = OFF; // top LEDs: daytime // bottom LEDs: rain/snow/… switch (id) { case 1: case 2: case 3: case 4: if (isDay) { states[LBLUET] = OFF; states[LRED] = ON; } else { states[LBLUET] = ON; states[LRED] = OFF; } states[LBLUEB] = OFF; break; case 9: case 10: if (isDay) { states[LBLUET] = OFF; states[LRED] = ON; } else { states[LBLUET] = ON; states[LRED] = OFF; } states[LBLUEB] = FADEIN; break; case 11: if (isDay) { states[LBLUET] = OFF; states[LRED] = ON; } else { states[LBLUET] = ON; states[LRED] = OFF; } states[LBLUEB] = BLINK; break; case 13: states[LBLUET] = FADEIN; states[LRED] = OFF; states[LBLUEB] = FADEOUT; break; case 50: states[LBLUET] = OFF; states[LRED] = OFF; states[LBLUEB] = FADEIN; break; } resetLeds(); setWeatherIcon(code); }