diff options
| author | schneefux <schneefux+commit@schneefux.xyz> | 2015-12-25 13:38:31 +0100 |
|---|---|---|
| committer | schneefux <schneefux+commit@schneefux.xyz> | 2015-12-25 13:38:31 +0100 |
| commit | 76edd6c448c55c4f9bcf37509ec372149c966993 (patch) | |
| tree | c4eed3c1ecb40987cbe2b77df47b2e6e23c71494 /snowman.ino | |
| download | weather-snowman-76edd6c448c55c4f9bcf37509ec372149c966993.tar.gz weather-snowman-76edd6c448c55c4f9bcf37509ec372149c966993.zip | |
first commit
Diffstat (limited to 'snowman.ino')
| -rw-r--r-- | snowman.ino | 301 |
1 files changed, 301 insertions, 0 deletions
diff --git a/snowman.ino b/snowman.ino new file mode 100644 index 0000000..963ad5e --- /dev/null +++ b/snowman.ino @@ -0,0 +1,301 @@ +#include <ESP8266WiFi.h> +#include <ESP8266WebServer.h> +#include <Ticker.h> +#include <ArduinoJson.h> +#include <ESP8266mDNS.h> +#include <ArduinoOTA.h> + +// 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); +} |
