|
|
|
|
@ -46,9 +46,6 @@ void IntegrationPluginKeba::init()
|
|
|
|
|
m_macAddressParamTypeIds.insert(kebaThingClassId, kebaThingMacAddressParamTypeId);
|
|
|
|
|
m_macAddressParamTypeIds.insert(kebaSimpleThingClassId, kebaSimpleThingMacAddressParamTypeId);
|
|
|
|
|
|
|
|
|
|
m_ipAddressParamTypeIds.insert(kebaThingClassId, kebaThingIpAddressParamTypeId);
|
|
|
|
|
m_ipAddressParamTypeIds.insert(kebaSimpleThingClassId, kebaSimpleThingIpAddressParamTypeId);
|
|
|
|
|
|
|
|
|
|
m_modelParamTypeIds.insert(kebaThingClassId, kebaThingModelParamTypeId);
|
|
|
|
|
m_modelParamTypeIds.insert(kebaSimpleThingClassId, kebaSimpleThingModelParamTypeId);
|
|
|
|
|
|
|
|
|
|
@ -80,6 +77,7 @@ void IntegrationPluginKeba::discoverThings(ThingDiscoveryInfo *info)
|
|
|
|
|
// Create a discovery with the info as parent for auto deleting the object once the discovery info is done
|
|
|
|
|
KebaDiscovery *discovery = new KebaDiscovery(m_kebaDataLayer, hardwareManager()->networkDeviceDiscovery(), info);
|
|
|
|
|
connect(discovery, &KebaDiscovery::discoveryFinished, info, [=](){
|
|
|
|
|
|
|
|
|
|
foreach (const KebaDiscovery::KebaDiscoveryResult &result, discovery->discoveryResults()) {
|
|
|
|
|
|
|
|
|
|
KebaProductInfo productInformation(result.product);
|
|
|
|
|
@ -110,7 +108,6 @@ void IntegrationPluginKeba::discoverThings(ThingDiscoveryInfo *info)
|
|
|
|
|
|
|
|
|
|
ParamList params;
|
|
|
|
|
params << Param(m_macAddressParamTypeIds.value(discoveredThingClassId), result.networkDeviceInfo.macAddress());
|
|
|
|
|
params << Param(m_ipAddressParamTypeIds.value(discoveredThingClassId), result.networkDeviceInfo.address().toString());
|
|
|
|
|
params << Param(m_modelParamTypeIds.value(discoveredThingClassId), result.product);
|
|
|
|
|
params << Param(m_serialNumberParamTypeIds.value(discoveredThingClassId), result.serialNumber);
|
|
|
|
|
descriptor.setParams(params);
|
|
|
|
|
@ -150,124 +147,44 @@ void IntegrationPluginKeba::setupThing(ThingSetupInfo *info)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QHostAddress address = QHostAddress(thing->paramValue(m_ipAddressParamTypeIds.value(thing->thingClassId())).toString());
|
|
|
|
|
// Check if we have a keba with this ip, if reconfigure the object would already been removed from the hash
|
|
|
|
|
foreach (KeContact *kebaConnect, m_kebaDevices.values()) {
|
|
|
|
|
if (kebaConnect->address() == address) {
|
|
|
|
|
qCWarning(dcKeba()) << "Failed to set up keba for host address" << address.toString() << "because there has already been configured a keba for this IP.";
|
|
|
|
|
info->finish(Thing::ThingErrorThingInUse, QT_TR_NOOP("Already configured for this IP address."));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Make sure we have a valid mac address, otherwise no monitor and not auto searching is possible
|
|
|
|
|
MacAddress macAddress = MacAddress(thing->paramValue(m_macAddressParamTypeIds.value(thing->thingClassId())).toString());
|
|
|
|
|
if (macAddress.isNull()) {
|
|
|
|
|
qCWarning(dcKeba()) << "Failed to set up keba because the MAC address is not valid:" << thing->paramValue(m_macAddressParamTypeIds.value(thing->thingClassId())).toString() << macAddress.toString();
|
|
|
|
|
info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The MAC address is not vaild. Please reconfigure the device to fix this."));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
KeContact *keba = new KeContact(address, m_kebaDataLayer, this);
|
|
|
|
|
connect(keba, &KeContact::reachableChanged, this, &IntegrationPluginKeba::onConnectionChanged);
|
|
|
|
|
connect(keba, &KeContact::commandExecuted, this, &IntegrationPluginKeba::onCommandExecuted);
|
|
|
|
|
connect(keba, &KeContact::reportTwoReceived, this, &IntegrationPluginKeba::onReportTwoReceived);
|
|
|
|
|
connect(keba, &KeContact::reportThreeReceived, this, &IntegrationPluginKeba::onReportThreeReceived);
|
|
|
|
|
connect(keba, &KeContact::report1XXReceived, this, &IntegrationPluginKeba::onReport1XXReceived);
|
|
|
|
|
connect(keba, &KeContact::broadcastReceived, this, &IntegrationPluginKeba::onBroadcastReceived);
|
|
|
|
|
|
|
|
|
|
connect(info, &ThingSetupInfo::aborted, keba, &KeContact::deleteLater); // Clean up if the setup fails
|
|
|
|
|
|
|
|
|
|
// Make sure we receive data from the keba and the DIP switches are configured correctly
|
|
|
|
|
connect(keba, &KeContact::reportOneReceived, info, [=] (const KeContact::ReportOne &report) {
|
|
|
|
|
Thing *thing = info->thing();
|
|
|
|
|
qCDebug(dcKeba()) << "Report one received for" << thing->name();
|
|
|
|
|
qCDebug(dcKeba()) << " - Firmware" << report.firmware;
|
|
|
|
|
qCDebug(dcKeba()) << " - Serial" << report.serialNumber;
|
|
|
|
|
qCDebug(dcKeba()) << " - Product" << report.product;
|
|
|
|
|
qCDebug(dcKeba()) << " - Uptime" << report.seconds / 60 << "[min]";
|
|
|
|
|
qCDebug(dcKeba()) << " - Com Module" << report.comModule;
|
|
|
|
|
qCDebug(dcKeba()) << " - DIP switch 1" << report.dipSw1;
|
|
|
|
|
qCDebug(dcKeba()) << " - DIP switch 2" << report.dipSw2;
|
|
|
|
|
|
|
|
|
|
KebaProductInfo productInformation(report.product);
|
|
|
|
|
|
|
|
|
|
if (thing->paramValue(m_serialNumberParamTypeIds.value(thing->thingClassId())).toString().isEmpty()) {
|
|
|
|
|
qCDebug(dcKeba()) << "Update serial number parameter for" << thing << "to" << report.serialNumber;
|
|
|
|
|
thing->setParamValue(m_serialNumberParamTypeIds.value(thing->thingClassId()), report.serialNumber);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (thing->paramValue(m_modelParamTypeIds.value(thing->thingClassId())).toString().isEmpty()) {
|
|
|
|
|
qCDebug(dcKeba()) << "Update model parameter for" << thing << "to" << report.product;
|
|
|
|
|
thing->setParamValue(m_modelParamTypeIds.value(thing->thingClassId()), report.product);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify the DIP switches and warn the user in case if wrong configuration
|
|
|
|
|
// For having UPD controll on the keba we need DIP Switch 1.3 enabled
|
|
|
|
|
KeContact::DipSwitchOneFlag dipSwOne(report.dipSw1);
|
|
|
|
|
qCDebug(dcKeba()) << dipSwOne;
|
|
|
|
|
if (!dipSwOne.testFlag(KeContact::DipSwitchOneSmartHomeInterface)) {
|
|
|
|
|
qCWarning(dcKeba()) << "Connected successfully to Keba but the DIP Switch for controlling it is not enabled.";
|
|
|
|
|
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The required communication interface is not enabled on this keba. Please make sure the DIP switch 1.3 is switched on and try again."));
|
|
|
|
|
// Create a monitor so we always get the correct IP in the network and see if the device is reachable without polling on our own
|
|
|
|
|
NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(macAddress);
|
|
|
|
|
connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){
|
|
|
|
|
// Only if the setup has been finished
|
|
|
|
|
KeContact *keba = m_kebaDevices.value(thing->id());
|
|
|
|
|
if (!keba)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
qCDebug(dcKeba()) << "Network device monitor reachable changed for" << thing->name() << reachable;
|
|
|
|
|
if (reachable) {
|
|
|
|
|
// Update address and refresh
|
|
|
|
|
keba->setAddress(monitor->networkDeviceInfo().address());
|
|
|
|
|
refresh(thing, keba);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse the product code and check if the model actually supports the UDP/Modbus communication
|
|
|
|
|
// Supported are:
|
|
|
|
|
// - The A series (german edition), no meter DE440 (green edition)
|
|
|
|
|
// - The B series (german edition), no meter DE440
|
|
|
|
|
// - All C series
|
|
|
|
|
// - All X series
|
|
|
|
|
|
|
|
|
|
if (productInformation.isValid()) {
|
|
|
|
|
|
|
|
|
|
bool supported = false;
|
|
|
|
|
|
|
|
|
|
qCDebug(dcKeba()) << "Product information are valid. Evaluating if model supports UDP/Modbus communication...";
|
|
|
|
|
|
|
|
|
|
switch (productInformation.series()) {
|
|
|
|
|
case KebaProductInfo::SeriesA:
|
|
|
|
|
if (productInformation.model() == "P30" && productInformation.germanEdition()) {
|
|
|
|
|
qCDebug(dcKeba()) << "The P30 A series german edition is supported (DE440 GREEN EDITION)";
|
|
|
|
|
supported = true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case KebaProductInfo::SeriesB:
|
|
|
|
|
if (productInformation.model() == "P30" && productInformation.germanEdition()) {
|
|
|
|
|
qCDebug(dcKeba()) << "The P30 B series german edition is supported (DE440)";
|
|
|
|
|
supported = true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case KebaProductInfo::SeriesC:
|
|
|
|
|
case KebaProductInfo::SeriesXWlan:
|
|
|
|
|
case KebaProductInfo::SeriesXWlan3G:
|
|
|
|
|
case KebaProductInfo::SeriesXWlan4G:
|
|
|
|
|
case KebaProductInfo::SeriesX3G:
|
|
|
|
|
case KebaProductInfo::SeriesX4G:
|
|
|
|
|
qCDebug(dcKeba()) << "The keba" << productInformation.series() << "is capable of communicating using UDP";
|
|
|
|
|
supported = true;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!supported) {
|
|
|
|
|
qCWarning(dcKeba()) << "Connected successfully to Keba but this model" << productInformation.series() << "has no communication module.";
|
|
|
|
|
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("This model does not support communication with smart devices."));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
qCWarning(dcKeba()) << "Product information are not valid. Cannot determin if this model supports UDP/Modbus communication, assuming yes so let's try to init...";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_kebaDevices.insert(thing->id(), keba);
|
|
|
|
|
info->finish(Thing::ThingErrorNoError);
|
|
|
|
|
qCDebug(dcKeba()) << "Setup finsihed successfully for" << thing << thing->params();
|
|
|
|
|
|
|
|
|
|
thing->setStateValue("connected", true);
|
|
|
|
|
thing->setStateValue("firmware", report.firmware);
|
|
|
|
|
thing->setStateValue("uptime", report.seconds / 60);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
keba->getReport1();
|
|
|
|
|
|
|
|
|
|
connect(keba, &KeContact::destroyed, this, [this, thing]{
|
|
|
|
|
m_kebaDevices.remove(thing->id());
|
|
|
|
|
// Setup failed, lets search the network, maybe the IP has changed...
|
|
|
|
|
searchNetworkDevices();
|
|
|
|
|
});
|
|
|
|
|
// Continue with setup only if we know that the network device is reachable
|
|
|
|
|
m_monitors.insert(thing, monitor);
|
|
|
|
|
if (monitor->reachable()) {
|
|
|
|
|
setupKeba(info, monitor->networkDeviceInfo().address());
|
|
|
|
|
} else {
|
|
|
|
|
// otherwise wait until we reach the networkdevice before setting up the device
|
|
|
|
|
qCDebug(dcKeba()) << "Network device" << thing->name() << "is not reachable yet. Continue with the setup once reachable.";
|
|
|
|
|
connect(monitor, &NetworkDeviceMonitor::reachableChanged, info, [=](bool reachable){
|
|
|
|
|
if (reachable) {
|
|
|
|
|
qCDebug(dcKeba()) << "Network device" << thing->name() << "is now reachable. Continue with the setup...";
|
|
|
|
|
setupKeba(info, monitor->networkDeviceInfo().address());
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void IntegrationPluginKeba::postSetupThing(Thing *thing)
|
|
|
|
|
@ -278,18 +195,9 @@ void IntegrationPluginKeba::postSetupThing(Thing *thing)
|
|
|
|
|
if (!keba) {
|
|
|
|
|
qCWarning(dcKeba()) << "No Keba connection found for this thing while doing post setup.";
|
|
|
|
|
return;
|
|
|
|
|
} else {
|
|
|
|
|
keba->getReport2();
|
|
|
|
|
// No valid information if no meter
|
|
|
|
|
if (thing->thingClassId() != kebaSimpleThingClassId)
|
|
|
|
|
keba->getReport3();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try to find the mac address in case the user added the ip manually
|
|
|
|
|
if (thing->paramValue(m_macAddressParamTypeIds.value(thing->thingClassId())).toString().isEmpty()
|
|
|
|
|
|| thing->paramValue(m_macAddressParamTypeIds.value(thing->thingClassId())).toString() == "00:00:00:00:00:00") {
|
|
|
|
|
searchNetworkDevices();
|
|
|
|
|
}
|
|
|
|
|
refresh(thing, keba);
|
|
|
|
|
|
|
|
|
|
if (!m_updateTimer) {
|
|
|
|
|
m_updateTimer = hardwareManager()->pluginTimerManager()->registerTimer(10);
|
|
|
|
|
@ -297,75 +205,47 @@ void IntegrationPluginKeba::postSetupThing(Thing *thing)
|
|
|
|
|
foreach (const ThingId &thingId, m_kebaDevices.keys()) {
|
|
|
|
|
KeContact *keba = m_kebaDevices.value(thingId);
|
|
|
|
|
Thing *thing = myThings().findById(thingId);
|
|
|
|
|
if (!thing)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (!keba) {
|
|
|
|
|
qCWarning(dcKeba()) << "No Keba connection found for" << thing->name();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
keba->getReport2();
|
|
|
|
|
keba->getReport3();
|
|
|
|
|
if (thing->stateValue("activity").toString() == "Charging") {
|
|
|
|
|
keba->getReport1XX(100);
|
|
|
|
|
}
|
|
|
|
|
refresh(thing, keba);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
m_updateTimer->start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!m_reconnectTimer) {
|
|
|
|
|
m_reconnectTimer = hardwareManager()->pluginTimerManager()->registerTimer(60 * 5);
|
|
|
|
|
connect(m_reconnectTimer, &PluginTimer::timeout, this, [this] {
|
|
|
|
|
bool startDiscoveryRequired = false;
|
|
|
|
|
// Only search for new network devices if there is one keba which is not connected
|
|
|
|
|
foreach (const ThingId &thingId, m_kebaDevices.keys()) {
|
|
|
|
|
KeContact *keba = m_kebaDevices.value(thingId);
|
|
|
|
|
Thing *thing = myThings().findById(thingId);
|
|
|
|
|
if (!keba) {
|
|
|
|
|
qCWarning(dcKeba()) << "No Keba connection found for" << thing->name();
|
|
|
|
|
startDiscoveryRequired = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!keba->reachable()) {
|
|
|
|
|
startDiscoveryRequired = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (startDiscoveryRequired)
|
|
|
|
|
searchNetworkDevices();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
m_reconnectTimer->start();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void IntegrationPluginKeba::thingRemoved(Thing *thing)
|
|
|
|
|
{
|
|
|
|
|
qCDebug(dcKeba()) << "Deleting" << thing->name();
|
|
|
|
|
qCDebug(dcKeba()) << "Removing" << thing->name();
|
|
|
|
|
if (m_kebaDevices.contains(thing->id())) {
|
|
|
|
|
KeContact *keba = m_kebaDevices.take(thing->id());
|
|
|
|
|
keba->deleteLater();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_monitors.contains(thing)) {
|
|
|
|
|
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_lastSessionId.remove(thing->id());
|
|
|
|
|
|
|
|
|
|
if (myThings().empty()) {
|
|
|
|
|
qCDebug(dcKeba()) << "Closing UDP Ports";
|
|
|
|
|
m_kebaDataLayer->deleteLater();
|
|
|
|
|
m_kebaDataLayer= nullptr;
|
|
|
|
|
|
|
|
|
|
qCDebug(dcKeba()) << "Stopping plugin timers ...";
|
|
|
|
|
if (m_reconnectTimer) {
|
|
|
|
|
hardwareManager()->pluginTimerManager()->unregisterTimer(m_reconnectTimer);
|
|
|
|
|
m_reconnectTimer = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_updateTimer) {
|
|
|
|
|
hardwareManager()->pluginTimerManager()->unregisterTimer(m_updateTimer);
|
|
|
|
|
m_updateTimer = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qCDebug(dcKeba()) << "Closing keba data layer...";
|
|
|
|
|
m_kebaDataLayer->deleteLater();
|
|
|
|
|
m_kebaDataLayer= nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -442,6 +322,115 @@ void IntegrationPluginKeba::executeAction(ThingActionInfo *info)
|
|
|
|
|
connect(info, &ThingActionInfo::aborted, this, [requestId, this]{ m_asyncActions.remove(requestId); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void IntegrationPluginKeba::setupKeba(ThingSetupInfo *info, const QHostAddress &address)
|
|
|
|
|
{
|
|
|
|
|
Thing *thing = info->thing();
|
|
|
|
|
KeContact *keba = new KeContact(address, m_kebaDataLayer, this);
|
|
|
|
|
connect(keba, &KeContact::reachableChanged, thing, [=](bool reachable){
|
|
|
|
|
thing->setStateValue("connected", reachable);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
connect(keba, &KeContact::commandExecuted, this, &IntegrationPluginKeba::onCommandExecuted);
|
|
|
|
|
connect(keba, &KeContact::reportTwoReceived, this, &IntegrationPluginKeba::onReportTwoReceived);
|
|
|
|
|
connect(keba, &KeContact::reportThreeReceived, this, &IntegrationPluginKeba::onReportThreeReceived);
|
|
|
|
|
connect(keba, &KeContact::report1XXReceived, this, &IntegrationPluginKeba::onReport1XXReceived);
|
|
|
|
|
connect(keba, &KeContact::broadcastReceived, this, &IntegrationPluginKeba::onBroadcastReceived);
|
|
|
|
|
|
|
|
|
|
// Clean up if the setup fails
|
|
|
|
|
connect(info, &ThingSetupInfo::aborted, keba, &KeContact::deleteLater);
|
|
|
|
|
|
|
|
|
|
// Make sure we receive data from the keba and the DIP switches are configured correctly
|
|
|
|
|
connect(keba, &KeContact::reportOneReceived, info, [=] (const KeContact::ReportOne &report) {
|
|
|
|
|
Thing *thing = info->thing();
|
|
|
|
|
qCDebug(dcKeba()) << "Report one received for" << thing->name();
|
|
|
|
|
qCDebug(dcKeba()) << " - Firmware" << report.firmware;
|
|
|
|
|
qCDebug(dcKeba()) << " - Serial" << report.serialNumber;
|
|
|
|
|
qCDebug(dcKeba()) << " - Product" << report.product;
|
|
|
|
|
qCDebug(dcKeba()) << " - Uptime" << report.seconds / 60 << "[min]";
|
|
|
|
|
qCDebug(dcKeba()) << " - Com Module" << report.comModule;
|
|
|
|
|
qCDebug(dcKeba()) << " - DIP switch 1" << report.dipSw1;
|
|
|
|
|
qCDebug(dcKeba()) << " - DIP switch 2" << report.dipSw2;
|
|
|
|
|
|
|
|
|
|
KebaProductInfo productInformation(report.product);
|
|
|
|
|
|
|
|
|
|
if (thing->paramValue(m_serialNumberParamTypeIds.value(thing->thingClassId())).toString().isEmpty()) {
|
|
|
|
|
qCDebug(dcKeba()) << "Update serial number parameter for" << thing << "to" << report.serialNumber;
|
|
|
|
|
thing->setParamValue(m_serialNumberParamTypeIds.value(thing->thingClassId()), report.serialNumber);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (thing->paramValue(m_modelParamTypeIds.value(thing->thingClassId())).toString().isEmpty()) {
|
|
|
|
|
qCDebug(dcKeba()) << "Update model parameter for" << thing << "to" << report.product;
|
|
|
|
|
thing->setParamValue(m_modelParamTypeIds.value(thing->thingClassId()), report.product);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify the DIP switches and warn the user in case if wrong configuration
|
|
|
|
|
// For having UPD controll on the keba we need DIP Switch 1.3 enabled
|
|
|
|
|
KeContact::DipSwitchOneFlag dipSwOne(report.dipSw1);
|
|
|
|
|
qCDebug(dcKeba()) << dipSwOne;
|
|
|
|
|
if (!dipSwOne.testFlag(KeContact::DipSwitchOneSmartHomeInterface)) {
|
|
|
|
|
qCWarning(dcKeba()) << "Connected successfully to Keba but the DIP Switch for controlling it is not enabled.";
|
|
|
|
|
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The required communication interface is not enabled on this keba. Please make sure the DIP switch 1.3 is switched on and try again."));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse the product code and check if the model actually supports the UDP/Modbus communication
|
|
|
|
|
// Supported are:
|
|
|
|
|
// - The A series (german edition), no meter DE440 (green edition)
|
|
|
|
|
// - The B series (german edition), no meter DE440
|
|
|
|
|
// - All C series
|
|
|
|
|
// - All X series
|
|
|
|
|
|
|
|
|
|
if (productInformation.isValid()) {
|
|
|
|
|
bool supported = false;
|
|
|
|
|
qCDebug(dcKeba()) << "Product information are valid. Evaluating if model supports UDP/Modbus communication...";
|
|
|
|
|
|
|
|
|
|
switch (productInformation.series()) {
|
|
|
|
|
case KebaProductInfo::SeriesA:
|
|
|
|
|
if (productInformation.model() == "P30" && productInformation.germanEdition()) {
|
|
|
|
|
qCDebug(dcKeba()) << "The P30 A series german edition is supported (DE440 GREEN EDITION)";
|
|
|
|
|
supported = true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case KebaProductInfo::SeriesB:
|
|
|
|
|
if (productInformation.model() == "P30" && productInformation.germanEdition()) {
|
|
|
|
|
qCDebug(dcKeba()) << "The P30 B series german edition is supported (DE440)";
|
|
|
|
|
supported = true;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case KebaProductInfo::SeriesC:
|
|
|
|
|
case KebaProductInfo::SeriesXWlan:
|
|
|
|
|
case KebaProductInfo::SeriesXWlan3G:
|
|
|
|
|
case KebaProductInfo::SeriesXWlan4G:
|
|
|
|
|
case KebaProductInfo::SeriesX3G:
|
|
|
|
|
case KebaProductInfo::SeriesX4G:
|
|
|
|
|
qCDebug(dcKeba()) << "The keba" << productInformation.series() << "is capable of communicating using UDP";
|
|
|
|
|
supported = true;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!supported) {
|
|
|
|
|
qCWarning(dcKeba()) << "Connected successfully to Keba but this model" << productInformation.series() << "has no communication module.";
|
|
|
|
|
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("This model does not support communication with smart devices."));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
qCWarning(dcKeba()) << "Product information are not valid. Cannot determin if this model supports UDP/Modbus communication, assuming yes so let's try to init...";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_kebaDevices.insert(thing->id(), keba);
|
|
|
|
|
info->finish(Thing::ThingErrorNoError);
|
|
|
|
|
qCDebug(dcKeba()) << "Setup finsihed successfully for" << thing << thing->params();
|
|
|
|
|
|
|
|
|
|
thing->setStateValue("connected", true);
|
|
|
|
|
thing->setStateValue("firmware", report.firmware);
|
|
|
|
|
thing->setStateValue("uptime", report.seconds / 60);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
keba->getReport1();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void IntegrationPluginKeba::onCommandExecuted(QUuid requestId, bool success)
|
|
|
|
|
{
|
|
|
|
|
if (m_asyncActions.contains(requestId)) {
|
|
|
|
|
@ -534,79 +523,19 @@ void IntegrationPluginKeba::setDevicePlugState(Thing *thing, KeContact::PlugStat
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void IntegrationPluginKeba::searchNetworkDevices()
|
|
|
|
|
void IntegrationPluginKeba::refresh(Thing *thing, KeContact *keba)
|
|
|
|
|
{
|
|
|
|
|
if (m_runningDiscovery) {
|
|
|
|
|
qCDebug(dcKeba()) << "Keba discovery already running.";
|
|
|
|
|
if (m_monitors.contains(thing) && !m_monitors.value(thing)->reachable())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
keba->getReport2();
|
|
|
|
|
// No valid information if no meter
|
|
|
|
|
if (thing->thingClassId() != kebaSimpleThingClassId) {
|
|
|
|
|
keba->getReport3();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!m_kebaDataLayer) {
|
|
|
|
|
qCDebug(dcKeba()) << "Could not search keba wallboxes in the network. The data layer seems not to be available";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qCDebug(dcKeba()) << "Start searching keba wallboxes in the network...";
|
|
|
|
|
m_runningDiscovery = new KebaDiscovery(m_kebaDataLayer, hardwareManager()->networkDeviceDiscovery(), this);
|
|
|
|
|
connect(m_runningDiscovery, &KebaDiscovery::discoveryFinished, this, [=](){
|
|
|
|
|
foreach (const KebaDiscovery::KebaDiscoveryResult &result, m_runningDiscovery->discoveryResults()) {
|
|
|
|
|
foreach (const ThingId &thingId, m_kebaDevices.keys()) {
|
|
|
|
|
Thing *existingThing = myThings().findById(thingId);
|
|
|
|
|
if (!existingThing)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (existingThing->paramValue(m_macAddressParamTypeIds.value(existingThing->thingClassId())).toString().isEmpty()) {
|
|
|
|
|
//This device got probably manually setup, to enable auto rediscovery the MAC address needs to setup
|
|
|
|
|
if (existingThing->paramValue(m_ipAddressParamTypeIds.value(existingThing->thingClassId())).toString() == result.networkDeviceInfo.address().toString()) {
|
|
|
|
|
qCDebug(dcKeba()) << "Wallbox MAC address has been discovered" << existingThing->name() << result.networkDeviceInfo.macAddress();
|
|
|
|
|
existingThing->setParamValue(m_macAddressParamTypeIds.value(existingThing->thingClassId()), result.networkDeviceInfo.macAddress());
|
|
|
|
|
}
|
|
|
|
|
} else if (existingThing->paramValue(m_macAddressParamTypeIds.value(existingThing->thingClassId())).toString() == result.networkDeviceInfo.macAddress()) {
|
|
|
|
|
// We found the existing keba thing, lets check if the ip has changed
|
|
|
|
|
if (existingThing->paramValue(m_ipAddressParamTypeIds.value(existingThing->thingClassId())).toString() != result.networkDeviceInfo.address().toString()) {
|
|
|
|
|
// Update the ip address of the thing.
|
|
|
|
|
// FIXME: as of now the thing manager does not store the changed param
|
|
|
|
|
qCDebug(dcKeba()) << "Wallbox IP Address has changed, from" << existingThing->paramValue(m_ipAddressParamTypeIds.value(existingThing->thingClassId())).toString()
|
|
|
|
|
<< "to" << result.networkDeviceInfo.address().toString();
|
|
|
|
|
existingThing->setParamValue(m_ipAddressParamTypeIds.value(existingThing->thingClassId()), result.networkDeviceInfo.address().toString());
|
|
|
|
|
|
|
|
|
|
// Make sure the setup has already run for this thing, if not, the thingmanager will retry with the new ip every 15 seconds
|
|
|
|
|
KeContact *keba = m_kebaDevices.value(existingThing->id());
|
|
|
|
|
if (keba) {
|
|
|
|
|
keba->setAddress(QHostAddress(result.networkDeviceInfo.address()));
|
|
|
|
|
|
|
|
|
|
// Refresh
|
|
|
|
|
keba->getReport2();
|
|
|
|
|
keba->getReport3();
|
|
|
|
|
} else {
|
|
|
|
|
qCWarning(dcKeba()) << "Could not update IP address since the keba connection has not been set up yet for" << existingThing;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
qCDebug(dcKeba()) << "Wallbox" << existingThing->name() << "IP address has not changed" << result.networkDeviceInfo.address().toString();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Clean up
|
|
|
|
|
m_runningDiscovery->deleteLater();
|
|
|
|
|
m_runningDiscovery = nullptr;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void IntegrationPluginKeba::onConnectionChanged(bool status)
|
|
|
|
|
{
|
|
|
|
|
KeContact *keba = static_cast<KeContact *>(sender());
|
|
|
|
|
Thing *thing = myThings().findById(m_kebaDevices.key(keba));
|
|
|
|
|
if (!thing) {
|
|
|
|
|
qCDebug(dcKeba()) << "Received connected changed but the thing seems not to be setup yet.";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
thing->setStateValue("connected", status);
|
|
|
|
|
if (!status) {
|
|
|
|
|
searchNetworkDevices();
|
|
|
|
|
if (thing->stateValue("activity").toString() == "Charging") {
|
|
|
|
|
keba->getReport1XX(100);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|