From 32b567199d840989602857a6a2edb285faf00cee Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Tue, 13 Dec 2022 17:31:41 +0100 Subject: [PATCH] New Plugin: spot-hinta.fi --- debian/control | 9 + debian/nymea-plugin-spothinta.install.in | 2 + nymea-plugins.pro | 7 +- spothinta/README.md | 4 + spothinta/integrationpluginspothinta.cpp | 180 ++++++++++++++++++ spothinta/integrationpluginspothinta.h | 60 ++++++ spothinta/integrationpluginspothinta.json | 90 +++++++++ spothinta/meta.json | 13 ++ spothinta/spothinta.pro | 11 ++ ...9bf72-b28a-49c8-9889-09752210729d-en_US.ts | 80 ++++++++ 10 files changed, 453 insertions(+), 3 deletions(-) create mode 100644 debian/nymea-plugin-spothinta.install.in create mode 100644 spothinta/README.md create mode 100644 spothinta/integrationpluginspothinta.cpp create mode 100644 spothinta/integrationpluginspothinta.h create mode 100644 spothinta/integrationpluginspothinta.json create mode 100644 spothinta/meta.json create mode 100644 spothinta/spothinta.pro create mode 100644 spothinta/translations/d309bf72-b28a-49c8-9889-09752210729d-en_US.ts diff --git a/debian/control b/debian/control index fb28f9ea..2f25bb0c 100644 --- a/debian/control +++ b/debian/control @@ -711,6 +711,15 @@ Description: nymea integration plugin for Sonos smart speakers This package contains the nymea integration plugin for Sonos smart speakers. +Package: nymea-plugin-spothinta +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends}, +Description: nymea integration plugin for spot-hinta.fi + This package contains the nymea integration plugin to retrieve Finlands + spotmarket energy prices from spot-hinta.fi. + + Package: nymea-plugin-sunposition Architecture: any Depends: ${misc:Depends}, diff --git a/debian/nymea-plugin-spothinta.install.in b/debian/nymea-plugin-spothinta.install.in new file mode 100644 index 00000000..074c4c79 --- /dev/null +++ b/debian/nymea-plugin-spothinta.install.in @@ -0,0 +1,2 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginspothinta.so +spothinta/translations/*qm usr/share/nymea/translations/ diff --git a/nymea-plugins.pro b/nymea-plugins.pro index 094e8882..ec5f7808 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -53,17 +53,18 @@ PLUGIN_DIRS = \ powerfox \ pushbullet \ pushnotifications \ - shelly \ - solarlog \ - systemmonitor \ reversessh \ senic \ serialportcommander \ sgready \ + shelly \ simpleheatpump \ + solarlog \ somfytahoma \ sonos \ + spothinta \ sunposition \ + systemmonitor \ tado \ tasmota \ tcpcommander \ diff --git a/spothinta/README.md b/spothinta/README.md new file mode 100644 index 00000000..432a740b --- /dev/null +++ b/spothinta/README.md @@ -0,0 +1,4 @@ +# spot-hinta + +This integration allows to retrieve the current finnish energy market price from [spot-hinta.fi](https://spot-hinta.fi). + diff --git a/spothinta/integrationpluginspothinta.cpp b/spothinta/integrationpluginspothinta.cpp new file mode 100644 index 00000000..57014bc0 --- /dev/null +++ b/spothinta/integrationpluginspothinta.cpp @@ -0,0 +1,180 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "integrationpluginspothinta.h" +#include "integrations/thing.h" +#include "plugininfo.h" +#include "hardwaremanager.h" +#include "network/networkaccessmanager.h" + +#include +#include +#include + +IntegrationPluginSpotHinta::IntegrationPluginSpotHinta() +{ +} + +IntegrationPluginSpotHinta::~IntegrationPluginSpotHinta() +{ +} + +void IntegrationPluginSpotHinta::setupThing(ThingSetupInfo *info) +{ + qCDebug(dcSpothinta) << "Setup thing" << info->thing()->name() << info->thing()->params(); + + + if (!m_pluginTimer) { + m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(60 * 60); + connect(m_pluginTimer, &PluginTimer::timeout, this, &IntegrationPluginSpotHinta::onPluginTimer); + } + + requestPriceData(info->thing(), info); +} + +void IntegrationPluginSpotHinta::thingRemoved(Thing *thing) +{ + Q_UNUSED(thing) + if (m_pluginTimer && myThings().isEmpty()) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); + m_pluginTimer = nullptr; + } +} + +void IntegrationPluginSpotHinta::onPluginTimer() +{ + foreach (Thing *thing, myThings()) { + requestPriceData(thing); + } +} + +void IntegrationPluginSpotHinta::requestPriceData(Thing* thing, ThingSetupInfo *setup) +{ + QNetworkRequest request(QUrl("https://api.spot-hinta.fi/Today")); + QNetworkReply *reply = hardwareManager()->networkManager()->get(request); + connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater); + connect(reply, &QNetworkReply::finished, thing, [this, reply, thing, setup](){ + + if (reply->error() != QNetworkReply::NoError) { + qCWarning(dcSpothinta()) << "Failed to retrieve spot-hinta market prices:" << reply->error() << reply->errorString(); + if (setup) { + setup->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Error retrieving spot sprices from spot-hinta.fi.")); + } else { + thing->setStateValue(spothintaConnectedStateTypeId, false); + } + return; + } + + QByteArray data = reply->readAll(); + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcSpothinta()) << "Error parsing json from server:" << error.errorString() << qUtf8Printable(data); + if (setup) { + setup->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The server returned unexpected data.")); + } else { + thing->setStateValue(spothintaConnectedStateTypeId, false); + } + return; + } + + if (setup) { + setup->finish(Thing::ThingErrorNoError); + } + + thing->setStateValue(spothintaConnectedStateTypeId, true); + processPriceData(thing, jsonDoc.toVariant()); + }); +} + + +void IntegrationPluginSpotHinta::processPriceData(Thing *thing, const QVariant &data) +{ + + QDateTime currentTime = QDateTime::currentDateTime(); + double sum = 0; + int count = 0; + double currentPrice = 0; + double averagePrice = 0; + int deviation = 0; + double maxPrice = -1000; + double minPrice = 1000; + foreach (QVariant element, data.toList()) { + QVariantMap elementMap = element.toMap(); + QDateTime startTime = QDateTime::fromString(elementMap.value("DateTime").toString(), Qt::ISODate); + QDateTime endTime = startTime.addMSecs(60 * 60 * 1000); + double price = elementMap.value("PriceWithTax").toDouble(); + uint rank = elementMap.value("Rank").toUInt(); + + sum += price; + count++; + + if (price > maxPrice) + maxPrice = price; + + if (price < minPrice) + minPrice = price; + + if (currentTime >= startTime && currentTime <= endTime) { + currentPrice = price; + sum += price; + count++; + + if (price > maxPrice) + maxPrice = price; + + if (price < minPrice) + minPrice = price; + + thing->setStateValue(spothintaCurrentMarketPriceStateTypeId, price); + thing->setStateValue(spothintaValidUntilStateTypeId, endTime.toLocalTime().toTime_t()); + thing->setStateValue(spothintaCurrentRankStateTypeId, rank); + } + } + + // calculate averagePrice and mean deviation + if (count > 0) { + averagePrice = sum / count; + } else { + averagePrice = 0; + } + + if (currentPrice <= averagePrice) { + deviation = -1 * qRound(100 + (-100 * (currentPrice - minPrice) / (averagePrice - minPrice))); + } else { + deviation = qRound(-100 * (averagePrice - currentPrice) / (maxPrice - averagePrice)); + } + + thing->setStateValue(spothintaAveragePriceStateTypeId, averagePrice); + thing->setStateValue(spothintaLowestPriceStateTypeId, minPrice); + thing->setStateValue(spothintaHighestPriceStateTypeId, maxPrice); + thing->setStateValue(spothintaAverageDeviationStateTypeId, deviation); +} + diff --git a/spothinta/integrationpluginspothinta.h b/spothinta/integrationpluginspothinta.h new file mode 100644 index 00000000..a1f88c23 --- /dev/null +++ b/spothinta/integrationpluginspothinta.h @@ -0,0 +1,60 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2022, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef INTEGRATIONPLUGINSPOTHINTA_H +#define INTEGRATIONPLUGINSPOTHINTA_H + +#include "integrations/integrationplugin.h" +#include "plugintimer.h" +#include "extern-plugininfo.h" + +class IntegrationPluginSpotHinta : public IntegrationPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginspothinta.json") + Q_INTERFACES(IntegrationPlugin) + +public: + explicit IntegrationPluginSpotHinta(); + ~IntegrationPluginSpotHinta(); + + void setupThing(ThingSetupInfo *info) override; + void thingRemoved(Thing *thing) override; + +private slots: + void onPluginTimer(); + void requestPriceData(Thing* thing, ThingSetupInfo *setup = nullptr); + void processPriceData(Thing *thing, const QVariant &data); + +private: + PluginTimer *m_pluginTimer = nullptr; +}; + +#endif // INTEGRATIONPLUGINSPOTHINTA_H diff --git a/spothinta/integrationpluginspothinta.json b/spothinta/integrationpluginspothinta.json new file mode 100644 index 00000000..41b9b18f --- /dev/null +++ b/spothinta/integrationpluginspothinta.json @@ -0,0 +1,90 @@ +{ + "displayName": "spot-hinta", + "name": "spothinta", + "id": "d309bf72-b28a-49c8-9889-09752210729d", + "vendors": [ + { + "displayName": "spot-hinta", + "name": "spothinta", + "id": "5a7a9eeb-7b11-44a8-97c6-8a7f3dfc27b2", + "thingClasses": [ + { + "id": "11ac3def-e99d-466a-94a5-b68d8af97d8b", + "displayName": "spot-hinta", + "name": "spothinta", + "createMethods": ["user"], + "setupMethod": "justAdd", + "interfaces": ["connectable"], + "stateTypes": [ + { + "id": "e778644e-bc4a-4864-9f9c-e5c374121f9e", + "name": "connected", + "displayName": "Online", + "type": "bool", + "defaultValue": false, + "cached": false + }, + { + "id": "067c4bbb-edb9-4dbb-b8fc-c3a57d529850", + "name": "currentMarketPrice", + "displayName": "Current market price", + "type": "double", + "unit": "EuroCentPerKiloWattHour", + "defaultValue": 0, + "suggestLogging": true + }, + { + "id": "6475f494-eebb-4dcc-8617-61a3e7e18871", + "name": "currentRank", + "displayName": "Current rank", + "type": "uint", + "minValue": 1, + "maxValue": 24, + "defaultValue": 12 + }, + { + "id": "a52af483-e0d0-4a3e-8e12-481836730f9b", + "name": "validUntil", + "displayName": "Valid until", + "unit": "UnixTime", + "type": "int", + "defaultValue": 0 + }, + { + "id": "2835aea6-61e3-4e0f-a65b-5e2d9988bde1", + "name": "averagePrice", + "displayName": "Average market price today", + "type": "double", + "unit": "EuroCentPerKiloWattHour", + "defaultValue": 0 + }, + { + "id": "a0cf5dc9-b9de-4566-b297-3ff26b8ac888", + "name": "lowestPrice", + "displayName": "Lowest market price today", + "type": "double", + "unit": "EuroCentPerKiloWattHour", + "defaultValue": 0 + }, + { + "id": "ab03aa5c-5097-44b1-b810-11d78c8d1c18", + "name": "highestPrice", + "displayName": "Highest market price today", + "type": "double", + "unit": "EuroCentPerKiloWattHour", + "defaultValue": 0 + }, + { + "id": "72ba6521-97fe-45dc-9dbc-34509fb29c4f", + "name": "averageDeviation", + "displayName": "Average deviation", + "type": "int", + "unit": "Percentage", + "defaultValue": 0 + } + ] + } + ] + } + ] +} diff --git a/spothinta/meta.json b/spothinta/meta.json new file mode 100644 index 00000000..e18531a7 --- /dev/null +++ b/spothinta/meta.json @@ -0,0 +1,13 @@ +{ + "title": "spot-hinta", + "tagline": "Get Findlands current energy price from spot-hinta.fi.", + "icon": "", + "stability": "consumer", + "offline": false, + "technologies": [ + "cloud" + ], + "categories": [ + "online-service" + ] +} diff --git a/spothinta/spothinta.pro b/spothinta/spothinta.pro new file mode 100644 index 00000000..b38e3031 --- /dev/null +++ b/spothinta/spothinta.pro @@ -0,0 +1,11 @@ +include(../plugins.pri) + +QT += network + +SOURCES += \ + integrationpluginspothinta.cpp + +HEADERS += \ + integrationpluginspothinta.h + + diff --git a/spothinta/translations/d309bf72-b28a-49c8-9889-09752210729d-en_US.ts b/spothinta/translations/d309bf72-b28a-49c8-9889-09752210729d-en_US.ts new file mode 100644 index 00000000..8c07c3bc --- /dev/null +++ b/spothinta/translations/d309bf72-b28a-49c8-9889-09752210729d-en_US.ts @@ -0,0 +1,80 @@ + + + + + IntegrationPluginSpotHinta + + + Error retrieving spot sprices from spot-hinta.fi. + + + + + The server returned unexpected data. + + + + + spothinta + + + Average deviation + The name of the StateType ({72ba6521-97fe-45dc-9dbc-34509fb29c4f}) of ThingClass spothinta + + + + + Average market price today + The name of the StateType ({2835aea6-61e3-4e0f-a65b-5e2d9988bde1}) of ThingClass spothinta + + + + + Current market price + The name of the StateType ({067c4bbb-edb9-4dbb-b8fc-c3a57d529850}) of ThingClass spothinta + + + + + Current rank + The name of the StateType ({6475f494-eebb-4dcc-8617-61a3e7e18871}) of ThingClass spothinta + + + + + Highest market price today + The name of the StateType ({ab03aa5c-5097-44b1-b810-11d78c8d1c18}) of ThingClass spothinta + + + + + Lowest market price today + The name of the StateType ({a0cf5dc9-b9de-4566-b297-3ff26b8ac888}) of ThingClass spothinta + + + + + Online + The name of the StateType ({e778644e-bc4a-4864-9f9c-e5c374121f9e}) of ThingClass spothinta + + + + + Valid until + The name of the StateType ({a52af483-e0d0-4a3e-8e12-481836730f9b}) of ThingClass spothinta + + + + + + + spot-hinta + The name of the ThingClass ({11ac3def-e99d-466a-94a5-b68d8af97d8b}) +---------- +The name of the vendor ({5a7a9eeb-7b11-44a8-97c6-8a7f3dfc27b2}) +---------- +The name of the plugin spothinta ({d309bf72-b28a-49c8-9889-09752210729d}) + + + +