diff --git a/keba/integrationpluginkeba.cpp b/keba/integrationpluginkeba.cpp index fc51f26d..2b3ee8e0 100644 --- a/keba/integrationpluginkeba.cpp +++ b/keba/integrationpluginkeba.cpp @@ -52,7 +52,14 @@ void IntegrationPluginKeba::init() continue; foreach (Thing *existingThing, myThings()) { - if (existingThing->paramValue(wallboxThingMacAddressParamTypeId).toString() == host.macAddress()) { + if (existingThing->paramValue(wallboxThingMacAddressParamTypeId).toString().isEmpty()) { + //This device got probably manually setup, to enable auto rediscovery the MAC address needs to setup + if (existingThing->paramValue(wallboxThingIpAddressParamTypeId).toString() == host.address()) { + qCDebug(dcKebaKeContact()) << "Keba Wallbox MAC Address has been discovered" << existingThing->name() << host.macAddress(); + existingThing->setParamValue(wallboxThingMacAddressParamTypeId, host.macAddress()); + + } + } else 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()); @@ -109,8 +116,16 @@ void IntegrationPluginKeba::setupThing(ThingSetupInfo *info) if (thing->thingClassId() == wallboxThingClassId) { + if(!m_udpSocket){ + m_udpSocket = new QUdpSocket(this); + if (!m_udpSocket->bind(QHostAddress::AnyIPv4, 7090, QAbstractSocket::ShareAddress)) { + qCWarning(dcKebaKeContact()) << "Cannot bind to port" << 7090; + return info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Error opening network port.")); + } + } + QHostAddress address = QHostAddress(thing->paramValue(wallboxThingIpAddressParamTypeId).toString()); - KeContact *keba = new KeContact(address, this); + KeContact *keba = new KeContact(address, m_udpSocket, m_udpSocket); connect(keba, &KeContact::reachableChanged, this, &IntegrationPluginKeba::onConnectionChanged); connect(keba, &KeContact::commandExecuted, this, &IntegrationPluginKeba::onCommandExecuted); connect(keba, &KeContact::reportTwoReceived, this, &IntegrationPluginKeba::onReportTwoReceived); @@ -118,7 +133,6 @@ void IntegrationPluginKeba::setupThing(ThingSetupInfo *info) connect(keba, &KeContact::report1XXReceived, this, &IntegrationPluginKeba::onReport1XXReceived); connect(keba, &KeContact::broadcastReceived, this, &IntegrationPluginKeba::onBroadcastReceived); if (!keba->init()){ - qCWarning(dcKebaKeContact()) << "Cannot bind to port" << 7090; keba->deleteLater(); return info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Error opening network port.")); } @@ -154,6 +168,11 @@ void IntegrationPluginKeba::setupThing(ThingSetupInfo *info) void IntegrationPluginKeba::postSetupThing(Thing *thing) { qCDebug(dcKebaKeContact()) << "Post setup" << thing->name(); + if (thing->thingClassId() != wallboxThingClassId) { + qCWarning(dcKebaKeContact()) << "Thing class id not supported" << thing->thingClassId(); + return; + } + KeContact *keba = m_kebaDevices.value(thing->id()); if (!keba) { qCWarning(dcKebaKeContact()) << "No Keba connection found for this thing"; @@ -161,6 +180,9 @@ void IntegrationPluginKeba::postSetupThing(Thing *thing) keba->getReport2(); keba->getReport3(); } + if (thing->paramValue(wallboxThingMacAddressParamTypeId).toString().isEmpty()) { + m_discovery->discoverHosts(25); + } if (!m_updateTimer) { m_updateTimer = hardwareManager()->pluginTimerManager()->registerTimer(60); @@ -173,17 +195,8 @@ void IntegrationPluginKeba::postSetupThing(Thing *thing) } keba->getReport2(); keba->getReport3(); - - if (m_chargingSessionStartTime.contains(thing->id())) { - QDateTime startTime = m_chargingSessionStartTime.value(thing->id()); - - QTimeZone tz = QTimeZone(QTimeZone::systemTimeZoneId()); - QDateTime currentTime = QDateTime::currentDateTime().toTimeZone(tz); - - int minutes = (currentTime.toMSecsSinceEpoch() - startTime.toMSecsSinceEpoch())/60000; - thing->setStateValue(wallboxSessionTimeStateTypeId, minutes); - } else { - thing->setStateValue(wallboxSessionTimeStateTypeId, 0); + if (thing->stateValue(wallboxActivityStateTypeId).toString() == "Charging") { + keba->getReport1XX(100); } } }); @@ -241,16 +254,6 @@ void IntegrationPluginKeba::setDeviceState(Thing *thing, KeContact::State state) thing->setStateValue(wallboxActivityStateTypeId, "Authorization rejected"); break; } - - if (state == KeContact::StateCharging) { - //Set charging session - QTimeZone tz = QTimeZone(QTimeZone::systemTimeZoneId()); - QDateTime startedChargingSession = QDateTime::currentDateTime().toTimeZone(tz); - m_chargingSessionStartTime.insert(thing->id(), startedChargingSession); - } else { - m_chargingSessionStartTime.remove(thing->id()); - thing->setStateValue(wallboxSessionTimeStateTypeId, 0); - } } void IntegrationPluginKeba::setDevicePlugState(Thing *thing, KeContact::PlugState plugState) @@ -403,7 +406,7 @@ void IntegrationPluginKeba::onReport1XXReceived(int reportNumber, const KeContac qCDebug(dcKebaKeContact()) << " - Session Id" << report.sessionId; qCDebug(dcKebaKeContact()) << " - Curr HW" << report.currHW; qCDebug(dcKebaKeContact()) << " - Energy start" << report.startEnergy; - qCDebug(dcKebaKeContact()) << " - Energy present" << report.ePres; + qCDebug(dcKebaKeContact()) << " - Energy present" << report.presentEnergy; qCDebug(dcKebaKeContact()) << " - Start time" << report.startTime; qCDebug(dcKebaKeContact()) << " - End time" << report.endTime; qCDebug(dcKebaKeContact()) << " - Stop reason" << report.stopReason; @@ -412,18 +415,32 @@ void IntegrationPluginKeba::onReport1XXReceived(int reportNumber, const KeContac qCDebug(dcKebaKeContact()) << " - Serial number" << report.serialNumber; qCDebug(dcKebaKeContact()) << " - Uptime" << report.seconds; - // Report 101 is the lastest finished session - if (reportNumber == 101) { + if (reportNumber == 100) { + // Report 100 is the current charging session + if (report.endTime == 0) { + // if the charing session is finished the end time will be set + double duration = (report.seconds - report.startTime)/60.00; + thing->setStateValue(wallboxSessionTimeStateTypeId, duration); + } else { + // Charging session is finished and copied to Report 101 + } + + } else if (reportNumber == 101) { + // Report 101 is the lastest finished session if (report.serialNumber == thing->stateValue(wallboxSerialnumberStateTypeId).toString()) { if (!m_lastSessionId.contains(thing->id())) { + // This happens after reboot m_lastSessionId.insert(thing->id(), report.sessionId); } else { if (m_lastSessionId.value(thing->id()) != report.sessionId) { qCDebug(dcKebaKeContact()) << "New session id receivd"; Event event; + event.setEventTypeId(wallboxChargingSessionFinishedEventTypeId); event.setThingId(thing->id()); ParamList params; - //params << Param(wallboxSessio); + params << Param(wallboxChargingSessionFinishedEventEnergyParamTypeId, report.presentEnergy); + params << Param(wallboxChargingSessionFinishedEventDurationParamTypeId, report.endTime); + params << Param(wallboxChargingSessionFinishedEventIdParamTypeId); event.setParams(params); emitEvent(event); } @@ -443,14 +460,17 @@ void IntegrationPluginKeba::onBroadcastReceived(KeContact::BroadcastType type, c if (!thing) return; + qCDebug(dcKebaKeContact()) << "Broadcast received" << type << "value" << content; + switch (type) { case KeContact::BroadcastTypePlug: setDevicePlugState(thing, KeContact::PlugState(content.toInt())); break; case KeContact::BroadcastTypeInput: + thing->setStateValue(wallboxInputStateTypeId, (content.toInt() == 1)); break; case KeContact::BroadcastTypeEPres: - thing->setStateValue(wallboxSessionEnergyStateTypeId, content.toInt()); + thing->setStateValue(wallboxSessionEnergyStateTypeId, content.toInt()/10000.00); break; case KeContact::BroadcastTypeState: setDeviceState(thing, KeContact::State(content.toInt())); @@ -459,6 +479,7 @@ void IntegrationPluginKeba::onBroadcastReceived(KeContact::BroadcastType type, c thing->setStateValue(wallboxMaxChargingCurrentStateTypeId, content.toInt()); break; case KeContact::BroadcastTypeEnableSys: + qCDebug(dcKebaKeContact()) << "Broadcast enable sys not implemented"; break; } } @@ -491,6 +512,10 @@ void IntegrationPluginKeba::executeAction(ThingActionInfo *info) m_asyncActions.insert(requestId, info); connect(info, &ThingActionInfo::aborted, this, [requestId, this]{m_asyncActions.remove(requestId);}); + } else if(action.actionTypeId() == wallboxOutputX2ActionTypeId){ + + } else if(action.actionTypeId() == wallboxFailsafeModeActionTypeId){ + } else { qCWarning(dcKebaKeContact()) << "Unhandled ActionTypeId:" << action.actionTypeId(); info->finish(Thing::ThingErrorActionTypeNotFound); diff --git a/keba/integrationpluginkeba.h b/keba/integrationpluginkeba.h index c0689dcf..ec7d2338 100644 --- a/keba/integrationpluginkeba.h +++ b/keba/integrationpluginkeba.h @@ -41,6 +41,7 @@ #include #include #include +#include class IntegrationPluginKeba : public IntegrationPlugin { @@ -65,13 +66,13 @@ private: PluginTimer *m_updateTimer = nullptr; PluginTimer *m_reconnectTimer = nullptr; + QUdpSocket *m_udpSocket = nullptr; + Discovery *m_discovery = nullptr; QHash m_kebaDevices; QHash m_lastSessionId; - QHash m_asyncSetup; QHash m_asyncActions; - QHash m_chargingSessionStartTime; void setDeviceState(Thing *device, KeContact::State state); void setDevicePlugState(Thing *device, KeContact::PlugState plugState); diff --git a/keba/integrationpluginkeba.json b/keba/integrationpluginkeba.json index 1f49bf49..28dcd33c 100644 --- a/keba/integrationpluginkeba.json +++ b/keba/integrationpluginkeba.json @@ -65,13 +65,7 @@ "displayName": "Model", "displayNameEvent": "Model changed", "type": "QString", - "defaultValue": "Unknown", - "possibleValues": [ - "Unknown", - "Keba P20", - "Keba P30", - "BMW" - ] + "defaultValue": "Unknown" }, { "id": "e941ace5-fb7f-4dc2-b3f2-188233f4e934", @@ -107,6 +101,14 @@ "writable": true, "defaultValue": false }, + { + "id": "e5631593-f486-47cb-9951-b7597d0b769b", + "name": "systemEnabled", + "displayName": "System enabled", + "displayNameEvent": "System enabled changed", + "type": "bool", + "defaultValue": false + }, { "id": "539e5602-6dd9-465d-9705-3bb59bcf8982", "name": "activity", @@ -216,7 +218,7 @@ "displayName": "Power consumption", "displayNameEvent": "Power consumtion changed", "type": "double", - "unit": "Watt", + "unit": "KiloWatt", "defaultValue": 0 }, { @@ -281,6 +283,16 @@ "type": "int", "unit": "Seconds", "defaultValue": 0 + }, + { + "id": "f1758c5c-2c02-41cb-93ec-b778a3c78d28", + "name": "failsafeMode", + "displayName": "Failsafe mode", + "displayNameEvent": "Failsafe mode changed", + "displayNameAction": "Set failsafe mode", + "writable": true, + "type": "bool", + "defaultValue": false } ], "actionTypes": [ diff --git a/keba/kecontact.cpp b/keba/kecontact.cpp index cb263f39..286d087b 100644 --- a/keba/kecontact.cpp +++ b/keba/kecontact.cpp @@ -34,8 +34,9 @@ #include -KeContact::KeContact(QHostAddress address, QObject *parent) : +KeContact::KeContact(const QHostAddress &address, QUdpSocket *udpSocket, QObject *parent) : QObject(parent), + m_udpSocket(udpSocket), m_address(address) { m_requestTimeoutTimer = new QTimer(this); @@ -54,15 +55,16 @@ KeContact::~KeContact() { } bool KeContact::init(){ - - if(!m_udpSocket){ - m_udpSocket = new QUdpSocket(this); - if (!m_udpSocket->bind(QHostAddress::AnyIPv4, 7090, QAbstractSocket::ShareAddress)) { - qCWarning(dcKebaKeContact()) << "Cannot bind to port" << 7090; - delete m_udpSocket; + qCDebug(dcKebaKeContact()) << "Initializing Keba connection"; + if(m_udpSocket){ + connect(m_udpSocket, &QUdpSocket::readyRead, this, &KeContact::readPendingDatagrams); + if (!m_udpSocket->bind(QHostAddress::AnyIPv4, m_port, QAbstractSocket::ShareAddress)) { + qCWarning(dcKebaKeContact()) << "Cannot bind to port" << m_port; return false; } - connect(m_udpSocket, &QUdpSocket::readyRead, this, &KeContact::readPendingDatagrams); + } else { + qCWarning(dcKebaKeContact()) << "UDP socket not valid"; + return false; } return true; } @@ -94,6 +96,7 @@ QUuid KeContact::stop(const QByteArray &rfidToken) void KeContact::setAddress(const QHostAddress &address) { + qCDebug(dcKebaKeContact()) << "Updating Keba connection address" << address.toString(); m_address = address; } @@ -123,7 +126,7 @@ void KeContact::sendCommand(const QByteArray &command) m_commandList.append(command); } else { //send command - m_udpSocket->writeDatagram(command, m_address, 7090); + m_udpSocket->writeDatagram(command, m_address, m_port); m_requestTimeoutTimer->start(5000); m_deviceBlocked = true; } @@ -142,7 +145,7 @@ void KeContact::handleNextCommandInQueue() qCDebug(dcKebaKeContact()) << "Handle Command Queue- Pending commands" << m_commandList.length() << "Pending requestIds" << m_pendingRequests.length(); if (!m_commandList.isEmpty()) { QByteArray command = m_commandList.takeFirst(); - m_udpSocket->writeDatagram(command, m_address, 7090); + m_udpSocket->writeDatagram(command, m_address, m_port); m_requestTimeoutTimer->start(5000); } else { //nothing to do @@ -161,7 +164,7 @@ QUuid KeContact::enableOutput(bool state) } else{ datagram.append("ena 0"); } - qCDebug(dcKebaKeContact()) << "Datagram : " << datagram; + qCDebug(dcKebaKeContact()) << "Enable output, command:" << datagram; sendCommand(datagram, requestId); return requestId; } @@ -174,7 +177,7 @@ QUuid KeContact::setMaxAmpere(int milliAmpere) qCDebug(dcKebaKeContact()) << "Update max current to : " << milliAmpere; QByteArray data; data.append("curr " + QVariant(milliAmpere).toByteArray()); - qCDebug(dcKebaKeContact()) << "sSnd command: " << data; + qCDebug(dcKebaKeContact()) << "Set max. ampere, command: " << data; sendCommand(data, requestId); return requestId; } @@ -196,7 +199,7 @@ QUuid KeContact::displayMessage(const QByteArray &message) modifiedMessage.resize(23); } data.append("display 0 0 0 0 " + modifiedMessage); - qCDebug(dcKebaKeContact()) << "Send command: " << data; + qCDebug(dcKebaKeContact()) << "Display message, command: " << data; sendCommand(data, requestId); return requestId; } @@ -208,7 +211,7 @@ QUuid KeContact::chargeWithEnergyLimit(double energy) QByteArray data; data.append("setenergy " + QVariant(static_cast(energy*10000)).toByteArray()); - qCDebug(dcKebaKeContact()) << "Send command: " << data; + qCDebug(dcKebaKeContact()) << "Charge with energy limit, command: " << data; sendCommand(data, requestId); return requestId; } @@ -223,7 +226,7 @@ QUuid KeContact::setFailsafe(int timeout, int current, bool save) data.append(" "+QVariant(timeout).toByteArray()); data.append(" "+QVariant(current).toByteArray()); data.append((save ? " 1":" 0")); - qCDebug(dcKebaKeContact()) << "Send command: " << data; + qCDebug(dcKebaKeContact()) << "Set failsafe mode, command: " << data; sendCommand(data, requestId); return requestId; } @@ -233,31 +236,35 @@ void KeContact::getDeviceInformation() { QByteArray data; data.append("i"); - qCDebug(dcKebaKeContact()) << "send command: " << data; + qCDebug(dcKebaKeContact()) << "Get device information, command: " << data; sendCommand(data); } void KeContact::getReport1() { - QByteArray data; - data.append("report 1"); - qCDebug(dcKebaKeContact()) << "send command : " << data; - sendCommand(data); + getReport(1); } void KeContact::getReport2() { - QByteArray data; - data.append("report 2"); - qCDebug(dcKebaKeContact()) << "send command: " << data; - sendCommand(data); + getReport(2); } void KeContact::getReport3() +{ + getReport(3); +} + +void KeContact::getReport1XX(int reportNumber) +{ + getReport(reportNumber); +} + +void KeContact::getReport(int reportNumber) { QByteArray data; - data.append("report 3"); - qCDebug(dcKebaKeContact()) << "data: " << data; + data.append("report "+QVariant(reportNumber).toByteArray()); + qCDebug(dcKebaKeContact()) << "Get report" << reportNumber << "Command:" << data; sendCommand(data); } @@ -267,7 +274,7 @@ QUuid KeContact::unlockCharger() m_pendingRequests.append(requestId); QByteArray data; data.append("unlock"); - qCDebug(dcKebaKeContact()) << "send command: " << data; + qCDebug(dcKebaKeContact()) << "Unlock charger, command: " << data; sendCommand(data); return requestId; } @@ -281,18 +288,21 @@ void KeContact::readPendingDatagrams() quint16 senderPort; while (socket->hasPendingDatagrams()) { - datagram.resize(socket->pendingDatagramSize()); - socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort); + if (sender != m_address) { //Only process data from the target device continue; } + + datagram.resize(socket->pendingDatagramSize()); + socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort); + qCDebug(dcKebaKeContact()) << "Data received" << datagram; + if (m_reachable != true) { m_reachable = true; emit reachableChanged(true); } - qCDebug(dcKebaKeContact()) << "Data received" << datagram; if(datagram.contains("TCH-OK")){ //Command response has been received, now send the next command @@ -399,14 +409,19 @@ void KeContact::readPendingDatagrams() reportThree.seconds = data.value("Sec").toInt(); emit reportThreeReceived(reportThree); } else if (id >= 100) { + Report1XX report; report.sessionId = data.value("Session ID").toInt(); report.currHW = data.value("Curr HW").toInt(); - //report. = data.value("Curr HW").toInt(); TODO - report.currHW = data.value("Curr HW").toInt(); - report.currHW = data.value("Curr HW").toInt(); - report.currHW = data.value("Curr HW").toInt(); - report.currHW = data.value("Curr HW").toInt(); + report.startTime = data.value("E Start ").toInt()/10000.00; + report.presentEnergy = data.value("E Pres ").toInt()/10000.00; + report.startTime = data.value("started[s]").toInt(); + report.endTime = data.value("ended[s] ").toInt(); + report.stopReason = data.value("reason ").toInt(); + report.rfidTag = data.value("RFID tag").toByteArray(); + report.rfidClass = data.value("RFID class").toByteArray(); + report.serialNumber = data.value("Serial").toString(); + report.seconds = data.value("Sec").toInt(); emit report1XXReceived(id, report); } } else { diff --git a/keba/kecontact.h b/keba/kecontact.h index 91ba7159..62c20053 100644 --- a/keba/kecontact.h +++ b/keba/kecontact.h @@ -42,18 +42,10 @@ class KeContact : public QObject { Q_OBJECT public: - explicit KeContact(QHostAddress address, QObject *parent = nullptr); + explicit KeContact(const QHostAddress &address, QUdpSocket *udpSocket, QObject *parent = nullptr); ~KeContact(); bool init(); - enum Model { - ModelUnkown, - ModelP20, - ModelP30, - ModelBMW - }; - Q_ENUM(Model) - enum State { StateStarting = 0, StateNotReady, @@ -81,6 +73,7 @@ public: BroadcastTypeMaxCurr, BroadcastTypeEPres }; + Q_ENUM(BroadcastType) struct ReportOne { QString product; // Model name (variant @@ -131,7 +124,7 @@ public: int sessionId; // running session counter; not resettable" int currHW; // maximum charging current of the cable and the charging station setting (equal to report 2)"E double startEnergy; // total energy value at the beginning of the session" - double ePres; // delivered energy until now (equal to E pres in report 3)" + double presentEnergy; // delivered energy until now (equal to E pres in report 3)" int startTime; // system time when the session was started (seconds from reboot; NTP implementation is still under progress)" int endTime; // system time when the session has ended" int stopReason; // reason for stopping the session (1 = vehicle unplug; 10 = Rfid token)" @@ -160,16 +153,13 @@ public: void getReport1(); // Command “report” void getReport2(); void getReport3(); - void getReport1XX(int reportNumber = 100); - - // Command “report 1xx” + void getReport1XX(int reportNumber = 100); // Command “report 1xx” // Command “currtime” // Command “output” - - private: + int m_port = 7090; bool m_reachable = false; QUdpSocket *m_udpSocket = nullptr; QHostAddress m_address; @@ -180,7 +170,7 @@ private: int m_serialNumber; QList m_pendingRequests; - + void getReport(int reportNumber); void sendCommand(const QByteArray &data, const QUuid &requestId); void sendCommand(const QByteArray &data); void handleNextCommandInQueue();