Add openmeteo integration plugin (site + PV plane)
- Weather/solar current conditions from Open-Meteo (Meteo-France model) - Satellite-observed GHI + clear-sky index (MTG) - Per-plane GTI with geographic azimuth convention (180=South) - Self-hosted ready via baseUrl/satelliteBaseUrl plugin settings
This commit is contained in:
parent
957ab589cb
commit
2b2cb19276
@ -47,6 +47,7 @@ PLUGIN_DIRS = \
|
|||||||
notifyevents \
|
notifyevents \
|
||||||
nuki \
|
nuki \
|
||||||
onewire \
|
onewire \
|
||||||
|
openmeteo \
|
||||||
openuv \
|
openuv \
|
||||||
openweathermap \
|
openweathermap \
|
||||||
osdomotics \
|
osdomotics \
|
||||||
|
|||||||
66
openmeteo/CLAUDE_CODE_PROMPT_build_test_deb.md
Normal file
66
openmeteo/CLAUDE_CODE_PROMPT_build_test_deb.md
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# Prompt Claude Code — build, test et packaging du module `openmeteo`
|
||||||
|
|
||||||
|
> À coller dans Claude Code, à la racine du dépôt `etm-powersync-plugins`.
|
||||||
|
> Le module `openmeteo/` existe déjà (`.json`, `.h`, `.cpp`, `.pro`, `meta.json`, `README.md`, `openmeteo.png`).
|
||||||
|
> Lis d'abord `CLAUDE.md` (conventions du repo) et le code existant **avant** de modifier quoi que ce soit.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Faire compiler, tester manuellement, puis packager en `.deb` le plugin d'intégration nymea `openmeteo`, sans en changer le comportement fonctionnel décrit dans `integrationpluginopenmeteo.cpp`.
|
||||||
|
|
||||||
|
## Étape 1 — Intégration au build
|
||||||
|
|
||||||
|
1. Ajouter `openmeteo` à la liste `SUBDIRS` de `nymea-plugins.pro` (ordre alphabétique, comme les autres modules).
|
||||||
|
2. Vérifier que `openmeteo/openmeteo.pro` inclut bien `../plugins.pri` et `QT *= network`.
|
||||||
|
|
||||||
|
## Étape 2 — Compilation et correction des constantes générées
|
||||||
|
|
||||||
|
1. Compiler le module (QMake, dans l'arbre, Qt5 d'abord) :
|
||||||
|
```
|
||||||
|
qmake && make -j$(nproc)
|
||||||
|
```
|
||||||
|
2. `plugininfo.h` est **généré** à partir de `integrationpluginopenmeteo.json` par l'outil nymea. Ne pas l'éditer.
|
||||||
|
3. Le `.cpp` référence des constantes (`openMeteoSite*StateTypeId`, `openMeteoPlugin*ParamTypeId`, `openMeteoPvPlane*`, `dcOpenMeteo`, etc.) déduites de la convention nymea. **Comparer ces noms au `plugininfo.h` réellement généré** et corriger toute divergence **dans le `.cpp`** (jamais dans le header généré). Lister les corrections faites.
|
||||||
|
4. Corriger les éventuels warnings de compilation (variables inutilisées, signedness, etc.).
|
||||||
|
5. Rejouer le build en **Qt6** et corriger les différences éventuelles.
|
||||||
|
|
||||||
|
## Étape 3 — Vérification du point satellite
|
||||||
|
|
||||||
|
Le `.cpp` contient un `TODO(verify)` dans `updateSiteSatellite()` : l'endpoint satellite (`{satelliteBaseUrl}/v1/archive`, modèle `satellite_radiation_seamless`) doit accepter `current=shortwave_radiation,shortwave_radiation_clear_sky`.
|
||||||
|
|
||||||
|
1. Tester l'URL réellement construite (avec une vraie lat/lon, p.ex. 48.9 / 7.85) avec `curl`.
|
||||||
|
2. Si `current=` n'est pas supporté : basculer sur `hourly=` et prendre le dernier échantillon **non nul** (garder le calcul `clearSkyIndex = ghiObs/clearSky`, garde `clearSky > 20`).
|
||||||
|
3. Documenter le comportement retenu en commentaire.
|
||||||
|
|
||||||
|
## Étape 4 — Test manuel (fonctionnel)
|
||||||
|
|
||||||
|
Installer le plugin localement et, via `nymea-cli` ou l'app :
|
||||||
|
|
||||||
|
1. Ajouter un thing **openMeteoSite** (latitude 48.9, longitude 7.85). Vérifier qu'au premier cycle se remplissent : `temperature`, `windSpeed`, `cloudiness`, `humidity`, `pressure`, `weatherCondition`, `ghi`, `dni`, `dhi`, `terrestrialRadiation`, `sunriseTime`, `sunsetTime`, `daylight`, et les daily (`temperatureMin/Max`, `shortwaveRadiationSumToday`).
|
||||||
|
2. Vérifier les states satellite : `ghiObserved`, `clearSkyGhi`, `clearSkyIndex`.
|
||||||
|
3. Ajouter un thing **openMeteoPvPlane** enfant du site (tilt 30, azimuth 180 = plein sud). Vérifier `gtiNow` cohérent (proche du GHI à midi pour un plan ~sud).
|
||||||
|
4. Vérifier la conversion d'azimut : un pan azimuth 90 (Est) doit produire un GTI matinal supérieur à un pan azimuth 270 (Ouest) le matin.
|
||||||
|
5. Contrôle **de nuit** : `daylight=false`, `ghi≈0`, `clearSkyIndex=0`, pas de division par zéro, pas de crash.
|
||||||
|
6. Déclencher l'action `refreshWeather` sur le site et vérifier que site + pans se mettent à jour immédiatement.
|
||||||
|
|
||||||
|
## Étape 5 — Packaging `.deb`
|
||||||
|
|
||||||
|
1. Utiliser les dossiers `debian-qt5` / `debian-qt6` existants du dépôt (le lien `debian -> debian-qt5` est en place).
|
||||||
|
2. Vérifier/compléter le `debian/control` et les fichiers d'install pour inclure le paquet `nymea-plugin-openmeteo` (binaire du plugin + `integrationpluginopenmeteo.json` + ressources).
|
||||||
|
3. Construire le paquet (p.ex. `dpkg-buildpackage -b -us -uc` ou la cible utilisée par le repo) et vérifier que le `.deb` contient bien la librairie du plugin et son métadonnée.
|
||||||
|
4. Indiquer le nom exact du `.deb` produit et son emplacement.
|
||||||
|
|
||||||
|
## Contraintes
|
||||||
|
|
||||||
|
- **Ne pas modifier les autres plugins** de l'arbre.
|
||||||
|
- **Aucune logique propriétaire** (cf. `CLAUDE.md` / frontière de licence) : ce plugin reste une source de données.
|
||||||
|
- Garder le code et les commentaires en anglais (convention nymea-plugins).
|
||||||
|
- Montrer chaque diff avant de l'appliquer ; procéder étape par étape.
|
||||||
|
|
||||||
|
## Livrables attendus
|
||||||
|
|
||||||
|
- Module compilé en Qt5 et Qt6, sans warning.
|
||||||
|
- Liste des corrections de constantes (le cas échéant).
|
||||||
|
- Comportement satellite confirmé/ajusté.
|
||||||
|
- Compte rendu du test manuel (states remplis jour/nuit, GTI cohérent par orientation).
|
||||||
|
- `.deb` `nymea-plugin-openmeteo` produit, nom et emplacement indiqués.
|
||||||
49
openmeteo/README.md
Normal file
49
openmeteo/README.md
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# OpenMeteo
|
||||||
|
|
||||||
|
This plugin allows to get current weather and solar irradiance data from [Open-Meteo](https://open-meteo.com), including plane-of-array irradiance (GTI) for photovoltaic surfaces and satellite-observed clear-sky data.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The data is refreshed every 15 minutes automatically, and can also be refreshed manually.
|
||||||
|
|
||||||
|
A **site** is added by entering its latitude and longitude (no discovery). It provides the shared weather and solar data for the location, including a satellite-observed GHI and clear-sky index.
|
||||||
|
|
||||||
|
One or more **PV planes** can be added as children of a site. Each plane has its own tilt, azimuth and peak power, and reports the global tilted irradiance (GTI) for that orientation. Multiple orientations are handled by adding multiple planes.
|
||||||
|
|
||||||
|
> Note: the azimuth is entered in geographic convention (180 = South, 90 = East, 270 = West).
|
||||||
|
|
||||||
|
By default the plugin queries the public Open-Meteo API. For commercial deployments, the `Base URL` and `Base URL (satellite radiation)` plugin settings can be pointed to a self-hosted Open-Meteo instance (the public API is for non-commercial use only).
|
||||||
|
|
||||||
|
## Supported Things
|
||||||
|
|
||||||
|
* Open-Meteo site (interfaces: weather, daylightsensor)
|
||||||
|
* Weather condition
|
||||||
|
* Temperature (current, daily min and max)
|
||||||
|
* Humidity
|
||||||
|
* Pressure
|
||||||
|
* Wind speed and direction
|
||||||
|
* Cloudiness
|
||||||
|
* Snowfall
|
||||||
|
* Global, direct normal and diffuse irradiance (GHI / DNI / DHI)
|
||||||
|
* Terrestrial (top-of-atmosphere) radiation
|
||||||
|
* Daily shortwave radiation sum
|
||||||
|
* Satellite-observed GHI, clear-sky GHI and clear-sky index
|
||||||
|
* Sunrise and sunset time
|
||||||
|
* Daylight
|
||||||
|
|
||||||
|
* Open-Meteo PV plane
|
||||||
|
* Global tilted irradiance (GTI) for the configured tilt and azimuth
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
* Internet connection, or a reachable self-hosted Open-Meteo instance
|
||||||
|
* The package 'nymea-plugin-openmeteo' must be installed
|
||||||
|
* No API key is required. For commercial use, a self-hosted instance is required (the public Open-Meteo API is non-commercial only).
|
||||||
|
|
||||||
|
## More
|
||||||
|
|
||||||
|
Open-Meteo https://open-meteo.com
|
||||||
|
|
||||||
|
Weather data by Météo-France, satellite radiation by EUMETSAT, provided through Open-Meteo (CC-BY 4.0).
|
||||||
|
|
||||||
|
For the overall architecture (role of this plugin, conventions, license boundary), see the `etm-powersync-docs` repository.
|
||||||
367
openmeteo/integrationpluginopenmeteo.cpp
Normal file
367
openmeteo/integrationpluginopenmeteo.cpp
Normal file
@ -0,0 +1,367 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026, ETM-Schurig SARL
|
||||||
|
*
|
||||||
|
* This file is part of etm-powersync-plugins.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#include "integrationpluginopenmeteo.h"
|
||||||
|
#include "plugininfo.h"
|
||||||
|
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonParseError>
|
||||||
|
#include <QVariantMap>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QUrlQuery>
|
||||||
|
|
||||||
|
#include <integrations/thing.h>
|
||||||
|
#include <network/networkaccessmanager.h>
|
||||||
|
|
||||||
|
// NOTE: the *StateTypeId / *ParamTypeId / *ThingClassId / *ActionTypeId / dcOpenMeteo
|
||||||
|
// identifiers below are generated into plugininfo.h from integrationpluginopenmeteo.json.
|
||||||
|
// If a name does not match, align it with the generated header (do not edit plugininfo.h).
|
||||||
|
|
||||||
|
IntegrationPluginOpenMeteo::IntegrationPluginOpenMeteo()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void IntegrationPluginOpenMeteo::init()
|
||||||
|
{
|
||||||
|
// No API key required. Plugin parameters (baseUrl, satelliteBaseUrl, weatherModel)
|
||||||
|
// are read at request time via configValue().
|
||||||
|
}
|
||||||
|
|
||||||
|
void IntegrationPluginOpenMeteo::setupThing(ThingSetupInfo *info)
|
||||||
|
{
|
||||||
|
Thing *thing = info->thing();
|
||||||
|
|
||||||
|
if (thing->thingClassId() == openMeteoSiteThingClassId) {
|
||||||
|
double lat, lon;
|
||||||
|
if (!resolveCoordinates(thing, lat, lon)) {
|
||||||
|
info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("Missing coordinates for this site."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateSiteForecast(thing);
|
||||||
|
updateSiteSatellite(thing);
|
||||||
|
} else if (thing->thingClassId() == openMeteoPvPlaneThingClassId) {
|
||||||
|
double lat, lon;
|
||||||
|
if (!resolveCoordinates(thing, lat, lon)) {
|
||||||
|
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Parent site not available."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updatePlane(thing);
|
||||||
|
}
|
||||||
|
|
||||||
|
info->finish(Thing::ThingErrorNoError);
|
||||||
|
|
||||||
|
if (!m_pluginTimer) {
|
||||||
|
// 900 s = 15 min, aligned with the OpenWeatherMap reference plugin.
|
||||||
|
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(900);
|
||||||
|
connect(m_pluginTimer, &PluginTimer::timeout, this, [this]() {
|
||||||
|
foreach (Thing *thing, myThings()) {
|
||||||
|
refresh(thing);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IntegrationPluginOpenMeteo::executeAction(ThingActionInfo *info)
|
||||||
|
{
|
||||||
|
Thing *thing = info->thing();
|
||||||
|
|
||||||
|
if (thing->thingClassId() == openMeteoSiteThingClassId
|
||||||
|
&& info->action().actionTypeId() == openMeteoSiteRefreshWeatherActionTypeId) {
|
||||||
|
refresh(thing);
|
||||||
|
// Also refresh the child PV planes of this site.
|
||||||
|
foreach (Thing *child, myThings()) {
|
||||||
|
if (child->thingClassId() == openMeteoPvPlaneThingClassId && child->parentId() == thing->id())
|
||||||
|
updatePlane(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info->finish(Thing::ThingErrorNoError);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IntegrationPluginOpenMeteo::thingRemoved(Thing *thing)
|
||||||
|
{
|
||||||
|
Q_UNUSED(thing)
|
||||||
|
if (myThings().isEmpty() && m_pluginTimer) {
|
||||||
|
hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer);
|
||||||
|
m_pluginTimer = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IntegrationPluginOpenMeteo::refresh(Thing *thing)
|
||||||
|
{
|
||||||
|
if (thing->thingClassId() == openMeteoSiteThingClassId) {
|
||||||
|
updateSiteForecast(thing);
|
||||||
|
updateSiteSatellite(thing);
|
||||||
|
} else if (thing->thingClassId() == openMeteoPvPlaneThingClassId) {
|
||||||
|
updatePlane(thing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IntegrationPluginOpenMeteo::resolveCoordinates(Thing *thing, double &latitude, double &longitude) const
|
||||||
|
{
|
||||||
|
Thing *site = nullptr;
|
||||||
|
if (thing->thingClassId() == openMeteoSiteThingClassId) {
|
||||||
|
site = thing;
|
||||||
|
} else if (thing->thingClassId() == openMeteoPvPlaneThingClassId) {
|
||||||
|
site = myThings().findById(thing->parentId());
|
||||||
|
}
|
||||||
|
if (!site)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
latitude = site->paramValue(openMeteoSiteLatitudeParamTypeId).toDouble();
|
||||||
|
longitude = site->paramValue(openMeteoSiteLongitudeParamTypeId).toDouble();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IntegrationPluginOpenMeteo::updateSiteForecast(Thing *thing)
|
||||||
|
{
|
||||||
|
double lat, lon;
|
||||||
|
if (!resolveCoordinates(thing, lat, lon))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const QString baseUrl = configValue(openMeteoPluginBaseUrlParamTypeId).toString();
|
||||||
|
const QString model = configValue(openMeteoPluginWeatherModelParamTypeId).toString();
|
||||||
|
|
||||||
|
QUrl url(baseUrl + "/v1/forecast");
|
||||||
|
QUrlQuery query;
|
||||||
|
query.addQueryItem("latitude", QString::number(lat));
|
||||||
|
query.addQueryItem("longitude", QString::number(lon));
|
||||||
|
const double elevation = thing->paramValue(openMeteoSiteElevationParamTypeId).toDouble();
|
||||||
|
if (elevation != 0.0)
|
||||||
|
query.addQueryItem("elevation", QString::number(elevation));
|
||||||
|
query.addQueryItem("models", model);
|
||||||
|
query.addQueryItem("wind_speed_unit", "ms");
|
||||||
|
query.addQueryItem("timezone", "Europe/Paris");
|
||||||
|
query.addQueryItem("timeformat", "unixtime");
|
||||||
|
query.addQueryItem("current", "temperature_2m,wind_speed_10m,wind_direction_10m,cloud_cover,"
|
||||||
|
"relative_humidity_2m,surface_pressure,weather_code,snowfall,is_day,"
|
||||||
|
"shortwave_radiation,direct_normal_irradiance,diffuse_radiation,"
|
||||||
|
"terrestrial_radiation");
|
||||||
|
query.addQueryItem("daily", "temperature_2m_max,temperature_2m_min,sunrise,sunset,shortwave_radiation_sum");
|
||||||
|
url.setQuery(query);
|
||||||
|
|
||||||
|
qCDebug(dcOpenMeteo()) << "Requesting forecast for" << thing->name() << url.toString();
|
||||||
|
|
||||||
|
QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(url));
|
||||||
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||||
|
connect(reply, &QNetworkReply::finished, thing, [this, thing, reply]() {
|
||||||
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
qCWarning(dcOpenMeteo()) << "Forecast reply error:" << reply->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QJsonParseError error;
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(reply->readAll(), &error);
|
||||||
|
if (error.error != QJsonParseError::NoError) {
|
||||||
|
qCWarning(dcOpenMeteo()) << "Forecast parse error:" << error.errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QVariantMap map = doc.toVariant().toMap();
|
||||||
|
const QVariantMap current = map.value("current").toMap();
|
||||||
|
const QVariantMap daily = map.value("daily").toMap();
|
||||||
|
|
||||||
|
if (current.contains("time"))
|
||||||
|
thing->setStateValue(openMeteoSiteUpdateTimeStateTypeId, current.value("time").toUInt());
|
||||||
|
if (current.contains("temperature_2m"))
|
||||||
|
thing->setStateValue(openMeteoSiteTemperatureStateTypeId, current.value("temperature_2m").toDouble());
|
||||||
|
if (current.contains("wind_speed_10m"))
|
||||||
|
thing->setStateValue(openMeteoSiteWindSpeedStateTypeId, current.value("wind_speed_10m").toDouble());
|
||||||
|
if (current.contains("wind_direction_10m"))
|
||||||
|
thing->setStateValue(openMeteoSiteWindDirectionStateTypeId, current.value("wind_direction_10m").toInt());
|
||||||
|
if (current.contains("cloud_cover"))
|
||||||
|
thing->setStateValue(openMeteoSiteCloudinessStateTypeId, current.value("cloud_cover").toInt());
|
||||||
|
if (current.contains("relative_humidity_2m"))
|
||||||
|
thing->setStateValue(openMeteoSiteHumidityStateTypeId, current.value("relative_humidity_2m").toInt());
|
||||||
|
if (current.contains("surface_pressure"))
|
||||||
|
thing->setStateValue(openMeteoSitePressureStateTypeId, current.value("surface_pressure").toDouble());
|
||||||
|
if (current.contains("snowfall"))
|
||||||
|
thing->setStateValue(openMeteoSiteSnowfallStateTypeId, current.value("snowfall").toDouble());
|
||||||
|
if (current.contains("shortwave_radiation"))
|
||||||
|
thing->setStateValue(openMeteoSiteGhiStateTypeId, current.value("shortwave_radiation").toDouble());
|
||||||
|
if (current.contains("direct_normal_irradiance"))
|
||||||
|
thing->setStateValue(openMeteoSiteDniStateTypeId, current.value("direct_normal_irradiance").toDouble());
|
||||||
|
if (current.contains("diffuse_radiation"))
|
||||||
|
thing->setStateValue(openMeteoSiteDhiStateTypeId, current.value("diffuse_radiation").toDouble());
|
||||||
|
if (current.contains("terrestrial_radiation"))
|
||||||
|
thing->setStateValue(openMeteoSiteTerrestrialRadiationStateTypeId, current.value("terrestrial_radiation").toDouble());
|
||||||
|
|
||||||
|
bool isDay = current.value("is_day").toInt() == 1;
|
||||||
|
if (current.contains("is_day"))
|
||||||
|
thing->setStateValue(openMeteoSiteDaylightStateTypeId, isDay);
|
||||||
|
if (current.contains("weather_code"))
|
||||||
|
thing->setStateValue(openMeteoSiteWeatherConditionStateTypeId,
|
||||||
|
weatherConditionFromCode(current.value("weather_code").toInt(), isDay));
|
||||||
|
|
||||||
|
// Daily arrays: take index 0 (today).
|
||||||
|
const QVariantList tmax = daily.value("temperature_2m_max").toList();
|
||||||
|
const QVariantList tmin = daily.value("temperature_2m_min").toList();
|
||||||
|
const QVariantList sunrise = daily.value("sunrise").toList();
|
||||||
|
const QVariantList sunset = daily.value("sunset").toList();
|
||||||
|
const QVariantList swSum = daily.value("shortwave_radiation_sum").toList();
|
||||||
|
if (!tmax.isEmpty())
|
||||||
|
thing->setStateValue(openMeteoSiteTemperatureMaxStateTypeId, tmax.first().toDouble());
|
||||||
|
if (!tmin.isEmpty())
|
||||||
|
thing->setStateValue(openMeteoSiteTemperatureMinStateTypeId, tmin.first().toDouble());
|
||||||
|
if (!sunrise.isEmpty())
|
||||||
|
thing->setStateValue(openMeteoSiteSunriseTimeStateTypeId, sunrise.first().toUInt());
|
||||||
|
if (!sunset.isEmpty())
|
||||||
|
thing->setStateValue(openMeteoSiteSunsetTimeStateTypeId, sunset.first().toUInt());
|
||||||
|
if (!swSum.isEmpty())
|
||||||
|
// Open-Meteo shortwave_radiation_sum is in MJ/m^2; expose kWh/m^2 (/ 3.6).
|
||||||
|
thing->setStateValue(openMeteoSiteShortwaveRadiationSumTodayStateTypeId, swSum.first().toDouble() / 3.6);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void IntegrationPluginOpenMeteo::updateSiteSatellite(Thing *thing)
|
||||||
|
{
|
||||||
|
double lat, lon;
|
||||||
|
if (!resolveCoordinates(thing, lat, lon))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const QString satelliteBaseUrl = configValue(openMeteoPluginSatelliteBaseUrlParamTypeId).toString();
|
||||||
|
|
||||||
|
// Observation product (geostationary satellite). Used for the observed GHI and the
|
||||||
|
// native clear-sky -> clear-sky index (kt). Observation only, no forward forecast.
|
||||||
|
// TODO(verify): confirm the satellite endpoint supports "current="; otherwise switch to
|
||||||
|
// "hourly=" and take the latest non-null sample.
|
||||||
|
QUrl url(satelliteBaseUrl + "/v1/archive");
|
||||||
|
QUrlQuery query;
|
||||||
|
query.addQueryItem("latitude", QString::number(lat));
|
||||||
|
query.addQueryItem("longitude", QString::number(lon));
|
||||||
|
query.addQueryItem("models", "satellite_radiation_seamless");
|
||||||
|
query.addQueryItem("timezone", "Europe/Paris");
|
||||||
|
query.addQueryItem("timeformat", "unixtime");
|
||||||
|
query.addQueryItem("current", "shortwave_radiation,shortwave_radiation_clear_sky");
|
||||||
|
url.setQuery(query);
|
||||||
|
|
||||||
|
qCDebug(dcOpenMeteo()) << "Requesting satellite observation for" << thing->name() << url.toString();
|
||||||
|
|
||||||
|
QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(url));
|
||||||
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||||
|
connect(reply, &QNetworkReply::finished, thing, [this, thing, reply]() {
|
||||||
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
qCWarning(dcOpenMeteo()) << "Satellite reply error:" << reply->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QJsonParseError error;
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(reply->readAll(), &error);
|
||||||
|
if (error.error != QJsonParseError::NoError) {
|
||||||
|
qCWarning(dcOpenMeteo()) << "Satellite parse error:" << error.errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QVariantMap current = doc.toVariant().toMap().value("current").toMap();
|
||||||
|
double ghiObs = current.value("shortwave_radiation").toDouble();
|
||||||
|
double clearSky = current.value("shortwave_radiation_clear_sky").toDouble();
|
||||||
|
|
||||||
|
if (current.contains("shortwave_radiation"))
|
||||||
|
thing->setStateValue(openMeteoSiteGhiObservedStateTypeId, ghiObs);
|
||||||
|
if (current.contains("shortwave_radiation_clear_sky"))
|
||||||
|
thing->setStateValue(openMeteoSiteClearSkyGhiStateTypeId, clearSky);
|
||||||
|
|
||||||
|
// Clear-sky index kt = GHI_observed / GHI_clearsky, with a night guard.
|
||||||
|
double kt = 0.0;
|
||||||
|
if (clearSky > 20.0)
|
||||||
|
kt = qBound(0.0, ghiObs / clearSky, 1.2);
|
||||||
|
thing->setStateValue(openMeteoSiteClearSkyIndexStateTypeId, kt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void IntegrationPluginOpenMeteo::updatePlane(Thing *thing)
|
||||||
|
{
|
||||||
|
double lat, lon;
|
||||||
|
if (!resolveCoordinates(thing, lat, lon))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const QString baseUrl = configValue(openMeteoPluginBaseUrlParamTypeId).toString();
|
||||||
|
const QString model = configValue(openMeteoPluginWeatherModelParamTypeId).toString();
|
||||||
|
|
||||||
|
const double tilt = thing->paramValue(openMeteoPvPlaneTiltParamTypeId).toDouble();
|
||||||
|
// Stored azimuth is geographic (180 = South). Open-Meteo expects 0 = South,
|
||||||
|
// -90 = East, +90 = West. Convert here (see ARCHITECTURE.md, conventions).
|
||||||
|
const double azimuthOm = thing->paramValue(openMeteoPvPlaneAzimuthParamTypeId).toDouble() - 180.0;
|
||||||
|
|
||||||
|
QUrl url(baseUrl + "/v1/forecast");
|
||||||
|
QUrlQuery query;
|
||||||
|
query.addQueryItem("latitude", QString::number(lat));
|
||||||
|
query.addQueryItem("longitude", QString::number(lon));
|
||||||
|
query.addQueryItem("models", model);
|
||||||
|
query.addQueryItem("timezone", "Europe/Paris");
|
||||||
|
query.addQueryItem("timeformat", "unixtime");
|
||||||
|
query.addQueryItem("tilt", QString::number(tilt));
|
||||||
|
query.addQueryItem("azimuth", QString::number(azimuthOm));
|
||||||
|
query.addQueryItem("current", "global_tilted_irradiance");
|
||||||
|
url.setQuery(query);
|
||||||
|
|
||||||
|
qCDebug(dcOpenMeteo()) << "Requesting GTI for plane" << thing->name() << url.toString();
|
||||||
|
|
||||||
|
QNetworkReply *reply = hardwareManager()->networkManager()->get(QNetworkRequest(url));
|
||||||
|
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||||
|
connect(reply, &QNetworkReply::finished, thing, [this, thing, reply]() {
|
||||||
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
qCWarning(dcOpenMeteo()) << "GTI reply error:" << reply->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QJsonParseError error;
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(reply->readAll(), &error);
|
||||||
|
if (error.error != QJsonParseError::NoError) {
|
||||||
|
qCWarning(dcOpenMeteo()) << "GTI parse error:" << error.errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QVariantMap current = doc.toVariant().toMap().value("current").toMap();
|
||||||
|
if (current.contains("global_tilted_irradiance"))
|
||||||
|
thing->setStateValue(openMeteoPvPlaneGtiNowStateTypeId, current.value("global_tilted_irradiance").toDouble());
|
||||||
|
if (current.contains("time"))
|
||||||
|
thing->setStateValue(openMeteoPvPlaneUpdateTimeStateTypeId, current.value("time").toUInt());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QString IntegrationPluginOpenMeteo::weatherConditionFromCode(int code, bool isDay) const
|
||||||
|
{
|
||||||
|
// WMO weather interpretation codes -> weatherCondition enum (see metadata possibleValues).
|
||||||
|
switch (code) {
|
||||||
|
case 0:
|
||||||
|
return isDay ? "clear-day" : "clear-night";
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
return isDay ? "few-clouds-day" : "few-clouds-night";
|
||||||
|
case 3:
|
||||||
|
return "overcast";
|
||||||
|
case 45:
|
||||||
|
case 48:
|
||||||
|
return "fog";
|
||||||
|
case 51: case 53: case 55: case 56: case 57:
|
||||||
|
return "light-rain";
|
||||||
|
case 61: case 63: case 65: case 66: case 67:
|
||||||
|
case 80: case 81: case 82:
|
||||||
|
return "shower-rain";
|
||||||
|
case 71: case 73: case 75: case 77:
|
||||||
|
case 85: case 86:
|
||||||
|
return "snow";
|
||||||
|
case 95: case 96: case 99:
|
||||||
|
return "thunderstorm";
|
||||||
|
default:
|
||||||
|
return "clouds";
|
||||||
|
}
|
||||||
|
}
|
||||||
65
openmeteo/integrationpluginopenmeteo.h
Normal file
65
openmeteo/integrationpluginopenmeteo.h
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026, ETM-Schurig SARL
|
||||||
|
*
|
||||||
|
* This file is part of etm-powersync-plugins.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#ifndef INTEGRATIONPLUGINOPENMETEO_H
|
||||||
|
#define INTEGRATIONPLUGINOPENMETEO_H
|
||||||
|
|
||||||
|
#include <integrations/integrationplugin.h>
|
||||||
|
#include <plugintimer.h>
|
||||||
|
|
||||||
|
class IntegrationPluginOpenMeteo : public IntegrationPlugin
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginopenmeteo.json")
|
||||||
|
Q_INTERFACES(IntegrationPlugin)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit IntegrationPluginOpenMeteo();
|
||||||
|
|
||||||
|
void init() override;
|
||||||
|
void setupThing(ThingSetupInfo *info) override;
|
||||||
|
void executeAction(ThingActionInfo *info) override;
|
||||||
|
void thingRemoved(Thing *thing) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
PluginTimer *m_pluginTimer = nullptr;
|
||||||
|
|
||||||
|
// Refresh dispatch
|
||||||
|
void refresh(Thing *thing);
|
||||||
|
|
||||||
|
// Site (parent): two requests — forecast (Meteo-France) + satellite observation (MTG)
|
||||||
|
void updateSiteForecast(Thing *thing);
|
||||||
|
void updateSiteSatellite(Thing *thing);
|
||||||
|
|
||||||
|
// PV plane (child): one GTI request, using the parent site coordinates
|
||||||
|
void updatePlane(Thing *thing);
|
||||||
|
|
||||||
|
// WMO weather_code -> weatherCondition enum value (with -day/-night via isDay)
|
||||||
|
QString weatherConditionFromCode(int code, bool isDay) const;
|
||||||
|
|
||||||
|
// Helper: resolve the site coordinates for a thing (site itself, or a plane's parent)
|
||||||
|
bool resolveCoordinates(Thing *thing, double &latitude, double &longitude) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // INTEGRATIONPLUGINOPENMETEO_H
|
||||||
353
openmeteo/integrationpluginopenmeteo.json
Normal file
353
openmeteo/integrationpluginopenmeteo.json
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
{
|
||||||
|
"name": "OpenMeteo",
|
||||||
|
"displayName": "Open-Meteo",
|
||||||
|
"id": "d1ed979d-b802-463e-a51f-fef79245cfdc",
|
||||||
|
"paramTypes": [
|
||||||
|
{
|
||||||
|
"id": "590cf57d-c1ca-4645-a443-7c5a6c97db93",
|
||||||
|
"name": "baseUrl",
|
||||||
|
"displayName": "Base URL (forecast)",
|
||||||
|
"type": "QString",
|
||||||
|
"defaultValue": "https://api.open-meteo.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6cd410dc-7f1c-4681-8e47-8df525252d2d",
|
||||||
|
"name": "satelliteBaseUrl",
|
||||||
|
"displayName": "Base URL (satellite radiation)",
|
||||||
|
"type": "QString",
|
||||||
|
"defaultValue": "https://satellite-api.open-meteo.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f904db60-527a-47b2-9dc5-371ac5f7e164",
|
||||||
|
"name": "weatherModel",
|
||||||
|
"displayName": "Forecast model",
|
||||||
|
"type": "QString",
|
||||||
|
"defaultValue": "meteofrance_seamless"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"vendors": [
|
||||||
|
{
|
||||||
|
"name": "openMeteo",
|
||||||
|
"displayName": "Open-Meteo",
|
||||||
|
"id": "b1f520ea-dcac-4b16-8cab-89204a225c1b",
|
||||||
|
"thingClasses": [
|
||||||
|
{
|
||||||
|
"id": "59e23cb4-4820-45d4-9c2e-e7bc7b8c7ca6",
|
||||||
|
"name": "openMeteoSite",
|
||||||
|
"displayName": "Open-Meteo site",
|
||||||
|
"interfaces": [
|
||||||
|
"weather",
|
||||||
|
"daylightsensor"
|
||||||
|
],
|
||||||
|
"createMethods": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"paramTypes": [
|
||||||
|
{
|
||||||
|
"id": "cc7f6cde-c3dd-4924-be5e-a014e20df624",
|
||||||
|
"name": "name",
|
||||||
|
"displayName": "Name",
|
||||||
|
"type": "QString",
|
||||||
|
"inputType": "TextLine",
|
||||||
|
"defaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "28b4574a-19ea-41d2-9e58-51f0de11165b",
|
||||||
|
"name": "latitude",
|
||||||
|
"displayName": "Latitude",
|
||||||
|
"type": "double",
|
||||||
|
"defaultValue": 48.9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8bb8ca04-7a28-4cf5-8a4d-1e1081e75c4b",
|
||||||
|
"name": "longitude",
|
||||||
|
"displayName": "Longitude",
|
||||||
|
"type": "double",
|
||||||
|
"defaultValue": 7.85
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "eb16b64a-588f-4d45-b50a-3ca6bc3decc2",
|
||||||
|
"name": "elevation",
|
||||||
|
"displayName": "Elevation (m, optional)",
|
||||||
|
"type": "double",
|
||||||
|
"defaultValue": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"actionTypes": [
|
||||||
|
{
|
||||||
|
"id": "d31b0484-0149-4dcd-92dc-408392cc5ec3",
|
||||||
|
"name": "refreshWeather",
|
||||||
|
"displayName": "Refresh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateTypes": [
|
||||||
|
{
|
||||||
|
"id": "3ac82ea4-b824-4c0c-8ca9-216a1fc9c587",
|
||||||
|
"name": "weatherCondition",
|
||||||
|
"displayName": "Weather condition",
|
||||||
|
"displayNameEvent": "Weather condition changed",
|
||||||
|
"type": "QString",
|
||||||
|
"defaultValue": "clear-day",
|
||||||
|
"possibleValues": [
|
||||||
|
"clear-day",
|
||||||
|
"clear-night",
|
||||||
|
"few-clouds-day",
|
||||||
|
"few-clouds-night",
|
||||||
|
"clouds",
|
||||||
|
"overcast",
|
||||||
|
"light-rain",
|
||||||
|
"shower-rain",
|
||||||
|
"thunderstorm",
|
||||||
|
"snow",
|
||||||
|
"fog"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ed48e3ed-f3fd-4401-9b62-0766d7f5d315",
|
||||||
|
"name": "updateTime",
|
||||||
|
"displayName": "Last update",
|
||||||
|
"displayNameEvent": "Last update changed",
|
||||||
|
"type": "int",
|
||||||
|
"defaultValue": 0,
|
||||||
|
"unit": "UnixTime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b0089fa5-fe4b-4dfc-a1ed-dbd85ca75b20",
|
||||||
|
"name": "temperature",
|
||||||
|
"displayName": "Temperature",
|
||||||
|
"displayNameEvent": "Temperature changed",
|
||||||
|
"type": "double",
|
||||||
|
"defaultValue": 0,
|
||||||
|
"unit": "DegreeCelsius"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "348879d1-0433-416f-a9ce-a05d522a2d26",
|
||||||
|
"name": "temperatureMin",
|
||||||
|
"displayName": "Minimum temperature (today)",
|
||||||
|
"displayNameEvent": "Minimum temperature (today) changed",
|
||||||
|
"type": "double",
|
||||||
|
"defaultValue": 0,
|
||||||
|
"unit": "DegreeCelsius"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6530fa30-d1bf-4f84-9a2f-30f1aa65e130",
|
||||||
|
"name": "temperatureMax",
|
||||||
|
"displayName": "Maximum temperature (today)",
|
||||||
|
"displayNameEvent": "Maximum temperature (today) changed",
|
||||||
|
"type": "double",
|
||||||
|
"defaultValue": 0,
|
||||||
|
"unit": "DegreeCelsius"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "36413dbc-245b-4067-b5dc-41e71b37c0e9",
|
||||||
|
"name": "humidity",
|
||||||
|
"displayName": "Humidity",
|
||||||
|
"displayNameEvent": "Humidity changed",
|
||||||
|
"type": "int",
|
||||||
|
"defaultValue": 0,
|
||||||
|
"unit": "Percentage"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6bf903e5-6c04-492b-bc43-00db60d2edf4",
|
||||||
|
"name": "pressure",
|
||||||
|
"displayName": "Pressure",
|
||||||
|
"displayNameEvent": "Pressure changed",
|
||||||
|
"type": "double",
|
||||||
|
"defaultValue": 0,
|
||||||
|
"unit": "HectoPascal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "41ec4108-3651-4162-b782-c957e14355b9",
|
||||||
|
"name": "windSpeed",
|
||||||
|
"displayName": "Wind speed",
|
||||||
|
"displayNameEvent": "Wind speed changed",
|
||||||
|
"type": "double",
|
||||||
|
"defaultValue": 0,
|
||||||
|
"unit": "MeterPerSecond"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d64aa5ab-0b1a-477d-95a6-11b755154d88",
|
||||||
|
"name": "windDirection",
|
||||||
|
"displayName": "Wind direction",
|
||||||
|
"displayNameEvent": "Wind direction changed",
|
||||||
|
"type": "int",
|
||||||
|
"defaultValue": 0,
|
||||||
|
"unit": "Degree"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "758af353-7e2b-4073-961f-77c5f8bd3b32",
|
||||||
|
"name": "cloudiness",
|
||||||
|
"displayName": "Cloudiness",
|
||||||
|
"displayNameEvent": "Cloudiness changed",
|
||||||
|
"type": "int",
|
||||||
|
"defaultValue": 0,
|
||||||
|
"unit": "Percentage"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "175bf804-9881-4272-a83f-fb81a9a029b3",
|
||||||
|
"name": "snowfall",
|
||||||
|
"displayName": "Snowfall (preceding hour)",
|
||||||
|
"displayNameEvent": "Snowfall (preceding hour) changed",
|
||||||
|
"type": "double",
|
||||||
|
"defaultValue": 0,
|
||||||
|
"unit": "CentiMeter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c8325cc4-de67-4581-a3d7-8248ecbd70e9",
|
||||||
|
"name": "ghi",
|
||||||
|
"displayName": "GHI forecast (W/m2)",
|
||||||
|
"displayNameEvent": "GHI forecast (W/m2) changed",
|
||||||
|
"type": "double",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "0b0c5565-e5c5-4927-8a89-1e6b05c19250",
|
||||||
|
"name": "dni",
|
||||||
|
"displayName": "DNI forecast (W/m2)",
|
||||||
|
"displayNameEvent": "DNI forecast (W/m2) changed",
|
||||||
|
"type": "double",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "67e64a1f-3dfa-46fa-8438-89df0f7f5c2e",
|
||||||
|
"name": "dhi",
|
||||||
|
"displayName": "DHI forecast (W/m2)",
|
||||||
|
"displayNameEvent": "DHI forecast (W/m2) changed",
|
||||||
|
"type": "double",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c9681fca-1020-4138-8d6e-d6d73d5a002e",
|
||||||
|
"name": "terrestrialRadiation",
|
||||||
|
"displayName": "TOA radiation (W/m2)",
|
||||||
|
"displayNameEvent": "TOA radiation (W/m2) changed",
|
||||||
|
"type": "double",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a25a9740-749e-455a-b4e7-60fff3d402f3",
|
||||||
|
"name": "shortwaveRadiationSumToday",
|
||||||
|
"displayName": "GHI sum today (kWh/m2)",
|
||||||
|
"displayNameEvent": "GHI sum today (kWh/m2) changed",
|
||||||
|
"type": "double",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "de0f16e8-8e88-4aa7-b00f-c744e1e13072",
|
||||||
|
"name": "ghiObserved",
|
||||||
|
"displayName": "GHI observed sat (W/m2)",
|
||||||
|
"displayNameEvent": "GHI observed sat (W/m2) changed",
|
||||||
|
"type": "double",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a8d0074b-5696-464b-a9c6-7da16f3cd021",
|
||||||
|
"name": "clearSkyGhi",
|
||||||
|
"displayName": "Clear-sky GHI sat (W/m2)",
|
||||||
|
"displayNameEvent": "Clear-sky GHI sat (W/m2) changed",
|
||||||
|
"type": "double",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c6b6ccb6-a5cb-458a-bb7d-9dd6f818a962",
|
||||||
|
"name": "clearSkyIndex",
|
||||||
|
"displayName": "Clear-sky index kt",
|
||||||
|
"displayNameEvent": "Clear-sky index kt changed",
|
||||||
|
"type": "double",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2c6918d0-9758-4c71-b75b-1f08c3701ddf",
|
||||||
|
"name": "sunriseTime",
|
||||||
|
"displayName": "Sunrise time",
|
||||||
|
"displayNameEvent": "Sunrise time changed",
|
||||||
|
"type": "int",
|
||||||
|
"defaultValue": 0,
|
||||||
|
"unit": "UnixTime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c005cf92-1e29-4033-a65b-578d5d541240",
|
||||||
|
"name": "sunsetTime",
|
||||||
|
"displayName": "Sunset time",
|
||||||
|
"displayNameEvent": "Sunset time changed",
|
||||||
|
"type": "int",
|
||||||
|
"defaultValue": 0,
|
||||||
|
"unit": "UnixTime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "291eea88-0a6d-4ca2-be1c-8a291cfbbdef",
|
||||||
|
"name": "daylight",
|
||||||
|
"displayName": "Daylight",
|
||||||
|
"displayNameEvent": "Daylight changed",
|
||||||
|
"type": "bool",
|
||||||
|
"defaultValue": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fdfb5cb3-e5da-49d1-8d4e-1a316a68061a",
|
||||||
|
"name": "openMeteoPvPlane",
|
||||||
|
"displayName": "Open-Meteo PV plane",
|
||||||
|
"interfaces": [],
|
||||||
|
"createMethods": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"paramTypes": [
|
||||||
|
{
|
||||||
|
"id": "f5c6d83f-32a2-4cf8-85ae-137eb9c60635",
|
||||||
|
"name": "name",
|
||||||
|
"displayName": "Name",
|
||||||
|
"type": "QString",
|
||||||
|
"inputType": "TextLine",
|
||||||
|
"defaultValue": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e2914db5-d5d6-4c7b-9da6-5fba8a49e023",
|
||||||
|
"name": "tilt",
|
||||||
|
"displayName": "Tilt (deg, 0=horizontal)",
|
||||||
|
"type": "double",
|
||||||
|
"minValue": 0,
|
||||||
|
"maxValue": 90,
|
||||||
|
"defaultValue": 30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ecba346a-e5f5-4264-92ab-a92a8fb2cf38",
|
||||||
|
"name": "azimuth",
|
||||||
|
"displayName": "Azimuth (deg geographic, 180=South)",
|
||||||
|
"type": "double",
|
||||||
|
"minValue": 0,
|
||||||
|
"maxValue": 360,
|
||||||
|
"defaultValue": 180
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a8291abd-8106-49a1-868e-8907e16bba8a",
|
||||||
|
"name": "kwp",
|
||||||
|
"displayName": "Peak power kWp",
|
||||||
|
"type": "double",
|
||||||
|
"defaultValue": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateTypes": [
|
||||||
|
{
|
||||||
|
"id": "e0efeb24-d63c-48d7-93d7-34174c9f4e15",
|
||||||
|
"name": "gtiNow",
|
||||||
|
"displayName": "GTI current (W/m2)",
|
||||||
|
"displayNameEvent": "GTI current (W/m2) changed",
|
||||||
|
"type": "double",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "95ed4db9-a231-41bb-b313-78735b63fe9c",
|
||||||
|
"name": "updateTime",
|
||||||
|
"displayName": "Last update",
|
||||||
|
"displayNameEvent": "Last update changed",
|
||||||
|
"type": "int",
|
||||||
|
"defaultValue": 0,
|
||||||
|
"unit": "UnixTime"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
14
openmeteo/meta.json
Normal file
14
openmeteo/meta.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"title": "OpenMeteo",
|
||||||
|
"tagline": "Get current weather data for your location.",
|
||||||
|
"icon": "openmeteo.png",
|
||||||
|
"stability": "consumer",
|
||||||
|
"offline": false,
|
||||||
|
"technologies": [
|
||||||
|
"cloud"
|
||||||
|
],
|
||||||
|
"categories": [
|
||||||
|
"weather",
|
||||||
|
"online-service"
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
openmeteo/openmeteo.png
Normal file
BIN
openmeteo/openmeteo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
9
openmeteo/openmeteo.pro
Normal file
9
openmeteo/openmeteo.pro
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
include(../plugins.pri)
|
||||||
|
|
||||||
|
QT *= network
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
integrationpluginopenmeteo.cpp
|
||||||
|
|
||||||
|
HEADERS += \
|
||||||
|
integrationpluginopenmeteo.h
|
||||||
Loading…
x
Reference in New Issue
Block a user