diff --git a/knx/devicepluginknx.cpp b/knx/devicepluginknx.cpp new file mode 100644 index 00000000..2db1954f --- /dev/null +++ b/knx/devicepluginknx.cpp @@ -0,0 +1,1115 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Simon Stürz * + * * + * This file is part of nymea. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "plugininfo.h" +#include "devicepluginknx.h" + +#include +#include +#include +#include +#include +#include +#include + +DevicePluginKnx::DevicePluginKnx() +{ + +} + +void DevicePluginKnx::init() +{ + m_discovery = new KnxServerDiscovery(this); + connect(m_discovery, &KnxServerDiscovery::discoveryFinished, this, &DevicePluginKnx::onDiscoveryFinished); + + connect(this, &DevicePluginKnx::configValueChanged, this, &DevicePluginKnx::onPluginConfigurationChanged); +} + +void DevicePluginKnx::startMonitoringAutoDevices() +{ + // Start seaching for devices which can be discovered and added automatically +} + +DeviceManager::DeviceError DevicePluginKnx::discoverDevices(const DeviceClassId &deviceClassId, const ParamList ¶ms) +{ + Q_UNUSED(params) + + if (deviceClassId == knxNetIpServerDeviceClassId) { + if (!m_discovery->startDisovery()) { + return DeviceManager::DeviceErrorDeviceInUse; + } else { + return DeviceManager::DeviceErrorAsync; + } + } + + return DeviceManager::DeviceErrorNoError; +} + +DeviceManager::DeviceSetupStatus DevicePluginKnx::setupDevice(Device *device) +{ + qCDebug(dcKnx()) << "Setup device" << device->name() << device->params(); + + if (!m_pluginTimer) { + m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(300); + connect(m_pluginTimer, &PluginTimer::timeout, this, &DevicePluginKnx::onPluginTimerTimeout); + } + + if (device->deviceClassId() == knxNetIpServerDeviceClassId) { + QHostAddress remoteAddress = QHostAddress(device->paramValue(knxNetIpServerDeviceAddressParamTypeId).toString()); + KnxTunnel *tunnel = new KnxTunnel(remoteAddress, this); + connect(tunnel, &KnxTunnel::connectedChanged, this, &DevicePluginKnx::onTunnelConnectedChanged); + connect(tunnel, &KnxTunnel::frameReceived, this, &DevicePluginKnx::onTunnelFrameReceived); + m_tunnels.insert(tunnel, device); + } + + if (device->deviceClassId() == knxShutterDeviceClassId) { + QHostAddress tunnelAddress = QHostAddress(device->paramValue(knxShutterDeviceAddressParamTypeId).toString()); + KnxTunnel *tunnel = nullptr; + foreach (KnxTunnel *knxTunnel, m_tunnels.keys()) { + if (knxTunnel->remoteAddress() == tunnelAddress) { + tunnel = knxTunnel; + } + } + + if (!tunnel) { + qCWarning(dcKnx()) << "Could not find tunnel for address" << tunnelAddress.toString(); + return DeviceManager::DeviceSetupStatusFailure; + } + + device->setParentId(m_tunnels.value(tunnel)->id()); + } + + if (device->deviceClassId() == knxLightDeviceClassId) { + QHostAddress tunnelAddress = QHostAddress(device->paramValue(knxLightDeviceAddressParamTypeId).toString()); + KnxTunnel *tunnel = nullptr; + foreach (KnxTunnel *knxTunnel, m_tunnels.keys()) { + if (knxTunnel->remoteAddress() == tunnelAddress) { + tunnel = knxTunnel; + } + } + + if (!tunnel) { + qCWarning(dcKnx()) << "Could not find tunnel for address" << tunnelAddress.toString(); + return DeviceManager::DeviceSetupStatusFailure; + } + + device->setParentId(m_tunnels.value(tunnel)->id()); + } + + if (device->deviceClassId() == knxDimmableLightDeviceClassId) { + QHostAddress tunnelAddress = QHostAddress(device->paramValue(knxDimmableLightDeviceAddressParamTypeId).toString()); + KnxTunnel *tunnel = nullptr; + foreach (KnxTunnel *knxTunnel, m_tunnels.keys()) { + if (knxTunnel->remoteAddress() == tunnelAddress) { + tunnel = knxTunnel; + } + } + + if (!tunnel) { + qCWarning(dcKnx()) << "Could not find tunnel for address" << tunnelAddress.toString(); + return DeviceManager::DeviceSetupStatusFailure; + } + + device->setParentId(m_tunnels.value(tunnel)->id()); + } + + return DeviceManager::DeviceSetupStatusSuccess; +} + +void DevicePluginKnx::postSetupDevice(Device *device) +{ + qCDebug(dcKnx()) << "Post setup device" << device->name() << device->params(); + if (device->deviceClassId() == knxNetIpServerDeviceClassId) { + KnxTunnel *tunnel = m_tunnels.key(device); + tunnel->connectTunnel(); + if (configValue(knxPluginGenericDevicesEnabledParamTypeId).toBool()) { + createGenericDevices(device); + } else { + destroyGenericDevices(device); + } + } + + if (device->deviceClassId() == knxGenericSwitchDeviceClassId) { + KnxTunnel *tunnel = getTunnelForDevice(device); + if (!tunnel) return; + device->setStateValue(knxGenericSwitchConnectedStateTypeId, tunnel->connected()); + + // Read initial state + if (tunnel->connected()) { + QKnxAddress knxAddress = QKnxAddress(QKnxAddress::Type::Group, device->paramValue(knxGenericSwitchDeviceKnxAddressParamTypeId).toString()); + tunnel->readKnxGroupValue(knxAddress); + } + } + + if (device->deviceClassId() == knxGenericUpDownDeviceClassId) { + KnxTunnel *tunnel = getTunnelForDevice(device); + if (tunnel) device->setStateValue(knxGenericUpDownConnectedStateTypeId, tunnel->connected()); + } + + if (device->deviceClassId() == knxGenericScalingDeviceClassId) { + KnxTunnel *tunnel = getTunnelForDevice(device); + if (tunnel) device->setStateValue(knxGenericScalingConnectedStateTypeId, tunnel->connected()); + } + + if (device->deviceClassId() == knxGenericTemperatureSensorDeviceClassId) { + KnxTunnel *tunnel = getTunnelForDevice(device); + if (!tunnel) return; + device->setStateValue(knxGenericTemperatureSensorConnectedStateTypeId, tunnel->connected()); + + // Read initial state + if (tunnel->connected()) { + QKnxAddress knxAddress = QKnxAddress(QKnxAddress::Type::Group, device->paramValue(knxGenericTemperatureSensorDeviceKnxAddressParamTypeId).toString()); + tunnel->readKnxGroupValue(knxAddress); + } + } + + if (device->deviceClassId() == knxGenericLightSensorDeviceClassId) { + KnxTunnel *tunnel = getTunnelForDevice(device); + if (!tunnel) return; + device->setStateValue(knxGenericLightSensorConnectedStateTypeId, tunnel->connected()); + + // Read initial state + if (tunnel->connected()) { + QKnxAddress knxAddress = QKnxAddress(QKnxAddress::Type::Group, device->paramValue(knxGenericLightSensorDeviceKnxAddressParamTypeId).toString()); + tunnel->readKnxGroupValue(knxAddress); + } + } + + if (device->deviceClassId() == knxGenericWindSpeedSensorDeviceClassId) { + KnxTunnel *tunnel = getTunnelForDevice(device); + if (!tunnel) return; + device->setStateValue(knxGenericWindSpeedSensorConnectedStateTypeId, tunnel->connected()); + + // Read initial state + if (tunnel->connected()) { + QKnxAddress knxAddress = QKnxAddress(QKnxAddress::Type::Group, device->paramValue(knxGenericWindSpeedSensorDeviceKnxAddressParamTypeId).toString()); + tunnel->readKnxGroupValue(knxAddress); + } + } + + + if (device->deviceClassId() == knxShutterDeviceClassId) { + QHostAddress tunnelAddress = QHostAddress(device->paramValue(knxShutterDeviceAddressParamTypeId).toString()); + KnxTunnel *tunnel = nullptr; + foreach (KnxTunnel *knxTunnel, m_tunnels.keys()) { + if (knxTunnel->remoteAddress() == tunnelAddress) { + tunnel = knxTunnel; + } + } + + if (!tunnel) return; + device->setStateValue(knxShutterConnectedStateTypeId, tunnel->connected()); + } + + if (device->deviceClassId() == knxLightDeviceClassId) { + QHostAddress tunnelAddress = QHostAddress(device->paramValue(knxLightDeviceAddressParamTypeId).toString()); + KnxTunnel *tunnel = nullptr; + foreach (KnxTunnel *knxTunnel, m_tunnels.keys()) { + if (knxTunnel->remoteAddress() == tunnelAddress) { + tunnel = knxTunnel; + } + } + + if (!tunnel) return; + device->setStateValue(knxLightConnectedStateTypeId, tunnel->connected()); + + // Read initial state + if (tunnel->connected()) { + QKnxAddress knxAddress = QKnxAddress(QKnxAddress::Type::Group, device->paramValue(knxLightDeviceKnxAddressParamTypeId).toString()); + tunnel->readKnxGroupValue(knxAddress); + } + } + + if (device->deviceClassId() == knxDimmableLightDeviceClassId) { + QHostAddress tunnelAddress = QHostAddress(device->paramValue(knxDimmableLightDeviceAddressParamTypeId).toString()); + KnxTunnel *tunnel = nullptr; + foreach (KnxTunnel *knxTunnel, m_tunnels.keys()) { + if (knxTunnel->remoteAddress() == tunnelAddress) { + tunnel = knxTunnel; + } + } + + if (tunnel) device->setStateValue(knxDimmableLightConnectedStateTypeId, tunnel->connected()); + + // Read initial state + if (tunnel->connected()) { + QKnxAddress knxAddress = QKnxAddress(QKnxAddress::Type::Group, device->paramValue(knxDimmableLightDeviceKnxSwitchAddressParamTypeId).toString()); + tunnel->readKnxGroupValue(knxAddress); + // TODO: read brightness + } + } +} + +void DevicePluginKnx::deviceRemoved(Device *device) +{ + qCDebug(dcKnx()) << "Remove device" << device->name() << device->params(); + if (device->deviceClassId() == knxNetIpServerDeviceClassId) { + KnxTunnel *tunnel = m_tunnels.key(device); + m_tunnels.remove(tunnel); + tunnel->disconnectTunnel(); + tunnel->deleteLater(); + } + + if (myDevices().isEmpty() && m_pluginTimer) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer); + m_pluginTimer = nullptr; + } +} + +DeviceManager::DeviceError DevicePluginKnx::executeAction(Device *device, const Action &action) +{ + qCDebug(dcKnx()) << "Executing action for device" << device->name() << action.actionTypeId().toString() << action.params(); + + // Knx NetIp server + if (device->deviceClassId() == knxNetIpServerDeviceClassId) { + if (action.actionTypeId() == knxNetIpServerAutoCreateDevicesActionTypeId) { + autoCreateKnownDevices(device); + return DeviceManager::DeviceErrorNoError; + } + } + + // Generic switch + if (device->deviceClassId() == knxGenericSwitchDeviceClassId) { + KnxTunnel *tunnel = getTunnelForDevice(device); + if (!tunnel) { + qCWarning(dcKnx()) << "Could not find tunnel for this device"; + return DeviceManager::DeviceErrorHardwareNotAvailable; + } + + if (!tunnel->connected()) { + qCWarning(dcKnx()) << "The corresponding tunnel is not connected."; + return DeviceManager::DeviceErrorHardwareNotAvailable; + } + + QKnxAddress knxAddress = QKnxAddress(QKnxAddress::Type::Group, device->paramValue(knxGenericSwitchDeviceKnxAddressParamTypeId).toString()); + + if (action.actionTypeId() == knxGenericSwitchPowerActionTypeId) { + tunnel->sendKnxDpdSwitchFrame(knxAddress, action.param(knxGenericSwitchPowerActionPowerParamTypeId).value().toBool()); + } + + if (action.actionTypeId() == knxGenericSwitchReadActionTypeId) { + tunnel->readKnxGroupValue(knxAddress); + } + } + + // Generic UpDown + if (device->deviceClassId() == knxGenericUpDownDeviceClassId) { + KnxTunnel *tunnel = getTunnelForDevice(device); + if (!tunnel) { + qCWarning(dcKnx()) << "Could not find tunnel for this device"; + return DeviceManager::DeviceErrorHardwareNotAvailable; + } + + if (!tunnel->connected()) { + qCWarning(dcKnx()) << "The corresponding tunnel is not connected."; + return DeviceManager::DeviceErrorHardwareNotAvailable; + } + QKnxAddress knxAddress = QKnxAddress(QKnxAddress::Type::Group, device->paramValue(knxGenericUpDownDeviceKnxAddressParamTypeId).toString()); + + if (action.actionTypeId() == knxGenericUpDownOpenActionTypeId) { + tunnel->sendKnxDpdUpDownFrame(knxAddress, true); + } + + if (action.actionTypeId() == knxGenericUpDownCloseActionTypeId) { + QKnxAddress knxAddress = QKnxAddress(QKnxAddress::Type::Group, device->paramValue(knxGenericUpDownDeviceKnxAddressParamTypeId).toString()); + tunnel->sendKnxDpdUpDownFrame(knxAddress, false); + } + } + + // Generic Scaling + if (device->deviceClassId() == knxGenericScalingDeviceClassId) { + KnxTunnel *tunnel = getTunnelForDevice(device); + if (!tunnel) { + qCWarning(dcKnx()) << "Could not find tunnel for this device"; + return DeviceManager::DeviceErrorHardwareNotAvailable; + } + + if (!tunnel->connected()) { + qCWarning(dcKnx()) << "The corresponding tunnel is not connected."; + return DeviceManager::DeviceErrorHardwareNotAvailable; + } + + QKnxAddress knxAddress = QKnxAddress(QKnxAddress::Type::Group, device->paramValue(knxGenericScalingDeviceKnxAddressParamTypeId).toString()); + int scaling = action.param(knxGenericScalingScaleActionScaleParamTypeId).value().toInt(); + tunnel->sendKnxDpdScalingFrame(knxAddress, scaling); + } + + // Generic Temperature + if (device->deviceClassId() == knxGenericTemperatureSensorDeviceClassId) { + KnxTunnel *tunnel = getTunnelForDevice(device); + if (!tunnel) { + qCWarning(dcKnx()) << "Could not find tunnel for this device"; + return DeviceManager::DeviceErrorHardwareNotAvailable; + } + + if (!tunnel->connected()) { + qCWarning(dcKnx()) << "The corresponding tunnel is not connected."; + return DeviceManager::DeviceErrorHardwareNotAvailable; + } + + QKnxAddress knxAddress = QKnxAddress(QKnxAddress::Type::Group, device->paramValue(knxGenericTemperatureSensorDeviceKnxAddressParamTypeId).toString()); + + if (action.actionTypeId() == knxGenericTemperatureSensorReadActionTypeId) { + qCDebug(dcKnx()) << "Send temperature read request" << knxAddress.toString(); + tunnel->readKnxGroupValue(knxAddress); + } + } + + // Generic Light sensor + if (device->deviceClassId() == knxGenericLightSensorDeviceClassId) { + KnxTunnel *tunnel = getTunnelForDevice(device); + if (!tunnel) { + qCWarning(dcKnx()) << "Could not find tunnel for this device"; + return DeviceManager::DeviceErrorHardwareNotAvailable; + } + + if (!tunnel->connected()) { + qCWarning(dcKnx()) << "The corresponding tunnel is not connected."; + return DeviceManager::DeviceErrorHardwareNotAvailable; + } + + QKnxAddress knxAddress = QKnxAddress(QKnxAddress::Type::Group, device->paramValue(knxGenericLightSensorDeviceKnxAddressParamTypeId).toString()); + + if (action.actionTypeId() == knxGenericLightSensorReadActionTypeId) { + qCDebug(dcKnx()) << "Send temperature read request" << knxAddress.toString(); + tunnel->readKnxGroupValue(knxAddress); + } + } + + // Generic wind speed sensor + if (device->deviceClassId() == knxGenericWindSpeedSensorDeviceClassId) { + KnxTunnel *tunnel = getTunnelForDevice(device); + if (!tunnel) { + qCWarning(dcKnx()) << "Could not find tunnel for this device"; + return DeviceManager::DeviceErrorHardwareNotAvailable; + } + + if (!tunnel->connected()) { + qCWarning(dcKnx()) << "The corresponding tunnel is not connected."; + return DeviceManager::DeviceErrorHardwareNotAvailable; + } + + QKnxAddress knxAddress = QKnxAddress(QKnxAddress::Type::Group, device->paramValue(knxGenericWindSpeedSensorDeviceKnxAddressParamTypeId).toString()); + + if (action.actionTypeId() == knxGenericWindSpeedSensorReadActionTypeId) { + qCDebug(dcKnx()) << "Send wind speed read request" << knxAddress.toString(); + tunnel->readKnxGroupValue(knxAddress); + } + } + + // Shutter + if (device->deviceClassId() == knxShutterDeviceClassId) { + KnxTunnel *tunnel = getTunnelForDevice(device); + if (!tunnel) { + qCWarning(dcKnx()) << "Could not find tunnel for this device"; + return DeviceManager::DeviceErrorHardwareNotAvailable; + } + + if (!tunnel->connected()) { + qCWarning(dcKnx()) << "The corresponding tunnel is not connected."; + return DeviceManager::DeviceErrorHardwareNotAvailable; + } + + QKnxAddress knxAddressStep = QKnxAddress(QKnxAddress::Type::Group, device->paramValue(knxShutterDeviceKnxAddressStepParamTypeId).toString()); + QKnxAddress knxAddressUpDown = QKnxAddress(QKnxAddress::Type::Group, device->paramValue(knxShutterDeviceKnxAddressUpDownParamTypeId).toString()); + + if (action.actionTypeId() == knxShutterOpenActionTypeId) { + // Note: first send step, then delayed the up/down command + tunnel->sendKnxDpdStepFrame(knxAddressStep, false); + tunnel->sendKnxDpdUpDownFrame(knxAddressUpDown, true); + return DeviceManager::DeviceErrorNoError; + } + + if (action.actionTypeId() == knxShutterCloseActionTypeId) { + // Note: first send step, then delayed the up/down command + tunnel->sendKnxDpdStepFrame(knxAddressStep, true); + tunnel->sendKnxDpdUpDownFrame(knxAddressUpDown, false); + return DeviceManager::DeviceErrorNoError; + } + + if (action.actionTypeId() == knxShutterStopActionTypeId) { + tunnel->sendKnxDpdStepFrame(knxAddressStep, true); + return DeviceManager::DeviceErrorNoError; + } + } + + // Light + if (device->deviceClassId() == knxLightDeviceClassId) { + KnxTunnel *tunnel = getTunnelForDevice(device); + if (!tunnel) { + qCWarning(dcKnx()) << "Could not find tunnel for this device"; + return DeviceManager::DeviceErrorHardwareNotAvailable; + } + + if (!tunnel->connected()) { + qCWarning(dcKnx()) << "The corresponding tunnel is not connected."; + return DeviceManager::DeviceErrorHardwareNotAvailable; + } + + QKnxAddress knxAddress = QKnxAddress(QKnxAddress::Type::Group, device->paramValue(knxLightDeviceKnxAddressParamTypeId).toString()); + if (action.actionTypeId() == knxLightPowerActionTypeId) { + tunnel->sendKnxDpdSwitchFrame(knxAddress, action.param(knxLightPowerActionPowerParamTypeId).value().toBool()); + } + + if (action.actionTypeId() == knxLightReadActionTypeId) { + tunnel->readKnxGroupValue(knxAddress); + } + } + + // Dimmable Light + if (device->deviceClassId() == knxDimmableLightDeviceClassId) { + KnxTunnel *tunnel = getTunnelForDevice(device); + if (!tunnel) { + qCWarning(dcKnx()) << "Could not find tunnel for this device"; + return DeviceManager::DeviceErrorHardwareNotAvailable; + } + + if (!tunnel->connected()) { + qCWarning(dcKnx()) << "The corresponding tunnel is not connected."; + return DeviceManager::DeviceErrorHardwareNotAvailable; + } + + QKnxAddress knxSwitchAddress = QKnxAddress(QKnxAddress::Type::Group, device->paramValue(knxDimmableLightDeviceKnxSwitchAddressParamTypeId).toString()); + QKnxAddress knxScalingAddress = QKnxAddress(QKnxAddress::Type::Group, device->paramValue(knxDimmableLightDeviceKnxScalingAddressParamTypeId).toString()); + + if (action.actionTypeId() == knxDimmableLightPowerActionTypeId) { + tunnel->sendKnxDpdSwitchFrame(knxSwitchAddress, action.param(knxDimmableLightPowerActionPowerParamTypeId).value().toBool()); + } + + if (action.actionTypeId() == knxDimmableLightBrightnessActionTypeId) { + int percentage = action.param(knxDimmableLightBrightnessActionBrightnessParamTypeId).value().toInt(); + int scaled = qRound(percentage * 255.0 / 100.0); + qCDebug(dcKnx()) << "Percentage" << percentage << "-->" << scaled; + device->setStateValue(knxDimmableLightBrightnessStateTypeId, percentage); + tunnel->sendKnxDpdScalingFrame(knxScalingAddress, percentage); + } + + if (action.actionTypeId() == knxDimmableLightReadActionTypeId) { + tunnel->readKnxGroupValue(knxSwitchAddress); + tunnel->readKnxGroupValue(knxScalingAddress); + } + } + + return DeviceManager::DeviceErrorNoError; +} + +KnxTunnel *DevicePluginKnx::getTunnelForDevice(Device *device) +{ + Device *parentDevice = nullptr; + foreach (Device *d, myDevices()) { + if (d->id() == device->parentId()) { + parentDevice = d; + } + } + + if (!parentDevice) { + qCWarning(dcKnx()) << "Could not find parent device for" << device->name() << device->id().toString(); + return nullptr; + } + + return m_tunnels.key(parentDevice); +} + +void DevicePluginKnx::autoCreateKnownDevices(Device *parentDevice) +{ + // Load project file and compair name/DataPoint values + if (parentDevice->deviceClassId() != knxNetIpServerDeviceClassId) { + qCWarning(dcKnx()) << "Cannot create generic devices for" << parentDevice->name() << ". This is not a NetIpServer device"; + return; + } + + // Load project file + QString projectFilePath = parentDevice->paramValue(knxNetIpServerDeviceKnxProjectFileParamTypeId).toString(); + QKnxGroupAddressInfos groupInformation(projectFilePath); + qCDebug(dcKnx()) << "Opening project file" << groupInformation.projectFile(); + if (!groupInformation.parse()) { + qCWarning(dcKnx()) << "Could not parse project file" << groupInformation.projectFile() << groupInformation.errorString(); + return; + } + + QHash> nameDatapointHash; + + qCDebug(dcKnx()) << "Found" << groupInformation.projectIds().count() << "project ids"; + foreach (const QString &projectId, groupInformation.projectIds()) { + qCDebug(dcKnx()) << "Project id" << projectId << groupInformation.projectName(projectId); + foreach (const QString &installation, groupInformation.installations(projectId)) { + QVector groupAddresses = groupInformation.addressInfos(projectId, installation); + qCDebug(dcKnx()) << "Installation contains" << groupAddresses.count() << "group addresses."; + foreach (const QKnxGroupAddressInfo &addressInfo, groupAddresses) { + if (nameDatapointHash.keys().contains(addressInfo.name())) { + QList dataPointList = nameDatapointHash.value(addressInfo.name()); + if (!dataPointList.contains(addressInfo.datapointType())) { + dataPointList.append(addressInfo.datapointType()); + nameDatapointHash[addressInfo.name()] = dataPointList; + } + } else { + nameDatapointHash.insert(addressInfo.name(), QList() << addressInfo.datapointType()); + } + } + } + } + + qCDebug(dcKnx()) << "Show name datapoint list"; + foreach (const QString &name, nameDatapointHash.keys()) { + qCDebug(dcKnx()) << "-->" << name; + QList dataPointList = nameDatapointHash.value(name); + + foreach (const QKnxDatapointType::Type &type, dataPointList) { + qCDebug(dcKnx()) << " -" << type; + } + + // TODO: find known data point combinations + + } +} + +void DevicePluginKnx::createGenericDevices(Device *parentDevice) +{ + // Make sure the feature is enabled + if (!configValue(knxPluginGenericDevicesEnabledParamTypeId).toBool()) { + qCDebug(dcKnx()) << "Do not scan the project file for autocreation of devices"; + return; + } + + if (parentDevice->deviceClassId() != knxNetIpServerDeviceClassId) { + qCWarning(dcKnx()) << "Cannot create generic devices for" << parentDevice->name() << ". This is not a NetIpServer device"; + return; + } + + // Load project file + QString projectFilePath = parentDevice->paramValue(knxNetIpServerDeviceKnxProjectFileParamTypeId).toString(); + QKnxGroupAddressInfos groupInformation(projectFilePath); + qCDebug(dcKnx()) << "Opening project file" << groupInformation.projectFile(); + if (!groupInformation.parse()) { + qCWarning(dcKnx()) << "Could not parse project file" << groupInformation.projectFile() << groupInformation.errorString(); + return; + } + + // Create descriptor lists for generic devices + QList knxGenericSwitchDeviceDescriptors; + QList knxGenericUpDownDeviceDescriptors; + QList knxGenericScalingDeviceDescriptors; + QList knxGenericTemperatureSensorDeviceDescriptors; + QList knxGenericLightSensorDeviceDescriptors; + QList knxGenericWindSpeedSensorDeviceDescriptors; + + qCDebug(dcKnx()) << "Found" << groupInformation.projectIds().count() << "project ids"; + foreach (const QString &projectId, groupInformation.projectIds()) { + qCDebug(dcKnx()) << "Project id" << projectId << groupInformation.projectName(projectId); + foreach (const QString &installation, groupInformation.installations(projectId)) { + QVector groupAddresses = groupInformation.addressInfos(projectId, installation); + qCDebug(dcKnx()) << "Installation contains" << groupAddresses.count() << "group addresses."; + + foreach (const QKnxGroupAddressInfo &addressInfo, groupAddresses) { + qCDebug(dcKnx()) << "-" << addressInfo.name(); + qCDebug(dcKnx()) << " Description:" << addressInfo.description(); + qCDebug(dcKnx()) << " Address:" << addressInfo.address().toString(); + qCDebug(dcKnx()) << " DataPoint Type:" << addressInfo.datapointType(); + switch (addressInfo.datapointType()) { + case QKnxDatapointType::Type::DptSwitch: { + // Check if we already added a switch device + ParamList params; + params.append(Param(knxGenericSwitchDeviceKnxNameParamTypeId, addressInfo.name())); + params.append(Param(knxGenericSwitchDeviceKnxAddressParamTypeId, addressInfo.address().toString())); + Device *device = findDeviceByParams(params); + + // If there is already a device with this params, continue + if (device) continue; + + qCDebug(dcKnx()) << "Found new switch" << params; + + DeviceDescriptor descriptor(knxGenericSwitchDeviceClassId, addressInfo.name() + " (" + tr("Generic Switch") + ")", addressInfo.address().toString(), parentDevice->id()); + descriptor.setParams(params); + knxGenericSwitchDeviceDescriptors.append(descriptor); + break; + } + case QKnxDatapointType::Type::DptUpDown: { + // Check if we already added a shutter device + ParamList params; + params.append(Param(knxGenericUpDownDeviceKnxNameParamTypeId, addressInfo.name())); + params.append(Param(knxGenericUpDownDeviceKnxAddressParamTypeId, addressInfo.address().toString())); + Device *device = findDeviceByParams(params); + + // If there is already a device with this params, continue + if (device) continue; + + qCDebug(dcKnx()) << "Found new shutter" << params; + + DeviceDescriptor descriptor(knxGenericUpDownDeviceClassId, addressInfo.name() + " (" + tr("Generic UpDown") + ")", addressInfo.address().toString(), parentDevice->id()); + descriptor.setParams(params); + knxGenericUpDownDeviceDescriptors.append(descriptor); + break; + } + case QKnxDatapointType::Type::DptScaling: { + // Check if we already added a dimmer device + ParamList params; + params.append(Param(knxGenericScalingDeviceKnxNameParamTypeId, addressInfo.name())); + params.append(Param(knxGenericScalingDeviceKnxAddressParamTypeId, addressInfo.address().toString())); + Device *device = findDeviceByParams(params); + + // If there is already a device with this params, continue + if (device) continue; + + qCDebug(dcKnx()) << "Found new dimmer" << params; + DeviceDescriptor descriptor(knxGenericScalingDeviceClassId, addressInfo.name() + " (" + tr("Generic Scaling") + ")", addressInfo.address().toString(), parentDevice->id()); + descriptor.setParams(params); + knxGenericScalingDeviceDescriptors.append(descriptor); + break; + } + case QKnxDatapointType::Type::DptTemperatureCelsius: { + // Check if we already added a generic temperature sensor device + ParamList params; + params.append(Param(knxGenericTemperatureSensorDeviceKnxNameParamTypeId, addressInfo.name())); + params.append(Param(knxGenericTemperatureSensorDeviceKnxAddressParamTypeId, addressInfo.address().toString())); + Device *device = findDeviceByParams(params); + + // If there is already a device with this params, continue + if (device) continue; + + qCDebug(dcKnx()) << "Found new temperature sensor" << params; + DeviceDescriptor descriptor(knxGenericTemperatureSensorDeviceClassId, addressInfo.name() + " (" + tr("Generic temperature sensor") + ")", addressInfo.address().toString(), parentDevice->id()); + descriptor.setParams(params); + knxGenericTemperatureSensorDeviceDescriptors.append(descriptor); + break; + } + case QKnxDatapointType::Type::DptValueLux: { + // Check if we already added a generic light sensor device + ParamList params; + params.append(Param(knxGenericLightSensorDeviceKnxNameParamTypeId, addressInfo.name())); + params.append(Param(knxGenericLightSensorDeviceKnxAddressParamTypeId, addressInfo.address().toString())); + Device *device = findDeviceByParams(params); + + // If there is already a device with this params, continue + if (device) continue; + + qCDebug(dcKnx()) << "Found new light sensor" << params; + DeviceDescriptor descriptor(knxGenericLightSensorDeviceClassId, addressInfo.name() + " (" + tr("Generic light sensor") + ")", addressInfo.address().toString(), parentDevice->id()); + descriptor.setParams(params); + knxGenericLightSensorDeviceDescriptors.append(descriptor); + break; + } + case QKnxDatapointType::Type::DptWindSpeed: { + // Check if we already added a generic wind speed sensor device + ParamList params; + params.append(Param(knxGenericWindSpeedSensorDeviceKnxNameParamTypeId, addressInfo.name())); + params.append(Param(knxGenericWindSpeedSensorDeviceKnxAddressParamTypeId, addressInfo.address().toString())); + Device *device = findDeviceByParams(params); + + // If there is already a device with this params, continue + if (device) continue; + + qCDebug(dcKnx()) << "Found new wind speed sensor" << params; + DeviceDescriptor descriptor(knxGenericWindSpeedSensorDeviceClassId, addressInfo.name() + " (" + tr("Generic wind speed sensor") + ")", addressInfo.address().toString(), parentDevice->id()); + descriptor.setParams(params); + knxGenericWindSpeedSensorDeviceDescriptors.append(descriptor); + break; + } + default: + qCWarning(dcKnx()) << "Unkandled DataPoint Type" << addressInfo.datapointType(); + break; + } + } + } + } + + if (!knxGenericSwitchDeviceDescriptors.isEmpty()) { + qCDebug(dcKnx()) << "--> Found" << knxGenericSwitchDeviceDescriptors.count() << "new KNX switch devices"; + emit autoDevicesAppeared(knxGenericSwitchDeviceClassId, knxGenericSwitchDeviceDescriptors); + } + + if (!knxGenericUpDownDeviceDescriptors.isEmpty()) { + qCDebug(dcKnx()) << "--> Found" << knxGenericUpDownDeviceDescriptors.count() << "new KNX shutter devices"; + emit autoDevicesAppeared(knxGenericUpDownDeviceClassId, knxGenericUpDownDeviceDescriptors); + } + + if (!knxGenericScalingDeviceDescriptors.isEmpty()) { + qCDebug(dcKnx()) << "--> Found" << knxGenericScalingDeviceDescriptors.count() << "new KNX dimmer devices"; + emit autoDevicesAppeared(knxGenericScalingDeviceClassId, knxGenericScalingDeviceDescriptors); + } + + if (!knxGenericTemperatureSensorDeviceDescriptors.isEmpty()) { + qCDebug(dcKnx()) << "--> Found" << knxGenericTemperatureSensorDeviceDescriptors.count() << "new KNX temperature sensor devices"; + emit autoDevicesAppeared(knxGenericTemperatureSensorDeviceClassId, knxGenericTemperatureSensorDeviceDescriptors); + } + + if (!knxGenericLightSensorDeviceDescriptors.isEmpty()) { + qCDebug(dcKnx()) << "--> Found" << knxGenericLightSensorDeviceDescriptors.count() << "new KNX light sensor devices"; + emit autoDevicesAppeared(knxGenericLightSensorDeviceClassId, knxGenericLightSensorDeviceDescriptors); + } + + if (!knxGenericWindSpeedSensorDeviceDescriptors.isEmpty()) { + qCDebug(dcKnx()) << "--> Found" << knxGenericWindSpeedSensorDeviceDescriptors.count() << "new KNX wind speed sensor devices"; + emit autoDevicesAppeared(knxGenericWindSpeedSensorDeviceClassId, knxGenericWindSpeedSensorDeviceDescriptors); + } +} + +void DevicePluginKnx::destroyGenericDevices(Device *parentDevice) +{ + foreach (Device *d, myDevices()) { + if (d->parentId() == parentDevice->id()) { + if (d->deviceClassId() == knxGenericSwitchDeviceClassId + || d->deviceClassId() == knxGenericUpDownDeviceClassId + || d->deviceClassId() == knxGenericScalingDeviceClassId) { + qCDebug(dcKnx()) << "--> Destroy generic knx device" << d->name() << d->id(); + emit autoDeviceDisappeared(d->id()); + } + } + } +} + +void DevicePluginKnx::onPluginTimerTimeout() +{ + qCDebug(dcKnx()) << "Refresh sensor data from KNX devices"; + foreach (Device *device, myDevices()) { + + // Refresh temperature sensor data + if (device->deviceClassId() == knxGenericTemperatureSensorDeviceClassId) { + KnxTunnel *tunnel = getTunnelForDevice(device); + if (!tunnel) { + qCWarning(dcKnx()) << "Could not find tunnel for this device"; + return; + } + + if (tunnel->connected()) { + QKnxAddress knxAddress = QKnxAddress(QKnxAddress::Type::Group, device->paramValue(knxGenericTemperatureSensorDeviceKnxAddressParamTypeId).toString()); + tunnel->readKnxGroupValue(knxAddress); + } + } + + // Refresh light sensor data + if (device->deviceClassId() == knxGenericLightSensorDeviceClassId) { + KnxTunnel *tunnel = getTunnelForDevice(device); + if (!tunnel) { + qCWarning(dcKnx()) << "Could not find tunnel for this device"; + return; + } + + if (tunnel->connected()) { + QKnxAddress knxAddress = QKnxAddress(QKnxAddress::Type::Group, device->paramValue(knxGenericLightSensorDeviceKnxAddressParamTypeId).toString()); + tunnel->readKnxGroupValue(knxAddress); + } + } + } +} + +void DevicePluginKnx::onPluginConfigurationChanged(const ParamTypeId ¶mTypeId, const QVariant &value) +{ + if (paramTypeId == knxPluginGenericDevicesEnabledParamTypeId) { + if (value.toBool()) { + qCDebug(dcKnx()) << "Generic Knx devices enabled."; + // Add all generic devices + foreach (Device *knxNetIpServer, m_tunnels.values()) { + qCDebug(dcKnx()) << "Start adding generic knx devices from project file of" << knxNetIpServer->name() << knxNetIpServer->id().toString(); + createGenericDevices(knxNetIpServer); + } + + } else { + qCDebug(dcKnx()) << "Generic Knx devices disabled"; + // Remove all generic devices + foreach (Device *knxNetIpServer, m_tunnels.values()) { + qCDebug(dcKnx()) << "Start removing generic knx devices from project file of" << knxNetIpServer->name() << knxNetIpServer->id().toString(); + destroyGenericDevices(knxNetIpServer); + } + } + } +} + +void DevicePluginKnx::onDiscoveryFinished() +{ + qCDebug(dcKnx()) << "Discovery finished."; + QList deviceDescriptors; + foreach (const QKnxNetIpServerInfo &serverInfo, m_discovery->discoveredServers()) { + qCDebug(dcKnx()) << "Found server:" << QString("%1:%2").arg(serverInfo.controlEndpointAddress().toString()).arg(serverInfo.controlEndpointPort()); + KnxServerDiscovery::printServerInfo(serverInfo); + DeviceDescriptor descriptor(knxNetIpServerDeviceClassId, "KNX NetIp Server", QString("%1:%2").arg(serverInfo.controlEndpointAddress().toString()).arg(serverInfo.controlEndpointPort())); + ParamList params; + params.append(Param(knxNetIpServerDeviceAddressParamTypeId, serverInfo.controlEndpointAddress().toString())); + params.append(Param(knxNetIpServerDevicePortParamTypeId, serverInfo.controlEndpointPort())); + descriptor.setParams(params); + foreach (Device *existingDevice, myDevices()) { + if (existingDevice->paramValue(knxNetIpServerDeviceAddressParamTypeId).toString() == serverInfo.controlEndpointAddress().toString()) { + descriptor.setDeviceId(existingDevice->id()); + break; + } + } + deviceDescriptors.append(descriptor); + } + + emit devicesDiscovered(knxNetIpServerDeviceClassId, deviceDescriptors); +} + +void DevicePluginKnx::onTunnelConnectedChanged() +{ + KnxTunnel *tunnel = static_cast(sender()); + Device *device = m_tunnels.value(tunnel); + if (!device) return; + device->setStateValue(knxNetIpServerConnectedStateTypeId, tunnel->connected()); + + // Update child devices connected state + foreach (Device *d, myDevices()) { + if (d->parentId() == device->id()) { + if (d->deviceClassId() == knxGenericSwitchDeviceClassId) { + d->setStateValue(knxGenericSwitchConnectedStateTypeId, tunnel->connected()); + // Read initial state + if (tunnel->connected()) { + QKnxAddress knxAddress = QKnxAddress(QKnxAddress::Type::Group, d->paramValue(knxGenericSwitchDeviceKnxAddressParamTypeId).toString()); + tunnel->readKnxGroupValue(knxAddress); + } + } else if (d->deviceClassId() == knxGenericUpDownDeviceClassId) { + d->setStateValue(knxGenericUpDownConnectedStateTypeId, tunnel->connected()); + } else if (d->deviceClassId() == knxGenericScalingDeviceClassId) { + d->setStateValue(knxGenericScalingConnectedStateTypeId, tunnel->connected()); + } else if (d->deviceClassId() == knxGenericTemperatureSensorDeviceClassId) { + d->setStateValue(knxGenericTemperatureSensorConnectedStateTypeId, tunnel->connected()); + // Read initial state + if (tunnel->connected()) { + QKnxAddress knxAddress = QKnxAddress(QKnxAddress::Type::Group, d->paramValue(knxGenericTemperatureSensorDeviceKnxAddressParamTypeId).toString()); + tunnel->readKnxGroupValue(knxAddress); + } + } else if (d->deviceClassId() == knxGenericLightSensorDeviceClassId) { + d->setStateValue(knxGenericLightSensorConnectedStateTypeId, tunnel->connected()); + // Read initial state + if (tunnel->connected()) { + QKnxAddress knxAddress = QKnxAddress(QKnxAddress::Type::Group, d->paramValue(knxGenericLightSensorDeviceKnxAddressParamTypeId).toString()); + tunnel->readKnxGroupValue(knxAddress); + } + } else if (d->deviceClassId() == knxGenericWindSpeedSensorDeviceClassId) { + d->setStateValue(knxGenericWindSpeedSensorConnectedStateTypeId, tunnel->connected()); + // Read initial state + if (tunnel->connected()) { + QKnxAddress knxAddress = QKnxAddress(QKnxAddress::Type::Group, d->paramValue(knxGenericWindSpeedSensorDeviceKnxAddressParamTypeId).toString()); + tunnel->readKnxGroupValue(knxAddress); + } + } else if (d->deviceClassId() == knxShutterDeviceClassId) { + d->setStateValue(knxShutterConnectedStateTypeId, tunnel->connected()); + } else if (d->deviceClassId() == knxLightDeviceClassId) { + d->setStateValue(knxLightConnectedStateTypeId, tunnel->connected()); + // Read initial state + if (tunnel->connected()) { + QKnxAddress knxAddress = QKnxAddress(QKnxAddress::Type::Group, d->paramValue(knxLightDeviceKnxAddressParamTypeId).toString()); + tunnel->readKnxGroupValue(knxAddress); + } + } else if (d->deviceClassId() == knxDimmableLightDeviceClassId) { + d->setStateValue(knxDimmableLightConnectedStateTypeId, tunnel->connected()); + // Read initial state + if (tunnel->connected()) { + QKnxAddress knxAddress = QKnxAddress(QKnxAddress::Type::Group, d->paramValue(knxDimmableLightDeviceKnxSwitchAddressParamTypeId).toString()); + tunnel->readKnxGroupValue(knxAddress); + // TODO: read brightness + } + } else { + qCWarning(dcKnx()) << "Unhandled device class on tunnel connected changed" << d->deviceClassId(); + } + } + } +} + +void DevicePluginKnx::onTunnelFrameReceived(const QKnxLinkLayerFrame &frame) +{ + foreach (Device *device, myDevices()) { + + // Generic switch + if (device->deviceClassId() == knxGenericSwitchDeviceClassId) { + if (device->paramValue(knxGenericSwitchDeviceKnxAddressParamTypeId).toString() == frame.destinationAddress().toString()) { + bool power = static_cast(frame.tpdu().data().toByteArray().at(0)); + qCDebug(dcKnx()) << QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss") + << "Generic Switch notification" + << device->name() + << frame.sourceAddress().toString() << "-->" << frame.destinationAddress().toString() + << frame.tpdu().data().toHex().toByteArray() + << (!frame.tpdu().data().toByteArray().isEmpty() ? QString("%1").arg(power) : ""); + + if (!frame.tpdu().data().toByteArray().isEmpty()) { + device->setStateValue(knxGenericSwitchPowerStateTypeId, power); + } + } + } + + // Generic UpDown + if (device->deviceClassId() == knxGenericUpDownDeviceClassId) { + if (device->paramValue(knxGenericUpDownDeviceKnxAddressParamTypeId).toString() == frame.destinationAddress().toString()) { + qCDebug(dcKnx()) << QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss") + << "Generic UpDown notification" + << device->name() + << frame.sourceAddress().toString() << "-->" << frame.destinationAddress().toString() + << frame.tpdu().data().toHex().toByteArray(); + } + } + + // Generic Scaling + if (device->deviceClassId() == knxGenericScalingDeviceClassId) { + if (device->paramValue(knxGenericScalingDeviceKnxAddressParamTypeId).toString() == frame.destinationAddress().toString()) { + QKnxScaling knxScaling; + knxScaling.setBytes(frame.tpdu().data(), 0, 1); + int scaling = static_cast(knxScaling.value()); + int percentage = qRound(scaling * 100.0 / 255.0); + qCDebug(dcKnx()) << QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss") + << "Generic Scaling notification" + << device->name() + << frame.sourceAddress().toString() << "-->" << frame.destinationAddress().toString() + << frame.tpdu().data().toHex().toByteArray() + << (!frame.tpdu().data().toByteArray().isEmpty() ? QString("%1 %2 [%]").arg(scaling).arg(percentage) : ""); + + if (!frame.tpdu().data().toByteArray().isEmpty()) { + device->setStateValue(knxGenericScalingScaleStateTypeId, percentage); + } + } + } + + // Generic temperature sensor + if (device->deviceClassId() == knxGenericTemperatureSensorDeviceClassId) { + if (device->paramValue(knxGenericTemperatureSensorDeviceKnxAddressParamTypeId).toString() == frame.destinationAddress().toString()) { + + QKnxTemperatureCelsius knxTemperature; + knxTemperature.setBytes(frame.tpdu().data(), 0, 2); + double temperature = static_cast(knxTemperature.value()); + qCDebug(dcKnx()) << QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss") + << "Generic Temperature sensor notification" + << device->name() + << frame.sourceAddress().toString() << "-->" << frame.destinationAddress().toString() + << frame.tpdu().data().toHex().toByteArray() + << (!frame.tpdu().data().toByteArray().isEmpty() ? QString("%1 [°C]").arg(temperature) : ""); + + if (!frame.tpdu().data().toByteArray().isEmpty()) { + device->setStateValue(knxGenericTemperatureSensorTemperatureStateTypeId, temperature); + } + } + } + + + // Generic light sensor + if (device->deviceClassId() == knxGenericLightSensorDeviceClassId) { + if (device->paramValue(knxGenericLightSensorDeviceKnxAddressParamTypeId).toString() == frame.destinationAddress().toString()) { + + QKnxValueLux knxLightIntensity; + knxLightIntensity.setBytes(frame.tpdu().data(), 0, 2); + double lightIntensity = static_cast(knxLightIntensity.value()); + qCDebug(dcKnx()) << QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss") + << "Generic light sensor notification" + << device->name() + << frame.sourceAddress().toString() << "-->" << frame.destinationAddress().toString() + << frame.tpdu().data().toHex().toByteArray() + << (!frame.tpdu().data().toByteArray().isEmpty() ? QString("%1 [Lux]").arg(lightIntensity) : ""); + + if (!frame.tpdu().data().toByteArray().isEmpty()) { + device->setStateValue(knxGenericLightSensorLightIntensityStateTypeId, lightIntensity); + } + } + } + + // Generic wind speed sensor + if (device->deviceClassId() == knxGenericWindSpeedSensorDeviceClassId) { + if (device->paramValue(knxGenericWindSpeedSensorDeviceKnxAddressParamTypeId).toString() == frame.destinationAddress().toString()) { + + QKnxWindSpeed knxWindSpeed; + knxWindSpeed.setBytes(frame.tpdu().data(), 0, 2); + double windSpeed = static_cast(knxWindSpeed.value()); + qCDebug(dcKnx()) << QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss") + << "Generic light sensor notification" + << device->name() + << frame.sourceAddress().toString() << "-->" << frame.destinationAddress().toString() + << frame.tpdu().data().toHex().toByteArray() + << (!frame.tpdu().data().toByteArray().isEmpty() ? QString("%1 [m/s]").arg(windSpeed) : ""); + + if (!frame.tpdu().data().toByteArray().isEmpty()) { + device->setStateValue(knxGenericWindSpeedSensorWindSpeedStateTypeId, windSpeed); + } + } + } + + // Shutter + if (device->deviceClassId() == knxShutterDeviceClassId) { + if (device->paramValue(knxShutterDeviceKnxAddressStepParamTypeId).toString() == frame.destinationAddress().toString()) { + qCDebug(dcKnx()) << QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss") + << "Shutter Step notification" + << device->name() + << frame.sourceAddress().toString() << "-->" << frame.destinationAddress().toString() + << frame.tpdu().data().toHex().toByteArray(); + + + } else if (device->paramValue(knxShutterDeviceKnxAddressUpDownParamTypeId).toString() == frame.destinationAddress().toString()) { + qCDebug(dcKnx()) << QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss") + << "Shutter UpDown notification" + << device->name() + << frame.sourceAddress().toString() << "-->" << frame.destinationAddress().toString() + << frame.tpdu().data().toHex().toByteArray(); + } + } + + // Light + if (device->deviceClassId() == knxLightDeviceClassId) { + if (device->paramValue(knxLightDeviceKnxAddressParamTypeId).toString() == frame.destinationAddress().toString()) { + bool power = static_cast(frame.tpdu().data().toByteArray().at(0)); + qCDebug(dcKnx()) << QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss") + << "Light notification" + << device->name() + << frame.sourceAddress().toString() << "-->" << frame.destinationAddress().toString() + << frame.tpdu().data().toHex().toByteArray() + << (!frame.tpdu().data().toByteArray().isEmpty() ? QString("%1").arg(power) : ""); + + if (!frame.tpdu().data().toByteArray().isEmpty()) { + device->setStateValue(knxLightPowerStateTypeId, power); + } + } + } + + // Dimmable light + if (device->deviceClassId() == knxDimmableLightDeviceClassId) { + + // Switch + if (device->paramValue(knxDimmableLightDeviceKnxSwitchAddressParamTypeId).toString() == frame.destinationAddress().toString()) { + bool power = static_cast(frame.tpdu().data().toByteArray().at(0)); + qCDebug(dcKnx()) << QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss") + << "Dimmable light Switch notification" + << device->name() + << frame.sourceAddress().toString() << "-->" << frame.destinationAddress().toString() + << frame.tpdu().data().toHex().toByteArray() + << (!frame.tpdu().data().toByteArray().isEmpty() ? QString("%1").arg(power) : ""); + + if (!frame.tpdu().data().toByteArray().isEmpty()) { + device->setStateValue(knxDimmableLightPowerStateTypeId, power); + } + } + + // Scale + if (device->paramValue(knxDimmableLightDeviceKnxScalingAddressParamTypeId).toString() == frame.destinationAddress().toString()) { + + QKnxScaling knxScaling; + knxScaling.setBytes(frame.tpdu().data(), 0, 1); + double scaling = knxScaling.value(); + int percentage = qRound(scaling * 100.0 / 255.0); + qCDebug(dcKnx()) << QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss") + << "Dimmable light scaling notification" + << device->name() + << frame.sourceAddress().toString() << "-->" << frame.destinationAddress().toString() + << frame.tpdu().data().toHex().toByteArray() + << (!frame.tpdu().data().toByteArray().isEmpty() ? QString("%1 %2 [%]").arg(scaling).arg(percentage) : ""); + + // if (!frame.tpdu().data().toByteArray().isEmpty()) { + // device->setStateValue(knxDimmableLightBrightnessStateTypeId, percentage); + // } + } + + } + } +} + diff --git a/knx/devicepluginknx.h b/knx/devicepluginknx.h new file mode 100644 index 00000000..6509360f --- /dev/null +++ b/knx/devicepluginknx.h @@ -0,0 +1,78 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * Copyright (C) 2019 Simon Stürz * + * * + * This file is part of nymea. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library 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 library; If not, see * + * . * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef DEVICEPLUGINKNX_H +#define DEVICEPLUGINKNX_H + +#include "plugintimer.h" +#include "devicemanager.h" +#include "plugin/deviceplugin.h" + +#include "knxtunnel.h" +#include "knxserverdiscovery.h" + + +class DevicePluginKnx: public DevicePlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.DevicePlugin" FILE "devicepluginknx.json") + Q_INTERFACES(DevicePlugin) + +public: + explicit DevicePluginKnx(); + + void init() override; + void startMonitoringAutoDevices() override; + + DeviceManager::DeviceError discoverDevices(const DeviceClassId &deviceClassId, const ParamList ¶ms) override; + DeviceManager::DeviceSetupStatus setupDevice(Device *device) override; + + void postSetupDevice(Device *device) override; + void deviceRemoved(Device *device) override; + + DeviceManager::DeviceError executeAction(Device *device, const Action &action) override; + +private: + PluginTimer *m_pluginTimer = nullptr; + + KnxServerDiscovery *m_discovery = nullptr; + QHash m_tunnels; + + KnxTunnel *getTunnelForDevice(Device *device); + + void autoCreateKnownDevices(Device *parentDevice); + + void createGenericDevices(Device *parentDevice); + void destroyGenericDevices(Device *parentDevice); + +private slots: + void onPluginTimerTimeout(); + void onPluginConfigurationChanged(const ParamTypeId ¶mTypeId, const QVariant &value); + + void onDiscoveryFinished(); + void onTunnelConnectedChanged(); + void onTunnelFrameReceived(const QKnxLinkLayerFrame &frame); + +}; + +#endif // DEVICEPLUGINKNX_H diff --git a/knx/devicepluginknx.json b/knx/devicepluginknx.json new file mode 100644 index 00000000..0539ab85 --- /dev/null +++ b/knx/devicepluginknx.json @@ -0,0 +1,549 @@ +{ + "name": "Knx", + "displayName": "Knx", + "id": "cadef44b-223b-405f-a240-3e08fb9540ac", + "paramTypes": [ + { + "id": "42f72457-f9d1-4166-95ea-81b8dec768c6", + "name": "genericDevicesEnabled", + "displayName": "Create generic devices for known DataPoints", + "type": "bool", + "defaultValue": true + } + ], + "vendors": [ + { + "name": "Knx", + "displayName": "Knx", + "id": "5971d18f-770c-4bc2-9025-8f2c70196f31", + "deviceClasses": [ + { + "id": "1322570a-cc4d-417c-90da-f24ccf59ebdb", + "name": "knxNetIpServer", + "displayName": "KNX NetIp server", + "setupMethod": "JustAdd", + "createMethods": ["user", "discovery"], + "interfaces": [ "gateway" ], + "paramTypes": [ + { + "id": "6eec90a3-936c-4885-830e-a2500426ed64", + "name": "address", + "displayName": "Server address", + "type": "QString", + "inputType": "IPv4Address", + "defaultValue": "192.168.8.140" + }, + { + "id": "fab7c5dd-c5cf-445f-833b-cf3379672b14", + "name": "port", + "displayName": "Server port", + "type": "uint", + "defaultValue": 3671 + }, + { + "id": "a75955c9-edce-46b2-a95a-1c1ab88007f7", + "name": "knxProjectFile", + "displayName": "Knx project file path", + "type": "QString", + "defaultValue": "/etc/nymea/installation.knxproj" + } + ], + "stateTypes": [ + { + "id": "aa75257f-45cc-440a-990d-6f3a8f696883", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false + } + ], + "actionTypes": [ + { + "id": "d0845f3d-fe84-4eb8-a11f-fe48a6f7cae7", + "name": "autoCreateDevices", + "displayName": "Auto create devices from project file" + } + ] + }, + { + "id": "943e1329-cf5f-4759-94b3-141b6d3ce0ba", + "name": "knxGenericSwitch", + "displayName": "Knx generic switch", + "setupMethod": "JustAdd", + "createMethods": ["auto"], + "interfaces": [ "power", "connectable" ], + "paramTypes": [ + { + "id": "dc38bb80-3d84-4f40-8939-4fd1e8573f80", + "name": "knxName", + "displayName": "Knx name", + "type": "QString", + "defaultValue": "Internal name" + }, + { + "id": "69908c22-7fe1-4573-b3a2-32bf214d38c6", + "name": "knxAddress", + "displayName": "Knx address", + "type": "QString", + "defaultValue": "0/0/1" + } + ], + "stateTypes": [ + { + "id": "5453d80d-6a1e-4fe1-b9e0-bc9177ccef55", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "dc82f8ca-6bb8-4fc6-b46c-2d6a6e6e6d2f", + "name": "power", + "displayName": "Power", + "displayNameEvent": "Power changed", + "displayNameAction": "Set power", + "type": "bool", + "defaultValue": false, + "writable": true + } + ], + "actionTypes": [ + { + "id": "7ad6cea9-993c-4e09-aeee-348948425bc6", + "name": "read", + "displayName": "Read value" + } + ] + }, + { + "id": "7fd223fb-002f-4ab7-b77e-1073576f9241", + "name": "knxGenericUpDown", + "displayName": "Knx generic UpDown", + "setupMethod": "JustAdd", + "createMethods": ["auto"], + "interfaces": [ "simpleclosable", "connectable" ], + "paramTypes": [ + { + "id": "9c4c0a21-5a7b-4ab1-a096-6adea3d4a277", + "name": "knxName", + "displayName": "Knx name", + "type": "QString", + "defaultValue": "Internal name" + }, + { + "id": "c81816fa-0394-473c-b0ca-5e74965d1f43", + "name": "knxAddress", + "displayName": "Knx address", + "type": "QString", + "defaultValue": "0/0/1" + } + ], + "stateTypes": [ + { + "id": "f9403240-2425-49b1-bd9d-8b0f1c872d8b", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false + } + ], + "actionTypes": [ + { + "id": "5f1d6f15-6874-485e-a0ca-7bbd397ea25f", + "name": "open", + "displayName": "Up" + }, + { + "id": "f3c0f798-ccaa-4943-975d-736dc09f5850", + "name": "close", + "displayName": "Down" + } + ] + }, + { + "id": "9b24c5ad-a36f-42d0-8489-275935149894", + "name": "knxGenericScaling", + "displayName": "Knx generic scale", + "setupMethod": "JustAdd", + "createMethods": ["auto"], + "interfaces": [ "connectable" ], + "paramTypes": [ + { + "id": "2d984fc0-daa5-45d4-aee0-e46a74d5d905", + "name": "knxName", + "displayName": "Knx name", + "type": "QString", + "defaultValue": "Internal name" + }, + { + "id": "d3558e8d-3f33-4276-977e-a02d23d0d985", + "name": "knxAddress", + "displayName": "Knx address", + "type": "QString", + "defaultValue": "0/0/1" + } + ], + "stateTypes": [ + { + "id": "8b912679-d42c-48cc-834d-cdea68f44e1e", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "aa8f8366-b65a-45e3-a10c-9f31b8f6d189", + "name": "scale", + "displayName": "Scale", + "displayNameEvent": "Scale changed", + "displayNameAction": "Set scale", + "minValue": 0, + "maxValue": 100, + "defaultValue": 0, + "type": "int", + "writable": true + } + ] + }, + { + "id": "1bfa8fbd-0845-4e85-9c50-cacf22f71363", + "name": "knxGenericTemperatureSensor", + "displayName": "Knx generic temperature sensor", + "setupMethod": "JustAdd", + "createMethods": ["auto"], + "interfaces": [ "temperaturesensor", "connectable" ], + "paramTypes": [ + { + "id": "51ed9d20-b716-434f-ac00-60f10580fd9e", + "name": "knxName", + "displayName": "Knx name", + "type": "QString", + "defaultValue": "Internal name" + }, + { + "id": "fa55f9de-229c-46ce-aeff-99c3a8c5dad9", + "name": "knxAddress", + "displayName": "Knx address", + "type": "QString", + "defaultValue": "0/0/1" + } + ], + "stateTypes": [ + { + "id": "8c9c94e8-90c6-471d-8b5a-63728776223e", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "d6a04ed7-df21-4bf4-8946-76a2802d7172", + "name": "temperature", + "displayName": "Temperature", + "displayNameEvent": "Temperature changed", + "defaultValue": 0, + "unit": "DegreeCelsius", + "type": "double" + } + ], + "actionTypes": [ + { + "id": "07b829fb-31b5-4219-8f64-1a11e0176282", + "name": "read", + "displayName": "Read value" + } + ] + }, + { + "id": "3d53cf0b-75b3-44a5-b0ec-36b054d9faa9", + "name": "knxGenericLightSensor", + "displayName": "Knx generic light sensor", + "setupMethod": "JustAdd", + "createMethods": ["auto"], + "interfaces": [ "lightsensor", "connectable" ], + "paramTypes": [ + { + "id": "4f8419ee-d753-4894-b9b4-b1c3a3bc326a", + "name": "knxName", + "displayName": "Knx name", + "type": "QString", + "defaultValue": "Internal name" + }, + { + "id": "0c09a401-fb87-4855-925e-515fb84e06af", + "name": "knxAddress", + "displayName": "Knx address", + "type": "QString", + "defaultValue": "0/0/1" + } + ], + "stateTypes": [ + { + "id": "4308c130-0c6f-4b7d-84da-766a1724c2cd", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "8bfb2a30-c49f-4a0b-aae1-65b04e0f02d7", + "name": "lightIntensity", + "displayName": "Temperature", + "displayNameEvent": "Temperature changed", + "defaultValue": 0, + "unit": "Lux", + "type": "double" + } + ], + "actionTypes": [ + { + "id": "96c531b9-9bd8-4e61-8e26-019381f3063d", + "name": "read", + "displayName": "Read value" + } + ] + }, + { + "id": "c0e8778a-040a-472c-9eca-d9e21d4e10e6", + "name": "knxGenericWindSpeedSensor", + "displayName": "Knx generic wind speed sensor", + "setupMethod": "JustAdd", + "createMethods": ["auto"], + "interfaces": [ "windspeedsensor", "connectable" ], + "paramTypes": [ + { + "id": "8579633a-834f-4741-8105-bc3db55814b1", + "name": "knxName", + "displayName": "Knx name", + "type": "QString", + "defaultValue": "Internal name" + }, + { + "id": "db454198-a24d-463d-81c4-6260fe93f3bf", + "name": "knxAddress", + "displayName": "Knx address", + "type": "QString", + "defaultValue": "0/0/1" + } + ], + "stateTypes": [ + { + "id": "20cbdab2-0746-4d11-be46-fb1621dfd5ec", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "8c4ab56f-7d6f-4ed0-b602-02591cd55583", + "name": "windSpeed", + "displayName": "Wind speed", + "displayNameEvent": "Wind speed changed", + "defaultValue": 0, + "unit": "MeterPerSecond", + "type": "double" + } + ], + "actionTypes": [ + { + "id": "3d113a52-4514-41d9-9b15-53b361a36f84", + "name": "read", + "displayName": "Read value" + } + ] + }, + { + "id": "fef0f771-cd90-4f27-8538-59c4eb5acb35", + "name": "knxShutter", + "displayName": "Knx shutter", + "setupMethod": "JustAdd", + "createMethods": ["user", "auto"], + "interfaces": [ "closable", "connectable" ], + "paramTypes": [ + { + "id": "6ce97eeb-37d9-4980-963d-f3ca4084e189", + "name": "address", + "displayName": "Server address", + "type": "QString", + "inputType": "IPv4Address", + "defaultValue": "192.168.8.140" + }, + { + "id": "881461a9-e38a-4305-bad4-9770c204db54", + "name": "knxAddressUpDown", + "displayName": "Knx address UpDown", + "type": "QString", + "defaultValue": "1/0/0" + }, + { + "id": "94991675-3b8d-438e-acfa-bb8694b4ccdb", + "name": "knxAddressStep", + "displayName": "Knx address step", + "type": "QString", + "defaultValue": "1/1/0" + } + ], + "stateTypes": [ + { + "id": "0a2f783b-3f2c-45d2-8b04-7e317127c65b", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false + } + ], + "actionTypes": [ + { + "id": "f46af15e-5f9b-4d34-bacc-6e0f220caf5d", + "name": "open", + "displayName": "Open" + }, + { + "id": "178021d3-3916-4cba-812d-a010953d6311", + "name": "close", + "displayName": "Close" + }, + { + "id": "3f133a78-c75e-48a6-990e-d2ace96ffdae", + "name": "stop", + "displayName": "Stop" + } + ] + }, + { + "id": "34582d5d-8755-41f3-9eb5-43898466d86f", + "name": "knxLight", + "displayName": "Knx light", + "setupMethod": "JustAdd", + "createMethods": ["user"], + "interfaces": [ "light", "connectable" ], + "paramTypes": [ + { + "id": "c1204f72-f0a1-42cd-a579-596bae4d0de7", + "name": "address", + "displayName": "Server address", + "type": "QString", + "inputType": "IPv4Address", + "defaultValue": "192.168.8.140" + }, + { + "id": "97abac83-54ca-477f-b1c7-6f5ebaa79102", + "name": "knxAddress", + "displayName": "Knx address", + "type": "QString", + "defaultValue": "1/0/0" + } + ], + "stateTypes": [ + { + "id": "1a39109f-d1d4-45e8-832a-d94b9bc90c1b", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "b6fcc7fc-345c-40cf-88ab-698391e584d9", + "name": "power", + "displayName": "Power", + "displayNameEvent": "Power changed", + "displayNameAction": "Set power", + "type": "bool", + "defaultValue": false, + "writable": true + } + ], + "actionTypes": [ + { + "id": "7510dada-5be0-4c66-baa3-4d03cf13289e", + "name": "read", + "displayName": "Read value" + } + ] + }, + { + "id": "9debbc19-538b-4ad2-9831-8bc93649cdbd", + "name": "knxDimmableLight", + "displayName": "Knx dimmable light", + "setupMethod": "JustAdd", + "createMethods": ["user", "auto"], + "interfaces": [ "dimmablelight", "connectable" ], + "paramTypes": [ + { + "id": "c3176e2e-aeb8-40fa-a555-ae1620af7cc0", + "name": "address", + "displayName": "Server address", + "type": "QString", + "inputType": "IPv4Address", + "defaultValue": "192.168.8.140" + }, + { + "id": "bf149d09-8014-4572-8170-28b48bb67c0c", + "name": "knxSwitchAddress", + "displayName": "Knx switch address", + "type": "QString", + "defaultValue": "1/0/0" + }, + { + "id": "c8728c5b-ac6e-4545-8ed7-bdd347757b12", + "name": "knxScalingAddress", + "displayName": "Knx scaling address", + "type": "QString", + "defaultValue": "1/0/0" + } + ], + "stateTypes": [ + { + "id": "f73c112c-2a22-4d6a-8f94-13a9aa4f3abf", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "b5dbb059-f408-468c-85e7-c052622d9a59", + "name": "power", + "displayName": "Power", + "displayNameEvent": "Power changed", + "displayNameAction": "Set power", + "type": "bool", + "defaultValue": false, + "writable": true + }, + { + "id": "ce8fc00a-e8cb-4517-94a8-6e2b2ae71bf0", + "name": "brightness", + "displayName": "Brightness", + "displayNameEvent": "Brightness changed", + "displayNameAction": "Set brightness", + "minValue": 0, + "maxValue": 100, + "defaultValue": 0, + "type": "int", + "writable": true + } + ], + "actionTypes": [ + { + "id": "fa83f289-7bd2-4595-b4e4-52634f5098a1", + "name": "read", + "displayName": "Read value" + } + ] + } + ] + } + ] +} + + diff --git a/knx/docs/KNX-basic_course_full.pdf b/knx/docs/KNX-basic_course_full.pdf new file mode 100644 index 00000000..1c1d9d4a Binary files /dev/null and b/knx/docs/KNX-basic_course_full.pdf differ diff --git a/knx/knx.pro b/knx/knx.pro new file mode 100644 index 00000000..a37e8ace --- /dev/null +++ b/knx/knx.pro @@ -0,0 +1,22 @@ +# Note: make external qtknx linking +exists(/opt/qtknx/build/mkspecs/modules/qt_lib_knx.pri) { + include(/opt/qtknx/build/mkspecs/modules/qt_lib_knx.pri) +} + +include(../plugins.pri) + +# Note: requires qtknx +QT += network knx + +TARGET = $$qtLibraryTarget(nymea_devicepluginknx) + +SOURCES += \ + devicepluginknx.cpp \ + knxtunnel.cpp \ + knxserverdiscovery.cpp + +HEADERS += \ + devicepluginknx.h \ + knxtunnel.h \ + knxserverdiscovery.h + diff --git a/knx/knxserverdiscovery.cpp b/knx/knxserverdiscovery.cpp new file mode 100644 index 00000000..b0eb3151 --- /dev/null +++ b/knx/knxserverdiscovery.cpp @@ -0,0 +1,134 @@ +#include "knxserverdiscovery.h" +#include "extern-plugininfo.h" + +#include +#include + +KnxServerDiscovery::KnxServerDiscovery(QObject *parent) : + QObject(parent) +{ + +} + + +QList KnxServerDiscovery::discoveredServers() const +{ + return m_discoveredServers; +} + +QString KnxServerDiscovery::serviceFamilyToString(QKnx::NetIp::ServiceFamily id) +{ + switch (id) { + case QKnxNetIp::ServiceFamily::Core: + return "Core"; + case QKnxNetIp::ServiceFamily::DeviceManagement: + return "Device Management"; + case QKnxNetIp::ServiceFamily::IpTunneling: + return "Tunnel"; + case QKnxNetIp::ServiceFamily::IpRouting: + return "Routing"; + case QKnxNetIp::ServiceFamily::RemoteLogging: + return "Remote Logging"; + case QKnxNetIp::ServiceFamily::RemoteConfigAndDiagnosis: + return "Remote Configuration"; + case QKnxNetIp::ServiceFamily::ObjectServer: + return "Object Server"; + case QKnxNetIp::ServiceFamily::Security: + return "Security"; + default: + break; + } + return "Unknown"; +} + +void KnxServerDiscovery::printServerInfo(const QKnxNetIpServerInfo &serverInfo) +{ + qCDebug(dcKnx()) << "Server:" << serverInfo.deviceName(); + qCDebug(dcKnx()) << " Individual address:" << serverInfo.individualAddress().toString(); + qCDebug(dcKnx()) << " Server control endpoint:" << QString("%1:%2").arg(serverInfo.controlEndpointAddress().toString()).arg(serverInfo.controlEndpointPort()); + + QVector services = serverInfo.supportedServices(); + qCDebug(dcKnx()) << " Supported services:"; + foreach (const QKnxServiceInfo &service, services) + qCDebug(dcKnx()) << " KNXnet/IP" << serviceFamilyToString(service.ServiceFamily) << ", Version:" << service.ServiceFamilyVersion; + + qCDebug(dcKnx()) << " Mask version:" << serverInfo.maskVersion(); + qCDebug(dcKnx()) << " Endpoint:" << serverInfo.endpoint(); + + QKnxNetIpDib dib = serverInfo.hardware(); + QKnxNetIpExtendedDeviceDibProxy hardware(dib); + if (hardware.isValid()) { + qCDebug(dcKnx()) << " Extended hardware information:"; + qCDebug(dcKnx()).noquote() << QString::fromLatin1(" Mask version: %1").arg(hardware.deviceDescriptorType0(), 4, 16, QLatin1Char('0')); + qCDebug(dcKnx()).noquote() << QString::fromLatin1(" Max. local APDU length: %1").arg(hardware.maximumLocalApduLength()); + + auto status = serverInfo.mediumStatus(); + if (status == QKnx::MediumStatus::CommunicationPossible) + qCDebug(dcKnx()) << " Medium status: Communication possible"; + else if (status == QKnx::MediumStatus::CommunicationImpossible) + qCDebug(dcKnx()) << " Medium status: Communication impossible"; + else + qCDebug(dcKnx()) << " Medium status: Unknown"; + } +} + +void KnxServerDiscovery::onDiscoveryAgentErrorOccured(QKnxNetIpServerDiscoveryAgent::Error error) +{ + QKnxNetIpServerDiscoveryAgent *discovery = static_cast(sender()); + qCDebug(dcKnx()) << "Discovery error occured" << discovery->localAddress().toString() << error << discovery->errorString(); +} + +void KnxServerDiscovery::onDiscoveryAgentFinished() +{ + QKnxNetIpServerDiscoveryAgent *discovery = static_cast(sender()); + qCDebug(dcKnx()) << "Discovery agent for" << discovery->localAddress() << "has finished"; + qCDebug(dcKnx()) << "Found" << discovery->discoveredServers().count() << "servers"; + foreach (const QKnxNetIpServerInfo &serverInfo, discovery->discoveredServers()) { + m_discoveredServers.append(serverInfo); + } + + m_runningDiscoveryAgents.removeAll(discovery); + discovery->deleteLater(); + + if (m_runningDiscoveryAgents.isEmpty()) { + emit discoveryFinished(); + } +} + +bool KnxServerDiscovery::startDisovery() +{ + if (!m_runningDiscoveryAgents.isEmpty()) { + qCWarning(dcKnx()) << "Could not start discovery. There are still discovery agents running. (" << m_runningDiscoveryAgents.count() << "discoveries )"; + return false; + } + + qCDebug(dcKnx()) << "Start KNX server discovery on all interfaces"; + m_discoveredServers.clear(); + + foreach (const QNetworkInterface &interface, QNetworkInterface::allInterfaces()) { + //qCDebug(dcKnx()) << "Checking network interface" << interface.name() << interface.type(); + foreach (const QNetworkAddressEntry &addressEntry, interface.addressEntries()) { + // If ipv4 and not local host + if (addressEntry.ip().protocol() == QAbstractSocket::IPv4Protocol && !addressEntry.ip().isLoopback()) { + qCDebug(dcKnx()) << "Start discovery on" << interface.name() << addressEntry.ip().toString(); + + // Create discovery agent + QKnxNetIpServerDiscoveryAgent *discovery = new QKnxNetIpServerDiscoveryAgent(this); + discovery->setLocalAddress(addressEntry.ip()); + discovery->setLocalPort(0); + discovery->setSearchFrequency(60); + discovery->setResponseType(QKnxNetIpServerDiscoveryAgent::ResponseType::Unicast); + discovery->setDiscoveryMode(QKnxNetIpServerDiscoveryAgent::DiscoveryMode::CoreV1 | QKnxNetIpServerDiscoveryAgent::DiscoveryMode::CoreV2); + + connect(discovery, &QKnxNetIpServerDiscoveryAgent::finished, this, &KnxServerDiscovery::onDiscoveryAgentFinished); + connect(discovery, &QKnxNetIpServerDiscoveryAgent::errorOccurred, this, &KnxServerDiscovery::onDiscoveryAgentErrorOccured); + m_runningDiscoveryAgents.append(discovery); + + // Start the discovery + discovery->start(m_discoveryTimeout); + } + } + } + + return true; +} diff --git a/knx/knxserverdiscovery.h b/knx/knxserverdiscovery.h new file mode 100644 index 00000000..e0d27135 --- /dev/null +++ b/knx/knxserverdiscovery.h @@ -0,0 +1,38 @@ +#ifndef KNXSERVERDISCOVERY_H +#define KNXSERVERDISCOVERY_H + +#include +#include +#include +#include + +class KnxServerDiscovery : public QObject +{ + Q_OBJECT +public: + explicit KnxServerDiscovery(QObject *parent = nullptr); + + QList discoveredServers() const; + + static QString serviceFamilyToString(QKnx::NetIp::ServiceFamily id); + static void printServerInfo(const QKnxNetIpServerInfo &serverInfo); + +private: + int m_discoveryTimeout = 5000; + QList m_runningDiscoveryAgents; + QList m_discoveredServers; + +signals: + void discoveryFinished(); + +private slots: + void onDiscoveryAgentErrorOccured(QKnxNetIpServerDiscoveryAgent::Error error); + void onDiscoveryAgentFinished(); + + +public slots: + bool startDisovery(); + +}; + +#endif // KNXSERVERDISCOVERY_H diff --git a/knx/knxtunnel.cpp b/knx/knxtunnel.cpp new file mode 100644 index 00000000..925b152a --- /dev/null +++ b/knx/knxtunnel.cpp @@ -0,0 +1,330 @@ +#include "knxtunnel.h" + +#include "extern-plugininfo.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +KnxTunnel::KnxTunnel(const QHostAddress &remoteAddress, QObject *parent) : + QObject(parent), + m_remoteAddress(remoteAddress) +{ + m_timer = new QTimer(this); + m_timer->setSingleShot(false); + m_timer->setInterval(5000); + connect(m_timer, &QTimer::timeout, this, &KnxTunnel::onTimeout); + + m_queueTimer = new QTimer(this); + m_queueTimer->setSingleShot(false); + m_queueTimer->setInterval(500); + connect(m_queueTimer, &QTimer::timeout, this, &KnxTunnel::onQueueTimeout); + + + m_tunnel = new QKnxNetIpTunnel(this); + m_tunnel->setLocalPort(0); + m_tunnel->setHeartbeatTimeout(1000); + + connect(m_tunnel, &QKnxNetIpTunnel::frameReceived, this, &KnxTunnel::onTunnelFrameReceived); + connect(m_tunnel, &QKnxNetIpTunnel::connected, this, &KnxTunnel::onTunnelConnected); + connect(m_tunnel, &QKnxNetIpTunnel::disconnected, this, &KnxTunnel::onTunnelDisconnected); + connect(m_tunnel, &QKnxNetIpTunnel::stateChanged, this, &KnxTunnel::onTunnelStateChanged); +} + +QHostAddress KnxTunnel::remoteAddress() const +{ + return m_remoteAddress; +} + +void KnxTunnel::setRemoteAddress(const QHostAddress &remoteAddress) +{ + m_remoteAddress = remoteAddress; +} + +bool KnxTunnel::connected() const +{ + return m_tunnel->state() == QKnxNetIpTunnel::State::Connected; +} + +bool KnxTunnel::connectTunnel() +{ + // Get the local address for this tunnel remote address + QHostAddress localAddress = getLocalAddress(m_remoteAddress); + if (localAddress.isNull()) { + qCWarning(dcKnx()) << "Could connect to" << m_remoteAddress.toString() << ". There is no local interface for this server address. Make sure this device is connected to the correct network."; + return false; + } + + m_tunnel->setLocalAddress(localAddress); + qCDebug(dcKnx()) << "Connecting tunnel to" << m_remoteAddress.toString() << "using" << m_tunnel->localAddress(); + m_tunnel->connectToHost(m_remoteAddress, m_port); + return true; +} + +void KnxTunnel::disconnectTunnel() +{ + qCDebug(dcKnx()) << "Disonnecting tunnel from" << m_remoteAddress.toString(); + m_tunnel->disconnectFromHost(); +} + +void KnxTunnel::sendKnxDpdSwitchFrame(const QKnxAddress &knxAddress, bool power) +{ + qCDebug(dcKnx()) << "Send DpdSwitch" << knxAddress.toString() << (power ? "ON" : "OFF"); + + QKnxTpdu tpdu; + tpdu.setTransportControlField(QKnxTpdu::TransportControlField::DataGroup); + tpdu.setApplicationControlField(QKnxTpdu::ApplicationControlField::GroupValueWrite); + tpdu.setData(QKnxSwitch(power ? QKnxSwitch::State::On : QKnxSwitch::State::Off).bytes()); + + QKnxLinkLayerFrame frame = QKnxLinkLayerFrame::builder() + .setMedium(QKnx::MediumType::NetIP) + .setDestinationAddress(knxAddress) + .setTpdu(tpdu) + .createFrame(); + + requestSendFrame(frame); +} + +void KnxTunnel::sendKnxDpdUpDownFrame(const QKnxAddress &knxAddress, bool status) +{ + qCDebug(dcKnx()) << "Send DpdUpDown" << knxAddress.toString() << (status ? "UP" : "DOWN"); + + QKnxTpdu tpdu; + tpdu.setTransportControlField(QKnxTpdu::TransportControlField::DataGroup); + tpdu.setApplicationControlField(QKnxTpdu::ApplicationControlField::GroupValueWrite); + tpdu.setData(QKnxUpDown(status ? QKnxUpDown::State::Up : QKnxUpDown::State::Down).bytes()); + + QKnxLinkLayerFrame frame = QKnxLinkLayerFrame::builder() + .setMedium(QKnx::MediumType::NetIP) + .setDestinationAddress(knxAddress) + .setTpdu(tpdu) + .createFrame(); + + requestSendFrame(frame); +} + +void KnxTunnel::sendKnxDpdStepFrame(const QKnxAddress &knxAddress, bool status) +{ + qCDebug(dcKnx()) << "Send DpdStep" << knxAddress.toString() << (status ? "INCREASE" : "DECREASE"); + + QKnxTpdu tpdu; + tpdu.setTransportControlField(QKnxTpdu::TransportControlField::DataGroup); + tpdu.setApplicationControlField(QKnxTpdu::ApplicationControlField::GroupValueWrite); + tpdu.setData(QKnxStep(status ? QKnxStep::State::Increase : QKnxStep::State::Decrease).bytes()); + + QKnxLinkLayerFrame frame = QKnxLinkLayerFrame::builder() + .setMedium(QKnx::MediumType::NetIP) + .setDestinationAddress(knxAddress) + .setTpdu(tpdu) + .createFrame(); + + requestSendFrame(frame); +} + +void KnxTunnel::sendKnxDpdScalingFrame(const QKnxAddress &knxAddress, int scale) +{ + qCDebug(dcKnx()) << "Send DpdScaling" << knxAddress.toString() << scale; + + QKnxTpdu tpdu; + tpdu.setTransportControlField(QKnxTpdu::TransportControlField::DataGroup); + tpdu.setApplicationControlField(QKnxTpdu::ApplicationControlField::GroupValueWrite); + tpdu.setData(QKnxScaling(scale).bytes()); + + QKnxLinkLayerFrame frame = QKnxLinkLayerFrame::builder() + .setMedium(QKnx::MediumType::NetIP) + .setDestinationAddress(knxAddress) + .setTpdu(tpdu) + .createFrame(); + + requestSendFrame(frame); +} + +void KnxTunnel::readKnxGroupValue(const QKnxAddress &knxAddress) +{ + qCDebug(dcKnx()) << "Read knx group value from group address" << knxAddress.toString(); + + QKnxTpdu tpdu; + tpdu.setTransportControlField(QKnxTpdu::TransportControlField::DataGroup); + tpdu.setApplicationControlField(QKnxTpdu::ApplicationControlField::GroupValueRead); + + QKnxLinkLayerFrame frame = QKnxLinkLayerFrame::builder() + .setMessageCode(QKnxLinkLayerFrame::MessageCode::DataRequest) + .setMedium(QKnx::MediumType::NetIP) + .setDestinationAddress(knxAddress) + .setTpdu(tpdu) + .createFrame(); + + requestSendFrame(frame); +} + +void KnxTunnel::readKnxDpdSwitchState(const QKnxAddress &knxAddress) +{ + qCDebug(dcKnx()) << "Read switch group value" << knxAddress.toString(); + + QKnxTpdu tpdu; + tpdu.setTransportControlField(QKnxTpdu::TransportControlField::DataGroup); + tpdu.setApplicationControlField(QKnxTpdu::ApplicationControlField::GroupValueRead); + + QKnxLinkLayerFrame frame = QKnxLinkLayerFrame::builder() + .setMessageCode(QKnxLinkLayerFrame::MessageCode::DataRequest) + .setMedium(QKnx::MediumType::NetIP) + .setDestinationAddress(knxAddress) + .setTpdu(tpdu) + .createFrame(); + + requestSendFrame(frame); +} + +void KnxTunnel::readKnxDpdScalingState(const QKnxAddress &knxAddress) +{ + qCDebug(dcKnx()) << "Read scaling group value" << knxAddress.toString(); + + QKnxTpdu tpdu; + tpdu.setTransportControlField(QKnxTpdu::TransportControlField::DataGroup); + tpdu.setApplicationControlField(QKnxTpdu::ApplicationControlField::GroupValueRead); + + QKnxLinkLayerFrame frame = QKnxLinkLayerFrame::builder() + .setMessageCode(QKnxLinkLayerFrame::MessageCode::DataRequest) + .setMedium(QKnx::MediumType::NetIP) + .setDestinationAddress(knxAddress) + .setTpdu(tpdu) + .createFrame(); + + requestSendFrame(frame); +} + +void KnxTunnel::readKnxDpdTemperatureSensor(const QKnxAddress &knxAddress) +{ + qCDebug(dcKnx()) << "Read temperature sensor group value" << knxAddress.toString(); + + QKnxTpdu tpdu; + tpdu.setTransportControlField(QKnxTpdu::TransportControlField::DataGroup); + tpdu.setApplicationControlField(QKnxTpdu::ApplicationControlField::GroupValueRead); + + QKnxLinkLayerFrame frame = QKnxLinkLayerFrame::builder() + .setMessageCode(QKnxLinkLayerFrame::MessageCode::DataRequest) + .setMedium(QKnx::MediumType::NetIP) + .setDestinationAddress(knxAddress) + .setTpdu(tpdu) + .createFrame(); + + requestSendFrame(frame); +} + +void KnxTunnel::printFrame(const QKnxLinkLayerFrame &frame) +{ + qCDebug(dcKnx()) << "Frame: (" << frame.sourceAddress().toString() << "-->" << frame.destinationAddress().toString() << ")" << frame; + qCDebug(dcKnx()) << " Message code:" << frame.messageCode(); + qCDebug(dcKnx()) << " MediumType" << frame.mediumType(); + qCDebug(dcKnx()) << " Control field:" << frame.controlField(); + qCDebug(dcKnx()) << " Extended control field:" << frame.extendedControlField(); + qCDebug(dcKnx()) << " Additional infos:" << frame.additionalInfos(); + qCDebug(dcKnx()) << " Bytes:" << frame.bytes().toHex().toByteArray(); + qCDebug(dcKnx()) << " TPDU:" << frame.tpdu() << "Size:" << frame.tpdu().size(); + qCDebug(dcKnx()) << " " << frame.tpdu().transportControlField(); + qCDebug(dcKnx()) << " " << frame.tpdu().applicationControlField(); + qCDebug(dcKnx()) << " " << frame.tpdu().mediumType(); + qCDebug(dcKnx()) << " Sequence number:" << frame.tpdu().sequenceNumber(); + qCDebug(dcKnx()) << " Data:" << frame.tpdu().data().toHex().toByteArray(); +} + +void KnxTunnel::requestSendFrame(const QKnxLinkLayerFrame &frame) +{ + // If the timer is running, enqueue + if (m_queueTimer->isActive()) { + m_sendingQueue.enqueue(frame); + return; + } + + // No queue, timer not running, lets send the message directly + sendFrame(frame); + m_queueTimer->start(); +} + +void KnxTunnel::sendFrame(const QKnxLinkLayerFrame &frame) +{ + if (!connected()) { + qCWarning(dcKnx()) << "Cannot send frame on tunnel" << m_remoteAddress.toString() << " because the tunnel is not connected."; + return; + } + + qCDebug(dcKnx()) << QDateTime::currentDateTime().toString("yyyy.MM.dd hh:mm:ss") << "--> Sending frame" << frame << frame.destinationAddress().toString() << frame.tpdu().data().toHex().toByteArray(); + //printFrame(frame); + m_tunnel->sendFrame(frame); +} + +QHostAddress KnxTunnel::getLocalAddress(const QHostAddress &remoteAddress) +{ + QHostAddress localAddress; + foreach (const QNetworkInterface &interface, QNetworkInterface::allInterfaces()) { + qCDebug(dcKnx()) << "Network interface" << interface.name() << interface.type(); + foreach (const QNetworkAddressEntry &addressEntry, interface.addressEntries()) { + if (addressEntry.ip().protocol() == QAbstractSocket::IPv4Protocol) { + qCDebug(dcKnx()) << " - " << addressEntry.ip().toString() << addressEntry.netmask().toString(); + if (remoteAddress.isInSubnet(addressEntry.ip(), addressEntry.prefixLength())) { + qCDebug(dcKnx()) << "Found local interface address for" << remoteAddress.toString() << "-->" << addressEntry.ip().toString() << interface.name(); + localAddress = addressEntry.ip(); + } + } + } + } + + return localAddress; +} + +void KnxTunnel::onTimeout() +{ + qCDebug(dcKnx()) << "Tunnel reconnection timeout."; + connectTunnel(); +} + +void KnxTunnel::onQueueTimeout() +{ + // If queue is empty, we are don for now. + if (m_sendingQueue.isEmpty()) { + m_queueTimer->stop(); + return; + } + + // Send next message + QKnxLinkLayerFrame frame = m_sendingQueue.dequeue(); + sendFrame(frame); + m_queueTimer->start(); +} + +void KnxTunnel::onTunnelConnected() +{ + qCDebug(dcKnx()) << "Tunnel connected."; + emit connectedChanged(); + + // Stop the reconnect timer + m_timer->stop(); +} + +void KnxTunnel::onTunnelDisconnected() +{ + qCDebug(dcKnx()) << "Tunnel disconnected."; + emit connectedChanged(); + + // Start the reconnect timer + m_timer->start(); +} + +void KnxTunnel::onTunnelStateChanged(QKnxNetIpEndpointConnection::State state) +{ + qCDebug(dcKnx()) << "Tunnel state changed" << state; +} + +void KnxTunnel::onTunnelFrameReceived(const QKnxLinkLayerFrame &frame) +{ + qCDebug(dcKnx()) << "<-- Tunnel frame received" << frame; + //printFrame(frame); + emit frameReceived(frame); +} + diff --git a/knx/knxtunnel.h b/knx/knxtunnel.h new file mode 100644 index 00000000..63454e3c --- /dev/null +++ b/knx/knxtunnel.h @@ -0,0 +1,68 @@ +#ifndef KNXTUNNEL_H +#define KNXTUNNEL_H + +#include +#include + +#include +#include +#include + +class KnxTunnel : public QObject +{ + Q_OBJECT +public: + explicit KnxTunnel(const QHostAddress &remoteAddress, QObject *parent = nullptr); + + QHostAddress remoteAddress() const; + void setRemoteAddress(const QHostAddress &remoteAddress); + + bool connected() const; + + bool connectTunnel(); + void disconnectTunnel(); + + void sendKnxDpdSwitchFrame(const QKnxAddress &knxAddress, bool power); + void sendKnxDpdUpDownFrame(const QKnxAddress &knxAddress, bool status); + void sendKnxDpdStepFrame(const QKnxAddress &knxAddress, bool status); + void sendKnxDpdScalingFrame(const QKnxAddress &knxAddress, int scale); + + void readKnxGroupValue(const QKnxAddress &knxAddress); + + void readKnxDpdSwitchState(const QKnxAddress &knxAddress); + void readKnxDpdScalingState(const QKnxAddress &knxAddress); + void readKnxDpdTemperatureSensor(const QKnxAddress &knxAddress); + + static void printFrame(const QKnxLinkLayerFrame &frame); + +private: + QHostAddress m_localAddress; + QHostAddress m_remoteAddress; + quint16 m_port = 3671; + + QTimer *m_timer = nullptr; + QTimer *m_queueTimer = nullptr; + QKnxNetIpTunnel *m_tunnel = nullptr; + + QQueue m_sendingQueue; + + // Helper + void requestSendFrame(const QKnxLinkLayerFrame &frame); + void sendFrame(const QKnxLinkLayerFrame &frame); + QHostAddress getLocalAddress(const QHostAddress &remoteAddress); + +signals: + void connectedChanged(); + void frameReceived(const QKnxLinkLayerFrame &frame); + +private slots: + void onTimeout(); + void onQueueTimeout(); + + void onTunnelConnected(); + void onTunnelDisconnected(); + void onTunnelStateChanged(QKnxNetIpEndpointConnection::State state); + void onTunnelFrameReceived(const QKnxLinkLayerFrame &frame); +}; + +#endif // KNXTUNNEL_H diff --git a/nymea-plugins.pro b/nymea-plugins.pro index 6e31b8c3..974b6949 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -20,6 +20,7 @@ PLUGIN_DIRS = \ httpcommander \ intertechno \ keba \ + knx \ kodi \ leynew \ lgsmarttv \