Merge pull request #99 from 2Grey/feature/yandex-weather
This commit is contained in:
103
usermods/usermod_v2_yandex_weather/README.md
Normal file
103
usermods/usermod_v2_yandex_weather/README.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# Yandex Weaher Usermod
|
||||
V2 Usermod for getting weather data from [Yandex Weather](https://yandex.ru/pogoda) service
|
||||
|
||||
## Web interface
|
||||
The info page in the web interface shows:
|
||||
- Remaining time until new weather request
|
||||
- Current temperature (°C)
|
||||
- Feels temperature (°C)
|
||||
- Wind speed (m/s)
|
||||
- Wind direction
|
||||
|
||||
## Usage
|
||||
Compile the source with the buildflag `-D USERMOD_YA_WEATHER` added.
|
||||
|
||||
```ini
|
||||
build_flags = ${env:esp32dev.build_flags}
|
||||
-D USERMOD_YA_WEATHER
|
||||
```
|
||||
|
||||
## API for other Usermods
|
||||
- `void setEnable(bool enable)` – To change enable state
|
||||
- `bool isEnable()` – To query enable state
|
||||
- `WeatherInfo *currentWeather()` – To query current weather struct
|
||||
|
||||
### Access from other Usermod
|
||||
|
||||
There are two options to get access to the usermod instance:
|
||||
1. Include `yandex-weather-usermod-v2.h` before your Usermod in _usermods_list.cpp_
|
||||
1. Use `#include "yandex-weather-usermod-v2.h"` at the top of the your Usermod
|
||||
|
||||
### Usage example
|
||||
```cpp
|
||||
#include "wled.h"
|
||||
#include "yandex-weather-usermod-v2.h"
|
||||
|
||||
class MyUsermode: public Usermod {
|
||||
void foo() {
|
||||
#ifdef USERMOD_ID_YA_WEATHER
|
||||
YandexWeatherUsermod *weatherUserMode = (YandexWeatherUsermod *)usermods.lookup(USERMOD_ID_YA_WEATHER);
|
||||
if (weatherUserMode != nullptr) {
|
||||
weatherUserMode->setEnable(true);
|
||||
weatherUserMode->currentWeather();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Settings
|
||||
|
||||
### Enabled:
|
||||
Checkbox to enable or disable the Usermod
|
||||
|
||||
### ApiKey
|
||||
ApiKey for access to Yandex Weather API.
|
||||
You can get it [here](https://yandex.ru/dev/weather/doc/dg/concepts/about.html).
|
||||
|
||||
### UpdateInterval
|
||||
API access frequency in minutes.
|
||||
|
||||
*Note:* It should be greater or equal to 30, because free APIKey has a limit of 50 requests per day.
|
||||
|
||||
### ApiLanguage
|
||||
The language and country combinations for which weather wording data will be returned.
|
||||
|
||||
More info [here](https://yandex.ru/dev/weather/doc/dg/concepts/forecast-info.html)
|
||||
|
||||
### CustomCoordinates
|
||||
Enables the use of custom coordinates (more on them below) instead of the system ones from WLED.
|
||||
|
||||
### CityLatitude
|
||||
Custom city latitude coordinate (works only with `CustomCoordinates` enabled)
|
||||
|
||||
### CityLongitude
|
||||
Custom city longitude coordinate (works only with `CustomCoordinates` enabled)
|
||||
|
||||
### ShowInInfo
|
||||
Enable display of weather information in the "Info" page (on the WLED main scree)
|
||||
|
||||
### PostToMQTT
|
||||
Enable post weather data to MQTT topic (/yandexWeather)
|
||||
|
||||
## MQTT Topics
|
||||
|
||||
| Topic | Unit | Description |
|
||||
|:------------------------|:--------|:------------------------|
|
||||
| yandexWeather/date | String | Weather received date |
|
||||
| yandexWeather/tempReal | °C | Real temperature |
|
||||
| yandexWeather/tempFeels | °C | Feels like temperature |
|
||||
| yandexWeather/windSpeed | m/s | Wind speed |
|
||||
| yandexWeather/windDir | String | Wind direction |
|
||||
|
||||
## Additional Build Flags
|
||||
| Flag | Description |
|
||||
|:------------------------------|:----------------------------------------------------------------------------------------------|
|
||||
| YA_WEATHER_DEBUG | Show debug message with _[YandexWeatherUsermod]_ prefix |
|
||||
| YA_WEATHER_ALLOW_ALL_TIMEOUT | Allows you to set UpdateInterval to less than 30 (Use for tests or if you have a paid ApiKey) |
|
||||
| YA_WEATHER_HIDE_REMAINING | Hide the remaining time to update in Info |
|
||||
|
||||
-----
|
||||
Author:
|
||||
|
||||
2Grey | [Github](https://github.com/2Grey)
|
||||
596
usermods/usermod_v2_yandex_weather/usermod_v2_yandex_weather.h
Normal file
596
usermods/usermod_v2_yandex_weather/usermod_v2_yandex_weather.h
Normal file
@@ -0,0 +1,596 @@
|
||||
#pragma once
|
||||
|
||||
#include "wled.h"
|
||||
#include "time.h"
|
||||
|
||||
// #define YA_WEATHER_DEBUG // Show debug message with [YandexWeatherUsermod] prefix
|
||||
// #define YA_WEATHER_ALLOW_ALL_TIMEOUT // Allows you to set UpdateInterval to less than 30
|
||||
// #define YA_WEATHER_HIDE_REMAINING // Hide the remaining time to update in Info
|
||||
|
||||
class YandexWeatherUsermod: public Usermod
|
||||
{
|
||||
public:
|
||||
// Enums
|
||||
enum class WeatherInfoResult {
|
||||
Success = 0, // Data fetch is Ok
|
||||
Timeout, // The time hasn't come yet (Update interval)
|
||||
InternalError, // ApiKey or coordinate missed
|
||||
ServerError, // Some error from server
|
||||
};
|
||||
|
||||
// Weather info
|
||||
struct WeatherInfo {
|
||||
unsigned long date; // Server time (Unixtime)
|
||||
int tempReal; // Real temperature (°C)
|
||||
int tempFeels; // Feels temperature (°C)
|
||||
float windSpeed; // Wind speed (kmp)
|
||||
String windDir; // Wind directions
|
||||
|
||||
WeatherInfo(unsigned long dt, int tr, int tf, float ws, String wd): date(dt), tempReal(tr), tempFeels(tf), windSpeed(ws), windDir(wd) {}
|
||||
};
|
||||
|
||||
private:
|
||||
// Internal
|
||||
#ifndef _MoonModules_WLED_
|
||||
bool initDone = false;
|
||||
bool enabled = false;
|
||||
unsigned long lastTime = 0;
|
||||
#endif
|
||||
bool isConnected = false;
|
||||
char errorMessage[100] = "";
|
||||
WeatherInfo *_weatherInfo;
|
||||
|
||||
// configurable parameters
|
||||
String _apiKey = "";
|
||||
int _updateInterval = 30;
|
||||
int _apiLanguage = 0;
|
||||
bool _isUseCustomCoord = false;
|
||||
bool _isShowInInfo = false;
|
||||
bool _isPostToMQTT = false;
|
||||
float _apiLat = 0;
|
||||
float _apiLon = 0;
|
||||
|
||||
// Const chars
|
||||
static const char _cfg_key_enabled[];
|
||||
static const char _cfg_key_apiKey[];
|
||||
static const char _cfg_key_updateInterval[];
|
||||
static const char _cfg_key_apiLang[];
|
||||
static const char _cfg_key_custom_coord[];
|
||||
static const char _cfg_key_apiLat[];
|
||||
static const char _cfg_key_apiLon[];
|
||||
static const char _cfg_key_showInfo[];
|
||||
static const char _cfg_key_postMQTT[];
|
||||
#ifndef _MoonModules_WLED_
|
||||
static const char _name[];
|
||||
#endif
|
||||
|
||||
// Inlines
|
||||
inline bool isValidCoordinate(float lat, float lon) { return fabs(lat) > __FLT_EPSILON__ && fabs(lon) > __FLT_EPSILON__; }
|
||||
inline bool isValidCoordinatePair(std::pair<float, float> coord) { return isValidCoordinate(coord.first, coord.second); }
|
||||
inline bool isCharEmpty(String val) { return strcmp(val.c_str(), "") == 0; }
|
||||
inline long unsigned getUpdateInterval() { return (_updateInterval * 60 * 1000); }
|
||||
inline String uppercasedString(String str) { str.toUpperCase(); return str; }
|
||||
|
||||
inline const char* apiLanguageByIdx(int idx) {
|
||||
switch (idx) {
|
||||
case 1: return "ru_RU";
|
||||
case 2: return "ru_UA";
|
||||
case 3: return "uk_UA";
|
||||
case 4: return "be_BY";
|
||||
case 5: return "kk_KZ";
|
||||
case 6: return "tr_TR";
|
||||
default: return "en_US";
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string windCharByString(std::string wd) {
|
||||
if (wd.compare("nw") == 0) { return "⬊"; }
|
||||
if (wd.compare("n") == 0) { return "⬇︎"; }
|
||||
if (wd.compare("ne") == 0) { return "⬋"; }
|
||||
if (wd.compare("e") == 0) { return "⬅︎"; }
|
||||
if (wd.compare("se") == 0) { return "⬉"; }
|
||||
if (wd.compare("s") == 0) { return "⬆︎"; }
|
||||
if (wd.compare("sw") == 0) { return "⬈"; }
|
||||
if (wd.compare("w") == 0) { return "➡"; }
|
||||
return "●";
|
||||
}
|
||||
|
||||
inline String firstWeatherLine() {
|
||||
if (_weatherInfo == nullptr) return String(F(""));
|
||||
char r[20];
|
||||
sprintf_P(r, "%d (like %d)", _weatherInfo->tempReal, _weatherInfo->tempFeels);
|
||||
return String(r);
|
||||
}
|
||||
|
||||
inline String secondWeatherLine() {
|
||||
if (_weatherInfo == nullptr) return String(F(""));
|
||||
char r[15];
|
||||
sprintf(r, "%.2f m/s", _weatherInfo->windSpeed);
|
||||
|
||||
if (!_weatherInfo->windDir.isEmpty()) {
|
||||
char wd[4];
|
||||
sprintf(wd, "(%s)", windCharByString(_weatherInfo->windDir.c_str()).c_str());
|
||||
strcat(r, wd);
|
||||
}
|
||||
return String(r);
|
||||
}
|
||||
|
||||
// Helping functions
|
||||
std::pair<float, float> getCoordinates()
|
||||
{
|
||||
std::pair<float, float> res { 0, 0};
|
||||
if (_isUseCustomCoord) {
|
||||
if (isValidCoordinate(_apiLat, _apiLon)) {
|
||||
res.first = _apiLat;
|
||||
res.second = _apiLon;
|
||||
}
|
||||
} else if (isValidCoordinate(latitude, longitude)) {
|
||||
res.first = latitude;
|
||||
res.second = longitude;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
String epochToStirng(time_t time) {
|
||||
tmElements_t tm;
|
||||
breakTime(time, tm);
|
||||
char buf[20];
|
||||
sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d", tm.Year + 1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
|
||||
return String(buf);
|
||||
}
|
||||
|
||||
String remainingHumanString() {
|
||||
unsigned long remainingTimeSec = (getUpdateInterval() - (millis() - lastTime)) / 1000;
|
||||
int h = remainingTimeSec / 3600;
|
||||
int m = (remainingTimeSec % 3600) / 60;
|
||||
int s = remainingTimeSec % 60;
|
||||
char buf[8];
|
||||
sprintf(buf, "%02d:%02d:%02d", h, m, s);
|
||||
return String(buf);
|
||||
}
|
||||
|
||||
void resetLastTime() {
|
||||
lastTime = millis() - getUpdateInterval();
|
||||
}
|
||||
|
||||
// API Calls
|
||||
bool parseReponse(WiFiClient client)
|
||||
{
|
||||
bool isSuccess = false;
|
||||
|
||||
StaticJsonDocument<256> filter;
|
||||
filter["now"] = true;
|
||||
filter["fact"]["temp"] = true;
|
||||
filter["fact"]["feels_like"] = true;
|
||||
filter["fact"]["wind_speed"] = true;
|
||||
filter["fact"]["wind_dir"] = true;
|
||||
|
||||
PSRAMDynamicJsonDocument weatherDoc(4096);
|
||||
DeserializationError parseError = deserializeJson(weatherDoc, client, DeserializationOption::Filter(filter));
|
||||
if (parseError) {
|
||||
strcat(errorMessage, PSTR("deserializeJson() failed: "));
|
||||
strcat(errorMessage, parseError.c_str());
|
||||
} else {
|
||||
isSuccess = true;
|
||||
|
||||
unsigned long nowDate = 0;
|
||||
getJsonValue(weatherDoc["now"], nowDate);
|
||||
|
||||
int tempReal;
|
||||
int tempFeels;
|
||||
float windSpeed;
|
||||
String windDir;
|
||||
|
||||
JsonObject weatherDocObject = weatherDoc.as<JsonObject>();
|
||||
JsonObject weatherFactObject = weatherDocObject["fact"];
|
||||
getJsonValue(weatherFactObject["temp"], tempReal);
|
||||
getJsonValue(weatherFactObject["feels_like"], tempFeels);
|
||||
getJsonValue(weatherFactObject["wind_speed"], windSpeed);
|
||||
getJsonValue(weatherFactObject["wind_dir"], windDir);
|
||||
|
||||
if (_weatherInfo) {
|
||||
_weatherInfo->date = nowDate;
|
||||
_weatherInfo->tempReal = tempReal;
|
||||
_weatherInfo->tempFeels = tempFeels;
|
||||
_weatherInfo->windSpeed = windSpeed;
|
||||
_weatherInfo->windDir = windDir;
|
||||
} else {
|
||||
_weatherInfo = new WeatherInfo(nowDate, tempReal, tempFeels, windSpeed, windDir);
|
||||
}
|
||||
if (_weatherInfo) {
|
||||
publishMQTT(_weatherInfo);
|
||||
} else {
|
||||
isSuccess = false;
|
||||
}
|
||||
}
|
||||
|
||||
return isSuccess;
|
||||
}
|
||||
|
||||
WeatherInfoResult apiGetWeather(char *errorMessage)
|
||||
{
|
||||
WiFiClient client;
|
||||
client.setTimeout(10000);
|
||||
|
||||
if(!client.connect("api.weather.yandex.ru", 80)) {
|
||||
strcat(errorMessage, PSTR("Connection failed"));
|
||||
#ifdef YA_WEATHER_DEBUG
|
||||
DEBUG_PRINTLN(F("[YandexWeatherUsermod] Connection failed"));
|
||||
#endif
|
||||
return WeatherInfoResult::ServerError;
|
||||
}
|
||||
|
||||
if (client.connected()) {
|
||||
char url[180];
|
||||
std::pair<float, float> coords = getCoordinates();
|
||||
sprintf(url, "GET /v2/informers?lat=%f&lon=%f&lang=%s HTTP/1.0", coords.first, coords.second, apiLanguageByIdx(_apiLanguage));
|
||||
|
||||
client.println(url);
|
||||
client.println(F("Host: api.weather.yandex.ru"));
|
||||
client.printf("X-Yandex-API-Key: %s\n", _apiKey.c_str());
|
||||
client.println(F("Connection: close"));
|
||||
}
|
||||
|
||||
bool isSuccess = false;
|
||||
if (client.println() == 0) {
|
||||
strcat(errorMessage, PSTR("Failed to send request"));
|
||||
#ifdef YA_WEATHER_DEBUG
|
||||
DEBUG_PRINTLN(F("[YandexWeatherUsermod] Failed to send request"));
|
||||
#endif
|
||||
} else {
|
||||
char status[32] = {0};
|
||||
client.readBytesUntil('\r', status, sizeof(status));
|
||||
if (strcmp(status, "HTTP/1.0 200 OK") != 0) {
|
||||
strcat(errorMessage, PSTR("Unexpected response: "));
|
||||
strcat(errorMessage, status);
|
||||
#ifdef YA_WEATHER_DEBUG
|
||||
DEBUG_PRINTF("[YandexWeatherUsermod] Unexpected response: %s\n", status);
|
||||
#endif
|
||||
} else {
|
||||
char endOfHeaders[] = "\r\n\r\n";
|
||||
if (!client.find(endOfHeaders)) {
|
||||
strcat(errorMessage, PSTR("Invalid response"));
|
||||
#ifdef YA_WEATHER_DEBUG
|
||||
DEBUG_PRINTLN(F("[YandexWeatherUsermod] Invalid response"));
|
||||
#endif
|
||||
} else {
|
||||
isSuccess |= parseReponse(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.stop();
|
||||
return isSuccess ? WeatherInfoResult::Success : WeatherInfoResult::ServerError;
|
||||
}
|
||||
|
||||
// MQTT
|
||||
void publishMQTT(WeatherInfo *wi) {
|
||||
if (!wi || !_isPostToMQTT) return;
|
||||
|
||||
#ifndef WLED_DISABLE_MQTT
|
||||
if (WLED_MQTT_CONNECTED) {
|
||||
String topicName = mqttDeviceTopic;
|
||||
topicName += F("/yandexWeather");
|
||||
|
||||
mqtt->publish((topicName + F("/date")).c_str(), 0, false, epochToStirng(_weatherInfo->date).c_str());
|
||||
|
||||
char buf[10];
|
||||
sprintf(buf, "%d", _weatherInfo->tempReal);
|
||||
mqtt->publish((topicName + F("/tempReal")).c_str(), 0, false, buf);
|
||||
|
||||
sprintf(buf, "%d", _weatherInfo->tempFeels);
|
||||
mqtt->publish((topicName + F("/tempFeels")).c_str(), 0, false, buf);
|
||||
|
||||
sprintf(buf, "%.2f", _weatherInfo->windSpeed);
|
||||
mqtt->publish((topicName + F("/windSpeed")).c_str(), 0, false, buf);
|
||||
|
||||
mqtt->publish((topicName + F("/windDir")).c_str(), 0, false, _weatherInfo->windDir.c_str());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public:
|
||||
// Class Constructor/Destructor
|
||||
#ifdef _MoonModules_WLED_
|
||||
YandexWeatherUsermod(const char *name, bool enabled) : Usermod(name, enabled), _weatherInfo(nullptr) {
|
||||
lastTime = 0;
|
||||
}
|
||||
#else
|
||||
YandexWeatherUsermod() : _weatherInfo(nullptr) {
|
||||
lastTime = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
~YandexWeatherUsermod() {
|
||||
if (_weatherInfo) { delete _weatherInfo; _weatherInfo = nullptr; }
|
||||
}
|
||||
// Public methods
|
||||
/**
|
||||
* Togle enabled for Usermod
|
||||
* @param enable New state of enabled of Usermod
|
||||
*/
|
||||
void setEnable(bool enable) { enabled = enable; }
|
||||
/**
|
||||
* @return Current enabled state of Usermod
|
||||
*/
|
||||
bool isEnable() { return enabled;}
|
||||
/**
|
||||
* @return Current weather struct (WeatherInfo)
|
||||
*/
|
||||
WeatherInfo *currentWeather() { return _weatherInfo; };
|
||||
|
||||
/**
|
||||
* Try to fetch new weather data from server
|
||||
* @param isForce If `true` – ignore update interval
|
||||
* @return Status result of fetching data (WeatherInfoResult)
|
||||
*/
|
||||
WeatherInfoResult tryGetWeather(bool isForce)
|
||||
{
|
||||
if (!isForce && millis() - lastTime < getUpdateInterval()) {
|
||||
return WeatherInfoResult::Timeout;
|
||||
}
|
||||
#ifdef YA_WEATHER_DEBUG
|
||||
DEBUG_PRINTLN(F("[YandexWeatherUsermod] Trying to get weather..."));
|
||||
#endif
|
||||
lastTime = millis();
|
||||
strcpy(errorMessage, "");
|
||||
|
||||
if (isCharEmpty(_apiKey)) {
|
||||
strcpy(errorMessage, PSTR("No API Key set"));
|
||||
#ifdef YA_WEATHER_DEBUG
|
||||
DEBUG_PRINTLN(F("[YandexWeatherUsermod] No API Key set"));
|
||||
#endif
|
||||
return WeatherInfoResult::InternalError;
|
||||
}
|
||||
|
||||
std::pair<float, float> coords = getCoordinates();
|
||||
if (!isValidCoordinatePair(coords)) {
|
||||
strcpy(errorMessage, PSTR("No coordinates set"));
|
||||
#ifdef YA_WEATHER_DEBUG
|
||||
DEBUG_PRINTLN(F("[YandexWeatherUsermod] No coordinates set"));
|
||||
#endif
|
||||
return WeatherInfoResult::InternalError;
|
||||
}
|
||||
|
||||
return apiGetWeather(errorMessage);
|
||||
}
|
||||
|
||||
// WLED lyfecycle
|
||||
|
||||
void setup() {
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
void connected() {
|
||||
isConnected = true;
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (!initDone) return;
|
||||
if (!enabled || !isConnected || strip.isUpdating()) return;
|
||||
|
||||
WeatherInfoResult res = tryGetWeather(false);
|
||||
switch (res) {
|
||||
case WeatherInfoResult::Success:
|
||||
lastTime = millis();
|
||||
break;
|
||||
|
||||
case WeatherInfoResult::ServerError:
|
||||
lastTime = millis() - getUpdateInterval() + 10000;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// MQTT
|
||||
|
||||
void onMqttConnect(bool sessionPresent) {
|
||||
publishMQTT(_weatherInfo);
|
||||
}
|
||||
|
||||
// Info page
|
||||
|
||||
void addToJsonInfo(JsonObject &root)
|
||||
{
|
||||
if (!initDone) return;
|
||||
|
||||
JsonObject user = root["u"];
|
||||
if (user.isNull()) user = root.createNestedObject("u");
|
||||
|
||||
JsonArray infoArr = user.createNestedArray(FPSTR(_name)); // name
|
||||
#ifndef YA_WEATHER_HIDE_REMAINING
|
||||
if (enabled) {
|
||||
infoArr.add(remainingHumanString());
|
||||
}
|
||||
#endif
|
||||
|
||||
String uiDomString = F("<button class=\"btn btn-xs\" onclick=\"requestJson({");
|
||||
uiDomString += FPSTR(_name);
|
||||
uiDomString += F(":{");
|
||||
uiDomString += FPSTR("enabled");
|
||||
uiDomString += enabled ? F(":false}});\">") : F(":true}});\">");
|
||||
uiDomString += F("<i class=\"icons ");
|
||||
uiDomString += enabled ? "on" : "off";
|
||||
uiDomString += F("\"></i></button>");
|
||||
infoArr.add(uiDomString);
|
||||
|
||||
if (!enabled || !_isShowInInfo) return;
|
||||
|
||||
if (!isCharEmpty(errorMessage)) {
|
||||
infoArr = user.createNestedArray(F("Weather error"));
|
||||
String errorString = F("<b style=\"color:red;\">");
|
||||
errorString += FPSTR(errorMessage);
|
||||
errorString += F("</b>");
|
||||
infoArr.add(errorString);
|
||||
} else if (!_weatherInfo) {
|
||||
infoArr = user.createNestedArray(F("Weather info"));
|
||||
infoArr.add(F("no weather data"));
|
||||
} else {
|
||||
infoArr = user.createNestedArray(F("Weather date"));
|
||||
infoArr.add(epochToStirng(_weatherInfo->date));
|
||||
infoArr.add(F(" (UTC)"));
|
||||
|
||||
infoArr = user.createNestedArray(F("Temperature"));
|
||||
infoArr.add(firstWeatherLine());
|
||||
|
||||
infoArr = user.createNestedArray(F("Wind"));
|
||||
infoArr.add(secondWeatherLine());
|
||||
}
|
||||
}
|
||||
|
||||
// JSON State
|
||||
|
||||
void readFromJsonState(JsonObject& root)
|
||||
{
|
||||
if (!initDone) return;
|
||||
|
||||
bool en = enabled;
|
||||
|
||||
JsonObject um = root[FPSTR(_name)];
|
||||
if (!um.isNull()) {
|
||||
if (um[FPSTR(_cfg_key_enabled)].is<bool>()) {
|
||||
en = um[FPSTR(_cfg_key_enabled)].as<bool>();
|
||||
} else {
|
||||
String str = um[FPSTR(_cfg_key_enabled)];
|
||||
en = (bool)(str!="off");
|
||||
}
|
||||
}
|
||||
if (en != enabled) {
|
||||
enabled = en;
|
||||
if (enabled) resetLastTime();
|
||||
}
|
||||
}
|
||||
|
||||
// Config
|
||||
|
||||
void addToConfig(JsonObject &root)
|
||||
{
|
||||
#ifdef _MoonModules_WLED_
|
||||
Usermod::addToConfig(root);
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
#else
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name)); // WLEDMM: set enabled and _name
|
||||
top[FPSTR("enabled")] = enabled;
|
||||
#endif
|
||||
|
||||
top[FPSTR(_cfg_key_apiKey)] = _apiKey;
|
||||
top[FPSTR(_cfg_key_updateInterval)] = _updateInterval;
|
||||
top[FPSTR(_cfg_key_apiLang)] = _apiLanguage;
|
||||
top[FPSTR(_cfg_key_custom_coord)] = _isUseCustomCoord;
|
||||
if (isValidCoordinate(_apiLat, _apiLon)) {
|
||||
top[FPSTR(_cfg_key_apiLat)] = _apiLat;
|
||||
top[FPSTR(_cfg_key_apiLon)] = _apiLon;
|
||||
} else {
|
||||
top[FPSTR(_cfg_key_apiLat)] = 0;
|
||||
top[FPSTR(_cfg_key_apiLon)] = 0;
|
||||
}
|
||||
top[FPSTR(_cfg_key_showInfo)] = _isShowInInfo;
|
||||
top[FPSTR(_cfg_key_postMQTT)] = _isPostToMQTT;
|
||||
}
|
||||
|
||||
void appendConfigData()
|
||||
{
|
||||
oappend(SET_F("addHB('YandexWeather');"));
|
||||
|
||||
oappend(SET_F("addInfo('"));
|
||||
oappend(String(FPSTR(_name)).c_str());
|
||||
oappend((":" + String(FPSTR(_cfg_key_updateInterval))).c_str());
|
||||
#ifdef YA_WEATHER_ALLOW_ALL_TIMEOUT
|
||||
oappend(SET_F("', 1,'minutes <i>(should be ≥ 1)</i>');"));
|
||||
#else
|
||||
oappend(SET_F("', 1,'minutes <i>(should be ≥ 30)</i>');"));
|
||||
#endif
|
||||
|
||||
oappend(SET_F("dd=addDropdown('"));
|
||||
oappend(String(FPSTR(_name)).c_str());
|
||||
oappend(("', '" + String(FPSTR(_cfg_key_apiLang)) + "');").c_str());
|
||||
oappend(SET_F("addOption(dd,'English',0);"));
|
||||
oappend(SET_F("addOption(dd,'Russian (Russian domain)',1);"));
|
||||
oappend(SET_F("addOption(dd,'Russian (Ukrainian domain)',2);"));
|
||||
oappend(SET_F("addOption(dd,'Ukrainian (Ukrainian domain)',3);"));
|
||||
oappend(SET_F("addOption(dd,'Belarusian',4);"));
|
||||
oappend(SET_F("addOption(dd,'Kazakh',5);"));
|
||||
oappend(SET_F("addOption(dd,'Turkish',6);"));
|
||||
}
|
||||
|
||||
virtual bool readFromConfig(JsonObject &root)
|
||||
{
|
||||
#ifdef _MoonModules_WLED_
|
||||
bool configComplete = Usermod::readFromConfig(root);
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
#else
|
||||
bool configComplete = true;
|
||||
JsonObject top = root[FPSTR(_name)];
|
||||
if (!top.isNull()) {
|
||||
configComplete &= getJsonValue(top[FPSTR("enabled")], enabled);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (top.isNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Old values (for re-call api for case when something changed)
|
||||
bool oldEnabledState = enabled;
|
||||
String oldAPIKey = _apiKey;
|
||||
int oldApiLanguage = _apiLanguage;
|
||||
bool oldIsUseCustomCoord = _isUseCustomCoord;
|
||||
std::pair<float, float> oldCoords { _apiLat, _apiLon};
|
||||
|
||||
// Config logic
|
||||
configComplete &= getJsonValue(top[FPSTR(_cfg_key_updateInterval)], _updateInterval);
|
||||
#ifdef YA_WEATHER_ALLOW_ALL_TIMEOUT
|
||||
_updateInterval = max(1, _updateInterval);
|
||||
#else
|
||||
_updateInterval = max(30, _updateInterval);
|
||||
#endif
|
||||
|
||||
configComplete &= getJsonValue(top[FPSTR(_cfg_key_apiKey)], _apiKey);
|
||||
configComplete &= getJsonValue(top[FPSTR(_cfg_key_apiLang)], _apiLanguage);
|
||||
_apiLanguage = max(0, min(6, _apiLanguage));
|
||||
|
||||
configComplete &= getJsonValue(top[FPSTR(_cfg_key_custom_coord)], _isUseCustomCoord, false);
|
||||
configComplete &= getJsonValue(top[FPSTR(_cfg_key_apiLat)], _apiLat);
|
||||
configComplete &= getJsonValue(top[FPSTR(_cfg_key_apiLon)], _apiLon);
|
||||
configComplete &= getJsonValue(top[FPSTR(_cfg_key_showInfo)], _isShowInInfo, false);
|
||||
configComplete &= getJsonValue(top[FPSTR(_cfg_key_postMQTT)], _isPostToMQTT, false);
|
||||
|
||||
// ––
|
||||
if (enabled) {
|
||||
bool isEnabledChanged = (oldEnabledState == false);
|
||||
bool isApiKeyChanged = (oldAPIKey.compareTo(_apiKey) != 0);
|
||||
bool isApiLanguageChanged = (oldApiLanguage != _apiLanguage);
|
||||
bool isUseCustomCoordChanged = (oldIsUseCustomCoord != _isUseCustomCoord);
|
||||
bool isCoordinateChanges = (oldCoords.first != _apiLat || oldCoords.second != _apiLon);
|
||||
|
||||
if (isEnabledChanged || isApiKeyChanged || isApiLanguageChanged || isUseCustomCoordChanged) resetLastTime();
|
||||
if (isUseCustomCoordChanged && isCoordinateChanges) resetLastTime();
|
||||
} else {
|
||||
strcpy(errorMessage, "");
|
||||
}
|
||||
|
||||
return configComplete;
|
||||
}
|
||||
|
||||
// Usermode ID
|
||||
|
||||
uint16_t getId() {
|
||||
return USERMOD_ID_YA_WEATHER;
|
||||
}
|
||||
};
|
||||
|
||||
// strings to reduce flash memory usage (used more than twice)
|
||||
const char YandexWeatherUsermod::_cfg_key_enabled[] PROGMEM = "enabled";
|
||||
const char YandexWeatherUsermod::_cfg_key_apiKey[] PROGMEM = "apiKey";
|
||||
const char YandexWeatherUsermod::_cfg_key_updateInterval[] PROGMEM = "updateInterval";
|
||||
const char YandexWeatherUsermod::_cfg_key_apiLang[] PROGMEM = "apiLanguage";
|
||||
const char YandexWeatherUsermod::_cfg_key_custom_coord[] PROGMEM = "customCoordinates";
|
||||
const char YandexWeatherUsermod::_cfg_key_apiLat[] PROGMEM = "cityLatitude";
|
||||
const char YandexWeatherUsermod::_cfg_key_apiLon[] PROGMEM = "cityLongitude";
|
||||
const char YandexWeatherUsermod::_cfg_key_showInfo[] PROGMEM = "showInInfo";
|
||||
const char YandexWeatherUsermod::_cfg_key_postMQTT[] PROGMEM = "postToMQTT";
|
||||
#ifndef _MoonModules_WLED_
|
||||
const char YandexWeatherUsermod::_name[] PROGMEM = "YandexWeather";
|
||||
#endif
|
||||
@@ -142,6 +142,7 @@
|
||||
#define USERMOD_ID_WEATHER 91 //Usermod "usermod_v2_weather.h"
|
||||
#define USERMOD_ID_GAMES 92 //Usermod "usermod_v2_games.h"
|
||||
#define USERMOD_ID_ANIMARTRIX 93 //Usermod "usermod_v2_animartrix.h"
|
||||
#define USERMOD_ID_YA_WEATHER 94
|
||||
|
||||
//Access point behavior
|
||||
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
|
||||
|
||||
@@ -216,6 +216,10 @@
|
||||
#include "../usermods/usermod_v2_animartrix/usermod_v2_animartrix.h"
|
||||
#endif
|
||||
|
||||
#ifdef USERMOD_YA_WEATHER
|
||||
#include "../usermods/usermod_v2_yandex_weather/usermod_v2_yandex_weather.h"
|
||||
#endif
|
||||
|
||||
void registerUsermods()
|
||||
{
|
||||
/*
|
||||
@@ -418,4 +422,8 @@ void registerUsermods()
|
||||
usermods.add(new AnimartrixUsermod("Animartrix", false));
|
||||
#endif
|
||||
|
||||
#ifdef USERMOD_YA_WEATHER
|
||||
usermods.add(new YandexWeatherUsermod("YandexWeather", false));
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user