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 This package contains the nymea integration plugin for sending arbitrary
TCP packets from and to nymea. TCP packets from and to nymea.
Package: nymea-plugin-httpcommander Package: nymea-plugin-httpcommander
Architecture: any Architecture: any
Depends: ${shlibs:Depends}, Depends: ${shlibs:Depends},
@ -713,6 +714,16 @@ Description: nymea integration plugin for HTTP commander
HTTP requests from and to nymea. 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 Package: nymea-plugin-senic
Architecture: any Architecture: any
Depends: ${shlibs:Depends}, 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 \ pushbullet \
pushnotifications \ pushnotifications \
reversessh \ reversessh \
senec \
senic \ senic \
serialportcommander \ serialportcommander \
sgready \ 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>