summaryrefslogtreecommitdiff
path: root/snowman.ino
diff options
context:
space:
mode:
authorschneefux <schneefux+commit@schneefux.xyz>2015-12-25 13:38:31 +0100
committerschneefux <schneefux+commit@schneefux.xyz>2015-12-25 13:38:31 +0100
commit76edd6c448c55c4f9bcf37509ec372149c966993 (patch)
treec4eed3c1ecb40987cbe2b77df47b2e6e23c71494 /snowman.ino
downloadweather-snowman-76edd6c448c55c4f9bcf37509ec372149c966993.tar.gz
weather-snowman-76edd6c448c55c4f9bcf37509ec372149c966993.zip
first commit
Diffstat (limited to 'snowman.ino')
-rw-r--r--snowman.ino301
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);
+}