Files
weather-panel-display/ESP32_LedPanel_Weather.ino
2019-12-15 18:04:59 -08:00

304 lines
8.4 KiB
C++

/*
* Copyright (C) 2019 Max Regan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <stdint.h>
#include <SmartMatrix3.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <WiFiManager.h>
#include "src/weather-pixel-icons-c/images.h"
#define COLOR_DEPTH 24
const uint8_t MatrixWidth = 32;
const uint8_t MatrixHeight = 16;
const uint8_t RefreshDepth = 36;
const uint8_t DmaBufferRows = 2;
const uint8_t PanelType = SMARTMATRIX_HUB75_16ROW_MOD8SCAN;
const uint8_t MatrixOptions = (SMARTMATRIX_OPTIONS_NONE);
const uint8_t LayerOptions = (SM_INDEXED_OPTIONS_NONE);
SMARTMATRIX_ALLOCATE_BUFFERS(matrix, MatrixWidth, MatrixHeight, RefreshDepth, DmaBufferRows, PanelType, MatrixOptions);
SMARTMATRIX_ALLOCATE_BACKGROUND_LAYER(layer, MatrixWidth, MatrixHeight, COLOR_DEPTH, LayerOptions);
const int defaultBrightness = (8 * 255) / 100; // dim: 10% brightness, still very bright
const rgb24 defaultBackgroundColor = {0, 0, 0};
const rgb24 defaultTextColor = {255, 255, 255};
// Use Seattle by default
#define DEFAULT_LATITUDE "47.6062"
#define DEFAULT_LONGITUDE "-122.3321"
static uint32_t display_timestamp = 0;
WiFiManagerParameter latitude_param("latitude", "Latitide", DEFAULT_LATITUDE, 16);
WiFiManagerParameter longitude_param("longitude", "Longitude", DEFAULT_LONGITUDE, 16);
WiFiManagerParameter darksky_api_token_param("api_token", "DarkSky API Token", "", 32);
void setup() {
// initialize the digital pin as an output.
Serial.begin(115200);
matrix.addLayer(&layer);
matrix.begin();
matrix.setBrightness(defaultBrightness);
layer.setFont(font3x5);
autoConnectWifi();
}
void autoConnectWifi()
{
WiFiManager wifi;
Serial.println("Setting up WiFi");
wifi.resetSettings();
wifi.addParameter(&latitude_param);
wifi.addParameter(&longitude_param);
wifi.addParameter(&darksky_api_token_param);
wifi.autoConnect();
Serial.println("WiFi Done");
}
static const uint64_t weather_update_millis = 1000 * 60 * 10; // 10 mins
static const char * darksky_url_fmt = "https://api.darksky.net/forecast/%s/%s,%s?exclude=minutely,hourly,alerts,flags&units=us";
static String weather_desc = "";
static bool is_weather_valid = false;
static int weather_code = 0;
static uint64_t weather_timestamp = 0;
static uint64_t weather_walltime = 0;
static uint64_t sunrise_walltime = 0;
static uint64_t sunset_walltime = 0;
static HTTPClient http;
enum weather {
NONE,
CLEAR,
RAIN,
SNOW,
SLEET,
WIND,
FOG,
CLOUDY,
PARTLY_CLOUDY,
};
static float temp_min = 0.0;
static float temp_max = 0.0;
static float temp_now = 0.0;
static enum weather weather_value = NONE;
void update_weather() {
uint64_t now = millis();
if (weather_timestamp != 0 && weather_timestamp + weather_update_millis > now) {
return;
}
Serial.println("Updating weather");
weather_value = NONE;
static char url[256];
url[0] = '\0';
snprintf(url, sizeof(url), darksky_url_fmt,
darksky_api_token_param.getValue(), latitude_param.getValue(), longitude_param.getValue());
Serial.println(url);
http.begin(url);
int resp_code = http.GET();
if (resp_code < 0) {
Serial.println("Failed to get weather");
return;
}
Serial.println("Got weather");
String json_resp = http.getString();
http.end();
DynamicJsonDocument json(16000);
DeserializationError error = deserializeJson(json, json_resp);
if (error) {
Serial.println(error.c_str());
return;
}
Serial.println("Weather parsed");
weather_timestamp = millis();
temp_min = json["daily"]["data"][0]["temperatureLow"];
temp_max = json["daily"]["data"][0]["temperatureHigh"];
temp_now = json["currently"]["temperature"];
weather_walltime = json["currently"]["time"];
sunrise_walltime = json["daily"]["data"][0]["sunriseTime"];
sunset_walltime = json["daily"]["data"][0]["sunsetTime"];
Serial.println(String("Temp now: ") + temp_now);
Serial.println(String("Temp low: ") + temp_min);
Serial.println(String("Temp high: ") + temp_max);
weather_code = NONE;
const char *icon = json["currently"]["icon"];
Serial.println(String("Weather: ") + icon);
if (!strcmp(icon, "clear-day")) {
weather_code = CLEAR;
} else if (!strcmp(icon, "clear-night")) {
weather_code = CLEAR;
} else if (!strcmp(icon, "rain")) {
weather_code = RAIN;
} else if (!strcmp(icon, "snow")) {
weather_code = SNOW;
} else if (!strcmp(icon, "sleet")) {
weather_code = SLEET;
} else if (!strcmp(icon, "wind")) {
weather_code = WIND;
} else if (!strcmp(icon, "fog")) {
weather_code = FOG;
} else if (!strcmp(icon, "cloudy")) {
weather_code = CLOUDY;
} else if (!strcmp(icon, "partly-cloudy-day")) {
weather_code = PARTLY_CLOUDY;
} else if (!strcmp(icon, "partly-cloudy-night")) {
weather_code = PARTLY_CLOUDY;
}
}
void draw_bitmap(const struct gimp_image *image, unsigned int x, unsigned int y) {
for (unsigned int i = 0; i < image->height; i++) {
for (unsigned int j = 0; j < image->width; j++) {
SM_RGB pixel = {
~(image->data[(i * image->width + j) * 3 + 0]),
~(image->data[(i * image->width + j) * 3 + 1]),
~(image->data[(i * image->width + j) * 3 + 2])
};
layer.drawPixel(x + j, y + i, pixel);
}
}
}
static const struct gimp_image *images[] = {
&cloud,
&cloud_moon,
&clouds,
&cloud_sun,
&cloud_wind,
&cloud_wind_moon,
&cloud_wind_sun,
&lightning,
&moon,
&rain0,
&rain0_sun,
&rain1,
&rain1_moon,
&rain1_sun,
&rain2,
&rain_lightning,
&rain_snow,
&snow,
&snow_moon,
&snow_sun,
&sun,
};
static const struct gimp_image *day_images[] = {
[NONE] = nullptr,
[CLEAR] = &sun,
[RAIN] = &rain0,
[SNOW] = &snow,
[SLEET] = &rain_snow,
[WIND] = nullptr, //FIXME: deleted the filed?
[FOG] = &clouds,
[CLOUDY] = &clouds,
[PARTLY_CLOUDY] = &cloud_sun,
};
static const struct gimp_image *night_images[] = {
[NONE] = nullptr,
[CLEAR] = &moon,
[RAIN] = &rain1_moon,
[SNOW] = &snow_moon,
[SLEET] = &rain1_moon,
[WIND] = nullptr, //FIXME: deleted the filed?
[FOG] = &clouds,
[CLOUDY] = &clouds,
[PARTLY_CLOUDY] = &cloud_moon,
};
void update_frame() {
layer.swapBuffers();
}
void display_weather() {
static uint64_t redraw_timestamp = 0;
if (redraw_timestamp >= weather_timestamp) {
return;
}
layer.fillScreen(defaultBackgroundColor);
const struct gimp_image *icon = nullptr;
Serial.print("Weather code:");
Serial.println(weather_code);
bool is_night = (weather_walltime < sunrise_walltime) || (weather_walltime > sunset_walltime);
Serial.print("It is ");
Serial.println(is_night ? "night" : "day");
if (is_night) {
icon = night_images[weather_code];
} else {
icon = day_images[weather_code];
}
if (icon) {
Serial.println("Drawing weather icon.");
draw_bitmap(icon, 0, 0);
} else {
Serial.println("No weather icon.");
}
char temp_text[32] = { 0 };
snprintf(temp_text, sizeof(temp_text), "%d", (int) temp_now);
layer.setFont(font5x7);
layer.drawString(19, 4, defaultTextColor, temp_text);
layer.swapBuffers();
redraw_timestamp = millis();
}
void loop() {
update_weather();
display_weather();
}