diff --git a/keba/discovery.cpp b/keba/discovery.cpp new file mode 100644 index 00000000..9d2b98b7 --- /dev/null +++ b/keba/discovery.cpp @@ -0,0 +1,272 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "discovery.h" +#include "extern-plugininfo.h" + +#include +#include +#include +#include +#include + +Discovery::Discovery(QObject *parent) : QObject(parent) +{ + connect(&m_timeoutTimer, &QTimer::timeout, this, &Discovery::onTimeout); +} + +void Discovery::discoverHosts(int timeout) +{ + if (isRunning()) { + qCWarning(dcKebaKeContact()) << "Discovery already running. Cannot start twice."; + return; + } + m_timeoutTimer.start(timeout * 1000); + + foreach (const QString &target, getDefaultTargets()) { + QProcess *discoveryProcess = new QProcess(this); + m_discoveryProcesses.append(discoveryProcess); + connect(discoveryProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(discoveryFinished(int,QProcess::ExitStatus))); + + QStringList arguments; + arguments << "-oX" << "-" << "-n" << "-sn"; + arguments << target; + + qCDebug(dcKebaKeContact()) << "Scanning network:" << "nmap" << arguments.join(" "); + discoveryProcess->start(QStringLiteral("nmap"), arguments); + } + +} + +void Discovery::abort() +{ + foreach (QProcess *discoveryProcess, m_discoveryProcesses) { + if (discoveryProcess->state() == QProcess::Running) { + qCDebug(dcKebaKeContact()) << "Kill running discovery process"; + discoveryProcess->terminate(); + discoveryProcess->waitForFinished(5000); + } + } + foreach (QProcess *p, m_pendingArpLookups.keys()) { + p->terminate(); + delete p; + } + m_pendingArpLookups.clear(); + m_pendingNameLookups.clear(); + qDeleteAll(m_scanResults); + m_scanResults.clear(); +} + +bool Discovery::isRunning() const +{ + return !m_discoveryProcesses.isEmpty() || !m_pendingArpLookups.isEmpty() || !m_pendingNameLookups.isEmpty(); +} + +void Discovery::discoveryFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + QProcess *discoveryProcess = static_cast(sender()); + + if (exitCode != 0 || exitStatus != QProcess::NormalExit) { + qCWarning(dcKebaKeContact()) << "Nmap error failed. Is nmap installed correctly?"; + m_discoveryProcesses.removeAll(discoveryProcess); + discoveryProcess->deleteLater(); + discoveryProcess = nullptr; + finishDiscovery(); + return; + } + + QByteArray data = discoveryProcess->readAll(); + m_discoveryProcesses.removeAll(discoveryProcess); + discoveryProcess->deleteLater(); + discoveryProcess = nullptr; + + QXmlStreamReader reader(data); + + int foundHosts = 0; + + while (!reader.atEnd() && !reader.hasError()) { + QXmlStreamReader::TokenType token = reader.readNext(); + if(token == QXmlStreamReader::StartDocument) + continue; + + if(token == QXmlStreamReader::StartElement && reader.name() == "host") { + bool isUp = false; + QString address; + QString macAddress; + QString vendor; + while (!reader.atEnd() && !reader.hasError() && !(token == QXmlStreamReader::EndElement && reader.name() == "host")) { + token = reader.readNext(); + + if (reader.name() == "address") { + QString addr = reader.attributes().value("addr").toString(); + QString type = reader.attributes().value("addrtype").toString(); + if (type == "ipv4" && !addr.isEmpty()) { + address = addr; + } else if (type == "mac") { + macAddress = addr; + vendor = reader.attributes().value("vendor").toString(); + } + } + + if (reader.name() == "status") { + QString state = reader.attributes().value("state").toString(); + if (!state.isEmpty()) + isUp = state == "up"; + } + } + + if (isUp) { + foundHosts++; + qCDebug(dcKebaKeContact()) << "Have host:" << address; + + Host *host = new Host(); + host->setAddress(address); + + if (!macAddress.isEmpty()) { + host->setMacAddress(macAddress); + } else { + QProcess *arpLookup = new QProcess(this); + m_pendingArpLookups.insert(arpLookup, host); + connect(arpLookup, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(arpLookupDone(int,QProcess::ExitStatus))); + arpLookup->start("arp", {"-vn"}); + } + + host->setHostName(vendor); + QHostInfo::lookupHost(address, this, SLOT(hostLookupDone(QHostInfo))); + m_pendingNameLookups.insert(address, host); + + m_scanResults.append(host); + } + } + } + + if (foundHosts == 0 && m_discoveryProcesses.isEmpty()) { + qCDebug(dcKebaKeContact()) << "Network scan successful but no hosts found in this network"; + finishDiscovery(); + } +} + +void Discovery::hostLookupDone(const QHostInfo &info) +{ + Host *host = m_pendingNameLookups.take(info.addresses().first().toString()); + if (!host) { + // Probably aborted... + return; + } + if (info.error() != QHostInfo::NoError) { + qWarning(dcKebaKeContact()) << "Host lookup failed:" << info.errorString(); + } + if (host->hostName().isEmpty() || info.hostName() != host->address()) { + host->setHostName(info.hostName()); + } + + finishDiscovery(); +} + +void Discovery::arpLookupDone(int exitCode, QProcess::ExitStatus exitStatus) +{ + QProcess *p = static_cast(sender()); + p->deleteLater(); + + Host *host = m_pendingArpLookups.take(p); + + if (exitCode != 0 || exitStatus != QProcess::NormalExit) { + qCWarning(dcKebaKeContact()) << "ARP lookup process failed for host" << host->address(); + finishDiscovery(); + return; + } + + QString data = QString::fromLatin1(p->readAll()); + foreach (QString line, data.split('\n')) { + line.replace(QRegExp("[ ]{1,}"), " "); + QStringList parts = line.split(" "); + if (parts.count() >= 3 && parts.first() == host->address() && parts.at(1) == "ether") { + host->setMacAddress(parts.at(2)); + break; + } + } + finishDiscovery(); +} + +void Discovery::onTimeout() +{ + qWarning(dcKebaKeContact()) << "Timeout hit. Stopping discovery"; + while (!m_discoveryProcesses.isEmpty()) { + QProcess *discoveryProcess = m_discoveryProcesses.takeFirst(); + disconnect(this, SLOT(discoveryFinished(int,QProcess::ExitStatus))); + discoveryProcess->terminate(); + delete discoveryProcess; + } + foreach (QProcess *p, m_pendingArpLookups.keys()) { + p->terminate(); + m_scanResults.removeAll(m_pendingArpLookups.value(p)); + delete p; + } + m_pendingArpLookups.clear(); + m_pendingNameLookups.clear(); + finishDiscovery(); +} + +QStringList Discovery::getDefaultTargets() +{ + QStringList targets; + foreach (const QHostAddress &interface, QNetworkInterface::allAddresses()) { + if (!interface.isLoopback() && interface.scopeId().isEmpty() && interface.protocol() == QAbstractSocket::IPv4Protocol) { + QPair pair = QHostAddress::parseSubnet(interface.toString() + "/24"); + QString newTarget = QString("%1/%2").arg(pair.first.toString()).arg(pair.second); + if (!targets.contains(newTarget)) { + targets.append(newTarget); + } + } + } + return targets; +} + +void Discovery::finishDiscovery() +{ + if (m_discoveryProcesses.count() > 0 || m_pendingNameLookups.count() > 0 || m_pendingArpLookups.count() > 0) { + // Still busy... + return; + } + + QList hosts; + foreach (Host *host, m_scanResults) { + if (!host->macAddress().isEmpty()) { + hosts.append(*host); + } + } + qDeleteAll(m_scanResults); + m_scanResults.clear(); + + qCDebug(dcKebaKeContact()) << "Emitting device discovered for" << hosts.count() << "devices"; + m_timeoutTimer.stop(); + emit finished(hosts); +} + diff --git a/keba/discovery.h b/keba/discovery.h new file mode 100644 index 00000000..59914cf1 --- /dev/null +++ b/keba/discovery.h @@ -0,0 +1,78 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef DISCOVERY_H +#define DISCOVERY_H + +#include +#include +#include +#include + +#include "host.h" + +class Discovery : public QObject +{ + Q_OBJECT +public: + explicit Discovery(QObject *parent = nullptr); + + void discoverHosts(int timeout); + void abort(); + + bool isRunning() const; + + +signals: + void finished(QList hosts); + +private: + QStringList getDefaultTargets(); + + void finishDiscovery(); + +private slots: + void discoveryFinished(int exitCode, QProcess::ExitStatus exitStatus); + void hostLookupDone(const QHostInfo &info); + void arpLookupDone(int exitCode, QProcess::ExitStatus exitStatus); + void onTimeout(); + +private: + QList m_discoveryProcesses; + QTimer m_timeoutTimer; + + QHash m_pendingArpLookups; + QHash m_pendingNameLookups; + QList m_scanResults; + +}; + +#endif // DISCOVERY_H + diff --git a/keba/host.cpp b/keba/host.cpp new file mode 100644 index 00000000..f1e80a53 --- /dev/null +++ b/keba/host.cpp @@ -0,0 +1,94 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "host.h" + +Host::Host() +{ + qRegisterMetaType(); + qRegisterMetaType >(); +} + +QString Host::macAddress() const +{ + return m_macAddress; +} + +void Host::setMacAddress(const QString &macAddress) +{ + m_macAddress = macAddress; +} + +QString Host::hostName() const +{ + return m_hostName; +} + +void Host::setHostName(const QString &hostName) +{ + m_hostName = hostName; +} + +QString Host::address() const +{ + return m_address; +} + +void Host::setAddress(const QString &address) +{ + m_address = address; +} + +void Host::seen() +{ + m_lastSeenTime = QDateTime::currentDateTime(); +} + +QDateTime Host::lastSeenTime() const +{ + return m_lastSeenTime; +} + +bool Host::reachable() const +{ + return m_reachable; +} + +void Host::setReachable(bool reachable) +{ + m_reachable = reachable; +} + +QDebug operator<<(QDebug dbg, const Host &host) +{ + dbg.nospace() << "Host(" << host.macAddress() << "," << host.hostName() << ", " << host.address() << ", " << (host.reachable() ? "up" : "down") << ")"; + return dbg.space(); +} + diff --git a/keba/host.h b/keba/host.h new file mode 100644 index 00000000..8fe28a23 --- /dev/null +++ b/keba/host.h @@ -0,0 +1,70 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef HOST_H +#define HOST_H + +#include +#include +#include + +class Host +{ +public: + Host(); + + QString macAddress() const; + void setMacAddress(const QString &macAddress); + + QString hostName() const; + void setHostName(const QString &hostName); + + QString address() const; + void setAddress(const QString &address); + + void seen(); + QDateTime lastSeenTime() const; + + bool reachable() const; + void setReachable(bool reachable); + +private: + QString m_macAddress; + QString m_hostName; + QString m_address; + QDateTime m_lastSeenTime; + bool m_reachable; +}; +Q_DECLARE_METATYPE(Host) + +QDebug operator<<(QDebug dbg, const Host &host); + +#endif // HOST_H + diff --git a/keba/kecontact.cpp b/keba/kecontact.cpp new file mode 100644 index 00000000..fe5a05c9 --- /dev/null +++ b/keba/kecontact.cpp @@ -0,0 +1,316 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "kecontact.h" +#include "extern-plugininfo.h" + +#include + + +KeContact::KeContact(QHostAddress address, QObject *parent) : + QObject(parent), + m_address(address) +{ + m_requestTimeoutTimer = new QTimer(this); + m_requestTimeoutTimer->setSingleShot(true); + 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 + emit connectionChanged(false); + if (!m_pendingRequests.isEmpty()){ + m_pendingRequests.removeFirst(); + } + //Try to send the next command + handleNextCommandInQueue(); + }); +} + +KeContact::~KeContact() { + qCDebug(dcKebaKeContact()) << "Deleting KeContact connection for address" << m_address; + + m_requestTimeoutTimer->deleteLater(); + m_requestTimeoutTimer->stop(); + m_udpSocket->close(); + m_udpSocket->deleteLater(); +} + +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; + return false; + } + connect(m_udpSocket, &QUdpSocket::readyRead, this, &KeContact::readPendingDatagrams); + } + return true; +} + +QHostAddress KeContact::address() +{ + return m_address; +} + +void KeContact::setAddress(QHostAddress address) +{ + m_address = address; +} + +void KeContact::sendCommand(const QByteArray &command) +{ + if (!m_udpSocket) { + qCWarning(dcKebaKeContact()) << "UDP socket not initialized"; + emit connectionChanged(false); + return; + } + if(!m_commandList.isEmpty()) { + //add command to queue + m_commandList.append(command); + } else { + //send command + m_udpSocket->writeDatagram(command, m_address, 7090); + m_requestTimeoutTimer->start(5000); + } +} + +void KeContact::handleNextCommandInQueue() +{ + if (!m_udpSocket) { + qCWarning(dcKebaKeContact()) << "UDP socket not initialized"; + emit connectionChanged(false); + return; + } + qCDebug(dcKebaKeContact()) << "Handle Command Queue- Pending commands" << m_commandList.length() << "Pending requestIds" << m_pendingRequests.length(); + if (!m_commandList.isEmpty()) { + QByteArray command = m_commandList.first(); + m_udpSocket->writeDatagram(command, m_address, 7090); + m_requestTimeoutTimer->start(5000); + } else { + //nothing to do + } +} + + +QUuid KeContact::enableOutput(bool state) +{ + QUuid requestId = QUuid::createUuid(); + m_pendingRequests.append(requestId); + // Print information that we are executing now the update action; + QByteArray datagram; + if(state){ + datagram.append("ena 1"); + } else{ + datagram.append("ena 0"); + } + qCDebug(dcKebaKeContact()) << "Datagram : " << datagram; + sendCommand(datagram); + return requestId; +} + +QUuid KeContact::setMaxAmpere(int milliAmpere) +{ + QUuid requestId = QUuid::createUuid(); + m_pendingRequests.append(requestId); + // Print information that we are executing now the update action + qCDebug(dcKebaKeContact()) << "Update max current to : " << milliAmpere; + QByteArray data; + data.append("curr " + QVariant(milliAmpere).toByteArray()); + qCDebug(dcKebaKeContact()) << "send command: " << data; + sendCommand(data); + return requestId; +} + +QUuid KeContact::displayMessage(const QByteArray &message) +{ + /* Text shown on the display. Maximum 23 ASCII characters can be used. 0 .. 23 characters + ~ == Σ + $ == blank + , == comma + */ + QUuid requestId = QUuid::createUuid(); + m_pendingRequests.append(requestId); + qCDebug(dcKebaKeContact()) << "Set display message: " << message; + QByteArray data; + QByteArray modifiedMessage = message; + modifiedMessage.replace(" ", "$"); + if (modifiedMessage.size() > 23) { + modifiedMessage.resize(23); + } + data.append("display 0 0 0 0 " + modifiedMessage); + qCDebug(dcKebaKeContact()) << "send command: " << data; + sendCommand(data); + return requestId; +} + + +void KeContact::getDeviceInformation() +{ + QByteArray data; + data.append("i"); + qCDebug(dcKebaKeContact()) << "send command: " << data; + sendCommand(data); +} + +void KeContact::getReport1() +{ + QByteArray data; + data.append("report 1"); + qCDebug(dcKebaKeContact()) << "send command : " << data; + sendCommand(data); +} + +void KeContact::getReport2() +{ + QByteArray data; + data.append("report 2"); + qCDebug(dcKebaKeContact()) << "send command: " << data; + sendCommand(data); +} + +void KeContact::getReport3() +{ + QByteArray data; + data.append("report 3"); + qCDebug(dcKebaKeContact()) << "data: " << data; + sendCommand(data); +} + +QUuid KeContact::unlockCharger() +{ + QUuid requestId = QUuid::createUuid(); + m_pendingRequests.append(requestId); + QByteArray data; + data.append("unlock"); + qCDebug(dcKebaKeContact()) << "send command: " << data; + sendCommand(data); + return requestId; +} + +void KeContact::readPendingDatagrams() +{ + QUdpSocket *socket= qobject_cast(sender()); + + QByteArray datagram; + QHostAddress sender; + 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; + } + emit connectionChanged(true); + + qCDebug(dcKebaKeContact()) << "Data received" << datagram; + if(datagram.contains("TCH-OK")){ + if (datagram.contains("done")) { + emit commandExecuted(m_pendingRequests.takeFirst(), true); + } else { + emit commandExecuted(m_pendingRequests.takeFirst(), false); + } + } + + //Command response has been received, now send the next command + m_requestTimeoutTimer->stop(); + handleNextCommandInQueue(); + + if(datagram.left(8).contains("Firmware")){ + qCDebug(dcKebaKeContact()) << "Firmware information reveiced"; + QByteArrayList firmware = datagram.split(':'); + if (firmware.length() >= 2) { + emit deviceInformationReceived(firmware[1]); + } + } + + // Convert the rawdata to a json document + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(datagram, &error); + if (error.error != QJsonParseError::NoError) { + qCWarning(dcKebaKeContact()) << "Failed to parse JSON data" << datagram << ":" << error.errorString(); + } + + QVariantMap data = jsonDoc.toVariant().toMap(); + + if(data.contains("ID")){ + + if (data.value("ID").toString() == "1") { + ReportOne reportOne; + qCDebug(dcKebaKeContact()) << "Report 1 received"; + reportOne.product = data.value("Product").toString(); + reportOne.firmware = data.value("Firmware").toString(); + reportOne.serialNumber = data.value("Serial").toString();; + emit reportOneReceived(reportOne); + + } else if(data.value("ID").toString() == "2"){ + + ReportTwo reportTwo; + qCDebug(dcKebaKeContact()) << "Report 2 reveiced"; + int state = data.value("State").toInt(); + reportTwo.state = State(state); + reportTwo.error1 = data.value("Error1").toInt(); + reportTwo.error2 = data.value("Error2").toInt(); + reportTwo.plugState = PlugState(data.value("Plug").toInt()); + reportTwo.enableUser = data.value("Enable user").toBool(); + reportTwo.enableSys = data.value("Enable sys").toBool(); + reportTwo.MaxCurrent = data.value("Max curr").toInt()/1000; + reportTwo.MaxCurrentPercentage = data.value("Max curr %").toInt()/10; + reportTwo.CurrentHardwareLimitation = data.value("Curr HW").toInt()/1000; + reportTwo.CurrentUser = data.value("Curr user").toInt(); + reportTwo.CurrFS = data.value("Curr FS").toInt(); + reportTwo.TmoFS = data.value("Tmo FS").toInt(); + reportTwo.output = data.value("Output").toInt(); + reportTwo.input= data.value("Input").toInt(); + reportTwo.serialNumber = data.value("Serial").toString(); + reportTwo.seconds = data.value("Sec").toInt(); + emit reportTwoReceived(reportTwo); + + } else if(data.value("ID").toString() == "3"){ + + ReportThree reportThree; + qCDebug(dcKebaKeContact()) << "Report 3 reveiced"; + reportThree.CurrentPhase1 = data.value("I1").toInt(); + reportThree.CurrentPhase2 = data.value("I2").toInt(); + reportThree.CurrentPhase3 = data.value("I3").toInt(); + reportThree.VoltagePhase1 = data.value("U1").toInt(); + reportThree.VoltagePhase2 = data.value("U2").toInt(); + reportThree.VoltagePhase3 = data.value("U3").toInt(); + reportThree.Power = data.value("P").toInt(); + reportThree.PowerFactor = data.value("PF").toInt()/10; + reportThree.EnergySession = data.value("E pres").toInt()/10000.00; + reportThree.EnergyTotal = data.value("E total").toInt()/10000.00; + reportThree.SerialNumber = data.value("Serial").toString(); + emit reportThreeReceived(reportThree); + } + } + } +} diff --git a/keba/kecontact.h b/keba/kecontact.h new file mode 100644 index 00000000..8455619a --- /dev/null +++ b/keba/kecontact.h @@ -0,0 +1,149 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef KECONTACT_H +#define KECONTACT_H + +#include +#include +#include +#include +#include +#include + +class KeContact : public QObject +{ + Q_OBJECT +public: + explicit KeContact(QHostAddress address, QObject *parent = nullptr); + ~KeContact(); + bool init(); + + enum State { + Starting = 0, + NotReady, + Ready, + Charging, + Error, + AuthorizationRejected + }; + + enum PlugState { + Unplugged = 0, + PluggedOnChargingStation = 1, + PluggedOnChargingStationAndPlugLocked = 3, + PluggedOnChargingStationAndPluggedOnEV = 5, + PluggedOnChargingStationAndPlugLockedAndPluggedOnEV = 7 + }; + + struct ReportOne { + QString product; + QString serialNumber; + QString firmware; + }; + + struct ReportTwo { + State state; //Current state of the charging station + int error1; //Detail code for state 4; exceptions see FAQ on www.kecontact.com + int error2; //Detail code for state 4 exception #6 see FAQ on www.kecontact.com + PlugState plugState; //Current condition of the loading connection + bool enableSys; //Enable state for charging (contains Enable input, RFID, UDP,..). + bool enableUser; //Enable condition via UDP. + 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 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 CurrFS; //Current preset value for the Failsafe function. + int TmoFS; //Communication timeout before triggering the Failsafe function. + 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. + QString serialNumber; // + int seconds; //Current system clock since restart of the charging station. + }; + + struct ReportThree { + int VoltagePhase1; //voltage in V + int VoltagePhase2; //voltage in V + int VoltagePhase3; //voltage in V + int CurrentPhase1; //current in mA + int CurrentPhase2; //current in mA + int CurrentPhase3; //current in mA + int Power; //Current power in mW (Real Power). + int PowerFactor; //Power factor in 0,1% (cosphi) + int EnergySession; //Power consumption of the current loading session in 0,1Wh; Reset with new loading session (state = 2). + int EnergyTotal; //Total power consumption (persistent) without current loading session 0,1Wh; Is summed up after each completed charging session (state = 0). + QString SerialNumber; + }; + + QHostAddress address(); + int serialNumber(); + + void setAddress(QHostAddress address); + bool getDeviceConnectedStatus(); + bool getDeviceBlockedStatus(); + + QUuid enableOutput(bool state); + QUuid setMaxAmpere(int milliAmpere); + void getDeviceInformation(); + void getReport1(); + void getReport2(); + void getReport3(); + QUuid unlockCharger(); + QUuid displayMessage(const QByteArray &message); + + +private: + QUdpSocket *m_udpSocket = nullptr; + QHostAddress m_address; + QByteArrayList m_commandList; + bool m_deviceBlocked = false; + bool m_connected = false; + QTimer *m_requestTimeoutTimer = nullptr; + int m_serialNumber; + QList m_pendingRequests; + + void sendCommand(const QByteArray &data); + void handleNextCommandInQueue(); + +signals: + void connectionChanged(bool status); + void commandExecuted(QUuid requestId, bool success); + void deviceInformationReceived(const QString &firmware); + void reportOneReceived(const ReportOne &reportOne); + void reportTwoReceived(const ReportTwo &reportTwo); + void reportThreeReceived(const ReportThree &reportThree); + +private slots: + void readPendingDatagrams(); +}; + + +#endif // KECONTACT_H +