From 59a9927f2c736ec1f9412072a1ba809bea00083d Mon Sep 17 00:00:00 2001 From: Boernsman Date: Wed, 22 Jan 2020 16:25:24 +0100 Subject: [PATCH] added wall-be plug-in --- debian/control | 18 +- debian/nymea-plugin-wallbe.install.in | 1 + drexelundweiss/drexelundweiss.pro | 2 + nymea-plugins-modbus.pro | 2 +- wallbe/README.md | 14 ++ wallbe/devicepluginwallbe.cpp | 207 ++++++++++++++++++ wallbe/devicepluginwallbe.h | 64 ++++++ wallbe/devicepluginwallbe.json | 99 +++++++++ wallbe/discover.cpp | 162 ++++++++++++++ wallbe/discover.h | 73 ++++++ wallbe/host.cpp | 73 ++++++ wallbe/host.h | 56 +++++ ...5bbd2-0dad-4727-9a17-3ee149106048-en_US.ts | 138 ++++++++++++ wallbe/wallbe.cpp | 190 ++++++++++++++++ wallbe/wallbe.h | 62 ++++++ wallbe/wallbe.pro | 19 ++ 16 files changed, 1178 insertions(+), 2 deletions(-) create mode 100644 debian/nymea-plugin-wallbe.install.in create mode 100644 wallbe/README.md create mode 100644 wallbe/devicepluginwallbe.cpp create mode 100644 wallbe/devicepluginwallbe.h create mode 100644 wallbe/devicepluginwallbe.json create mode 100644 wallbe/discover.cpp create mode 100644 wallbe/discover.h create mode 100644 wallbe/host.cpp create mode 100644 wallbe/host.h create mode 100644 wallbe/translations/0de5bbd2-0dad-4727-9a17-3ee149106048-en_US.ts create mode 100644 wallbe/wallbe.cpp create mode 100644 wallbe/wallbe.h create mode 100644 wallbe/wallbe.pro diff --git a/debian/control b/debian/control index 3ba6372..1ad4456 100644 --- a/debian/control +++ b/debian/control @@ -33,11 +33,26 @@ Description: nymea.io plugin for Drexel & Weiss heat pumps This package will install the nymea.io plugin for Drexel & Weiss heat pumps +Package: nymea-plugin-wallbe +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends}, + nymea-plugins-translations, + libmodbus, +Description: nymea.io plugin for wallbe 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 wallbe + + Package: nymea-plugins-translations Section: misc Architecture: all Depends: ${misc:Depends} -Replaces: guh-plugins-translations Description: Translation files for nymea plugins - translations The nymea daemon is a plugin based IoT (Internet of Things) server. The server works like a translator for devices, things and services and @@ -52,6 +67,7 @@ Package: nymea-plugins-modbus Section: libs Architecture: all Depends: nymea-plugin-drexelundweiss, + nymea-plugin-wallbe, Description: Plugins for nymea IoT server - the modbus plugin collection The nymea daemon is a plugin based IoT (Internet of Things) server. The server works like a translator for devices, things and services and diff --git a/debian/nymea-plugin-wallbe.install.in b/debian/nymea-plugin-wallbe.install.in new file mode 100644 index 0000000..c6bd4a3 --- /dev/null +++ b/debian/nymea-plugin-wallbe.install.in @@ -0,0 +1 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_devicepluginwallbe.so diff --git a/drexelundweiss/drexelundweiss.pro b/drexelundweiss/drexelundweiss.pro index b68e01e..f8fe629 100644 --- a/drexelundweiss/drexelundweiss.pro +++ b/drexelundweiss/drexelundweiss.pro @@ -1,5 +1,7 @@ include(../plugins.pri) +TARGET = $$qtLibraryTarget(nymea_deviceplugindrexelundweiss) + QT += \ serialport \ serialbus \ diff --git a/nymea-plugins-modbus.pro b/nymea-plugins-modbus.pro index 8f5a1f0..0a6f508 100644 --- a/nymea-plugins-modbus.pro +++ b/nymea-plugins-modbus.pro @@ -2,7 +2,7 @@ TEMPLATE = subdirs PLUGIN_DIRS = \ drexelundweiss \ - + wallbe \ message(============================================) message("Qt version:" $$[QT_VERSION]) diff --git a/wallbe/README.md b/wallbe/README.md new file mode 100644 index 0000000..00c5df2 --- /dev/null +++ b/wallbe/README.md @@ -0,0 +1,14 @@ +# Wallbe + +Plug-in for the EV-charger wall-be, +with network device discovery. + +Make sure to enable the Network interface in your Wallbe device, +have a look in the Wallbe manual or call the Wallbe support. + +## Technical Specification + +* Default IP Adress 192.168.0.8 +* Default Name EVCC_1 +* Port 502 +* Modbus-Slave Address 180 diff --git a/wallbe/devicepluginwallbe.cpp b/wallbe/devicepluginwallbe.cpp new file mode 100644 index 0000000..361e7de --- /dev/null +++ b/wallbe/devicepluginwallbe.cpp @@ -0,0 +1,207 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "devicepluginwallbe.h" +#include "plugininfo.h" + +#include "devices/devicemanager.h" +#include "devices/device.h" +#include "types/param.h" + +#include +#include +#include +#include + + +DevicePluginWallbe::DevicePluginWallbe() +{ +} + + +void DevicePluginWallbe::setupDevice(DeviceSetupInfo *info) +{ + Device *device = info->device(); + qCDebug(dcWallbe) << "Setting up a new device:" << device->params(); + + QHostAddress address(device->paramValue(wallbeEcoDeviceIpParamTypeId).toString()); + + if (address.isNull()){ + qCWarning(dcWallbe) << "IP address is null"; + info->finish(Device::DeviceErrorSetupFailed, tr("IP address parameter not valid")); + return; + } + + WallBe *wallbe = new WallBe(address, 502, this); + m_connections.insert(device, wallbe); + + info->finish(Device::DeviceErrorNoError); +} + + +void DevicePluginWallbe::discoverDevices(DeviceDiscoveryInfo *info) +{ + if (info->deviceClassId() == wallbeEcoDeviceClassId){ + + Discover *discover = new Discover(QStringList("-xO" "-p 502")); + connect(discover, &Discover::devicesDiscovered, this, [info, this](QList hosts){ + foreach (Host host, hosts) { + DeviceDescriptor descriptor; + foreach(Device *device, myDevices().filterByParam(wallbeEcoDeviceMacParamTypeId, host.macAddress())) { + descriptor.setDeviceId(device->id()); + break; + } + descriptor.setTitle(host.hostName()); + ParamList params; + params.append(Param(wallbeEcoDeviceIpParamTypeId, host.address())); + params.append(Param(wallbeEcoDeviceMacParamTypeId, host.macAddress())); + descriptor.setParams(params); + info->addDeviceDescriptor(descriptor); + } + info->finish(Device::DeviceErrorNoError); + }); + return; + } + Q_ASSERT_X(false, "discoverDevices", QString("Unhandled deviceClassId: %1").arg(info->deviceClassId().toString()).toUtf8()); +} + + +void DevicePluginWallbe::postSetupDevice(Device *device) +{ + if (!m_pluginTimer) { + m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(120); + connect(m_pluginTimer, &PluginTimer::timeout, this, [this] { + foreach(Device *device, m_connections.keys()) { + update(device); + } + }); + } + if (device->deviceClassId() == wallbeEcoDeviceClassId){ + update(device); + return; + } + Q_ASSERT_X(false, "postSetupDevice", QString("Unhandled deviceClassId: %1").arg(device->deviceClassId().toString()).toUtf8()); +} + + +void DevicePluginWallbe::executeAction(DeviceActionInfo *info) +{ + Device *device = info->device(); + Action action = info->action(); + + WallBe *wallbe = m_connections.value(device); + if (!wallbe) { + qCWarning(dcWallbe()) << "Wallbe object not available"; + info->finish(Device::DeviceErrorHardwareFailure); + return; + } + + if (device->deviceClassId() == wallbeEcoDeviceClassId){ + + // check if this is the "set power" action + if (action.actionTypeId() == wallbeEcoPowerActionTypeId) { + + // get the param value of the charging action + bool charging = action.param(wallbeEcoPowerActionPowerParamTypeId).value().toBool(); + qCDebug(dcWallbe) << "start Charging button" << device->name() << "set power to" << charging; + wallbe->setChargingStatus(charging); + // Set the "power" state + device->setStateValue(wallbeEcoPowerStateTypeId, charging); + info->finish(Device::DeviceErrorNoError); + return; + + } else if(action.actionTypeId() == wallbeEcoChargeCurrentActionTypeId){ + + uint16_t current = action.param(wallbeEcoChargeCurrentEventChargeCurrentParamTypeId).value().toUInt(); + qCDebug(dcWallbe) << "Charging power set to" << current; + wallbe->setChargingCurrent(current); + device->setStateValue(wallbeEcoChargeCurrentStateTypeId, current); + info->finish(Device::DeviceErrorNoError); + return; + } + Q_ASSERT_X(false, "executeAction", QString("Unhandled action: %1").arg(action.actionTypeId().toString()).toUtf8()); + } + Q_ASSERT_X(false, "executeAction", QString("Unhandled deviceClassId: %1").arg(device->deviceClassId().toString()).toUtf8()); +} + + +void DevicePluginWallbe::deviceRemoved(Device *device) +{ + m_address.removeOne(QHostAddress(device->paramValue(wallbeEcoDeviceIpParamTypeId).toString())); + WallBe *wallbe = m_connections.take(device); + if (wallbe) { + qCDebug(dcWallbe) << "Remove device" << device->name(); + wallbe->deleteLater(); + } + + if (myDevices().isEmpty()) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); + m_pluginTimer = nullptr; + } +} + +void DevicePluginWallbe::update(Device *device) +{ + WallBe * wallbe = m_connections.value(device); + if(!wallbe->isAvailable()) + return; + + device->setStateValue(wallbeEcoConnectedStateTypeId, true); + + //EV state - 16 bit ASCII (8bit) + switch (wallbe->getEvStatus()) { + case 65: + device->setStateValue(wallbeEcoEvStatusStateTypeId, "A - No car plugged in"); + break; + case 66: + device->setStateValue(wallbeEcoEvStatusStateTypeId, "B - Supply equipment not yet ready"); + break; + case 67: + device->setStateValue(wallbeEcoEvStatusStateTypeId, "C - Ready to charge"); + break; + case 68: + device->setStateValue(wallbeEcoEvStatusStateTypeId, "D - Ready to charge, ventilation needed"); + break; + case 69: + device->setStateValue(wallbeEcoEvStatusStateTypeId, "E - Short circuit detected"); + break; + case 70: + device->setStateValue(wallbeEcoEvStatusStateTypeId, "F - Supply equipment not available"); + break; + default: + device->setStateValue(wallbeEcoEvStatusStateTypeId, "F - Supply equipment not available"); + } + + qCDebug(dcWallbe) << "EV State:" << device->stateValue(wallbeEcoEvStatusStateTypeId).toString(); + + // Extract Input Register 102 - load time - 32bit integer + device->setStateValue(wallbeEcoChargeTimeStateTypeId, wallbe->getChargingTime()); + + // Read the charge current state + device->setStateValue(wallbeEcoChargeCurrentStateTypeId, wallbe->getChargingCurrent()); + device->setStateValue(wallbeEcoPowerStateTypeId, wallbe->getChargingStatus()); +} diff --git a/wallbe/devicepluginwallbe.h b/wallbe/devicepluginwallbe.h new file mode 100644 index 0000000..051cd3a --- /dev/null +++ b/wallbe/devicepluginwallbe.h @@ -0,0 +1,64 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 DEVICEPLUGINWALLBE_H +#define DEVICEPLUGINWALLBE_H + +#include "devices/deviceplugin.h" +#include "wallbe.h" +#include "host.h" +#include "discover.h" +#include "plugintimer.h" + +#include +#include + +class DevicePluginWallbe : public DevicePlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "devicepluginwallbe.json") + Q_INTERFACES(DevicePlugin) + +public: + explicit DevicePluginWallbe(); + + void discoverDevices(DeviceDiscoveryInfo *info) override; + void setupDevice(DeviceSetupInfo *info) override; + void postSetupDevice(Device *device) override; + void executeAction(DeviceActionInfo *info) override; + void deviceRemoved(Device *device) override; + +private: + QHash m_connections; + QList m_address; + PluginTimer *m_pluginTimer = nullptr; + + void update(Device *device); +}; + +#endif // DEVICEPLUGINWALLBE_H diff --git a/wallbe/devicepluginwallbe.json b/wallbe/devicepluginwallbe.json new file mode 100644 index 0000000..0e6aca3 --- /dev/null +++ b/wallbe/devicepluginwallbe.json @@ -0,0 +1,99 @@ +{ + "displayName": "wallbe plugin", + "name": "Wallbe", + "id": "0de5bbd2-0dad-4727-9a17-3ee149106048", + "vendors": [ + { + "displayName": "Petring", + "name": "petring", + "id": "831b4b87-0a6c-4d51-b055-967bb6e5fab5", + "deviceClasses": [ + { + "id": "e66c84f6-b398-47e9-8aeb-33840e7b4492", + "displayName": "Wallbe eco 2.0", + "name": "wallbeEco", + "createMethods": ["discovery"], + "interfaces": ["evcharger", "connectable"], + "paramTypes": [ + { + "id": "95f297a7-56a5-4789-9b14-6735717344b5", + "displayName": "IP address", + "name": "ip", + "type": "QString", + "inputType": "IPv4Address", + "defaultValue": "192.168.0.8" + }, + { + "id": "551b03f0-dd70-4463-929b-3668dbd3290f", + "displayName": "MAC address", + "name": "mac", + "type": "QString", + "defaultValue": "" + } + ], + "stateTypes":[ + { + "id": "39a8e92b-40e5-4648-b5a8-2ffcb5598081", + "displayName": "Connected", + "name": "connected", + "type": "bool", + "defaultValue": false, + "displayNameEvent": "Connected changed" + }, + { + "id": "8dc2fef8-d16e-422a-8498-456b818f5752", + "name": "chargeTime", + "displayName": "Charging Time", + "unit": "Minutes", + "type": "int", + "defaultValue": 0, + "displayNameEvent": "Charging time changed" + }, + { + "id": "2a95c4fb-9a15-4788-ae09-d34e71314da6", + "name": "evStatus", + "displayName": "EV Status", + "type": "QString", + "possibleValues": [ + "A - No car plugged in", + "B - Supply equipment not yet ready", + "C - Ready to charge", + "D - Ready to charge, ventilation needed", + "E - Short circuit detected", + "F - Supply equipment not available" + ], + "defaultValue": "F - Supply equipment not available", + "displayNameEvent": "EV status changed" + }, + { + "id": "26793adc-de10-426f-bb17-170c227891b2", + "name": "power", + "displayName": "Charging", + "type": "bool", + "defaultValue": false, + "displayNameAction": "Start charging", + "displayNameEvent": "Charging status changed", + "writable": true + }, + { + "id": "60b5b6b8-bcd3-4c3f-8501-f15af94bc8c1", + "name": "chargeCurrent", + "displayName": "Charging current", + "displayNameAction": "Set charging current", + "displayNameEvent": "Charging current changed", + "unit": "Ampere", + "type": "int", + "defaultValue": 0, + "type": "int", + "unit": "Ampere", + "minValue": 6, + "maxValue": 80, + "defaultValue": 6, + "writable": true + } + ] + } + ] + } + ] +} diff --git a/wallbe/discover.cpp b/wallbe/discover.cpp new file mode 100644 index 0000000..5af54e4 --- /dev/null +++ b/wallbe/discover.cpp @@ -0,0 +1,162 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 +#include +#include "discover.h" +#include "extern-plugininfo.h" + +Discover::Discover(QStringList arguments, QObject *parent) : + QObject(parent), + m_arguments(arguments), + m_discoveryProcess(nullptr), + m_aboutToQuit(false) +{ +} + +Discover::~Discover() +{ + // Stop running processes + m_aboutToQuit = true; + + if (m_discoveryProcess && m_discoveryProcess->state() == QProcess::Running) { + qCDebug(dcWallbe()) << "Kill running discovery process"; + m_discoveryProcess->terminate(); + m_discoveryProcess->waitForFinished(5000); + } +} + +QStringList Discover::getDefaultTargets() +{ + QStringList targets; + foreach (const QHostAddress &interface, QNetworkInterface::allAddresses()) { + if (!interface.isLoopback() && interface.scopeId().isEmpty()) { + QPair pair = QHostAddress::parseSubnet(interface.toString() + "/24"); + targets << QString("%1/%2").arg(pair.first.toString()).arg(pair.second); + } + } + return targets; +} + +QList Discover::parseProcessOutput(const QByteArray &processData) +{ + m_reader.clear(); + m_reader.addData(processData); + + QList hosts; + + while (!m_reader.atEnd() && !m_reader.hasError()) { + + QXmlStreamReader::TokenType token = m_reader.readNext(); + if(token == QXmlStreamReader::StartDocument) + continue; + + if(token == QXmlStreamReader::StartElement && m_reader.name() == "host") { + Host host = parseHost(); + if (host.isValid()) { + hosts.append(host); + } + } + } + return hosts; +} + +Host Discover::parseHost() +{ + if (!m_reader.isStartElement() || m_reader.name() != "host") + return Host(); + + QString address; QString hostName; QString status; QString mac; + while(!(m_reader.tokenType() == QXmlStreamReader::EndElement && m_reader.name() == "host")){ + + m_reader.readNext(); + + if (m_reader.isStartElement() && m_reader.name() == "hostname") { + QString name = m_reader.attributes().value("name").toString(); + if (!name.isEmpty()) + hostName = name; + + m_reader.readNext(); + } + + if (m_reader.name() == "address") { + QString addr = m_reader.attributes().value("addr").toString(); + if (!addr.isEmpty()) + address = addr; + } + + if (m_reader.name() == "state") { + QString state = m_reader.attributes().value("state").toString(); + if (!state.isEmpty()) + status = state; + } + + if (m_reader.name() == "mac") { //TODO CHeck Keyword + QString macAddress = m_reader.attributes().value("state").toString(); + if (!macAddress.isEmpty()) + mac = macAddress; + } + } + return Host(hostName, address, mac, (status == "open" ? true : false)); +} + +void Discover::processFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + QProcess *process = static_cast(sender()); + + // If the process was killed because nymead is shutting down...we dont't care any more about the result + if (m_aboutToQuit) + return; + + // Discovery + if (process == m_discoveryProcess) { + + qCDebug(dcWallbe()) << "Discovery process finished"; + + process->deleteLater(); + m_discoveryProcess = nullptr; + + if (exitCode != 0 || exitStatus != QProcess::NormalExit) { + qCWarning(dcWallbe()) << "Network scan error:" << process->readAllStandardError(); + return; + } + + QByteArray outputData = process->readAllStandardOutput(); + emit devicesDiscovered(parseProcessOutput(outputData)); + + } +} + +bool Discover::getProcessStatus() +{ + return m_discoveryProcess; +} + +void Discover::onTimeout() +{ + +} diff --git a/wallbe/discover.h b/wallbe/discover.h new file mode 100644 index 0000000..4c75916 --- /dev/null +++ b/wallbe/discover.h @@ -0,0 +1,73 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 DISCOVER_H +#define DISCOVER_H + +#include +#include +#include +#include +#include + +#include "host.h" + +class Discover : public QObject +{ + Q_OBJECT +public: + + explicit Discover(QStringList arguments, QObject *parent = nullptr); + ~Discover(); + bool getProcessStatus(); + +private: + QTimer *m_timer; + QStringList m_arguments; + + QProcess * m_discoveryProcess; + QProcess * m_scanProcess; + + QXmlStreamReader m_reader; + + bool m_aboutToQuit; + + QStringList getDefaultTargets(); + void processFinished(int exitCode, QProcess::ExitStatus exitStatus); + + // Process parsing + QList parseProcessOutput(const QByteArray &processData); + Host parseHost(); + +signals: + void devicesDiscovered(QList); + +private slots: + void onTimeout(); +}; + +#endif // DISCOVER_H diff --git a/wallbe/host.cpp b/wallbe/host.cpp new file mode 100644 index 0000000..bf6c388 --- /dev/null +++ b/wallbe/host.cpp @@ -0,0 +1,73 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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() +{ + +} + +Host::Host(const QString &hostName, const QString &address, const QString &macAddress, const bool &reachable): + m_hostName(hostName), + m_address(address), + m_macAddress(macAddress), + m_reachable(reachable) +{ + +} + +QString Host::hostName() const +{ + return m_hostName; +} + +QString Host::address() const +{ + return m_address; +} + +QString Host::macAddress() const +{ + return m_macAddress; +} + +bool Host::reachable() const +{ + return m_reachable; +} + +bool Host::isValid() const +{ + return !m_hostName.isEmpty() && !m_address.isEmpty(); +} + +QDebug operator<<(QDebug dbg, const Host &host) +{ + dbg.nospace() << "Host(" << host.hostName() << ", " << host.address() << ", " << (host.reachable() ? "up" : "down") << ")"; + return dbg.space(); +} diff --git a/wallbe/host.h b/wallbe/host.h new file mode 100644 index 0000000..71463c4 --- /dev/null +++ b/wallbe/host.h @@ -0,0 +1,56 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 + +class Host +{ +public: + Host(); + Host(const QString &hostName, const QString &address, const QString &macAddress, const bool &reachable); + + QString hostName() const; + QString address() const; + QString macAddress() const; + bool reachable() const; + + bool isValid() const; + +private: + QString m_hostName; + QString m_address; + QString m_macAddress; + bool m_reachable; +}; + +QDebug operator<<(QDebug dbg, const Host &host); + +#endif // HOST_H diff --git a/wallbe/translations/0de5bbd2-0dad-4727-9a17-3ee149106048-en_US.ts b/wallbe/translations/0de5bbd2-0dad-4727-9a17-3ee149106048-en_US.ts new file mode 100644 index 0000000..f153da9 --- /dev/null +++ b/wallbe/translations/0de5bbd2-0dad-4727-9a17-3ee149106048-en_US.ts @@ -0,0 +1,138 @@ + + + + + DevicePluginWallbe + + + IP address parameter not valid + + + + + Wallbe + + + + + Charging + The name of the ParamType (DeviceClass: wallbeEco, ActionType: power, ID: {26793adc-de10-426f-bb17-170c227891b2}) +---------- +The name of the ParamType (DeviceClass: wallbeEco, EventType: power, ID: {26793adc-de10-426f-bb17-170c227891b2}) +---------- +The name of the StateType ({26793adc-de10-426f-bb17-170c227891b2}) of DeviceClass wallbeEco + + + + + + Charging Time + The name of the ParamType (DeviceClass: wallbeEco, EventType: chargeTime, ID: {8dc2fef8-d16e-422a-8498-456b818f5752}) +---------- +The name of the StateType ({8dc2fef8-d16e-422a-8498-456b818f5752}) of DeviceClass wallbeEco + + + + + + + Charging current + The name of the ParamType (DeviceClass: wallbeEco, ActionType: chargeCurrent, ID: {60b5b6b8-bcd3-4c3f-8501-f15af94bc8c1}) +---------- +The name of the ParamType (DeviceClass: wallbeEco, EventType: chargeCurrent, ID: {60b5b6b8-bcd3-4c3f-8501-f15af94bc8c1}) +---------- +The name of the StateType ({60b5b6b8-bcd3-4c3f-8501-f15af94bc8c1}) of DeviceClass wallbeEco + + + + + Charging current changed + The name of the EventType ({60b5b6b8-bcd3-4c3f-8501-f15af94bc8c1}) of DeviceClass wallbeEco + + + + + Charging status changed + The name of the EventType ({26793adc-de10-426f-bb17-170c227891b2}) of DeviceClass wallbeEco + + + + + Charging time changed + The name of the EventType ({8dc2fef8-d16e-422a-8498-456b818f5752}) of DeviceClass wallbeEco + + + + + + Connected + The name of the ParamType (DeviceClass: wallbeEco, EventType: connected, ID: {39a8e92b-40e5-4648-b5a8-2ffcb5598081}) +---------- +The name of the StateType ({39a8e92b-40e5-4648-b5a8-2ffcb5598081}) of DeviceClass wallbeEco + + + + + Connected changed + The name of the EventType ({39a8e92b-40e5-4648-b5a8-2ffcb5598081}) of DeviceClass wallbeEco + + + + + + EV Status + The name of the ParamType (DeviceClass: wallbeEco, EventType: evStatus, ID: {2a95c4fb-9a15-4788-ae09-d34e71314da6}) +---------- +The name of the StateType ({2a95c4fb-9a15-4788-ae09-d34e71314da6}) of DeviceClass wallbeEco + + + + + EV status changed + The name of the EventType ({2a95c4fb-9a15-4788-ae09-d34e71314da6}) of DeviceClass wallbeEco + + + + + IP address + The name of the ParamType (DeviceClass: wallbeEco, Type: device, ID: {95f297a7-56a5-4789-9b14-6735717344b5}) + + + + + MAC address + The name of the ParamType (DeviceClass: wallbeEco, Type: device, ID: {551b03f0-dd70-4463-929b-3668dbd3290f}) + + + + + Petring + The name of the vendor ({831b4b87-0a6c-4d51-b055-967bb6e5fab5}) + + + + + Set charging current + The name of the ActionType ({60b5b6b8-bcd3-4c3f-8501-f15af94bc8c1}) of DeviceClass wallbeEco + + + + + Start charging + The name of the ActionType ({26793adc-de10-426f-bb17-170c227891b2}) of DeviceClass wallbeEco + + + + + Wallbe eco 2.0 + The name of the DeviceClass ({e66c84f6-b398-47e9-8aeb-33840e7b4492}) + + + + + wallbe plugin + The name of the plugin Wallbe ({0de5bbd2-0dad-4727-9a17-3ee149106048}) + + + + diff --git a/wallbe/wallbe.cpp b/wallbe/wallbe.cpp new file mode 100644 index 0000000..652eaec --- /dev/null +++ b/wallbe/wallbe.cpp @@ -0,0 +1,190 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "wallbe.h" +#include "extern-plugininfo.h" + +WallBe::WallBe(QHostAddress address, int port, QObject *parent) : QObject(parent) +{ + // TCP connction to target device + m_device = modbus_new_tcp(QVariant(address.toIPv4Address()).toByteArray(), port); + + if(m_device == nullptr){ + qCWarning(dcWallbe) << "Error Modbus TCP"; + free((void*)(m_device)); + return; + } + + if(modbus_connect(m_device) == -1){ + qCWarning(dcWallbe) << "Error Connecting Modbus"; + free((void*)(m_device)); + return; + } + + if(modbus_set_slave(m_device, 180) == -1){ + qCWarning(dcWallbe) << "Error Setting Slave ID"; + free((void*)(m_device)); + return; + } + + m_macAddress = getMacAddress(); +} + +/* +// Extract Input Register 104 - DIP - 16bit integer +if(!(tab_reg[4] && 0x0200)){ + // DIP Switch 10 has to be "ON" to enable remote charge control. + // DIP Switch 1 = LSB + qCWarning(dcWallbe) << "DIP switch 10 not on:" << tab_reg[4]; +} +*/ + +WallBe::~WallBe() +{ + if (m_device){ + modbus_close(m_device); + modbus_free(m_device); + } +} + +bool WallBe::isAvailable() +{ + uint16_t reg; + + if(!m_device) + return false; + + // Read random Register to check if the device is reachable + if (modbus_read_input_registers(m_device, 100, 1, ®) == -1){ + qDebug(dcWallbe) << "Connection Failed:" << modbus_strerror(errno) ; + return false; + } + return true; +} + +bool WallBe::connect() +{ + if(!m_device) + return false; + + // Conenct ot the device + if (modbus_connect(m_device) == -1) { + qCDebug(dcWallbe) << "Connection failed: " << modbus_strerror(errno); + return false; + } + return true; +} + +QString WallBe::getMacAddress() +{ + QString mac; + uint16_t reg[3]; + + if(!isAvailable()){ + if(!connect()){ + return ""; + } + } + + int ret = modbus_read_registers(m_device, 301, 3, reg); + if (ret == -1){ + qDebug(dcWallbe) << "Connection Failed:" << modbus_strerror(errno) ; + return ""; + } + // for(){ + mac = (reg[0] && 0x00ff) + (reg[0] >> 8);// + ":" + (reg[1] && 0x00ff) + (reg[1] >> 8) + ":" + (reg[2] && 0x00ff) + (reg[2] >> 8)); + + //} + qDebug(dcWallbe) << "Device Mac Address:" << mac ; + return mac; +} + +int WallBe::getEvStatus() +{ + uint16_t reg; + + if(modbus_read_input_registers(m_device, 100, 1, ®) == -1) + return 0; + + return (int)reg; +} + +int WallBe::getErrorCode() +{ + uint16_t reg; + + if(modbus_read_input_registers(m_device, 107, 1, ®) == -1) + return 0; + + return (int)reg; +} + +int WallBe::getChargingCurrent() +{ + uint16_t reg; + + if(modbus_read_input_registers(m_device, 300, 1, ®) == -1) + return 0; + + return (int)reg; +} + +bool WallBe::getChargingStatus() +{ + uint8_t reg; + + // Read if the charging is enabled + if(modbus_read_bits(m_device, 400, 1, ®) == -1) + return false; + + return (int)reg; +} + +int WallBe::getChargingTime() +{ + uint16_t reg[2]; + + if(modbus_read_registers(m_device, 102, 2, reg) == -1) + return 0; + + return (((uint32_t)(reg[2]<<16)|(uint32_t)(reg[3]))/60); //Converts to minutes +} + +void WallBe::setChargingCurrent(int current) +{ + if(modbus_write_register(m_device, 300, current) == -1){ //TODO + qDebug(dcWallbe) << "Could not set Current" ; + return; + } +} + +void WallBe::setChargingStatus(bool enable) +{ + if(modbus_write_bit(m_device, 400, enable) == -1){ //TODO + return; + } +} diff --git a/wallbe/wallbe.h b/wallbe/wallbe.h new file mode 100644 index 0000000..3bb8d7f --- /dev/null +++ b/wallbe/wallbe.h @@ -0,0 +1,62 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 WALLBE_H +#define WALLBE_H + +#include +#include +#include + +#include + +class WallBe : public QObject +{ + Q_OBJECT +public: + + WallBe(QHostAddress address, int port, QObject *parent = nullptr); + ~WallBe(); + bool isAvailable(); + bool connect(); + + int getEvStatus(); + int getChargingCurrent(); + bool getChargingStatus(); + int getChargingTime(); + int getErrorCode(); + + void setChargingCurrent(int current); + void setChargingStatus(bool enable); + +private: + modbus_t *m_device; + QString m_macAddress; + QString getMacAddress(); +}; + +#endif // WALLBE_H diff --git a/wallbe/wallbe.pro b/wallbe/wallbe.pro new file mode 100644 index 0000000..7af11bf --- /dev/null +++ b/wallbe/wallbe.pro @@ -0,0 +1,19 @@ +include(../plugins.pri) + +TARGET = $$qtLibraryTarget(nymea_devicepluginwallbe) + +QT += \ + network \ + serialbus \ + +SOURCES += \ + devicepluginwallbe.cpp \ + wallbe.cpp \ + host.cpp \ + discover.cpp + +HEADERS += \ + devicepluginwallbe.h \ + wallbe.h \ + host.h \ + discover.h