mirror of https://github.com/nymea/nymea.git
update awattar plugin
parent
e151c0f72b
commit
c377d4137f
Binary file not shown.
|
After Width: | Height: | Size: 136 KiB |
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,14 +41,19 @@ public:
|
|||
void deviceRemoved(Device *device) override;
|
||||
void networkManagerReplyReady(QNetworkReply *reply) override;
|
||||
|
||||
void guhTimer() override;
|
||||
|
||||
private:
|
||||
QTimer *m_timer;
|
||||
QHash<QNetworkReply *, Device *> m_asyncSetup;
|
||||
QHash<QNetworkReply *, Device *> m_update;
|
||||
QHash<QNetworkReply *, Device *> m_updatePrice;
|
||||
QHash<QNetworkReply *, Device *> 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:
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}.
|
||||
|
|
|
|||
Loading…
Reference in New Issue