webasto discovery works now

pull/13/head
Boernsman 2021-01-28 12:29:09 +01:00 committed by Boernsman
parent 12fcb94825
commit f2cb2d3744
16 changed files with 1219 additions and 0 deletions

15
debian/control vendored
View File

@ -110,6 +110,21 @@ Description: nymea.io plugin for wallbe ev charging stations
.
This package will install the nymea.io plugin for wallbe
Package: nymea-plugin-webasto
Architecture: any
Section: libs
Depends: ${shlibs:Depends},
${misc:Depends},
nymea-plugins-modbus-translations
Description: nymea.io plugin for Webasto Live EV charging stations
The nymea daemon is a plugin based IoT (Internet of Things) server. The
server works like a translator for devices, things and services and
allows them to interact.
With the powerful rule engine you are able to connect any device available
in the system and create individual scenes and behaviors for your environment.
.
This package will install the nymea.io plugin for webasto
Package: nymea-plugins-modbus-translations
Section: misc

View File

@ -0,0 +1 @@
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginwebasto.so

272
discovery/discovery.cpp Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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 <QDebug>
#include <QXmlStreamReader>
#include <QNetworkInterface>
#include <QHostInfo>
#include <QTimer>
#include <loggingcategories.h>
NYMEA_LOGGING_CATEGORY(dcDiscovery, "Discovery")
Discovery::Discovery(QObject *parent) : QObject(parent)
{
connect(&m_timeoutTimer, &QTimer::timeout, this, &Discovery::onTimeout);
}
void Discovery::discoverHosts(int timeout)
{
if (isRunning()) {
qCWarning(dcDiscovery()) << "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(dcDiscovery()) << "Scanning network:" << "nmap" << arguments.join(" ");
discoveryProcess->start(QStringLiteral("nmap"), arguments);
}
}
void Discovery::abort()
{
foreach (QProcess *discoveryProcess, m_discoveryProcesses) {
if (discoveryProcess->state() == QProcess::Running) {
qCDebug(dcDiscovery()) << "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<QProcess*>(sender());
if (exitCode != 0 || exitStatus != QProcess::NormalExit) {
qCWarning(dcDiscovery()) << "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;
qCDebug(dcDiscovery()) << "nmap finished network discovery:";
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(dcDiscovery()) << " - 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(dcDiscovery()) << "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(dcDiscovery()) << "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<QProcess*>(sender());
p->deleteLater();
Host *host = m_pendingArpLookups.take(p);
if (exitCode != 0 || exitStatus != QProcess::NormalExit) {
qCWarning(dcDiscovery()) << "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(dcDiscovery()) << "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<QHostAddress, int> 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<Host> hosts;
foreach (Host *host, m_scanResults) {
if (!host->macAddress().isEmpty()) {
hosts.append(*host);
}
}
qDeleteAll(m_scanResults);
m_scanResults.clear();
qCDebug(dcDiscovery()) << "Found" << hosts.count() << "network devices";
m_timeoutTimer.stop();
emit finished(hosts);
}

75
discovery/discovery.h Normal file
View File

@ -0,0 +1,75 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 <https://www.gnu.org/licenses/>.
*
* 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 <QObject>
#include <QProcess>
#include <QHostInfo>
#include <QTimer>
#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(const QList<Host> &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<QProcess*> m_discoveryProcesses;
QTimer m_timeoutTimer;
QHash<QProcess*, Host*> m_pendingArpLookups;
QHash<QString, Host*> m_pendingNameLookups;
QList<Host*> m_scanResults;
};
#endif // DISCOVERY_H

94
discovery/host.cpp Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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<Host>();
qRegisterMetaType<QList<Host> >();
}
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();
}

70
discovery/host.h Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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 <QString>
#include <QDebug>
#include <QDateTime>
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

View File

@ -7,6 +7,7 @@ PLUGIN_DIRS = \
sunspec \
unipi \
wallbe \
webasto \
message(============================================)
message("Qt version:" $$[QT_VERSION])

17
webasto/README.md Normal file
View File

@ -0,0 +1,17 @@
# Webasto
## Supported Things
* AC Wallbox Live
## Requirements
* The packages 'nymea-plugin-webasto' must be installed.
* The modbus server must be enabled
* The setting 'Modbus Slave Register Address Set' must be set to 'TQ-DM100'
* The setting 'Modbus TCP Server Port Number' must be set to 502
## More
https://dealers.webasto.com/Sections/Public/Documents.aspx?SectionId=6&CategoryId=9&ProductTypeId=66&ProductId=630&ShowResult=true

View File

@ -0,0 +1,200 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 <https://www.gnu.org/licenses/>.
*
* 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 "integrationpluginwebasto.h"
#include "plugininfo.h"
#include "types/param.h"
#include <QDebug>
#include <QStringList>
#include <QJsonDocument>
#include <QNetworkInterface>
IntegrationPluginWebasto::IntegrationPluginWebasto()
{
}
void IntegrationPluginWebasto::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("webasto", Qt::CaseSensitivity::CaseInsensitive))
continue;
foreach (Thing *existingThing, myThings()) {
if (existingThing->paramValue(liveWallboxThingMacAddressParamTypeId).toString().isEmpty()) {
//This device got probably manually setup, to enable auto rediscovery the MAC address needs to setup
if (existingThing->paramValue(liveWallboxThingIpAddressParamTypeId).toString() == host.address()) {
qCDebug(dcWebasto()) << "Wallbox MAC Address has been discovered" << existingThing->name() << host.macAddress();
existingThing->setParamValue(liveWallboxThingMacAddressParamTypeId, host.macAddress());
}
} else if (existingThing->paramValue(liveWallboxThingMacAddressParamTypeId).toString() == host.macAddress()) {
if (existingThing->paramValue(liveWallboxThingIpAddressParamTypeId).toString() != host.address()) {
qCDebug(dcWebasto()) << "Wallbox IP Address has changed, from" << existingThing->paramValue(liveWallboxThingIpAddressParamTypeId).toString() << "to" << host.address();
existingThing->setParamValue(liveWallboxThingIpAddressParamTypeId, host.address());
} else {
qCDebug(dcWebasto()) << "Wallbox" << existingThing->name() << "IP address has not changed" << host.address();
}
break;
}
}
}
});
}
void IntegrationPluginWebasto::discoverThings(ThingDiscoveryInfo *info)
{
qCDebug(dcWebasto()) << "Discover things";
if (info->thingClassId() == liveWallboxThingClassId) {
m_discovery->discoverHosts(25);
connect(m_discovery, &Discovery::finished, info, [this, info] (const QList<Host> &hosts) {
foreach (const Host &host, hosts) {
if (!host.hostName().contains("webasto", Qt::CaseSensitivity::CaseInsensitive))
continue;
qCDebug(dcWebasto()) << " - " << host.hostName() << host.address() << host.macAddress();
ThingDescriptor descriptor(liveWallboxThingClassId, "Wallbox", host.address() + " (" + host.macAddress() + ")");
// Rediscovery
foreach (Thing *existingThing, myThings()) {
if (existingThing->paramValue(liveWallboxThingMacAddressParamTypeId).toString() == host.macAddress()) {
qCDebug(dcWebasto()) << " - Device is already added";
descriptor.setThingId(existingThing->id());
break;
}
}
ParamList params;
params << Param(liveWallboxThingMacAddressParamTypeId, host.macAddress());
params << Param(liveWallboxThingIpAddressParamTypeId, host.address());
descriptor.setParams(params);
info->addThingDescriptor(descriptor);
}
info->finish(Thing::ThingErrorNoError);
});
} else {
Q_ASSERT_X(false, "discoverThings", QString("Unhandled thingClassId: %1").arg(info->thingClassId().toString()).toUtf8());
}
}
void IntegrationPluginWebasto::setupThing(ThingSetupInfo *info)
{
Thing *thing = info->thing();
qCDebug(dcWebasto()) << "Setup thing" << thing->name();
if (thing->thingClassId() == liveWallboxThingClassId) {
if (m_webastoConnections.contains(thing)) {
// Clean up after reconfiguration
m_webastoConnections.take(thing)->deleteLater();
}
QHostAddress address = QHostAddress(thing->paramValue(liveWallboxThingIpAddressParamTypeId).toString());
Webasto *webasto = new Webasto(address, 502, thing);
m_webastoConnections.insert(thing, webasto);
connect(webasto, &Webasto::destroyed, this, [thing, this] {m_webastoConnections.remove(thing);});
//TODO signal socket
//TODO emit setup finished
connect(webasto, &Webasto::connectionChanged, info, [info] (bool connected) {
if (connected)
info->finish(Thing::ThingErrorNoError);
});
} else {
Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
}
}
void IntegrationPluginWebasto::postSetupThing(Thing *thing)
{
qCDebug(dcWebasto()) << "Post setup thing" << thing->name();
if (thing->thingClassId() == liveWallboxThingClassId) {
Webasto *connection = m_webastoConnections.value(thing);
if (!connection) {
qCWarning(dcWebasto()) << "Can't find connection to thing";
}
} else {
Q_ASSERT_X(false, "postSetupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
}
}
void IntegrationPluginWebasto::executeAction(ThingActionInfo *info)
{
Thing *thing = info->thing();
Action action = info->action();
if (thing->thingClassId() == liveWallboxThingClassId) {
Webasto *connection = m_webastoConnections.value(thing);
if (!connection) {
qCWarning(dcWebasto()) << "Can't find connection to thing";
return info->finish(Thing::ThingErrorHardwareNotAvailable);
}
if (action.actionTypeId() == liveWallboxPowerActionTypeId) {
// Enable/Disable the charging process
} else if (action.actionTypeId() == liveWallboxChargeCurrentActionTypeId) {
} else {
Q_ASSERT_X(false, "executeAction", QString("Unhandled actionTypeId: %1").arg(action.actionTypeId().toString()).toUtf8());
}
} else {
Q_ASSERT_X(false, "executeAction", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
}
}
void IntegrationPluginWebasto::thingRemoved(Thing *thing)
{
qCDebug(dcWebasto()) << "Delete thing" << thing->name();
if (thing->thingClassId() == liveWallboxThingClassId) {
}
if (myThings().isEmpty()) {
//Stop timer
}
}
void IntegrationPluginWebasto::onConnectionChanged(bool connected)
{
Webasto *connection = static_cast<Webasto *>(sender());
Thing *thing = m_webastoConnections.key(connection);
if (!thing) {
qCWarning(dcWebasto()) << "On connection changed, thing not found for connection";
return;
}
thing->setStateValue(liveWallboxConnectedStateTypeId, connected);
}

View File

@ -0,0 +1,69 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 <https://www.gnu.org/licenses/>.
*
* 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 INTEGRATIONPLUGINWEBASTO_H
#define INTEGRATIONPLUGINWEBASTO_H
#include "integrations/integrationplugin.h"
#include "plugintimer.h"
#include "webasto.h"
#include "../discovery/discovery.h"
#include "../discovery/host.h"
#include <QObject>
#include <QHostAddress>
#include <QUuid>
class IntegrationPluginWebasto : public IntegrationPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginwebasto.json")
Q_INTERFACES(IntegrationPlugin)
public:
explicit IntegrationPluginWebasto();
void init() override;
void discoverThings(ThingDiscoveryInfo *info) override;
void setupThing(ThingSetupInfo *info) override;
void postSetupThing(Thing *thing) override;
void executeAction(ThingActionInfo *info) override;
void thingRemoved(Thing *thing) override;
private:
Discovery *m_discovery = nullptr;
QHash<Thing *, Webasto *> m_webastoConnections;
QHash<QUuid, ThingActionInfo *> m_asyncActions;
private slots:
void onConnectionChanged(bool connected);
};
#endif // INTEGRATIONPLUGINWEBASTO_H

View File

@ -0,0 +1,116 @@
{
"id": "9fa369ab-c225-4447-9a23-f4911d9b056c",
"name": "Webasto",
"displayName": "webasto",
"vendors": [
{
"id": "274f4453-6acf-4204-be21-379abbe3b5a7",
"name": "webasto",
"displayName": "Webasto",
"thingClasses": [
{
"id": "48472124-3199-4827-990a-b72069bd5658",
"displayName": "Live Wallbox",
"name": "liveWallbox",
"createMethods": ["discovery"],
"interfaces": ["evcharger", "connectable"],
"paramTypes": [
{
"id": "51fa3ea8-e819-46ca-b975-1bee6285441c",
"name": "ipAddress",
"displayName": "IP address",
"type": "QString",
"defaultValue": "0.0.0.0"
},
{
"id": "4aa97965-fc1c-488a-92a6-848c214564bc",
"name": "macAddress",
"displayName": "MAC address",
"type": "QString",
"defaultValue": "",
"readOnly": true
}
],
"stateTypes":[
{
"id": "7e6ed2b4-aa8a-4bf6-b20b-84ecc6cc1508",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"name": "connected",
"type": "bool",
"defaultValue": false,
"cached": false
},
{
"id": "537e01ac-9290-421d-a5cb-987d9e088941",
"name": "chargeTime",
"displayName": "Charging Time",
"unit": "Minutes",
"type": "int",
"defaultValue": 0,
"displayNameEvent": "Charging time changed"
},
{
"id": "b076353b-e911-444f-80ad-3f78c4075d1a",
"name": "chargePointState",
"displayName": "Charge point state",
"displayNameEvent": "Charge point state changed",
"type": "QString",
"possibleValues": [
"No vehicle attached",
"Vehicle attached, no permission",
"Charging authorized",
"Charging",
"Charging paused",
"Charge successful (car still attached)",
"Charging stopped by user (car still attached)",
"Charging error (car still attached)",
"Charging station reserved (No car attached)",
"User not authorized (car attached)"
],
"defaultValue": "No vehicle attached"
},
{
"id": "a1a452f9-de93-4c31-b71b-c74264f85a3e",
"name": "cableState",
"displayName": "Cable state",
"displayNameEvent": "Cable state changed",
"type": "QString",
"possibleValues": [
"No cable attached",
"Cable attached but no car attached)",
"Cable attached and car attached",
"Cable attached,car attached and lock active"
],
"defaultValue": "No cable attached"
},
{
"id": "3c054603-d933-4e30-a2cc-2177beaaffdb",
"name": "power",
"displayName": "Charging",
"type": "bool",
"defaultValue": false,
"displayNameAction": "Start charging",
"displayNameEvent": "Charging status changed",
"writable": true
},
{
"id": "96ed77ce-c5cf-4981-8a72-b619f5702724",
"name": "chargeCurrent",
"displayName": "Charging current",
"displayNameAction": "Set charging current",
"displayNameEvent": "Charging current changed",
"type": "double",
"unit": "Ampere",
"minValue": 6.00,
"maxValue": 80.00,
"defaultValue": 6.00,
"writable": true
}
]
}
]
}
]
}

13
webasto/meta.json Normal file
View File

@ -0,0 +1,13 @@
{
"title": "webasto",
"tagline": "Integrates Webasto Live DC wallbox into nymea.",
"icon": "webasto.png",
"stability": "consumer",
"offline": true,
"technologies": [
"network"
],
"categories": [
"tool"
]
}

86
webasto/webasto.cpp Normal file
View File

@ -0,0 +1,86 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 <https://www.gnu.org/licenses/>.
*
* 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 "webasto.h"
#include "extern-plugininfo.h"
Webasto::Webasto(const QHostAddress &address, uint port, QObject *parent) :
QObject(parent)
{
m_modbusConnection = new QModbusTcpClient(this);
m_modbusConnection->setConnectionParameter(QModbusDevice::NetworkPortParameter, port);
m_modbusConnection->setConnectionParameter(QModbusDevice::NetworkAddressParameter, address.toString());
m_modbusConnection->setNumberOfRetries(3);
m_modbusConnection->setTimeout(1000);
}
void Webasto::setAddress(const QHostAddress &address)
{
qCDebug(dcWebasto()) << "Webasto: set address" << address;
m_modbusConnection->setConnectionParameter(QModbusDevice::NetworkAddressParameter, address.toString());
}
QHostAddress Webasto::address() const
{
return QHostAddress(m_modbusConnection->connectionParameter(QModbusDevice::NetworkAddressParameter).toString());
}
bool Webasto::connected()
{
return (m_modbusConnection->state() == QModbusTcpClient::State::ConnectedState);
}
void Webasto::getBasicInformation()
{
}
void Webasto::getUserId()
{
}
void Webasto::getSessionInformation()
{
}
void Webasto::setLiveBit()
{
}
QUuid Webasto::writeHoldingRegister()
{
QUuid request = QUuid::createUuid();
return request;
}

173
webasto/webasto.h Normal file
View File

@ -0,0 +1,173 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 <https://www.gnu.org/licenses/>.
*
* 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 WEBASTO_H
#define WEBASTO_H
#include <QObject>
#include <QHostAddress>
#include <QTimer>
#include <QModbusTcpClient>
#include <QUuid>
class Webasto : public QObject
{
Q_OBJECT
public:
enum ChargePointState {
ChargePointStateNoVehicleAttached = 0,
ChargePointStateVehicleAttachedNoPermission,
ChargePointStateChargingAuthorized,
ChargePointStateCharging,
ChargePointStateChargingPaused,
ChargePointStateChargeSuccessfulCarStillAttached,
ChargePointStateChargingStoppedByUserCarStillAttached,
ChargePointStateChargingErrorCarStillAttached,
ChargePointStateChargingStationReservedNorCarAttached,
ChargePointStateUserNotAuthorizedCarAttached
};
Q_ENUM(ChargePointState)
enum CableState {
CableStateNoCableAttached = 0,
CableStateCableAttachedNoCarAttached,
CableStateCableAttachedCarAttached,
CableStateCableAttachedCarAttachedLockActive
};
Q_ENUM(CableState)
enum EvseSate {
EvseSateStarting = 0,
EvseSateRunning,
EvseSateError
};
Q_ENUM(EvseSate)
struct BasicInformation {
double currentL1; // [A]
double currentL2; // [A]
double currentL3; // [A]
double activePower; // [W]
uint activePowerL1;
uint activePowerL2;
uint activePowerL3;
double energyMeter; // [kWh]
};
struct CurrentLimitations {
double maxCurrent;
double minimumCurrentLimit;
double maxCurrentFromEvse;
double maxCurrentFromCable;
double maxCurrentFromEV;
};
explicit Webasto(const QHostAddress &address, uint port = 502, QObject *parent = nullptr);
void setAddress(const QHostAddress &address);
QHostAddress address() const;
bool connected();
private:
enum TqModbusRegister {
// Get Basic Information polls Register 1000 to 1037
TqChargePointState = 1000, // State of the charging device
TqChargeState = 1001, // Charging
TqEVSEState = 1002, // State of the charging station
TqCableState = 1004, // State of the charging cable
TqEVSEError = 1006, // Error code of the charging station
TqCurrentL1 = 1008, // Charging current L1
TqCurrentL2 = 1010, // Charging current L2
TqCurrentL3 = 1012, // Charging current L3
TqActivePower = 1020, // Electric Power that can be changed to f.e. mechanical, chemical, thermic power
TqActivePowerL1 = 1024, // Active power L1
TqActivePowerL2 = 1028, // Active power L2
TqActivePowerL3 = 1032, // Active power L3
TqEnergyMeter = 1036, // Meter reading of the charging station
// Get Current Limitatoins polls register 1100 to 1110
TqMaxCurrent = 1100, // Maximal charging current UINT of the hardware (EVSE, cable, EV)
TqMinimumCurrentLimit = 1102, // Minimal charging current of the hardware (EVSE, cable, EV)
TqMaxCurrentFromEVSE = 1104, // Maximal charging current of the charging station
TqMaxCurrentFromCable = 1106, // Maximal charging current of the cable
TqMaxCurrentFromEV = 1108, // Maximal charging current of the EV
// Get User Priority
TqUserPriority = 1200, // Priorities of the user 0: not defined 1: high priority - 10: low priority
// Get Battery state
TqEVBatteryState = 1300, // Returns an estimate of the SoC
TqEVBatteryCapacity = 1302, // Returns an estimate of the EV Battery Capacity
// Get Schedule polls register 1400 to 1414
TqScheduleType = 1400, // Type/information of traveling 0: energy that has to be charged, 1: Specification of the desired battery charge (Needs: state of the battery)
TqRequiredEnergy = 1402, // Desired energy
TqRequiredBatteryState = 1404, // Desired state of the battery
TqScheduledTime = 1408, // Departure time
TqScheduledDate = 1412, // Departure date
// Set session polls register 1500 to 15014
TqChargedEnergy = 1502, // Sum of charged energy for the current session
TqStartTime = 1504, // Start time of charging process
TqChargingTime = 1508, // Duration since beginning of charge
TqEndTime = 1512, // End time of charging process
// Get user id polls register 1600 to 1620
TqUserId = 1600, // 24 Bytes long User ID (OCPP IdTag) from the current session
TqSmartVehicleDetected = 1620, //Returns 1 if an EV currently connected is a smart vehicle, or 0 if no EV connected or it is not a smart vehicle,
// Get failsafe polls register 2000 to 2004
TqSafeCurrent = 2000, // Max. charge current under communication failure
TqComTimeout = 2002, // Communication timeout
// Get Charge power polls register 5000 to 5002
TqChargePower = 5000, // Charge power
TqChargeCurrent = 5001, // Charge current
TqLifeBit = 6000 // Communication monitoring 0/1 Toggle-Bit EM writes 1, Live deletes it and puts it on 0.
};
QModbusTcpClient *m_modbusConnection = nullptr;
QHostAddress m_address;
uint m_unitId = 255;
void getBasicInformation();
void getUserId();
void getCurrentLimitations();
void getSessionInformation();
void getFailsafeSpecs();
void getChargeCurrentAndPower();
void getUserPriority();
void getBatteryState();
void setLiveBit();
private:
QTimer *m_lifeBitTimer = nullptr;
QUuid writeHoldingRegister();
signals:
void connectionChanged(bool connected);
void userIdReceived(const QByteArray &userId);
void currentLimitationsReceived(const CurrentLimitations &limitations);
void userPriorityReceived(uint userPriority); // 0 lowest - 10 highest
};
#endif // WEBASTO_H

BIN
webasto/webasto.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

17
webasto/webasto.pro Normal file
View File

@ -0,0 +1,17 @@
include(../plugins.pri)
QT += \
serialbus \
network
SOURCES += \
integrationpluginwebasto.cpp \
webasto.cpp \
../discovery/discovery.cpp \
../discovery/host.cpp
HEADERS += \
integrationpluginwebasto.h \
webasto.h \
../discovery/discovery.h \
../discovery/host.h