New plugin: Senec: Add support for SENEC.home P4 energy storages

This commit is contained in:
Simon Stürz 2025-07-08 17:08:11 +02:00
parent 2575f43518
commit e9a0fe1f08
14 changed files with 1010 additions and 0 deletions

11
debian/control vendored
View File

@ -703,6 +703,7 @@ Description: nymea integration plugin for TCP commander
This package contains the nymea integration plugin for sending arbitrary
TCP packets from and to nymea.
Package: nymea-plugin-httpcommander
Architecture: any
Depends: ${shlibs:Depends},
@ -713,6 +714,16 @@ Description: nymea integration plugin for HTTP commander
HTTP requests from and to nymea.
Package: nymea-plugin-senec
Architecture: any
Depends: ${shlibs:Depends},
${misc:Depends},
Conflicts: nymea-plugins-translations (<< 1.0.1)
Description: nymea integration plugin for SENEC.home
This package contains the nymea integration plugin for the SENEC.home
energy storages.
Package: nymea-plugin-senic
Architecture: any
Depends: ${shlibs:Depends},

2
debian/nymea-plugin-senec.install.in vendored Normal file
View File

@ -0,0 +1,2 @@
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginsenec.so
senec/translations/*qm usr/share/nymea/translations/

View File

@ -60,6 +60,7 @@ PLUGIN_DIRS = \
pushbullet \
pushnotifications \
reversessh \
senec \
senic \
serialportcommander \
sgready \

11
senec/README.md Normal file
View File

@ -0,0 +1,11 @@
# SENEC.Home
Connects to the SENEC cloud and integrates the energy storages into the system.
Currently supported and tested models:
* SENEC.Home P4
## More
https://senec.com

View File

@ -0,0 +1,328 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2025, 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 <https://www.gnu.org/licenses/>.
*
* 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 "integrationpluginsenec.h"
#include "plugininfo.h"
#include <QJsonDocument>
#include <QJsonParseError>
IntegrationPluginSenec::IntegrationPluginSenec()
{
}
IntegrationPluginSenec::~IntegrationPluginSenec()
{
}
void IntegrationPluginSenec::startPairing(ThingPairingInfo *info)
{
info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Please enter username and password for your SENEC.home account."));
}
void IntegrationPluginSenec::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret)
{
qCDebug(dcSenec()) << "Start logging in" << username << secret.left(2) + QString(secret.length() - 2, '*');
QVariantMap requestMap;
requestMap.insert("username", username);
requestMap.insert("password", secret);
QNetworkRequest request(SenecAccount::loginUrl());
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QJsonDocument::fromVariant(requestMap).toJson(QJsonDocument::Indented));
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, this, [reply, info, username, this] {
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
qCWarning(dcSenec()) << "Login request finished with error. Status:" << status << "Error:" << reply->errorString();
info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Username or password is invalid."));
return;
}
// Note: as of now (API 4.4.3) the login seems to return a static token, which does not require any refresh.
// Not as bad as saving user and password on the device, but almost ...
// https://documenter.getpostman.com/view/932140/2s9YXib2td
QByteArray responseData = reply->readAll();
QJsonParseError jsonError;
QVariantMap responseMap = QJsonDocument::fromJson(responseData, &jsonError).toVariant().toMap();
if (jsonError.error != QJsonParseError::NoError) {
qCWarning(dcSenec()) << "Login request finished successfully, but the response contains invalid JSON object:" << responseData;
info->finish(Thing::ThingErrorAuthenticationFailure);
return;
}
if (!responseMap.contains("token") || !responseMap.contains("refreshToken")) {
qCWarning(dcSenec()) << "Login request finished successfully, but the response JSON does not contain the expected properties" << qUtf8Printable(responseData);
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
QString token = responseMap.value("token").toString();
QString refreshToken = responseMap.value("refreshToken").toString();
pluginStorage()->beginGroup(info->thingId().toString());
pluginStorage()->setValue("username", username);
pluginStorage()->setValue("token", token);
pluginStorage()->setValue("refreshToken", refreshToken);
pluginStorage()->endGroup();
info->finish(Thing::ThingErrorNoError);
});
}
void IntegrationPluginSenec::setupThing(ThingSetupInfo *info)
{
Thing *thing = info->thing();
qCDebug(dcSenec()) << "Setting up" << thing->name() << thing->params();
if (thing->thingClassId() == senecAccountThingClassId) {
// Load login information
pluginStorage()->beginGroup(thing->id().toString());
QString token = pluginStorage()->value("token").toString();
QString refreshToken = pluginStorage()->value("refreshToken").toString();
QString username = pluginStorage()->value("username").toString();
pluginStorage()->endGroup();
SenecAccount *account = new SenecAccount(hardwareManager()->networkManager(), username, token, refreshToken, this);
m_accounts.insert(thing, account);
info->finish(Thing::ThingErrorNoError);
thing->setStateValue(senecAccountUserDisplayNameStateTypeId, username);
} else if (thing->thingClassId() == senecStorageThingClassId) {
info->finish(Thing::ThingErrorNoError);
}
}
void IntegrationPluginSenec::postSetupThing(Thing *thing)
{
if (thing->thingClassId() == senecAccountThingClassId) {
// Search for now things, first poll the
SenecAccount *account = m_accounts.value(thing);
// Check installation, create things if not already created
QNetworkReply *reply = account->getSystems();
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, account, [reply, thing, this] {
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
qCWarning(dcSenec()) << "Systems request finished with error. Status:" << status << "Error:" << reply->errorString();
if (status == 401) {
qCWarning(dcSenec()) << "Authentication error, reconfigure and re-login should fix this problem.";
thing->setStateValue(senecAccountLoggedInStateTypeId, false);
}
thing->setStateValue(senecAccountConnectedStateTypeId, false);
return;
}
QByteArray responseData = reply->readAll();
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData, &jsonError);
QVariant responseVariant = jsonDoc.toVariant();
if (jsonError.error != QJsonParseError::NoError) {
qCWarning(dcSenec()) << "Systems request finished successfully, but the response contains invalid JSON object:" << responseData;
thing->setStateValue(senecAccountConnectedStateTypeId, false);
return;
}
qCDebug(dcSenec()) << "Systems request finished successfully" << qUtf8Printable(jsonDoc.toJson());
thing->setStateValue(senecAccountLoggedInStateTypeId, true);
thing->setStateValue(senecAccountConnectedStateTypeId, true);
ThingDescriptors descriptors;
foreach (const QVariant &installation, responseVariant.toList()) {
QVariantMap installationMap = installation.toMap();
// Note: for now we only support V4 systems
if (!installationMap.value("systemType").toString().toLower().contains("v4"))
continue;
// This is a V4 storage, let's check if we already created one
QString id = installationMap.value("id").toString();
Things existingThings = myThings().filterByThingClassId(senecStorageThingClassId).filterByParam(senecStorageThingIdParamTypeId, id);
if (existingThings.isEmpty()) {
qCDebug(dcSenec()) << "Creating new storage for" << id;
ThingDescriptor descriptor(senecStorageThingClassId, "SENEC.Home P4", id);
descriptor.setParentId(thing->id());
ParamList params;
params.append(Param(senecStorageThingIdParamTypeId, id));
descriptor.setParams(params);
descriptors.append(descriptor);
} else {
qCDebug(dcSenec()) << "Thing for storage" << id << "already created.";
}
}
if (!descriptors.isEmpty()) {
qCDebug(dcSenec()) << "Adding" << descriptors.count() << "new SENEC.Home" << (descriptors.count() > 1 ? "storages" : "storage");
emit autoThingsAppeared(descriptors);
}
});
} else if (thing->thingClassId() == senecStorageThingClassId) {
SenecAccount *account = m_accounts.value(myThings().findById(thing->parentId()));
QString id = thing->paramValue(senecStorageThingIdParamTypeId).toString();
QNetworkReply *reply = account->getTechnicalData(id);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, account, [reply, thing, this] {
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
qCWarning(dcSenec()) << "Technical data request finished with error. Status:" << status << "Error:" << reply->errorString();
thing->setStateValue(senecStorageConnectedStateTypeId, false);
return;
}
QByteArray responseData = reply->readAll();
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData, &jsonError);
//QVariantMap responseMap = jsonDoc.toVariant().toMap();
if (jsonError.error != QJsonParseError::NoError) {
qCWarning(dcSenec()) << "Technical data request finished successfully, but the response contains invalid JSON object:" << responseData;
return;
}
qCDebug(dcSenec()) << "Technical data request finished successfully" << qUtf8Printable(jsonDoc.toJson());
thing->setStateValue(senecStorageConnectedStateTypeId, true);
refresh(thing);
});
}
// Create the refresh timer if not already set up
if (!m_refreshTimer) {
qCDebug(dcSenec()) << "Starting refresh timer ...";
m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(5);
connect(m_refreshTimer, &PluginTimer::timeout, this, [this](){
refresh();
});
m_refreshTimer->start();
}
}
void IntegrationPluginSenec::thingRemoved(Thing *thing)
{
if (thing->thingClassId() == senecAccountThingClassId) {
if (m_accounts.contains(thing))
m_accounts.take(thing)->deleteLater();
// Wipe any stored login information
pluginStorage()->beginGroup(thing->id().toString());
pluginStorage()->remove("");
pluginStorage()->endGroup();
}
if (myThings().isEmpty()) {
qCDebug(dcSenec()) << "Stopping refresh timer";
hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer);
m_refreshTimer = nullptr;
}
}
void IntegrationPluginSenec::executeAction(ThingActionInfo *info)
{
info->finish(Thing::ThingErrorNoError);
}
void IntegrationPluginSenec::refresh(Thing *thing)
{
if (thing) {
SenecAccount *account = m_accounts.value(myThings().findById(thing->parentId()));
QString id = thing->paramValue(senecStorageThingIdParamTypeId).toString();
QNetworkReply *reply = account->getDashboard(id);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, account, [reply, thing] {
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status != 200 || reply->error() != QNetworkReply::NoError) {
qCWarning(dcSenec()) << "Dashboard request finished with error. Status:" << status << "Error:" << reply->errorString();
thing->setStateValue(senecStorageConnectedStateTypeId, false);
return;
}
QByteArray responseData = reply->readAll();
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData, &jsonError);
QVariantMap responseMap = jsonDoc.toVariant().toMap();
if (jsonError.error != QJsonParseError::NoError) {
qCWarning(dcSenec()) << "Dashboard request finished successfully, but the response contains invalid JSON object:" << responseData;
return;
}
qCDebug(dcSenec()) << "Dashboard request finished successfully" << qUtf8Printable(jsonDoc.toJson());
thing->setStateValue(senecStorageConnectedStateTypeId, true);
QVariantMap currentDataMap = responseMap.value("currently").toMap();
float batteryCharge = qRound(currentDataMap.value("batteryChargeInW").toFloat() * 100) / 100.0;
float batteryDischarge = qRound(currentDataMap.value("batteryDischargeInW").toFloat() * 100) / 100.0;
int batteryLevel = currentDataMap.value("batteryLevelInPercent").toInt();
// qCDebug(dcSenec()) << "charge:" << batteryCharge << "W" << "discharge:" << batteryDischarge << "W" << "level" << batteryLevel << "%";
float currentPower = 0;
if (batteryCharge != 0) {
currentPower = batteryCharge;
thing->setStateValue(senecStorageChargingStateStateTypeId, "charging");
} else if (batteryDischarge != 0) {
currentPower = -batteryDischarge;
thing->setStateValue(senecStorageChargingStateStateTypeId, "discharging");
} else {
thing->setStateValue(senecStorageChargingStateStateTypeId, "idle");
}
thing->setStateValue(senecStorageCurrentPowerStateTypeId, currentPower);
thing->setStateValue(senecStorageBatteryLevelStateTypeId, batteryLevel);
thing->setStateValue(senecStorageBatteryCriticalStateTypeId, batteryLevel < 10);
});
} else {
foreach (Thing *storageThing, myThings().filterByThingClassId(senecStorageThingClassId)) {
refresh(storageThing);
}
}
}

View File

@ -0,0 +1,70 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2025, 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 <https://www.gnu.org/licenses/>.
*
* 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 INTEGRATIONPLUGINSENEC_H
#define INTEGRATIONPLUGINSENEC_H
#include <plugintimer.h>
#include <integrations/integrationplugin.h>
#include "extern-plugininfo.h"
#include "senecaccount.h"
class IntegrationPluginSenec : public IntegrationPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginsenec.json")
Q_INTERFACES(IntegrationPlugin)
public:
explicit IntegrationPluginSenec();
~IntegrationPluginSenec();
void startPairing(ThingPairingInfo *info) override;
void confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret) override;
void setupThing(ThingSetupInfo *info) override;
void postSetupThing(Thing *thing) override;
void thingRemoved(Thing *thing) override;
void executeAction(ThingActionInfo *info) override;
private:
PluginTimer *m_refreshTimer = nullptr;
QHash<Thing *, SenecAccount *> m_accounts;
private slots:
void refresh(Thing *thing = nullptr);
};
#endif // INTEGRATIONPLUGINSENEC_H

View File

@ -0,0 +1,115 @@
{
"displayName": "SENEC",
"name": "Senec",
"id": "3f055ea5-a883-445d-9556-675f5eda6c9a",
"vendors": [
{
"displayName": "SENEC",
"name": "senec",
"id": "1be922b9-4439-42cf-9caa-67f2ac7cc425",
"thingClasses": [
{
"id": "f60497ea-a15a-4237-86bc-935182475e47",
"name": "senecAccount",
"displayName": "SENEC account",
"interfaces": ["account"],
"createMethods": ["user"],
"setupMethod": "userandpassword",
"providedInterfaces": ["energystorage"],
"stateTypes": [
{
"id": "2a508d04-3183-4c0b-9e92-90ff1ebce19e",
"name": "connected",
"displayName": "Connected",
"type": "bool",
"defaultValue": false
},
{
"id": "6842e3b3-ff25-4f34-b1c4-ff3f83c23746",
"name": "loggedIn",
"displayName": "Logged in",
"type": "bool",
"defaultValue": false
},
{
"id": "7670bfb4-5dfc-47f1-9c99-80b120f2aa8b",
"name": "userDisplayName",
"displayName": "Username",
"type": "QString",
"defaultValue": "-"
}
]
},
{
"name": "senecStorage",
"displayName": "SENEC.Home storage",
"id": "a983c5ee-1c58-4cad-87fb-1bc612cbe6e4",
"createMethods": [ "Auto" ],
"interfaces": ["energystorage", "connectable"],
"paramTypes": [
{
"id": "9a0fa7b1-bc35-44d4-b987-5ecb87fd8b00",
"name":"id",
"displayName": "System ID",
"type": "QString",
"readOnly": true,
"defaultValue": ""
}
],
"stateTypes":[
{
"id": "320cb3f0-d2c5-4a30-817f-474f0cc87253",
"name": "connected",
"displayName": "Connected",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "09225a15-f2d0-46cf-a84f-4a91f389d488",
"name": "currentPower",
"displayName": "Current power",
"type": "double",
"unit": "Watt",
"defaultValue": 0.00,
"cached": true
},
{
"id": "d6dd533a-beb2-4f57-a3d3-12c48a83306c",
"name": "batteryLevel",
"displayName": "Battery level",
"type": "int",
"unit": "Percentage",
"minValue": 0,
"maxValue": 100,
"defaultValue": 0
},
{
"id": "cba01bf1-5daf-4466-8ae3-ea864593bdc6",
"name": "batteryCritical",
"displayName": "Battery critical",
"type": "bool",
"defaultValue": false
},
{
"id": "cef34773-02c2-4eb1-8624-4e595847f674",
"name": "chargingState",
"displayName": "Charging state",
"type": "QString",
"possibleValues": ["idle", "charging", "discharging"],
"defaultValue": "idle"
},
{
"id": "4f571731-85f7-492e-ba51-eb6bf224776f",
"name": "capacity",
"displayName": "Capacity",
"type": "double",
"unit": "KiloWattHour",
"defaultValue": 0
}
]
}
]
}
]
}

13
senec/meta.json Normal file
View File

@ -0,0 +1,13 @@
{
"title": "SENEC",
"tagline": "Connect nymea to your SENEC account.",
"icon": "senec.svg",
"stability": "community",
"offline": false,
"technologies": [
"cloud"
],
"categories": [
"energy"
]
}

12
senec/senec.pro Normal file
View File

@ -0,0 +1,12 @@
include(../plugins.pri)
QT+= network
SOURCES += \
integrationpluginsenec.cpp \
senecaccount.cpp
HEADERS += \
integrationpluginsenec.h \
senecaccount.h

40
senec/senec.svg Normal file
View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
data-name="Ebene 1"
viewBox="0 0 100 23"
version="1.1"
id="svg6"
sodipodi:docname="senec_logo_de_clean_resize_1.svg"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
width="100"
height="23"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs10" />
<sodipodi:namedview
id="namedview8"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="3.0458796"
inkscape:cx="5.9096229"
inkscape:cy="65.170008"
inkscape:window-width="1728"
inkscape:window-height="1006"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg6" />
<path
d="m 16.483695,17.845517 c 0,2.942408 -2.713129,5.216087 -6.9738891,5.216087 H 1.045605 c -0.55408995,0 -1.01264754,-0.458557 -1.01264754,-1.012647 v -2.445638 c 0,-0.55409 0.43945112,-1.012647 0.99354104,-1.031754 h 7.5661923 c 0.6687298,0 2.7322372,0 2.7322372,-1.757802 V 15.973075 A 1.6259672,1.6259672 0 0 0 9.8537235,14.215273 C 9.2614206,14.06242 4.6376363,13.030667 4.1981852,12.95424 A 5.1797851,5.1797851 0 0 1 0.01385098,7.4897667 V 5.7892839 C 0.01385098,2.789556 2.6505554,0 7.1596997,0 h 8.2158163 c 0.592303,0 0.802475,0.43945066 0.515876,0.84068815 L 14.381975,3.592031 C 14.076271,4.1079077 13.522181,4.432719 12.910771,4.432719 H 7.2552332 c -1.1463929,0 -2.0444004,0.9553274 -2.0444004,2.1017203 v 0.5731964 c 0.057314,0.7642619 0.6305158,1.4138846 1.394777,1.5476304 0,0 4.0314822,0.8406881 5.5791122,1.10818 3.229007,0.6687289 4.337187,2.9424099 4.337187,5.0059169 v 3.076154 z M 20.66803,1.0317536 C 20.66803,0.47766372 21.107481,0.01910658 21.661571,0 h 14.501868 c 0.592304,0 0.840688,0.36302443 0.515877,0.84068815 L 35.131686,3.592031 C 34.787769,4.0696947 34.252785,4.3753994 33.660482,4.432719 H 21.623357 c -0.554089,0 -1.012647,-0.420344 -1.031753,-0.9744339 V 3.4200721 Z M 55.499266,0 c -0.55409,0 -1.012647,0.42034408 -1.031753,0.97443391 V 13.56565 c 0,0.592303 -0.229279,0.668729 -0.592303,0.152852 L 45.277263,0.84068815 C 44.914238,0.34391785 44.341042,0.03821306 43.729633,0 H 41.455954 C 40.901864,0 40.443306,0.42034408 40.4242,0.97443391 V 22.02985 c 0,0.55409 0.420344,1.012648 0.974434,1.031754 h 3.114367 c 0.55409,0 1.012647,-0.420344 1.031753,-0.974434 V 9.9162979 c 0,-0.5923027 0.229279,-0.6687289 0.592303,-0.1528518 l 8.521521,12.4956829 c 0.363025,0.496771 0.936221,0.802475 1.547631,0.840688 h 2.426531 c 0.55409,0 1.012647,-0.420343 1.031754,-0.974433 V 1.0317536 A 1.0317536,1.0317536 0 0 0 58.670953,0 H 55.480159 Z M 21.699784,18.590672 c -0.55409,0 -1.012648,0.420344 -1.031754,0.974434 v 0.03821 l -0.07643,2.407425 c 0,0.55409 0.420343,1.012647 0.974433,1.031754 h 12.094445 c 0.592303,-0.05732 1.127287,-0.363024 1.471204,-0.840688 l 1.54763,-2.732237 c 0.286599,-0.515877 0.07643,-0.840688 -0.515877,-0.840688 L 21.699784,18.571565 Z M 20.66803,10.069151 c 0,-0.5540902 0.439451,-1.0126474 0.993541,-1.031754 h 11.998911 c 0.592303,0 0.840689,0.3630245 0.515876,0.840688 l -1.54763,2.713131 c -0.343917,0.477664 -0.878901,0.783369 -1.471204,0.840688 H 21.60425 c -0.55409,0 -1.012646,-0.420344 -1.031753,-0.974434 v -0.05732 l 0.07643,-2.350106 z M 65.855015,0 c -0.55409,0 -1.012647,0.42034408 -1.031754,0.97443391 v 0.0573194 l 0.07643,2.4265316 c 0,0.5540899 0.439451,1.0126471 0.993541,1.0317535 h 11.903379 c 0.592304,-0.057319 1.127286,-0.3630243 1.471205,-0.840688 L 80.853655,0.85979441 C 81.159359,0.34391785 80.93008,0.01910658 80.337778,0.01910658 H 65.874122 Z m -1.031754,19.603319 c 0,-0.55409 0.420344,-1.012647 0.974434,-1.031754 h 14.520976 c 0.592303,0 0.840688,0.363024 0.515877,0.840688 l -1.54763,2.713131 c -0.343919,0.477663 -0.878901,0.783367 -1.471204,0.840688 H 65.950548 c -0.55409,0.03821 -1.031754,-0.382132 -1.069967,-0.936222 v -0.03821 l -0.07643,-2.407424 z M 65.855015,9.037397 c -0.55409,0 -1.012647,0.4203441 -1.031754,0.974434 v 0.05731 l 0.07643,2.426533 c -0.03821,0.55409 0.382132,1.031753 0.955328,1.069967 h 9.495954 c 0.592303,-0.05733 1.127287,-0.363025 1.471204,-0.840688 l 1.54763,-2.713132 c 0.305702,-0.515867 0.07642,-0.8406783 -0.51588,-0.8406783 L 65.874122,9.0756101 Z m 25.62188,14.024207 h 5.36894 c 0.611409,0 1.165502,-0.324811 1.471206,-0.840688 l 1.54763,-2.732237 c 0.305699,-0.43945 0.07643,-0.840688 -0.51588,-0.840688 H 93.043632 C 90.024797,18.495139 89.012151,17.253214 89.012151,14.406339 V 8.0820696 a 3.5347113,3.5347113 0 0 1 3.439178,-3.6111374 h 4.337186 c 0.61141,0 1.165498,-0.3248114 1.471201,-0.8406882 l 1.54763,-2.78955585 C 100.11305,0.40123749 99.883772,0 99.291477,0 H 91.610641 C 86.834004,0 83.81517,3.6684572 83.81517,8.3113482 v 6.5344408 c 0.09554,4.26076 2.311892,8.234922 7.661725,8.234922 z"
style="fill:#000099;fill-rule:evenodd;stroke-width:1"
id="path4" />
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

105
senec/senecaccount.cpp Normal file
View File

@ -0,0 +1,105 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2025, 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 <https://www.gnu.org/licenses/>.
*
* 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 "senecaccount.h"
#include "extern-plugininfo.h"
SenecAccount::SenecAccount(NetworkAccessManager *networkManager, const QString &username, const QString &token, const QString &refreshToken, QObject *parent)
: QObject{parent},
m_networkManager{networkManager},
m_username{username},
m_token{token},
m_refreshToken{refreshToken}
{}
QUrl SenecAccount::baseUrl()
{
return QUrl("https://app-gateway.prod.senec.dev");
}
QUrl SenecAccount::loginUrl()
{
QUrl url = SenecAccount::baseUrl();
url.setPath("/v1/senec/login");
return url;
}
QUrl SenecAccount::systemsUrl()
{
QUrl url = SenecAccount::baseUrl();
url.setPath("/v1/senec/systems");
return url;
}
bool SenecAccount::available() const
{
return m_available;
}
QNetworkReply *SenecAccount::getSystems()
{
QNetworkRequest request(SenecAccount::systemsUrl());
request.setRawHeader("authorization", m_token.toUtf8());
return m_networkManager->get(request);
}
QNetworkReply *SenecAccount::getDashboard(const QString &id)
{
QUrl url = SenecAccount::baseUrl();
url.setPath("/v2/senec/systems/" + id +"/dashboard");
QNetworkRequest request(url);
request.setRawHeader("authorization", m_token.toUtf8());
qCDebug(dcSenec()) << "Get" << url.toString();
return m_networkManager->get(request);
}
QNetworkReply *SenecAccount::getAbilities(const QString &id)
{
QUrl url = SenecAccount::baseUrl();
url.setPath("/v1/senec/systems/" + id +"/abilities");
QNetworkRequest request(url);
request.setRawHeader("authorization", m_token.toUtf8());
qCDebug(dcSenec()) << "Get" << url.toString();
return m_networkManager->get(request);
}
QNetworkReply *SenecAccount::getTechnicalData(const QString &id)
{
QUrl url = SenecAccount::baseUrl();
url.setPath("/v1/senec/systems/" + id +"/technical-data");
QNetworkRequest request(url);
request.setRawHeader("authorization", m_token.toUtf8());
qCDebug(dcSenec()) << "Get" << url.toString();
return m_networkManager->get(request);
}

70
senec/senecaccount.h Normal file
View File

@ -0,0 +1,70 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2025, 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 <https://www.gnu.org/licenses/>.
*
* 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 SENECACCOUNT_H
#define SENECACCOUNT_H
#include <QUrl>
#include <QObject>
#include <QNetworkReply>
#include <network/networkaccessmanager.h>
class SenecAccount : public QObject
{
Q_OBJECT
public:
explicit SenecAccount(NetworkAccessManager *networkManager, const QString &username, const QString &token, const QString &refreshToken, QObject *parent = nullptr);
static QUrl baseUrl();
static QUrl loginUrl();
static QUrl systemsUrl();
bool available() const;
QNetworkReply *getSystems();
QNetworkReply *getDashboard(const QString &id);
QNetworkReply *getAbilities(const QString &id);
QNetworkReply *getTechnicalData(const QString &id);
signals:
bool availableChanged(bool available);
private:
NetworkAccessManager *m_networkManager = nullptr;
QString m_username;
QString m_token;
QString m_refreshToken;
bool m_available = false;
};
#endif // SENECACCOUNT_H

View File

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="de">
<context>
<name>IntegrationPluginSenec</name>
<message>
<location filename="../integrationpluginsenec.cpp" line="49"/>
<source>Please enter username and password for your SENEC.home account.</source>
<translation>Logge dich mit deinem SENEC.home account ein.</translation>
</message>
<message>
<location filename="../integrationpluginsenec.cpp" line="71"/>
<source>Username or password is invalid.</source>
<translation>Benutzername oder Password ungültig.</translation>
</message>
</context>
<context>
<name>Senec</name>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="39"/>
<source>Battery critical</source>
<extracomment>The name of the StateType ({cba01bf1-5daf-4466-8ae3-ea864593bdc6}) of ThingClass senecStorage</extracomment>
<translation>Batterieladung kritisch</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="42"/>
<source>Battery level</source>
<extracomment>The name of the StateType ({d6dd533a-beb2-4f57-a3d3-12c48a83306c}) of ThingClass senecStorage</extracomment>
<translation>Ladestand</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="45"/>
<source>Capacity</source>
<extracomment>The name of the StateType ({4f571731-85f7-492e-ba51-eb6bf224776f}) of ThingClass senecStorage</extracomment>
<translation>Kapazität</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="48"/>
<source>Charging state</source>
<extracomment>The name of the StateType ({cef34773-02c2-4eb1-8624-4e595847f674}) of ThingClass senecStorage</extracomment>
<translation>Ladezustand</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="51"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="54"/>
<source>Connected</source>
<extracomment>The name of the StateType ({320cb3f0-d2c5-4a30-817f-474f0cc87253}) of ThingClass senecStorage
----------
The name of the StateType ({2a508d04-3183-4c0b-9e92-90ff1ebce19e}) of ThingClass senecAccount</extracomment>
<translation>Verbunden</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="57"/>
<source>Current power</source>
<extracomment>The name of the StateType ({09225a15-f2d0-46cf-a84f-4a91f389d488}) of ThingClass senecStorage</extracomment>
<translation>Aktuelle Leistung</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="60"/>
<source>Logged in</source>
<extracomment>The name of the StateType ({6842e3b3-ff25-4f34-b1c4-ff3f83c23746}) of ThingClass senecAccount</extracomment>
<translation>Eingelogged</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="63"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="66"/>
<source>SENEC</source>
<extracomment>The name of the vendor ({1be922b9-4439-42cf-9caa-67f2ac7cc425})
----------
The name of the plugin Senec ({3f055ea5-a883-445d-9556-675f5eda6c9a})</extracomment>
<translation>SENEC</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="69"/>
<source>SENEC account</source>
<extracomment>The name of the ThingClass ({f60497ea-a15a-4237-86bc-935182475e47})</extracomment>
<translation>SENEC Account</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="72"/>
<source>SENEC.Home storage</source>
<extracomment>The name of the ThingClass ({a983c5ee-1c58-4cad-87fb-1bc612cbe6e4})</extracomment>
<translation>SENEC.Home Speicher</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="75"/>
<source>System ID</source>
<extracomment>The name of the ParamType (ThingClass: senecStorage, Type: thing, ID: {9a0fa7b1-bc35-44d4-b987-5ecb87fd8b00})</extracomment>
<translation>System ID</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="78"/>
<source>Username</source>
<extracomment>The name of the StateType ({7670bfb4-5dfc-47f1-9c99-80b120f2aa8b}) of ThingClass senecAccount</extracomment>
<translation>Benutzername</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="81"/>
<source>charging</source>
<extracomment>The name of a possible value of StateType {cef34773-02c2-4eb1-8624-4e595847f674} of ThingClass senecStorage</extracomment>
<translation>lade</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="84"/>
<source>discharging</source>
<extracomment>The name of a possible value of StateType {cef34773-02c2-4eb1-8624-4e595847f674} of ThingClass senecStorage</extracomment>
<translation>entlade</translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="87"/>
<source>idle</source>
<extracomment>The name of a possible value of StateType {cef34773-02c2-4eb1-8624-4e595847f674} of ThingClass senecStorage</extracomment>
<translation>leerlauf</translation>
</message>
</context>
</TS>

View File

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>IntegrationPluginSenec</name>
<message>
<location filename="../integrationpluginsenec.cpp" line="49"/>
<source>Please enter username and password for your SENEC.home account.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../integrationpluginsenec.cpp" line="71"/>
<source>Username or password is invalid.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Senec</name>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="39"/>
<source>Battery critical</source>
<extracomment>The name of the StateType ({cba01bf1-5daf-4466-8ae3-ea864593bdc6}) of ThingClass senecStorage</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="42"/>
<source>Battery level</source>
<extracomment>The name of the StateType ({d6dd533a-beb2-4f57-a3d3-12c48a83306c}) of ThingClass senecStorage</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="45"/>
<source>Capacity</source>
<extracomment>The name of the StateType ({4f571731-85f7-492e-ba51-eb6bf224776f}) of ThingClass senecStorage</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="48"/>
<source>Charging state</source>
<extracomment>The name of the StateType ({cef34773-02c2-4eb1-8624-4e595847f674}) of ThingClass senecStorage</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="51"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="54"/>
<source>Connected</source>
<extracomment>The name of the StateType ({320cb3f0-d2c5-4a30-817f-474f0cc87253}) of ThingClass senecStorage
----------
The name of the StateType ({2a508d04-3183-4c0b-9e92-90ff1ebce19e}) of ThingClass senecAccount</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="57"/>
<source>Current power</source>
<extracomment>The name of the StateType ({09225a15-f2d0-46cf-a84f-4a91f389d488}) of ThingClass senecStorage</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="60"/>
<source>Logged in</source>
<extracomment>The name of the StateType ({6842e3b3-ff25-4f34-b1c4-ff3f83c23746}) of ThingClass senecAccount</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="63"/>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="66"/>
<source>SENEC</source>
<extracomment>The name of the vendor ({1be922b9-4439-42cf-9caa-67f2ac7cc425})
----------
The name of the plugin Senec ({3f055ea5-a883-445d-9556-675f5eda6c9a})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="69"/>
<source>SENEC account</source>
<extracomment>The name of the ThingClass ({f60497ea-a15a-4237-86bc-935182475e47})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="72"/>
<source>SENEC.Home storage</source>
<extracomment>The name of the ThingClass ({a983c5ee-1c58-4cad-87fb-1bc612cbe6e4})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="75"/>
<source>System ID</source>
<extracomment>The name of the ParamType (ThingClass: senecStorage, Type: thing, ID: {9a0fa7b1-bc35-44d4-b987-5ecb87fd8b00})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="78"/>
<source>Username</source>
<extracomment>The name of the StateType ({7670bfb4-5dfc-47f1-9c99-80b120f2aa8b}) of ThingClass senecAccount</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="81"/>
<source>charging</source>
<extracomment>The name of a possible value of StateType {cef34773-02c2-4eb1-8624-4e595847f674} of ThingClass senecStorage</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="84"/>
<source>discharging</source>
<extracomment>The name of a possible value of StateType {cef34773-02c2-4eb1-8624-4e595847f674} of ThingClass senecStorage</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build-nymea-plugins-Desktop-Debug/senec/plugininfo.h" line="87"/>
<source>idle</source>
<extracomment>The name of a possible value of StateType {cef34773-02c2-4eb1-8624-4e595847f674} of ThingClass senecStorage</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
</TS>