From 76437b34c1670037cf5d74b691ec43bf80b94806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Fri, 18 Feb 2022 08:52:38 +0100 Subject: [PATCH] Add login during setup for SMA inverters --- sma/integrationpluginsma.cpp | 62 ++++++++++++++++++++++++++++++++--- sma/integrationpluginsma.h | 4 +++ sma/integrationpluginsma.json | 1 + sma/speedwireinverter.cpp | 32 +++++++++++++----- sma/speedwireinverter.h | 9 +++-- 5 files changed, 91 insertions(+), 17 deletions(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index a8b7bfe4..ff4392c5 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -177,6 +177,32 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info) } } +void IntegrationPluginSma::startPairing(ThingPairingInfo *info) +{ + info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Please enter the password of your inverter. If no password has been explicitly set, leave it empty to use the default password for SMA inverters.")); +} + +void IntegrationPluginSma::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret) +{ + Q_UNUSED(username) + + // Init with the default password + QString password = "0000"; + if (!secret.isEmpty()) { + qCDebug(dcSma()) << "Pairing: Using password" << secret; + password = secret; + } else { + qCDebug(dcSma()) << "Pairing: Secret is empty. Using default password" << password; + } + + // Just store details, we'll test the login in setupDevice + pluginStorage()->beginGroup(info->thingId().toString()); + pluginStorage()->setValue("password", password); + pluginStorage()->endGroup(); + + info->finish(Thing::ThingErrorNoError); +} + void IntegrationPluginSma::setupThing(ThingSetupInfo *info) { Thing *thing = info->thing(); @@ -213,7 +239,9 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) connect(sunnyWebBox, &SunnyWebBox::plantOverviewReceived, this, &IntegrationPluginSma::onPlantOverviewReceived); m_sunnyWebBoxes.insert(info->thing(), sunnyWebBox); }); + } else if (thing->thingClassId() == speedwireMeterThingClassId) { + QHostAddress address = QHostAddress(thing->paramValue(speedwireMeterThingHostParamTypeId).toString()); quint32 serialNumber = static_cast(thing->paramValue(speedwireMeterThingSerialNumberParamTypeId).toUInt()); quint16 modelId = static_cast(thing->paramValue(speedwireMeterThingModelIdParamTypeId).toUInt()); @@ -258,7 +286,9 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) m_speedwireMeters.insert(thing, meter); info->finish(Thing::ThingErrorNoError); + } else if (thing->thingClassId() == speedwireInverterThingClassId) { + QHostAddress address = QHostAddress(thing->paramValue(speedwireInverterThingHostParamTypeId).toString()); quint32 serialNumber = static_cast(thing->paramValue(speedwireInverterThingSerialNumberParamTypeId).toUInt()); quint16 modelId = static_cast(thing->paramValue(speedwireInverterThingModelIdParamTypeId).toUInt()); @@ -276,6 +306,31 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) qCDebug(dcSma()) << "Inverter: Interface initialized successfully."; + QString password; + pluginStorage()->beginGroup(info->thing()->id().toString()); + password = pluginStorage()->value("password", "0000").toString(); + pluginStorage()->endGroup(); + + // Connection exists only as long info exists + connect(inverter, &SpeedwireInverter::loginFinished, info, [=](bool success){ + if (!success) { + qCWarning(dcSma()) << "Failed to set up inverter. Wrong password."; + delete inverter; + info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Failed to log in with the given password. Please try again.")); + return; + } + + qCDebug(dcSma()) << "Inverter set up successfully."; + m_speedwireInverters.insert(thing, inverter); + info->finish(Thing::ThingErrorNoError); + // Note: the data is already refreshing here + }); + + // Make sure an aborted setup will clean up the object + connect(info, &ThingSetupInfo::aborted, inverter, &SpeedwireInverter::deleteLater); + + + // Runtime connections connect(inverter, &SpeedwireInverter::reachableChanged, this, [=](bool reachable){ thing->setStateValue(speedwireInverterConnectedStateTypeId, reachable); }); @@ -296,14 +351,11 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) thing->setStateValue(speedwireInverterCurrentPowerMpp1StateTypeId, inverter->powerDcMpp1()); thing->setStateValue(speedwireInverterCurrentPowerMpp2StateTypeId, inverter->powerDcMpp2()); - }); - m_speedwireInverters.insert(thing, inverter); - info->finish(Thing::ThingErrorNoError); + qCDebug(dcSma()) << "Inverter: Start connecting using password" << password; + inverter->startConnecting(password); - // Initial refresh data - inverter->refresh(); } else { Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); } diff --git a/sma/integrationpluginsma.h b/sma/integrationpluginsma.h index ef3aedc1..d695ae3d 100644 --- a/sma/integrationpluginsma.h +++ b/sma/integrationpluginsma.h @@ -48,6 +48,10 @@ public: void init() override; void discoverThings(ThingDiscoveryInfo *info) override; + + 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; diff --git a/sma/integrationpluginsma.json b/sma/integrationpluginsma.json index b5b344c7..e268d48a 100644 --- a/sma/integrationpluginsma.json +++ b/sma/integrationpluginsma.json @@ -315,6 +315,7 @@ "name": "speedwireInverter", "displayName": "SMA Inverter", "createMethods": ["discovery", "user"], + "setupMethod": "EnterPin", "interfaces": [ "solarinverter" ], "paramTypes": [ { diff --git a/sma/speedwireinverter.cpp b/sma/speedwireinverter.cpp index ef3fc805..488cddec 100644 --- a/sma/speedwireinverter.cpp +++ b/sma/speedwireinverter.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2021, nymea GmbH +* Copyright 2013 - 2022, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -39,11 +39,9 @@ SpeedwireInverter::SpeedwireInverter(const QHostAddress &address, quint16 modelI m_modelId(modelId), m_serialNumber(serialNumber) { - qCDebug(dcSma()) << "Inverter: setup interface on" << m_address.toString(); m_interface = new SpeedwireInterface(m_address, false, this); connect(m_interface, &SpeedwireInterface::dataReceived, this, &SpeedwireInverter::processData); - } bool SpeedwireInverter::initialize() @@ -197,7 +195,7 @@ SpeedwireInverterReply *SpeedwireInverter::sendLoginRequest(const QString &passw encodedPassword[i] = (passwordData.at(i) + (loginAsUser ? 0x88 : 0xBB) % 0xff); } - // Add endoced password + // Add encoded password for (int i = 0; i < encodedPassword.count(); i++) { stream << static_cast(encodedPassword.at(i)); } @@ -328,6 +326,12 @@ SpeedwireInverterReply *SpeedwireInverter::sendDeviceTypeRequest() return createReply(request); } +void SpeedwireInverter::startConnecting(const QString &password) +{ + m_password = password; + refresh(); +} + void SpeedwireInverter::refresh() { // Only refresh if not already busy... @@ -1082,10 +1086,16 @@ void SpeedwireInverter::setState(State state) if (reply->error() != SpeedwireInverterReply::ErrorNoError) { if (reply->error() == SpeedwireInverterReply::ErrorTimeout) { qCWarning(dcSma()) << "Inverter: Failed to query data from inverter:" << reply->request().command() << reply->error(); + + // TODO: try to send identify request and retry 3 times before giving up, + // still need to figure out why the inverter stops responding sometimes and how we can + // make it communicative again, a reconfugre always fixes this issue...somehow... + setState(StateDisconnected); return; } + // Reachable, but received an inverter error, probably not logged if (reply->error() == SpeedwireInverterReply::ErrorInverterError) { qCDebug(dcSma()) << "Inverter: Query data request finished with inverter error. Try to login..."; setState(StateLogin); @@ -1093,10 +1103,14 @@ void SpeedwireInverter::setState(State state) } } + // We where able to read data...emit the signal for the setup just incase + emit loginFinished(true); + qCDebug(dcSma()) << "Inverter: Query request finished successfully" << reply->request().command(); processAcPowerResponse(reply->responseData()); - if (deviceInformationFetched) { + + if (m_deviceInformationFetched) { setState(StateQueryData); } else { setState(StateGetInformation); @@ -1104,18 +1118,18 @@ void SpeedwireInverter::setState(State state) }); break; } - case StateLogin: { - setState(StateLogin); - SpeedwireInverterReply *loginReply = sendLoginRequest(); + SpeedwireInverterReply *loginReply = sendLoginRequest(m_password); connect(loginReply, &SpeedwireInverterReply::finished, this, [=](){ if (loginReply->error() != SpeedwireInverterReply::ErrorNoError) { qCWarning(dcSma()) << "Inverter: Failed to login to inverter:" << loginReply->error(); + emit loginFinished(false); setState(StateDisconnected); return; } qCDebug(dcSma()) << "Inverter: Login request finished successfully."; + emit loginFinished(true); setReachable(true); // Logged in successfully, reinit the data fetch process @@ -1145,7 +1159,7 @@ void SpeedwireInverter::setState(State state) qCDebug(dcSma()) << "Inverter: Get device information finished successfully."; processDeviceTypeResponse(deviceTypeReply->responsePayload()); - deviceInformationFetched = true; + m_deviceInformationFetched = true; setState(StateQueryData); }); }); diff --git a/sma/speedwireinverter.h b/sma/speedwireinverter.h index 8750202b..50988b3c 100644 --- a/sma/speedwireinverter.h +++ b/sma/speedwireinverter.h @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2021, nymea GmbH +* Copyright 2013 - 2022, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -101,19 +101,22 @@ public: SpeedwireInverterReply *sendDeviceTypeRequest(); // Start connecting - void startConnecting(); + void startConnecting(const QString &password = "0000"); public slots: void refresh(); signals: void reachableChanged(bool reachable); + void loginFinished(bool success); void stateChanged(State state); void valuesUpdated(); private: SpeedwireInterface *m_interface = nullptr; QHostAddress m_address; + QString m_password; + bool m_initialized = false; quint16 m_modelId = 0; quint32 m_serialNumber = 0; @@ -122,7 +125,7 @@ private: State m_state = StateDisconnected; quint16 m_packetId = 1; - bool deviceInformationFetched = false; + bool m_deviceInformationFetched = false; SpeedwireInverterReply *m_currentReply = nullptr; QQueue m_replyQueue;