added auto rediscovery

This commit is contained in:
Boernsman 2021-01-13 12:19:16 +01:00
parent cdff51655f
commit 0803221e68
6 changed files with 393 additions and 143 deletions

View File

@ -4,17 +4,19 @@ This plugin allows to control Keba KeContact EV-Charging stations.
## Supported Things ## Supported Things
* KeContact * KeContact Wallbox
* Enable/disable the charging stations * P20
* Set maximum charging current * P30
* Get all informations e.g. voltage, current ... * BMW
* Print messages on the display
## Requirments ## Requirments
* nymea and the wallbox are required to be in the same network. * nymea and the wallbox are required to be in the same network.
* Port 7090 must not be blocked by a firewall or router. * UDP Port 7090 must not be blocked by a firewall or router.
* The package "nymea-plugin-keba" must be installed. * The package "nymea-plugin-keba" must be installed.
* KeContact P20 Charging station with network connection (LSA+ socket). Firmware version: 2.5 or higher.
* KeContact P30 Charging station or BMW wallbox. Firmware version 3.05 of higher.
* Enabled UDP function with DIP-switch DWS1.3 = ON.
## More ## More

View File

@ -42,20 +42,45 @@ IntegrationPluginKeba::IntegrationPluginKeba()
} }
void IntegrationPluginKeba::init()
{
m_discovery = new Discovery(this);
connect(m_discovery, &Discovery::finished, this, [this](const QList<Host> &hosts) {
foreach (const Host &host, hosts) {
if (!host.hostName().contains("keba", Qt::CaseSensitivity::CaseInsensitive))
continue;
foreach (Thing *existingThing, myThings()) {
if (existingThing->paramValue(wallboxThingMacAddressParamTypeId).toString() == host.macAddress()) {
if (existingThing->paramValue(wallboxThingIpAddressParamTypeId).toString() != host.address()) {
qCDebug(dcKebaKeContact()) << "Keba Wallbox IP Address has changed, from" << existingThing->paramValue(wallboxThingIpAddressParamTypeId).toString() << "to" << host.address();
existingThing->setParamValue(wallboxThingIpAddressParamTypeId, host.address());
} else {
qCDebug(dcKebaKeContact()) << "Keba Wallbox" << existingThing->name() << "IP address has not changed" << host.address();
}
break;
}
}
}
});
}
void IntegrationPluginKeba::discoverThings(ThingDiscoveryInfo *info) void IntegrationPluginKeba::discoverThings(ThingDiscoveryInfo *info)
{ {
if (info->thingClassId() == wallboxThingClassId) { if (info->thingClassId() == wallboxThingClassId) {
Discovery *discovery = new Discovery(info); qCDebug(dcKebaKeContact()) << "Discovering Keba Wallbox";
discovery->discoverHosts(25); m_discovery->discoverHosts(25);
connect(m_discovery, &Discovery::finished, info, [this, info] (const QList<Host> &hosts) {
connect(discovery, &Discovery::finished, info, [this, info](const QList<Host> &hosts) {
qCDebug(dcKebaKeContact()) << "Discovery finished. Found" << hosts.count() << "devices";
foreach (const Host &host, hosts) { foreach (const Host &host, hosts) {
if (!host.hostName().contains("keba", Qt::CaseSensitivity::CaseInsensitive)) if (!host.hostName().contains("keba", Qt::CaseSensitivity::CaseInsensitive))
continue; continue;
ThingDescriptor descriptor(wallboxThingClassId, "Wallbox", host.address() + " (" + host.macAddress() + ")"); ThingDescriptor descriptor(wallboxThingClassId, "Wallbox", host.address() + " (" + host.macAddress() + ")");
// Rediscovery
foreach (Thing *existingThing, myThings()) { foreach (Thing *existingThing, myThings()) {
if (existingThing->paramValue(wallboxThingMacAddressParamTypeId).toString() == host.macAddress()) { if (existingThing->paramValue(wallboxThingMacAddressParamTypeId).toString() == host.macAddress()) {
descriptor.setThingId(existingThing->id()); descriptor.setThingId(existingThing->id());
@ -86,7 +111,7 @@ void IntegrationPluginKeba::setupThing(ThingSetupInfo *info)
QHostAddress address = QHostAddress(thing->paramValue(wallboxThingIpAddressParamTypeId).toString()); QHostAddress address = QHostAddress(thing->paramValue(wallboxThingIpAddressParamTypeId).toString());
KeContact *keba = new KeContact(address, this); KeContact *keba = new KeContact(address, this);
connect(keba, &KeContact::connectionChanged, this, &IntegrationPluginKeba::onConnectionChanged); connect(keba, &KeContact::reachableChanged, this, &IntegrationPluginKeba::onConnectionChanged);
connect(keba, &KeContact::commandExecuted, this, &IntegrationPluginKeba::onCommandExecuted); connect(keba, &KeContact::commandExecuted, this, &IntegrationPluginKeba::onCommandExecuted);
connect(keba, &KeContact::reportOneReceived, this, &IntegrationPluginKeba::onReportOneReceived); connect(keba, &KeContact::reportOneReceived, this, &IntegrationPluginKeba::onReportOneReceived);
connect(keba, &KeContact::reportTwoReceived, this, &IntegrationPluginKeba::onReportTwoReceived); connect(keba, &KeContact::reportTwoReceived, this, &IntegrationPluginKeba::onReportTwoReceived);
@ -98,13 +123,26 @@ void IntegrationPluginKeba::setupThing(ThingSetupInfo *info)
return info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Error opening network port.")); return info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Error opening network port."));
} }
ThingId id = thing->id(); m_kebaDevices.insert(thing->id(), keba);
m_kebaDevices.insert(id, keba);
m_asyncSetup.insert(keba, info);
keba->getReport1(); keba->getReport1();
connect(info, &ThingSetupInfo::aborted, this, [id, keba, this]{ connect(keba, &KeContact::reportOneReceived, info, [info] (const KeContact::ReportOne &report) {
m_asyncSetup.remove(keba); Thing *thing = info->thing();
m_kebaDevices.remove(id);
qCDebug(dcKebaKeContact()) << "Report one received for" << thing->name();
qCDebug(dcKebaKeContact()) << " - Firmware" << report.firmware;
qCDebug(dcKebaKeContact()) << " - Product" << report.product;
qCDebug(dcKebaKeContact()) << " - Uptime" << report.seconds;
qCDebug(dcKebaKeContact()) << " - Com Module" << report.comModule;
thing->setStateValue(wallboxConnectedStateTypeId, true);
thing->setStateValue(wallboxFirmwareStateTypeId, report.firmware);
thing->setStateValue(wallboxModelStateTypeId, report.product);
thing->setStateValue(wallboxUptimeStateTypeId, report.seconds);
info->finish(Thing::ThingErrorNoError);
});
connect(info, &ThingSetupInfo::aborted, keba, &KeContact::deleteLater);
connect(keba, &KeContact::destroyed, this, [thing, keba, this]{
m_kebaDevices.remove(thing->id());
keba->deleteLater(); keba->deleteLater();
}); });
} else { } else {
@ -118,39 +156,24 @@ void IntegrationPluginKeba::postSetupThing(Thing *thing)
qCDebug(dcKebaKeContact()) << "Post setup" << thing->name(); qCDebug(dcKebaKeContact()) << "Post setup" << thing->name();
KeContact *keba = m_kebaDevices.value(thing->id()); KeContact *keba = m_kebaDevices.value(thing->id());
if (!keba) { if (!keba) {
return; qCWarning(dcKebaKeContact()) << "No Keba connection found for this thing";
} } else {
keba->getReport2();
keba->getReport3();
if (!m_pluginTimer) {
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(60);
connect(m_pluginTimer, &PluginTimer::timeout, this, &IntegrationPluginKeba::updateData);
}
}
void IntegrationPluginKeba::thingRemoved(Thing *thing)
{
if (thing->thingClassId() == wallboxThingClassId) {
KeContact *keba = m_kebaDevices.take(thing->id());
keba->deleteLater();
}
if (myThings().empty()) {
// last device has been removed the plug in timer can be stopped again
hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer);
m_pluginTimer = nullptr;
}
}
void IntegrationPluginKeba::updateData()
{
foreach (KeContact *keba, m_kebaDevices) {
keba->getReport2(); keba->getReport2();
keba->getReport3(); keba->getReport3();
} }
if (!m_updateTimer) {
m_updateTimer = hardwareManager()->pluginTimerManager()->registerTimer(60);
connect(m_updateTimer, &PluginTimer::timeout, this, [this] {
foreach (Thing *thing, myThings().filterByThingClassId(wallboxThingClassId)) { foreach (Thing *thing, myThings().filterByThingClassId(wallboxThingClassId)) {
KeContact *keba = m_kebaDevices.value(thing->id());
if (!keba) {
qCWarning(dcKebaKeContact()) << "No Keba connection found for" << thing->name();
}
keba->getReport2();
keba->getReport3();
if (m_chargingSessionStartTime.contains(thing->id())) { if (m_chargingSessionStartTime.contains(thing->id())) {
QDateTime startTime = m_chargingSessionStartTime.value(thing->id()); QDateTime startTime = m_chargingSessionStartTime.value(thing->id());
@ -163,6 +186,37 @@ void IntegrationPluginKeba::updateData()
thing->setStateValue(wallboxSessionTimeStateTypeId, 0); thing->setStateValue(wallboxSessionTimeStateTypeId, 0);
} }
} }
});
}
if (!m_reconnectTimer) {
m_reconnectTimer = hardwareManager()->pluginTimerManager()->registerTimer(60*5);
connect(m_reconnectTimer, &PluginTimer::timeout, this, [this] {
Q_FOREACH(Thing *thing, myThings().filterByThingClassId(wallboxThingClassId)) {
if (thing->stateValue(wallboxConnectedStateTypeId) == false) {
m_discovery->discoverHosts(25);
break; // start discovery once for every device as soon as one device is disconnected
}
}
});
}
}
void IntegrationPluginKeba::thingRemoved(Thing *thing)
{
qCDebug(dcKebaKeContact()) << "Deleting" << thing->name();
if (thing->thingClassId() == wallboxThingClassId) {
KeContact *keba = m_kebaDevices.take(thing->id());
keba->deleteLater();
}
if (myThings().empty()) {
qCDebug(dcKebaKeContact()) << "Stopping plugin timers";
hardwareManager()->pluginTimerManager()->unregisterTimer(m_reconnectTimer);
m_reconnectTimer = nullptr;
hardwareManager()->pluginTimerManager()->unregisterTimer(m_updateTimer);
m_updateTimer = nullptr;
}
} }
void IntegrationPluginKeba::setDeviceState(Thing *thing, KeContact::State state) void IntegrationPluginKeba::setDeviceState(Thing *thing, KeContact::State state)
@ -230,15 +284,17 @@ void IntegrationPluginKeba::onConnectionChanged(bool status)
} }
thing->setStateValue(wallboxConnectedStateTypeId, status); thing->setStateValue(wallboxConnectedStateTypeId, status);
if (!status) { if (!status) {
//TODO start rediscovery m_discovery->discoverHosts(25);
} }
} }
void IntegrationPluginKeba::onCommandExecuted(QUuid requestId, bool success) void IntegrationPluginKeba::onCommandExecuted(QUuid requestId, bool success)
{ {
updateData();
if (m_asyncActions.contains(requestId)) { if (m_asyncActions.contains(requestId)) {
KeContact *keba = static_cast<KeContact *>(sender()); KeContact *keba = static_cast<KeContact *>(sender());
keba->getReport2(); //Check if the state was actually set
Thing *thing = myThings().findById(m_kebaDevices.key(keba)); Thing *thing = myThings().findById(m_kebaDevices.key(keba));
if (!thing) { if (!thing) {
qCWarning(dcKebaKeContact()) << "On command executed: missing device object"; qCWarning(dcKebaKeContact()) << "On command executed: missing device object";
@ -253,18 +309,6 @@ void IntegrationPluginKeba::onCommandExecuted(QUuid requestId, bool success)
} }
} }
void IntegrationPluginKeba::onReportOneReceived(const KeContact::ReportOne &reportOne)
{
Q_UNUSED(reportOne);
KeContact *keba = static_cast<KeContact *>(sender());
if (m_asyncSetup.contains(keba)) {
ThingSetupInfo *info = m_asyncSetup.value(keba);
info->finish(Thing::ThingErrorNoError);
} else {
qCDebug(dcKebaKeContact()) << "Report one received without an associated async setup";
}
}
void IntegrationPluginKeba::onReportTwoReceived(const KeContact::ReportTwo &reportTwo) void IntegrationPluginKeba::onReportTwoReceived(const KeContact::ReportTwo &reportTwo)
{ {
KeContact *keba = static_cast<KeContact *>(sender()); KeContact *keba = static_cast<KeContact *>(sender());
@ -272,8 +316,36 @@ void IntegrationPluginKeba::onReportTwoReceived(const KeContact::ReportTwo &repo
if (!thing) if (!thing)
return; return;
thing->setStateValue()
thing->setStateValue(wallboxPowerStateTypeId, reportTwo.enableUser); thing->setStateValue(wallboxPowerStateTypeId, reportTwo.enableUser);
thing->setStateValue(wallboxMaxChargingCurrentPercentStateTypeId, reportTwo.MaxCurrentPercentage); thing->setStateValue(wallboxError1StateTypeId, reportTwo.error1);
thing->setStateValue(wallboxError2StateTypeId, reportTwo.error2);
qCDebug(dcKebaKeContact()) << "Report 2 recieved for" << thing->name();
qCDebug(dcKebaKeContact()) << " - State:" << reportTwo.state;
qCDebug(dcKebaKeContact()) << " - Error 1:" << reportTwo.error1;
qCDebug(dcKebaKeContact()) << " - Error 2:" << reportTwo.error2;
qCDebug(dcKebaKeContact()) << " - Plug:" << reportTwo.plugState;
qCDebug(dcKebaKeContact()) << " - Enable sys:" << reportTwo.enableSys;
qCDebug(dcKebaKeContact()) << " - Enable user:" << reportTwo.enableUser;
qCDebug(dcKebaKeContact()) << " - Max curr:" << reportTwo.maxCurrent;
qCDebug(dcKebaKeContact()) << " - Max curr %:" << reportTwo.maxCurrentPercentage;
qCDebug(dcKebaKeContact()) << " - Curr HW:" << reportTwo.currentHardwareLimitation;
qCDebug(dcKebaKeContact()) << " - Curr User:" << reportTwo.currentUser;
qCDebug(dcKebaKeContact()) << " - Curr FS:" << reportTwo.currentFailsafe;
qCDebug(dcKebaKeContact()) << " - Tmo FS:" << reportTwo.timeoutFailsafe;
qCDebug(dcKebaKeContact()) << " - Curr timer:" << reportTwo.currTimer;
qCDebug(dcKebaKeContact()) << " - Timeout CT:" << reportTwo.timeoutCt;
qCDebug(dcKebaKeContact()) << " - Output:" << reportTwo.output;
qCDebug(dcKebaKeContact()) << " - Input:" << reportTwo.input;
qCDebug(dcKebaKeContact()) << " - Serialnumber:" << reportTwo.serialNumber;
qCDebug(dcKebaKeContact()) << " - Uptime:" << reportTwo.seconds;
//thing->setStateValue(wallboxMaxChargingCurrentAmpereStateTypeId, reportTwo.maxCurrent);
thing->setStateValue(wallboxMaxChargingCurrentPercentStateTypeId, reportTwo.maxCurrentPercentage);
//thing->setStateValue(wallboxCurrentHardwareLimitationStateTypeId, reportTwo.currentHardwareLimitation);
setDeviceState(thing, reportTwo.state); setDeviceState(thing, reportTwo.state);
setDevicePlugState(thing, reportTwo.plugState); setDevicePlugState(thing, reportTwo.plugState);
@ -286,14 +358,15 @@ void IntegrationPluginKeba::onReportThreeReceived(const KeContact::ReportThree &
if (!thing) if (!thing)
return; return;
thing->setStateValue(wallboxI1EventTypeId, reportThree.CurrentPhase1); thing->setStateValue(wallboxCurrentPhase1EventTypeId, reportThree.CurrentPhase1);
thing->setStateValue(wallboxI2EventTypeId, reportThree.CurrentPhase2); thing->setStateValue(wallboxCurrentPhase2EventTypeId, reportThree.CurrentPhase2);
thing->setStateValue(wallboxI3EventTypeId, reportThree.CurrentPhase3); thing->setStateValue(wallboxCurrentPhase3EventTypeId, reportThree.CurrentPhase3);
thing->setStateValue(wallboxU1EventTypeId, reportThree.VoltagePhase1); thing->setStateValue(wallboxVoltagePhase1EventTypeId, reportThree.VoltagePhase1);
thing->setStateValue(wallboxU2EventTypeId, reportThree.VoltagePhase2); thing->setStateValue(wallboxVoltagePhase2EventTypeId, reportThree.VoltagePhase2);
thing->setStateValue(wallboxU3EventTypeId, reportThree.VoltagePhase3); thing->setStateValue(wallboxVoltagePhase3EventTypeId, reportThree.VoltagePhase3);
thing->setStateValue(wallboxPStateTypeId, reportThree.Power); thing->setStateValue(wallboxPowerConsumptionStateTypeId, reportThree.Power);
thing->setStateValue(wallboxEPStateTypeId, reportThree.EnergySession); thing->setStateValue(wallboxSessionEnergyStateTypeId, reportThree.EnergySession); //TODO check state name
thing->setStateValue(wallboxPowerFactorStateTypeId, reportThree.PowerFactor);
thing->setStateValue(wallboxTotalEnergyConsumedStateTypeId, reportThree.EnergyTotal); thing->setStateValue(wallboxTotalEnergyConsumedStateTypeId, reportThree.EnergyTotal);
} }
@ -311,7 +384,7 @@ void IntegrationPluginKeba::onBroadcastReceived(KeContact::BroadcastType type, c
case KeContact::BroadcastTypeInput: case KeContact::BroadcastTypeInput:
break; break;
case KeContact::BroadcastTypeEPres: case KeContact::BroadcastTypeEPres:
thing->setStateValue(wallboxEPStateTypeId, content.toInt()); thing->setStateValue(wallboxSessionEnergyStateTypeId, content.toInt());
break; break;
case KeContact::BroadcastTypeState: case KeContact::BroadcastTypeState:
setDeviceState(thing, KeContact::State(content.toInt())); setDeviceState(thing, KeContact::State(content.toInt()));

View File

@ -52,6 +52,7 @@ class IntegrationPluginKeba : public IntegrationPlugin
public: public:
explicit IntegrationPluginKeba(); explicit IntegrationPluginKeba();
void init() override;
void discoverThings(ThingDiscoveryInfo *info) override; void discoverThings(ThingDiscoveryInfo *info) override;
void setupThing(ThingSetupInfo *info) override; void setupThing(ThingSetupInfo *info) override;
@ -59,10 +60,12 @@ public:
void thingRemoved(Thing* thing) override; void thingRemoved(Thing* thing) override;
void executeAction(ThingActionInfo *info) override; void executeAction(ThingActionInfo *info) override;
void updateData();
private: private:
PluginTimer *m_pluginTimer = nullptr; PluginTimer *m_updateTimer = nullptr;
PluginTimer *m_reconnectTimer = nullptr;
Discovery *m_discovery = nullptr;
QHash<ThingId, KeContact *> m_kebaDevices; QHash<ThingId, KeContact *> m_kebaDevices;
QHash<KeContact *, ThingSetupInfo *> m_asyncSetup; QHash<KeContact *, ThingSetupInfo *> m_asyncSetup;
QHash<QUuid, ThingActionInfo *> m_asyncActions; QHash<QUuid, ThingActionInfo *> m_asyncActions;

View File

@ -29,7 +29,8 @@
"displayName": "MAC Address", "displayName": "MAC Address",
"type": "QString", "type": "QString",
"inputType": "TextLine", "inputType": "TextLine",
"defaultValue":"" "defaultValue":"",
"readOnly": true
} }
], ],
"stateTypes": [ "stateTypes": [
@ -42,6 +43,60 @@
"defaultValue": false, "defaultValue": false,
"cached": false "cached": false
}, },
{
"id": "b44bc948-1234-4f87-9a22-bfb6de09df4d",
"name": "error1",
"displayName": "Error 1",
"displayNameEvent": "Error 1 changed",
"type": "int",
"defaultValue": 0
},
{
"id": "afca201a-5213-43fe-bfec-cae6ce7509d2",
"name": "error2",
"displayName": "Error 2",
"displayNameEvent": "Error 2 changed",
"type": "int",
"defaultValue": 0
},
{
"id": "c3fca233-95b9-4948-88c6-4c0f13cf53b1",
"name": "model",
"displayName": "Model",
"displayNameEvent": "Model changed",
"type": "QString",
"defaultValue": "Unknown",
"possibleValues": [
"Unknown",
"Keba P20",
"Keba P30",
"BMW"
]
},
{
"id": "e941ace5-fb7f-4dc2-b3f2-188233f4e934",
"name": "firmware",
"displayName": "Firmware",
"displayNameEvent": "Firmware changed",
"type": "QString",
"defaultValue": ""
},
{
"id": "9a1b4316-ce01-4cd3-890f-a8c94b8b5029",
"name": "serialnumber",
"displayName": "Serial number",
"displayNameEvent": "Serial number changed",
"type": "QString",
"defaultValue": ""
},
{
"id": "1d30ce60-2ea0-450f-817e-5c88f59ebfbf",
"name": "sessionId",
"displayName": "Session ID",
"displayNameEvent": "Session ID changed",
"type": "uint",
"defaultValue": ""
},
{ {
"id": "83ed0774-2a91-434d-b03c-d920d02f2981", "id": "83ed0774-2a91-434d-b03c-d920d02f2981",
"name": "power", "name": "power",
@ -103,7 +158,7 @@
}, },
{ {
"id": "4a2d75d8-a3a0-4b40-9ca7-e8b6f11d0ef9", "id": "4a2d75d8-a3a0-4b40-9ca7-e8b6f11d0ef9",
"name": "U1", "name": "voltagePhase1",
"displayName": "Voltage phase 1", "displayName": "Voltage phase 1",
"displayNameEvent": "Voltage phase 1 changed", "displayNameEvent": "Voltage phase 1 changed",
"type": "int", "type": "int",
@ -112,7 +167,7 @@
}, },
{ {
"id": "c8344ca5-21ac-4cd1-8f4b-e5ed202c5862", "id": "c8344ca5-21ac-4cd1-8f4b-e5ed202c5862",
"name": "U2", "name": "voltagePhase2",
"displayName": "Voltage Phase 2", "displayName": "Voltage Phase 2",
"displayNameEvent": "Voltage phase 2 changed", "displayNameEvent": "Voltage phase 2 changed",
"type": "int", "type": "int",
@ -121,7 +176,7 @@
}, },
{ {
"id": "5f01e86c-0943-4849-a01a-db441916ebd5", "id": "5f01e86c-0943-4849-a01a-db441916ebd5",
"name": "U3", "name": "voltagePhase3",
"displayName": "Voltage Phase 3", "displayName": "Voltage Phase 3",
"displayNameEvent": "Voltage phase 3 changed", "displayNameEvent": "Voltage phase 3 changed",
"type": "int", "type": "int",
@ -130,40 +185,49 @@
}, },
{ {
"id": "31ec17b0-11e3-4332-92b0-fea821cf024f", "id": "31ec17b0-11e3-4332-92b0-fea821cf024f",
"name": "I1", "name": "currentPhase1",
"displayName": "Current Phase 1", "displayName": "Current Phase 1",
"displayNameEvent": "Current phase 1 changed", "displayNameEvent": "Current phase 1 changed",
"type": "int", "type": "int",
"unit": "MilliAmpere", "unit": "Ampere",
"defaultValue": 0 "defaultValue": 0
}, },
{ {
"id": "cdc7e10a-0d0a-4e93-ad2c-d34ffca45c97", "id": "cdc7e10a-0d0a-4e93-ad2c-d34ffca45c97",
"name": "I2", "name": "currentPhase2",
"displayName": "Current Phase 2", "displayName": "Current Phase 2",
"displayNameEvent": "Current phase 2 changed", "displayNameEvent": "Current phase 2 changed",
"type": "int", "type": "double",
"unit": "MilliAmpere", "unit": "Ampere",
"defaultValue": 0 "defaultValue": 0
}, },
{ {
"id": "da838dc8-85f0-4e55-b4b5-cb93a43b373d", "id": "da838dc8-85f0-4e55-b4b5-cb93a43b373d",
"name": "I3", "name": "currentPhase3",
"displayName": "Current Phase 3", "displayName": "Current Phase 3",
"displayNameEvent": "Current phase 3 changed", "displayNameEvent": "Current phase 3 changed",
"type": "int", "type": "double",
"unit": "MilliAmpere", "unit": "Ampere",
"defaultValue": 0 "defaultValue": 0
}, },
{ {
"id": "7af9e93b-099d-4d9d-a480-9c0f66aecd8b", "id": "7af9e93b-099d-4d9d-a480-9c0f66aecd8b",
"name": "P", "name": "powerConsumption",
"displayName": "Power consumption", "displayName": "Power consumption",
"displayNameEvent": "Power consumtion changed", "displayNameEvent": "Power consumtion changed",
"type": "int", "type": "double",
"unit": "MilliWatt", "unit": "Watt",
"defaultValue": 0 "defaultValue": 0
}, },
{
"id": "889c3c9a-96b4-4408-bd9a-d79e36ed9296",
"name": "powerFactor",
"displayName": "Power factor",
"displayNameEvent": "Power factor changed",
"type": "double",
"unit": "Percentage",
"defaultValue": 0.00
},
{ {
"id": "a6f35ea0-aaea-438b-b818-6d161762611e", "id": "a6f35ea0-aaea-438b-b818-6d161762611e",
"name": "sessionTime", "name": "sessionTime",
@ -175,9 +239,9 @@
}, },
{ {
"id": "8e277efe-21ef-4536-bfc0-901b32d44d7c", "id": "8e277efe-21ef-4536-bfc0-901b32d44d7c",
"name": "EP", "name": "sessionEnergy",
"displayName": "Present energy", "displayName": "Session energy",
"displayNameEvent": "Present energy changed", "displayNameEvent": "Session energy changed",
"type": "double", "type": "double",
"unit": "KiloWattHour", "unit": "KiloWattHour",
"defaultValue": 0 "defaultValue": 0
@ -190,6 +254,33 @@
"type": "double", "type": "double",
"unit": "KiloWattHour", "unit": "KiloWattHour",
"defaultValue": 0 "defaultValue": 0
},
{
"id": "96b2d176-6460-4109-8824-3af4679c6573",
"name": "outputX2",
"displayName": "Output X2",
"displayNameEvent": "Output X2 changed",
"displayNameAction": "Set output X2",
"type": "bool",
"writable": true,
"defaultValue": false
},
{
"id": "ba600276-8b36-4404-b8ec-415245e5bc15",
"name": "input",
"displayName": "Input",
"displayNameEvent": "Input changed",
"type": "bool",
"defaultValue": false
},
{
"id": "3421ecf9-c95f-4dc1-ad0c-144e9b6ae056",
"name": "uptime",
"displayName": "Uptime",
"displayNameEvent": "Uptime changed",
"type": "int",
"unit": "Seconds",
"defaultValue": 0
} }
], ],
"actionTypes": [ "actionTypes": [

View File

@ -42,7 +42,7 @@ KeContact::KeContact(QHostAddress address, QObject *parent) :
m_requestTimeoutTimer->setSingleShot(true); m_requestTimeoutTimer->setSingleShot(true);
connect(m_requestTimeoutTimer, &QTimer::timeout, this, [this] { connect(m_requestTimeoutTimer, &QTimer::timeout, this, [this] {
//This timer will be started when a request is sent and stopped or resetted when a response has been received //This timer will be started when a request is sent and stopped or resetted when a response has been received
emit connectionChanged(false); emit reachableChanged(false);
//Try to send the next command //Try to send the next command
handleNextCommandInQueue(); handleNextCommandInQueue();
m_deviceBlocked = false; m_deviceBlocked = false;
@ -72,18 +72,52 @@ QHostAddress KeContact::address()
return m_address; return m_address;
} }
QUuid KeContact::start(const QByteArray &rfidToken, const QByteArray &rfidClassifier)
{
QUuid requestId = QUuid::createUuid();
m_pendingRequests.append(requestId);
QByteArray datagram = "start "+rfidToken + " " + rfidClassifier;
qCDebug(dcKebaKeContact()) << "Datagram : " << datagram;
sendCommand(datagram, requestId);;
return requestId;
}
QUuid KeContact::stop(const QByteArray &rfidToken)
{
QUuid requestId = QUuid::createUuid();
m_pendingRequests.append(requestId);
QByteArray datagram = "stop "+rfidToken;
qCDebug(dcKebaKeContact()) << "Datagram : " << datagram;
sendCommand(datagram, requestId);
return requestId;
}
void KeContact::setAddress(QHostAddress address) void KeContact::setAddress(QHostAddress address)
{ {
m_address = address; m_address = address;
} }
void KeContact::sendCommand(const QByteArray &command, const QUuid &requestId)
{
QTimer::singleShot(5000, this, [requestId, this] {
if (m_pendingRequests.contains(requestId)) {
m_pendingRequests.removeOne(requestId);
emit commandExecuted(requestId, false);
}
});
sendCommand(command);
}
void KeContact::sendCommand(const QByteArray &command) void KeContact::sendCommand(const QByteArray &command)
{ {
if (!m_udpSocket) { if (!m_udpSocket) {
qCWarning(dcKebaKeContact()) << "UDP socket not initialized"; qCWarning(dcKebaKeContact()) << "UDP socket not initialized";
emit connectionChanged(false); emit reachableChanged(false);
return; return;
} }
if(m_deviceBlocked) { if(m_deviceBlocked) {
//add command to queue //add command to queue
m_commandList.append(command); m_commandList.append(command);
@ -99,7 +133,10 @@ void KeContact::handleNextCommandInQueue()
{ {
if (!m_udpSocket) { if (!m_udpSocket) {
qCWarning(dcKebaKeContact()) << "UDP socket not initialized"; qCWarning(dcKebaKeContact()) << "UDP socket not initialized";
emit connectionChanged(false); if (m_reachable == true) {
m_reachable = false;
emit reachableChanged(false);
}
return; return;
} }
qCDebug(dcKebaKeContact()) << "Handle Command Queue- Pending commands" << m_commandList.length() << "Pending requestIds" << m_pendingRequests.length(); qCDebug(dcKebaKeContact()) << "Handle Command Queue- Pending commands" << m_commandList.length() << "Pending requestIds" << m_pendingRequests.length();
@ -125,13 +162,7 @@ QUuid KeContact::enableOutput(bool state)
datagram.append("ena 0"); datagram.append("ena 0");
} }
qCDebug(dcKebaKeContact()) << "Datagram : " << datagram; qCDebug(dcKebaKeContact()) << "Datagram : " << datagram;
sendCommand(datagram); sendCommand(datagram, requestId);
QTimer::singleShot(5000, this, [requestId, this] {
if (m_pendingRequests.contains(requestId)) {
m_pendingRequests.removeOne(requestId);
emit commandExecuted(requestId, false);
}
});
return requestId; return requestId;
} }
@ -143,12 +174,8 @@ QUuid KeContact::setMaxAmpere(int milliAmpere)
qCDebug(dcKebaKeContact()) << "Update max current to : " << milliAmpere; qCDebug(dcKebaKeContact()) << "Update max current to : " << milliAmpere;
QByteArray data; QByteArray data;
data.append("curr " + QVariant(milliAmpere).toByteArray()); data.append("curr " + QVariant(milliAmpere).toByteArray());
qCDebug(dcKebaKeContact()) << "send command: " << data; qCDebug(dcKebaKeContact()) << "sSnd command: " << data;
sendCommand(data); sendCommand(data, requestId);
if (m_pendingRequests.contains(requestId)) {
m_pendingRequests.removeOne(requestId);
emit commandExecuted(requestId, false);
}
return requestId; return requestId;
} }
@ -169,12 +196,35 @@ QUuid KeContact::displayMessage(const QByteArray &message)
modifiedMessage.resize(23); modifiedMessage.resize(23);
} }
data.append("display 0 0 0 0 " + modifiedMessage); data.append("display 0 0 0 0 " + modifiedMessage);
qCDebug(dcKebaKeContact()) << "send command: " << data; qCDebug(dcKebaKeContact()) << "Send command: " << data;
sendCommand(data); sendCommand(data, requestId);
if (m_pendingRequests.contains(requestId)) { return requestId;
m_pendingRequests.removeOne(requestId);
emit commandExecuted(requestId, false);
} }
QUuid KeContact::chargeWithEnergyLimit(double energy)
{
QUuid requestId = QUuid::createUuid();
m_pendingRequests.append(requestId);
QByteArray data;
data.append("setenergy " + QVariant(static_cast<int>(energy*10000)).toByteArray());
qCDebug(dcKebaKeContact()) << "Send command: " << data;
sendCommand(data, requestId);
return requestId;
}
QUuid KeContact::setFailsafe(int timeout, int current, bool save)
{
QUuid requestId = QUuid::createUuid();
m_pendingRequests.append(requestId);
QByteArray data;
data.append("failsave");
data.append(" "+QVariant(timeout).toByteArray());
data.append(" "+QVariant(current).toByteArray());
data.append((save ? " 1":" 0"));
qCDebug(dcKebaKeContact()) << "Send command: " << data;
sendCommand(data, requestId);
return requestId; return requestId;
} }
@ -237,7 +287,10 @@ void KeContact::readPendingDatagrams()
//Only process data from the target device //Only process data from the target device
continue; continue;
} }
emit connectionChanged(true); if (m_reachable != true) {
m_reachable = true;
emit reachableChanged(true);
}
qCDebug(dcKebaKeContact()) << "Data received" << datagram; qCDebug(dcKebaKeContact()) << "Data received" << datagram;
if(datagram.contains("TCH-OK")){ if(datagram.contains("TCH-OK")){
@ -306,12 +359,12 @@ void KeContact::readPendingDatagrams()
reportTwo.plugState = PlugState(data.value("Plug").toInt()); reportTwo.plugState = PlugState(data.value("Plug").toInt());
reportTwo.enableUser = data.value("Enable user").toBool(); reportTwo.enableUser = data.value("Enable user").toBool();
reportTwo.enableSys = data.value("Enable sys").toBool(); reportTwo.enableSys = data.value("Enable sys").toBool();
reportTwo.MaxCurrent = data.value("Max curr").toInt()/1000; reportTwo.maxCurrent = data.value("Max curr").toInt()/1000;
reportTwo.MaxCurrentPercentage = data.value("Max curr %").toInt()/10; reportTwo.maxCurrentPercentage = data.value("Max curr %").toInt()/10;
reportTwo.CurrentHardwareLimitation = data.value("Curr HW").toInt()/1000; reportTwo.currentHardwareLimitation = data.value("Curr HW").toInt()/1000;
reportTwo.CurrentUser = data.value("Curr user").toInt(); reportTwo.currentUser = data.value("Curr user").toInt();
reportTwo.CurrFS = data.value("Curr FS").toInt(); reportTwo.currentFailsafe = data.value("Curr FS").toInt();
reportTwo.TmoFS = data.value("Tmo FS").toInt(); reportTwo.timeoutFailsafe = data.value("Tmo FS").toInt();
reportTwo.output = data.value("Output").toInt(); reportTwo.output = data.value("Output").toInt();
reportTwo.input= data.value("Input").toInt(); reportTwo.input= data.value("Input").toInt();
reportTwo.serialNumber = data.value("Serial").toString(); reportTwo.serialNumber = data.value("Serial").toString();

View File

@ -46,6 +46,14 @@ public:
~KeContact(); ~KeContact();
bool init(); bool init();
enum Model {
ModelUnkown,
ModelP20,
ModelP30,
ModelBMW
};
Q_ENUM(Model)
enum State { enum State {
StateStarting = 0, StateStarting = 0,
StateNotReady, StateNotReady,
@ -54,6 +62,7 @@ public:
StateError, StateError,
StateAuthorizationRejected StateAuthorizationRejected
}; };
Q_ENUM(State)
enum PlugState { enum PlugState {
PlugStateUnplugged = 0, PlugStateUnplugged = 0,
@ -62,6 +71,7 @@ public:
PlugStatePluggedOnChargingStationAndPluggedOnEV = 5, PlugStatePluggedOnChargingStationAndPluggedOnEV = 5,
PlugStatePluggedOnChargingStationAndPlugLockedAndPluggedOnEV = 7 PlugStatePluggedOnChargingStationAndPlugLockedAndPluggedOnEV = 7
}; };
Q_ENUM(PlugState)
enum BroadcastType { enum BroadcastType {
BroadcastTypeState = 0, BroadcastTypeState = 0,
@ -73,9 +83,11 @@ public:
}; };
struct ReportOne { struct ReportOne {
QString product; QString product; // Model name (variant
QString serialNumber; QString serialNumber; // Serial number
QString firmware; QString firmware; // Firmware version
bool comModule; // Communication module is installed (only P30)
int seconds; // Current system clock since restart of the charging station.(only P30)
}; };
struct ReportTwo { struct ReportTwo {
@ -85,15 +97,18 @@ public:
PlugState plugState; //Current condition of the loading connection PlugState plugState; //Current condition of the loading connection
bool enableSys; //Enable state for charging (contains Enable input, RFID, UDP,..). bool enableSys; //Enable state for charging (contains Enable input, RFID, UDP,..).
bool enableUser; //Enable condition via UDP. bool enableUser; //Enable condition via UDP.
int MaxCurrent; //Current preset value via Control pilot in milliampere. int maxCurrent; //Current preset value via Control pilot in milliampere.
int MaxCurrentPercentage; //Current preset value via Control pilot in 0,1% of the PWM value int maxCurrentPercentage; //Current preset value via Control pilot in 0,1% of the PWM value
int CurrentHardwareLimitation; //Highest possible charging current of the charging connection. Contains device maximum, DIP-switch setting, cable coding and temperature reduction. int currentHardwareLimitation; //Highest possible charging current of the charging connection. Contains device maximum, DIP-switch setting, cable coding and temperature reduction.
int CurrentUser; //Current preset value of the user via UDP; Default = 63000mA. int currentUser; //Current preset value of the user via UDP; Default = 63000mA.
int CurrFS; //Current preset value for the Failsafe function. int currentFailsafe; //Current preset value for the Failsafe function.
int TmoFS; //Communication timeout before triggering the Failsafe function. int timeoutFailsafe; //Communication timeout before triggering the Failsafe function.
int currTimer; //Shows the current preset value of currtime.
int timeoutCt; //Shows the remaining time until the current value is accepted.
int setEnergy; //Shows the set energy limit
bool output; //State of the output X2. bool output; //State of the output X2.
bool input; //State of the potential free Enable input X1. When using the input, please pay attention to the information in the installation manual. bool input; //State of the potential free Enable input X1. When using the input, please pay attention to the information in the installation manual.
QString serialNumber; // QString serialNumber; //Serial number
int seconds; //Current system clock since restart of the charging station. int seconds; //Current system clock since restart of the charging station.
}; };
@ -112,21 +127,34 @@ public:
}; };
QHostAddress address(); QHostAddress address();
int serialNumber(); void setAddress(const QHostAddress &address);
void setAddress(QHostAddress address); bool reachable();
QUuid enableOutput(bool state); QUuid start(const QByteArray &rfidToken, const QByteArray &rfidClassifier); // Command “start”
QUuid setMaxAmpere(int milliAmpere); QUuid stop(const QByteArray &rfidToken); // Command “stop”
QUuid unlockCharger();
QUuid displayMessage(const QByteArray &message);
void getDeviceInformation(); QUuid enableOutput(bool state); // Command “ena”
void getReport1(); QUuid setMaxAmpere(int milliAmpere); // Command “curr”
QUuid unlockCharger(); // Command “unlock"
QUuid displayMessage(const QByteArray &message); // Command “display”
QUuid chargeWithEnergyLimit(double energy); // Command “setenergy”
QUuid setFailsafe(int timeout, int current, bool save); // Command “failsafe”
void getDeviceInformation(); // Command “i”
void getReport1(); // Command “report”
void getReport2(); void getReport2();
void getReport3(); void getReport3();
// Command “report 1xx”
// Command “currtime”
// Command “output”
private: private:
bool m_reachable = false;
QUdpSocket *m_udpSocket = nullptr; QUdpSocket *m_udpSocket = nullptr;
QHostAddress m_address; QHostAddress m_address;
QByteArrayList m_commandList; QByteArrayList m_commandList;
@ -136,11 +164,13 @@ private:
int m_serialNumber; int m_serialNumber;
QList<QUuid> m_pendingRequests; QList<QUuid> m_pendingRequests;
void sendCommand(const QByteArray &data, const QUuid &requestId);
void sendCommand(const QByteArray &data); void sendCommand(const QByteArray &data);
void handleNextCommandInQueue(); void handleNextCommandInQueue();
signals: signals:
void connectionChanged(bool status); void reachableChanged(bool status);
void commandExecuted(QUuid requestId, bool success); void commandExecuted(QUuid requestId, bool success);
void deviceInformationReceived(const QString &firmware); void deviceInformationReceived(const QString &firmware);
void reportOneReceived(const ReportOne &reportOne); void reportOneReceived(const ReportOne &reportOne);
@ -151,7 +181,5 @@ signals:
private slots: private slots:
void readPendingDatagrams(); void readPendingDatagrams();
}; };
#endif // KECONTACT_H #endif // KECONTACT_H