diff --git a/doc/images/awattar-graph.png b/doc/images/awattar-graph.png new file mode 100644 index 00000000..149922f2 Binary files /dev/null and b/doc/images/awattar-graph.png differ diff --git a/plugins/deviceplugins/awattar/devicepluginawattar.cpp b/plugins/deviceplugins/awattar/devicepluginawattar.cpp index 6e0df105..8901b3a6 100644 --- a/plugins/deviceplugins/awattar/devicepluginawattar.cpp +++ b/plugins/deviceplugins/awattar/devicepluginawattar.cpp @@ -29,7 +29,24 @@ In order to use this plugin you need to enter the access token from your energy provider. You can find more information about you accesstoken \l{https://www.awattar.com/api-unser-datenfeed}{here}. - The data will be updated every hour. The API allows a maximum of 100 calls per day. + The pricing data will be updated every hour. + + \chapter Available data + + In following chart you can see an example of the market prices from -12 hours to + 12 hours from the current + time (0).The green line describes the current market price, the red point line describes the average + price of this interval and the red line describes the deviation. If the deviation is positiv, the current + price is above the average, if the deviation is negative, the current price is below the average. + + \list + \li -100 % \unicode{0x2192} current price equals lowest price in the interval [-12h < now < + 12h] + \li 0 % \unicode{0x2192} current price equals average price in the interval [-12h < now < + 12h] + \li +100 % \unicode{0x2192} current price equals highest price in the interval [-12h < now < + 12h] + \endlist + + \image awattar-graph.png + + Information about the smart grid modes can be found \l{https://www.waermepumpe.de/sg-ready/}{here}. \chapter Plugin properties Following JSON file contains the definition and the description of all available \l{DeviceClass}{DeviceClasses} @@ -57,16 +74,11 @@ DevicePluginAwattar::DevicePluginAwattar() { - m_timer = new QTimer(this); - m_timer->setSingleShot(false); - m_timer->setInterval(60000); - - connect(m_timer, &QTimer::timeout, this, &DevicePluginAwattar::onTimeout); } DeviceManager::HardwareResources DevicePluginAwattar::requiredHardware() const { - return DeviceManager::HardwareResourceNetworkManager; + return DeviceManager::HardwareResourceNetworkManager | DeviceManager::HardwareResourceTimer; } DeviceManager::DeviceSetupStatus DevicePluginAwattar::setupDevice(Device *device) @@ -74,7 +86,7 @@ DeviceManager::DeviceSetupStatus DevicePluginAwattar::setupDevice(Device *device QString token = device->paramValue("token").toString(); qCDebug(dcAwattar) << "Setup device with token" << token; - QNetworkReply *reply = requestData(token); + QNetworkReply *reply = requestPriceData(token); m_asyncSetup.insert(reply, device); return DeviceManager::DeviceSetupStatusAsync; @@ -83,10 +95,6 @@ DeviceManager::DeviceSetupStatus DevicePluginAwattar::setupDevice(Device *device void DevicePluginAwattar::deviceRemoved(Device *device) { Q_UNUSED(device) - - if (myDevices().isEmpty()) { - m_timer->stop(); - } } void DevicePluginAwattar::networkManagerReplyReady(QNetworkReply *reply) @@ -115,9 +123,13 @@ void DevicePluginAwattar::networkManagerReplyReady(QNetworkReply *reply) return; } - processData(device, jsonDoc.toVariant().toMap(), true); - } else if (m_update.keys().contains(reply)) { - Device *device = m_update.take(reply); + processPriceData(device, jsonDoc.toVariant().toMap(), true); + + QNetworkReply *userReply = requestUserData(device->paramValue("token").toString(), device->paramValue("user id").toString()); + m_updateUserData.insert(userReply, device); + + } else if (m_updatePrice.keys().contains(reply)) { + Device *device = m_updatePrice.take(reply); // check HTTP status code if (status != 200) { @@ -135,12 +147,44 @@ void DevicePluginAwattar::networkManagerReplyReady(QNetworkReply *reply) return; } - processData(device, jsonDoc.toVariant().toMap()); + processPriceData(device, jsonDoc.toVariant().toMap()); + + QNetworkReply *userReply = requestUserData(device->paramValue("token").toString(), device->paramValue("user id").toString()); + m_updateUserData.insert(userReply, device); + + } else if (m_updateUserData.keys().contains(reply)) { + Device *device = m_updateUserData.take(reply); + + // check HTTP status code + if (status != 200) { + qCWarning(dcAwattar) << "Update user data reply HTTP error:" << status << reply->errorString(); + reply->deleteLater(); + return; + } + + // check JSON file + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll(), &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcAwattar) << "Update user data reply JSON error:" << error.errorString(); + reply->deleteLater(); + return; + } + + processUserData(device, jsonDoc.toVariant().toMap()); } reply->deleteLater(); } -void DevicePluginAwattar::processData(Device *device, const QVariantMap &data, const bool &fromSetup) +void DevicePluginAwattar::guhTimer() +{ + foreach (Device *device, myDevices()) { + qCDebug(dcAwattar) << "Update device" << device->id().toString(); + updateDevice(device); + } +} + +void DevicePluginAwattar::processPriceData(Device *device, const QVariantMap &data, const bool &fromSetup) { if (!data.contains("data")) { if (fromSetup) { @@ -155,28 +199,111 @@ void DevicePluginAwattar::processData(Device *device, const QVariantMap &data, c QVariantList dataElements = data.value("data").toList(); QDateTime currentTime = QDateTime::currentDateTime(); + double sum = 0; + double count = 0; + double averagePrice = 0; + double currentPrice = 0; + int deviation = 0; + double maxPrice = -1000; + double minPrice = 1000; foreach (QVariant element, dataElements) { QVariantMap elementMap = element.toMap(); QDateTime startTime = QDateTime::fromMSecsSinceEpoch((qint64)elementMap.value("start_timestamp").toLongLong()); QDateTime endTime = QDateTime::fromMSecsSinceEpoch((qint64)elementMap.value("end_timestamp").toLongLong()); - double marketPrice = elementMap.value("marketprice").toDouble(); + currentPrice = elementMap.value("marketprice").toDouble(); + + // check interval [-12h < x < + 12h] + if ((startTime >= currentTime.addSecs(-(3600 * 12)) && endTime <= currentTime ) || + (endTime <= currentTime.addSecs(3600 * 12) && startTime >= currentTime )) { + //qCDebug(dcAwattar) << " - " << startTime.toString() << " - " << endTime.toString() << " : " << currentPrice; + sum += currentPrice; + count++; + + if (currentPrice > maxPrice) + maxPrice = currentPrice; + + if (currentPrice < minPrice) + minPrice = currentPrice; + } + if (currentTime >= startTime && currentTime <= endTime) { - qCDebug(dcAwattar) << "---------------------------------------"; - qCDebug(dcAwattar) << "start :" << startTime.toString(); - qCDebug(dcAwattar) << "end :" << endTime.toString(); - qCDebug(dcAwattar) << "price :" << marketPrice << elementMap.value("unit").toString(); - device->setStateValue(currentMarketPriceStateTypeId, marketPrice); + qCDebug(dcAwattar) << startTime.toString() << " -> " << endTime.toString(); + device->setStateValue(currentMarketPriceStateTypeId, currentPrice); device->setStateValue(validUntilStateTypeId, endTime.toLocalTime().toTime_t()); } } - if (fromSetup) { - m_timer->start(); + // calculate averagePrice and mean deviation + averagePrice = sum / count; + if (currentPrice <= averagePrice) { + deviation = -1 * qRound(100 + (-100 * (currentPrice - minPrice) / (averagePrice - minPrice))); + } else { + deviation = qRound(-100 * (averagePrice - currentPrice) / (maxPrice - averagePrice)); + } + + qCDebug(dcAwattar) << " price :" << currentPrice << "Eur/MWh"; + qCDebug(dcAwattar) << " average :" << averagePrice << "Eur/MWh"; + qCDebug(dcAwattar) << " deviation:" << deviation << "%"; + qCDebug(dcAwattar) << " min :" << minPrice << "Eur/MWh"; + qCDebug(dcAwattar) << " max :" << maxPrice << "Eur/MWh"; + + device->setStateValue(averagePriceStateTypeId, averagePrice); + device->setStateValue(lowestPriceStateTypeId, minPrice); + device->setStateValue(highestPriceStateTypeId, maxPrice); + device->setStateValue(meanDeviationStateTypeId, deviation); + + + if (fromSetup) emit deviceSetupFinished(device, DeviceManager::DeviceSetupStatusSuccess); + +} + +void DevicePluginAwattar::processUserData(Device *device, const QVariantMap &data) +{ + QVariantList dataElements = data.value("data").toList(); + + QDateTime currentTime = QDateTime::currentDateTime(); + foreach (QVariant element, dataElements) { + QVariantMap elementMap = element.toMap(); + QDateTime startTime = QDateTime::fromMSecsSinceEpoch((qint64)elementMap.value("start_timestamp").toLongLong()); + QDateTime endTime = QDateTime::fromMSecsSinceEpoch((qint64)elementMap.value("end_timestamp").toLongLong()); + + // check if we are in the current interval + if (currentTime >= startTime && currentTime <= endTime) { + //qCDebug(dcAwattar) << QJsonDocument::fromVariant(elementMap).toJson(); + int sgMode = 0; + if (elementMap.contains("data")) { + if (elementMap.value("data").toMap().contains("sg-mode")) { + sgMode = elementMap.value("data").toMap().value("sg-mode").toInt(); + } + } + + //qCDebug(dcAwattar) << startTime.toString() << " -> " << endTime.toString(); + qCDebug(dcAwattar) << "sg-mode:" << sgMode; + + switch (sgMode) { + case 1: + device->setStateValue(sgModeStateTypeId, "1 - Off"); + break; + case 2: + device->setStateValue(sgModeStateTypeId, "2 - Normal"); + break; + case 3: + device->setStateValue(sgModeStateTypeId, "3 - High Temperature"); + break; + case 4: + device->setStateValue(sgModeStateTypeId, "4 - On"); + break; + default: + break; + } + + // todo: send sg mode to 6LoWPAN node + } } } -QNetworkReply *DevicePluginAwattar::requestData(const QString &token) +QNetworkReply *DevicePluginAwattar::requestPriceData(const QString &token) { QByteArray data = QString(token + ":").toUtf8().toBase64(); QString header = "Basic " + data; @@ -186,20 +313,19 @@ QNetworkReply *DevicePluginAwattar::requestData(const QString &token) return networkManagerGet(request); } +QNetworkReply *DevicePluginAwattar::requestUserData(const QString &token, const QString &userId) +{ + QByteArray data = QString(token + ":").toUtf8().toBase64(); + QString header = "Basic " + data; + + QNetworkRequest request(QUrl(QString("https://api.awattar.com/v1/devices/%1/actuators").arg(userId))); + request.setRawHeader("Authorization", header.toLocal8Bit()); + request.setSslConfiguration(QSslConfiguration::defaultConfiguration()); + return networkManagerGet(request); +} + void DevicePluginAwattar::updateDevice(Device *device) { - QNetworkReply *reply = requestData(device->paramValue("token").toString()); - m_update.insert(reply, device); + QNetworkReply *priceReply = requestPriceData(device->paramValue("token").toString()); + m_updatePrice.insert(priceReply, device); } - -void DevicePluginAwattar::onTimeout() -{ - // check every hour - if(QDateTime::currentDateTime().time().minute() == 0) { - foreach (Device *device, myDevices()) { - qCDebug(dcAwattar) << "Update device" << device->id().toString(); - updateDevice(device); - } - } -} - diff --git a/plugins/deviceplugins/awattar/devicepluginawattar.h b/plugins/deviceplugins/awattar/devicepluginawattar.h index 0cc708bb..68c580d0 100644 --- a/plugins/deviceplugins/awattar/devicepluginawattar.h +++ b/plugins/deviceplugins/awattar/devicepluginawattar.h @@ -41,14 +41,19 @@ public: void deviceRemoved(Device *device) override; void networkManagerReplyReady(QNetworkReply *reply) override; + void guhTimer() override; + private: - QTimer *m_timer; QHash m_asyncSetup; - QHash m_update; + QHash m_updatePrice; + QHash m_updateUserData; - void processData(Device *device, const QVariantMap &data, const bool &fromSetup = false); + void processPriceData(Device *device, const QVariantMap &data, const bool &fromSetup = false); + void processUserData(Device *device, const QVariantMap &data); + + QNetworkReply *requestPriceData(const QString& token); + QNetworkReply *requestUserData(const QString& token, const QString &userId); - QNetworkReply *requestData(const QString& token); void updateDevice(Device *device); private slots: diff --git a/plugins/deviceplugins/awattar/devicepluginawattar.json b/plugins/deviceplugins/awattar/devicepluginawattar.json index 54c63069..4ba7fd52 100644 --- a/plugins/deviceplugins/awattar/devicepluginawattar.json +++ b/plugins/deviceplugins/awattar/devicepluginawattar.json @@ -23,7 +23,12 @@ "name": "name", "type": "QString", "inputType": "TextLine", - "defaultValue": "Energy price" + "defaultValue": "Heating system" + }, + { + "name": "user id", + "type": "QString", + "inputType": "TextLine" }, { "name": "token", @@ -40,6 +45,14 @@ "unit": "EuroPerMegaWattHour", "defaultValue": 0 }, + { + "id": "38b86cee-9588-4269-a585-128907929dc2", + "idName": "meanDeviation", + "name": "deviation", + "type": "double", + "unit": "Percentage", + "defaultValue": 0 + }, { "id": "d5a8a176-aca0-45b1-b043-95c43750f383", "idName": "validUntil", @@ -47,6 +60,43 @@ "unit": "UnixTime", "type": "int", "defaultValue": 0 + }, + { + "id": "55d6d7a8-446f-48ae-8014-1225810d03ee", + "idName": "averagePrice", + "name": "average market price [± 12 h]", + "type": "double", + "unit": "EuroPerMegaWattHour", + "defaultValue": 0 + }, + { + "id": "e7af5bdc-48d7-4e96-b877-331da4dcfae5", + "idName": "lowestPrice", + "name": "lowest market price [± 12 h]", + "type": "double", + "unit": "EuroPerMegaWattHour", + "defaultValue": 0 + }, + { + "id": "0c171c42-b070-453e-8a63-df9aebfa8533", + "idName": "highestPrice", + "name": " highest market price [± 12 h]", + "type": "double", + "unit": "EuroPerMegaWattHour", + "defaultValue": 0 + }, + { + "id": "b83d3533-aeae-4a9b-95d8-28466bf6c0cf", + "idName": "sgMode", + "name": "sg mode", + "type": "QString", + "possibleValues": [ + "1 - Off", + "2 - Normal", + "3 - High Temperature", + "4 - On" + ], + "defaultValue": "1 - Off" } ] } diff --git a/plugins/deviceplugins/datetime/deviceplugindatetime.cpp b/plugins/deviceplugins/datetime/deviceplugindatetime.cpp index cd4e4940..5be8b5df 100644 --- a/plugins/deviceplugins/datetime/deviceplugindatetime.cpp +++ b/plugins/deviceplugins/datetime/deviceplugindatetime.cpp @@ -79,7 +79,6 @@ \chapter Countdown The countdown plugin allowes you to define a countown which triggers an \l{Event} on timeout. - \chapter Plugin properties Following JSON file contains the definition and the description of all available \l{DeviceClass}{DeviceClasses} and \l{Vendor}{Vendors} of this \l{DevicePlugin}.