diff --git a/debian/control b/debian/control
index a732b4fe..ab8e6238 100644
--- a/debian/control
+++ b/debian/control
@@ -729,6 +729,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 f43183e2..b2b754f8 100644
--- a/nymea-plugins.pro
+++ b/nymea-plugins.pro
@@ -55,17 +55,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})
+
+
+
+