304 lines
8.4 KiB
C++
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();
|
|
}
|