multi keba bug fixed

This commit is contained in:
Boernsman 2021-01-14 11:36:40 +01:00
parent 5b38c6c9bc
commit 396804bbd5
5 changed files with 133 additions and 90 deletions

View File

@ -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);

View File

@ -41,6 +41,7 @@
#include <QNetworkReply>
#include <QUdpSocket>
#include <QDateTime>
#include <QUdpSocket>
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<ThingId, KeContact *> m_kebaDevices;
QHash<ThingId, int> m_lastSessionId;
QHash<KeContact *, ThingSetupInfo *> m_asyncSetup;
QHash<QUuid, ThingActionInfo *> m_asyncActions;
QHash<ThingId, QDateTime> m_chargingSessionStartTime;
void setDeviceState(Thing *device, KeContact::State state);
void setDevicePlugState(Thing *device, KeContact::PlugState plugState);

View File

@ -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": [

View File

@ -34,8 +34,9 @@
#include <QJsonDocument>
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<int>(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 {

View File

@ -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<QUuid> m_pendingRequests;
void getReport(int reportNumber);
void sendCommand(const QByteArray &data, const QUuid &requestId);
void sendCommand(const QByteArray &data);
void handleNextCommandInQueue();