added alert interface

master
Boernsman 2020-01-03 14:26:18 +01:00
parent 9e53feb985
commit 6771504edf
6 changed files with 510 additions and 175 deletions

View File

@ -1 +1,34 @@
# Nanoleaf
This Plug-In allows to control Nanoleaf Light Panels.
## Features
Controls:
* Power
* Brightness
* Color Temperature
* Color
* Set Effect
States:
* Connected
Browsing:
This plug-in implements also browsing for light effects, means if a new light effect is beeing added
nymea will find that.
## Device Setup
The Nanoleaf App is required to connect the device to the WiFi Network.
This Plug-In uses the local API of Nanoleaf devices, means
nymea must be in the same local area network.
The device will be discovered through Zeroconf, if it
can't be discovered the Network might not support Zeroconf
and the IP-Address must be entered manually.
More about Nanoleaf devices:
https://nanoleaf.me

View File

@ -1,24 +1,30 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2020 Bernhard Trinnes <bernhard.trinnes@nymea.io> *
* *
* 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 *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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
* This project may also contain libraries licensed under the open source software license GNU GPL v.3.
* Alternatively, this project may be redistributed and/or modified under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation; version 3.
* this project is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this project.
* If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under contact@nymea.io
* or see our FAQ/Licensing Information on https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "devicepluginnanoleaf.h"
#include "plugininfo.h"
@ -34,13 +40,11 @@ DevicePluginNanoleaf::DevicePluginNanoleaf()
}
void DevicePluginNanoleaf::init()
{
m_zeroconfBrowser = hardwareManager()->zeroConfController()->createServiceBrowser("_nanoleafapi._tcp");
}
void DevicePluginNanoleaf::discoverDevices(DeviceDiscoveryInfo *info)
{
QStringList serialNumbers;
@ -68,6 +72,7 @@ void DevicePluginNanoleaf::discoverDevices(DeviceDiscoveryInfo *info)
Device *existingDevice = myDevices().findByParams(ParamList() << Param(lightPanelsDeviceSerialNoParamTypeId, serialNo));
if (existingDevice) {
//For device rediscovery
descriptor.setDeviceId(existingDevice->id());
}
@ -81,47 +86,30 @@ void DevicePluginNanoleaf::discoverDevices(DeviceDiscoveryInfo *info)
descriptor.setParams(params);
info->addDeviceDescriptor(descriptor);
}
info->finish(Device::DeviceErrorNoError);
}
void DevicePluginNanoleaf::startPairing(DevicePairingInfo *info)
{
info->finish(Device::DeviceErrorNoError, tr("On the Nanoleaf controller, hold the on-off button for 5-7 seconds until the LED starts flashing."));
}
void DevicePluginNanoleaf::confirmPairing(DevicePairingInfo *info, const QString &username, const QString &secret)
{
Q_UNUSED(username)
Q_UNUSED(secret)
Nanoleaf *nanoleaf = new Nanoleaf(hardwareManager()->networkManager(), QHostAddress(info->params().paramValue(lightPanelsDeviceAddressParamTypeId).toString()), info->params().paramValue(lightPanelsDevicePortParamTypeId).toInt(), this);
connect(nanoleaf, &Nanoleaf::authTokenRecieved, this, &DevicePluginNanoleaf::onAuthTokenReceived);
connect(nanoleaf, &Nanoleaf::authenticationStatusChanged, this, &DevicePluginNanoleaf::onAuthenticationStatusChanged);
connect(nanoleaf, &Nanoleaf::requestExecuted, this, &DevicePluginNanoleaf::onRequestExecuted);
connect(nanoleaf, &Nanoleaf::connectionChanged, this, &DevicePluginNanoleaf::onConnectionChanged);
connect(nanoleaf, &Nanoleaf::brightnessReceived, this, &DevicePluginNanoleaf::onBrightnessReceived);
connect(nanoleaf, &Nanoleaf::powerReceived, this, &DevicePluginNanoleaf::onPowerReceived);
connect(nanoleaf, &Nanoleaf::colorModeReceived, this, &DevicePluginNanoleaf::onColorModeReceived);
connect(nanoleaf, &Nanoleaf::colorTemperatureReceived, this, &DevicePluginNanoleaf::onColorTemperatureReceived);
connect(nanoleaf, &Nanoleaf::saturationReceived, this, &DevicePluginNanoleaf::onSaturationReceived);
connect(nanoleaf, &Nanoleaf::hueReceived, this, &DevicePluginNanoleaf::onHueReceived);
connect(nanoleaf, &Nanoleaf::effectListReceived, this, &DevicePluginNanoleaf::onEffectListReceived);
connect(nanoleaf, &Nanoleaf::selectedEffectReceived, this, &DevicePluginNanoleaf::onSelectedEffectReceived);
Nanoleaf *nanoleaf = createNanoleafConnection(QHostAddress(info->params().paramValue(lightPanelsDeviceAddressParamTypeId).toString()), info->params().paramValue(lightPanelsDevicePortParamTypeId).toInt());
nanoleaf->addUser(); //push button pairing
m_unfinishedNanoleafConnections.insert(info->deviceId(), nanoleaf);
m_unfinishedPairing.insert(nanoleaf, info);
connect(info, &DevicePairingInfo::aborted, this, [info, this] {
Nanoleaf *nanoleaf = m_unfinishedNanoleafConnections.take(info->deviceId());
m_unfinishedPairing.remove(nanoleaf);
nanoleaf->deleteLater();
Nanoleaf *nanoleaf = m_unfinishedNanoleafConnections.take(info->deviceId());
m_unfinishedPairing.remove(nanoleaf);
nanoleaf->deleteLater();
});
}
void DevicePluginNanoleaf::setupDevice(DeviceSetupInfo *info)
{
Device *device = info->device();
@ -132,29 +120,17 @@ void DevicePluginNanoleaf::setupDevice(DeviceSetupInfo *info)
Nanoleaf *nanoleaf;
if (m_unfinishedNanoleafConnections.contains(device->id())) {
// This setupDevice is called after a discovery
nanoleaf = m_unfinishedNanoleafConnections.take(device->id());
m_nanoleafConnections.insert(device->id(), nanoleaf);
return info->finish(Device::DeviceErrorNoError);
} else {
// This setupDevice is called after a (re)start, with an already added device
QHostAddress address(device->paramValue(lightPanelsDeviceAddressParamTypeId).toString());
int port = device->paramValue(lightPanelsDevicePortParamTypeId).toInt();
nanoleaf = new Nanoleaf(hardwareManager()->networkManager(), address, port, this);
connect(nanoleaf, &Nanoleaf::authTokenRecieved, this, &DevicePluginNanoleaf::onAuthTokenReceived);
connect(nanoleaf, &Nanoleaf::authenticationStatusChanged, this, &DevicePluginNanoleaf::onAuthenticationStatusChanged);
connect(nanoleaf, &Nanoleaf::requestExecuted, this, &DevicePluginNanoleaf::onRequestExecuted);
connect(nanoleaf, &Nanoleaf::connectionChanged, this, &DevicePluginNanoleaf::onConnectionChanged);
connect(nanoleaf, &Nanoleaf::brightnessReceived, this, &DevicePluginNanoleaf::onBrightnessReceived);
connect(nanoleaf, &Nanoleaf::powerReceived, this, &DevicePluginNanoleaf::onPowerReceived);
connect(nanoleaf, &Nanoleaf::colorModeReceived, this, &DevicePluginNanoleaf::onColorModeReceived);
connect(nanoleaf, &Nanoleaf::saturationReceived, this, &DevicePluginNanoleaf::onSaturationReceived);
connect(nanoleaf, &Nanoleaf::hueReceived, this, &DevicePluginNanoleaf::onHueReceived);
connect(nanoleaf, &Nanoleaf::colorTemperatureReceived, this, &DevicePluginNanoleaf::onColorTemperatureReceived);
connect(nanoleaf, &Nanoleaf::effectListReceived, this, &DevicePluginNanoleaf::onEffectListReceived);
connect(nanoleaf, &Nanoleaf::selectedEffectReceived, this, &DevicePluginNanoleaf::onSelectedEffectReceived);
nanoleaf = createNanoleafConnection(address, port);
nanoleaf->setAuthToken(token);
nanoleaf->getControllerInfo(); //we don't care about the controller info, this is just to check if the device is available
nanoleaf->getControllerInfo(); //This is just to check if the device is available
m_nanoleafConnections.insert(device->id(), nanoleaf);
m_asyncDeviceSetup.insert(nanoleaf, info);
@ -164,19 +140,21 @@ void DevicePluginNanoleaf::setupDevice(DeviceSetupInfo *info)
}
}
void DevicePluginNanoleaf::postSetupDevice(Device *device)
{
if (device->deviceClassId() == lightPanelsDeviceClassId) {
//Nanoleaf *nanoleaf = m_nanoleafConnections.value(device->id());
//getDeviceStates(nanoleaf);
Nanoleaf *nanoleaf = m_nanoleafConnections.value(device->id());
if (!nanoleaf)
return;
nanoleaf->getControllerInfo();
nanoleaf->registerForEvents();
}
if(!m_pluginTimer) {
m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(5);
connect(m_pluginTimer, &PluginTimer::timeout, this, [this]() {
foreach (Nanoleaf *nanoleaf, m_nanoleafConnections) {
getDeviceStates(nanoleaf);
nanoleaf->getControllerInfo();
}
});
}
@ -204,6 +182,10 @@ void DevicePluginNanoleaf::executeAction(DeviceActionInfo *info)
if (device->deviceClassId() == lightPanelsDeviceClassId) {
Nanoleaf *nanoleaf = m_nanoleafConnections.value(device->id());
if (!nanoleaf) {
return info->finish(Device::DeviceErrorHardwareFailure);
}
if (action.actionTypeId() == lightPanelsPowerActionTypeId) {
bool power = action.param(lightPanelsPowerActionPowerParamTypeId).value().toBool();
QUuid requestId = nanoleaf->setPower(power);
@ -218,13 +200,17 @@ void DevicePluginNanoleaf::executeAction(DeviceActionInfo *info)
} else if (action.actionTypeId() == lightPanelsColorActionTypeId) {
QColor color(action.param(lightPanelsColorActionColorParamTypeId).value().toString());
QUuid requestId = nanoleaf->setHue(color);
QUuid requestId = nanoleaf->setColor(color);
connect(info, &DeviceActionInfo::aborted,[requestId, this](){m_asyncActions.remove(requestId);});
m_asyncActions.insert(requestId, info);
} else if (action.actionTypeId() == lightPanelsColorTemperatureActionTypeId) {
int colorTemperature = action.param(lightPanelsColorTemperatureActionColorTemperatureParamTypeId).value().toInt();
QUuid requestId = nanoleaf->setColorTemperature(colorTemperature);
QUuid requestId = nanoleaf->setMired(colorTemperature);
connect(info, &DeviceActionInfo::aborted,[requestId, this](){m_asyncActions.remove(requestId);});
m_asyncActions.insert(requestId, info);
} else if (action.actionTypeId() == lightPanelsAlertActionTypeId) {
QUuid requestId = nanoleaf->identify();
connect(info, &DeviceActionInfo::aborted,[requestId, this](){m_asyncActions.remove(requestId);});
m_asyncActions.insert(requestId, info);
}
@ -234,10 +220,10 @@ void DevicePluginNanoleaf::executeAction(DeviceActionInfo *info)
void DevicePluginNanoleaf::browseDevice(BrowseResult *result)
{
Device *device = result->device();
Nanoleaf *nanoleaf = m_nanoleafConnections.value(device->id());
nanoleaf->getEffects();
m_asyncBrowseResults.insert(nanoleaf, result);
connect(result, &BrowseResult::aborted, this, [nanoleaf, this]{m_asyncBrowseResults.remove(nanoleaf);});
Nanoleaf *nanoleaf = m_nanoleafConnections.value(device->id());
nanoleaf->getEffects();
m_asyncBrowseResults.insert(nanoleaf, result);
connect(result, &BrowseResult::aborted, this, [nanoleaf, this]{m_asyncBrowseResults.remove(nanoleaf);});
}
void DevicePluginNanoleaf::browserItem(BrowserItemResult *result)
@ -260,15 +246,24 @@ void DevicePluginNanoleaf::executeBrowserItem(BrowserActionInfo *info)
connect(info, &BrowserActionInfo::aborted, this, [requestId, this]{m_asyncBrowserItem.remove(requestId);});
}
void DevicePluginNanoleaf::getDeviceStates(Nanoleaf *nanoleaf)
Nanoleaf *DevicePluginNanoleaf::createNanoleafConnection(const QHostAddress &address, int port)
{
nanoleaf->getPower();
nanoleaf->getHue();
nanoleaf->getColorMode();
nanoleaf->getBrightness();
nanoleaf->getColorTemperature();
nanoleaf->getSelectedEffect();
Nanoleaf *nanoleaf = new Nanoleaf(hardwareManager()->networkManager(), address, port, this);
connect(nanoleaf, &Nanoleaf::authTokenRecieved, this, &DevicePluginNanoleaf::onAuthTokenReceived);
connect(nanoleaf, &Nanoleaf::authenticationStatusChanged, this, &DevicePluginNanoleaf::onAuthenticationStatusChanged);
connect(nanoleaf, &Nanoleaf::requestExecuted, this, &DevicePluginNanoleaf::onRequestExecuted);
connect(nanoleaf, &Nanoleaf::connectionChanged, this, &DevicePluginNanoleaf::onConnectionChanged);
connect(nanoleaf, &Nanoleaf::controllerInfoReceived, this, &DevicePluginNanoleaf::onControllerInfoReceived);
connect(nanoleaf, &Nanoleaf::brightnessReceived, this, &DevicePluginNanoleaf::onBrightnessReceived);
connect(nanoleaf, &Nanoleaf::powerReceived, this, &DevicePluginNanoleaf::onPowerReceived);
connect(nanoleaf, &Nanoleaf::colorModeReceived, this, &DevicePluginNanoleaf::onColorModeReceived);
connect(nanoleaf, &Nanoleaf::saturationReceived, this, &DevicePluginNanoleaf::onSaturationReceived);
connect(nanoleaf, &Nanoleaf::hueReceived, this, &DevicePluginNanoleaf::onHueReceived);
connect(nanoleaf, &Nanoleaf::colorTemperatureReceived, this, &DevicePluginNanoleaf::onColorTemperatureReceived);
connect(nanoleaf, &Nanoleaf::effectListReceived, this, &DevicePluginNanoleaf::onEffectListReceived);
connect(nanoleaf, &Nanoleaf::selectedEffectReceived, this, &DevicePluginNanoleaf::onSelectedEffectReceived);
return nanoleaf;
}
void DevicePluginNanoleaf::onAuthTokenReceived(const QString &token)
@ -326,12 +321,23 @@ void DevicePluginNanoleaf::onConnectionChanged(bool connected)
device->setStateValue(lightPanelsConnectedStateTypeId, connected);
}
void DevicePluginNanoleaf::onControllerInfoReceived(const Nanoleaf::ControllerInfo &controllerInfo)
{
Nanoleaf *nanoleaf = static_cast<Nanoleaf *>(sender());
Device *device = myDevices().findById(m_nanoleafConnections.key(nanoleaf));
if (!device)
return;
qCDebug(dcNanoleaf()) << "Controller Info received" << controllerInfo.name << controllerInfo.firmwareVersion;
device->setParamValue(lightPanelsDeviceFirmwareVersionParamTypeId, controllerInfo.firmwareVersion);
}
void DevicePluginNanoleaf::onPowerReceived(bool power)
{
Nanoleaf *nanoleaf = static_cast<Nanoleaf *>(sender());
Device *device = myDevices().findById(m_nanoleafConnections.key(nanoleaf));
if (!device)
return;
qCDebug(dcNanoleaf()) << "Power received" << power;
device->setStateValue(lightPanelsPowerStateTypeId, power);
}
@ -341,6 +347,7 @@ void DevicePluginNanoleaf::onBrightnessReceived(int percentage)
Device *device = myDevices().findById(m_nanoleafConnections.key(nanoleaf));
if (!device)
return;
qCDebug(dcNanoleaf()) << "Brightness received" << percentage;
device->setStateValue(lightPanelsBrightnessStateTypeId, percentage);
}
@ -350,6 +357,7 @@ void DevicePluginNanoleaf::onColorModeReceived(const QString &colorMode)
Device *device = myDevices().findById(m_nanoleafConnections.key(nanoleaf));
if (!device)
return;
qCDebug(dcNanoleaf()) << "Color mode received" << colorMode;
device->setStateValue(lightPanelsColorModeStateTypeId, colorMode);
}
@ -359,6 +367,7 @@ void DevicePluginNanoleaf::onHueReceived(int hue)
Device *device = myDevices().findById(m_nanoleafConnections.key(nanoleaf));
if (!device)
return;
qCDebug(dcNanoleaf()) << "Hue received" << hue;
m_hues.insert(device->id(), hue);
nanoleaf->getSaturation();
}
@ -372,7 +381,6 @@ void DevicePluginNanoleaf::onSaturationReceived(int saturation)
qCDebug(dcNanoleaf()) << "Saturation received" << saturation;
QColor color;
color.setHsv(m_hues.value(device->id()), saturation, 100);
//TODO get hue
device->setStateValue(lightPanelsColorStateTypeId, color);
}
@ -394,17 +402,20 @@ void DevicePluginNanoleaf::onEffectListReceived(const QStringList &effects)
item.setDisplayName(effect);
item.setDisabled(false);
result->addItem(item);
}
}
result->finish(Device::DeviceErrorNoError);
}
}
void DevicePluginNanoleaf::onColorTemperatureReceived(int mired)
void DevicePluginNanoleaf::onColorTemperatureReceived(int kelvin)
{
Nanoleaf *nanoleaf = static_cast<Nanoleaf *>(sender());
Device *device = myDevices().findById(m_nanoleafConnections.key(nanoleaf));
if (!device)
return;
qCDebug(dcNanoleaf()) << "Color temperature received" << kelvin;
//NOTE: this is just a rough estimation of the mired value
int mired = static_cast<int>(kelvin/11.12); //FIXME
device->setStateValue(lightPanelsColorTemperatureStateTypeId, mired);
}
@ -414,6 +425,7 @@ void DevicePluginNanoleaf::onSelectedEffectReceived(const QString &effect)
Device *device = myDevices().findById(m_nanoleafConnections.key(nanoleaf));
if (!device)
return;
device->setStateValue(lightPanelsEffectNameStateTypeId, effect);
qCDebug(dcNanoleaf()) << "Selected effect received" << effect;
device->setStateValue(lightPanelsEffectNameStateTypeId, QString(effect).remove('"').remove('*'));
}

View File

@ -1,24 +1,30 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2020 Bernhard Trinnes <bernhard.trinnes@nymea.io> *
* *
* 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 *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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
* This project may also contain libraries licensed under the open source software license GNU GPL v.3.
* Alternatively, this project may be redistributed and/or modified under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation; version 3.
* this project is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this project.
* If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under contact@nymea.io
* or see our FAQ/Licensing Information on https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef DEVICEPLUGINNANOLEAF_H
#define DEVICEPLUGINNANOLEAF_H
@ -68,7 +74,7 @@ private:
QHash<QUuid, BrowserActionInfo *> m_asyncBrowserItem;
QHash<DeviceId, int> m_hues;
void getDeviceStates(Nanoleaf *nanoleaf);
Nanoleaf *createNanoleafConnection(const QHostAddress &address, int port);
public slots:
void onAuthTokenReceived(const QString &token);
@ -76,13 +82,14 @@ public slots:
void onRequestExecuted(QUuid requestId, bool success);
void onConnectionChanged(bool connected);
void onControllerInfoReceived(const Nanoleaf::ControllerInfo &controllerInfo);
void onPowerReceived(bool power);
void onBrightnessReceived(int percentage);
void onColorModeReceived(const QString &colorMode);
void onHueReceived(int hue);
void onSaturationReceived(int percentage);
void onEffectListReceived(const QStringList &effects);
void onColorTemperatureReceived(int mired);
void onColorTemperatureReceived(int kelvin);
void onSelectedEffectReceived(const QString &effect);
};

View File

@ -12,7 +12,7 @@
"id": "d44ee383-9fa5-4751-babd-1129ac20896a",
"name": "lightPanels",
"displayName": "Light panels",
"interfaces": ["colorlight", "colortemperaturelight", "connectable"],
"interfaces": ["colorlight", "colortemperaturelight", "alert", "connectable"],
"createMethods": ["discovery"],
"setupMethod": "pushButton",
"browsable": true,
@ -52,6 +52,13 @@
"readOnly": true
}
],
"actionTypes": [
{
"id": "47a6a1a1-fb90-4f24-be8c-b4dba0aaaa84",
"name": "alert",
"displayName": "Alert"
}
],
"stateTypes": [
{
"id": "a3102107-a825-4ec8-a9ec-b2c2a9fb5c89",
@ -100,7 +107,7 @@
"name": "brightness",
"displayName": "Brightness",
"displayNameEvent": "Brightness changed",
"displayNameAction": "Set brigtness",
"displayNameAction": "Set brightness",
"type": "int",
"unit": "Percentage",
"defaultValue": 0,
@ -113,17 +120,14 @@
"name": "colorMode",
"displayName": "Color mode",
"displayNameEvent": "Color mode changed",
"displayNameAction": "Set color",
"type": "QString",
"defaultValue": "Color temperature",
"writable": true
"defaultValue": "Color temperature"
},
{
"id": "57f9831e-1b98-41c1-a21c-6073ff327237",
"name": "effectName",
"displayName": "Effect name",
"displayNameEvent": "Effect name changed",
"displayNameAction": "Set color",
"type": "QString",
"defaultValue": "-"
}

View File

@ -1,24 +1,30 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2020 Bernhard Trinnes <bernhard.trinnes@nymea.io> *
* *
* 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 *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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
* This project may also contain libraries licensed under the open source software license GNU GPL v.3.
* Alternatively, this project may be redistributed and/or modified under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation; version 3.
* this project is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this project.
* If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under contact@nymea.io
* or see our FAQ/Licensing Information on https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "nanoleaf.h"
#include "extern-plugininfo.h"
@ -103,7 +109,6 @@ void Nanoleaf::addUser()
qDebug(dcNanoleaf()) << "Recieved invalide JSON object";
return;
}
m_authToken = data.toVariant().toMap().value("auth_token").toString();
emit authTokenRecieved(m_authToken);
@ -151,13 +156,64 @@ void Nanoleaf::getControllerInfo()
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status != 204 || reply->error() != QNetworkReply::NoError) {
if (status < 200 || status > 204 || reply->error() != QNetworkReply::NoError) {
qCWarning(dcNanoleaf()) << "Request error:" << status << reply->errorString();
emit authenticationStatusChanged(false);
return;
}
QJsonParseError error;
QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error);
if (error.error != QJsonParseError::NoError) {
qDebug(dcNanoleaf()) << "Recieved invalide JSON object";
return;
}
emit connectionChanged(true);
emit authenticationStatusChanged(true);
QVariantMap map = data.toVariant().toMap();
ControllerInfo info;
info.name = map.value("name").toString();
info.serialNumber = map.value("serialNo").toString();
info.model = map.value("model").toString();
info.manufacturer = map.value("manufacturer").toString();
info.firmwareVersion = map.value("firmwareVersion").toString();
emit controllerInfoReceived(info);
if (map.contains("state")) {
QVariantMap state = map.value("state").toMap();
if (state.contains("on")) {
emit powerReceived(state["on"].toMap().value("value").toBool());
}
if (state.contains("brightness")) {
emit brightnessReceived(state["brightness"].toMap().value("value").toInt());
}
if (state.contains("hue")) {
emit hueReceived(state["hue"].toMap().value("value").toInt());
}
if (state.contains("sat")) {
emit saturationReceived(state["sat"].toMap().value("value").toInt());
}
if (state.contains("ct")) {
emit colorTemperatureReceived(state["ct"].toMap().value("value").toInt());
}
if (state.contains("colorMode")) {
emit colorModeReceived(state["colorMode"].toString());
}
}
if (map.contains("effects")) {
QVariantMap effects = map.value("effects").toMap();
emit selectedEffectReceived(effects.value("select").toString());
}
if (map.contains("panelLayout")) {
//QVariantMap panelLayout = map.value("panelLayout").toMap();
//emit panelLayoutReceived();
}
if (map.contains("rhythm")) {
//QVariantMap rhythm = map.value("rhythm").toMap();
//emit rhythmModulReceived(rhythm.value("select").toString());
}
});
}
@ -314,9 +370,9 @@ void Nanoleaf::getColorTemperature()
qDebug(dcNanoleaf()) << "Recieved invalide JSON object";
return;
}
int mired = data.toVariant().toMap().value("value").toInt();
int kelvin = data.toVariant().toMap().value("value").toInt();
emit connectionChanged(true);
emit colorTemperatureReceived(mired);
emit colorTemperatureReceived(kelvin);
});
}
@ -352,6 +408,76 @@ void Nanoleaf::getColorMode()
});
}
void Nanoleaf::registerForEvents()
{
QUrl url;
url.setHost(m_address.toString());
url.setPort(m_port);
url.setScheme("http");
url.setPath("/api/v1/"+m_authToken+"/events");
QUrlQuery query;
query.addQueryItem("id", "1,2,3,4");
url.setQuery(query);
QNetworkRequest request;
request.setUrl(url);
QNetworkReply *reply = m_networkManager->get(request);
connect(reply, &QNetworkReply::readyRead, this, [reply, this] {
QJsonParseError error;
QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error);
if (error.error != QJsonParseError::NoError) {
qDebug(dcNanoleaf()) << "Recieved invalide JSON object";
return;
}
qCDebug(dcNanoleaf()) << "On event stream" << data.toJson();
});
connect(reply, &QNetworkReply::finished, this, [reply, this] {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status != 200 || reply->error() != QNetworkReply::NoError) {
qCWarning(dcNanoleaf()) << "Request error:" << status << reply->errorString();
emit connectionChanged(false);
return;
}
QJsonParseError error;
QJsonDocument data = QJsonDocument::fromJson(reply->readAll(), &error);
if (error.error != QJsonParseError::NoError) {
qDebug(dcNanoleaf()) << "Recieved invalide JSON object";
return;
}
qCDebug(dcNanoleaf()) << "Event received" << data.toJson();
QVariantList events = data.toVariant().toList();
foreach (QVariant variant, events) {
QVariantMap event = variant.toMap();
switch (event["attr"].toInt()) {
case 1: //ON
emit powerReceived(event["value"].toBool());
break;
case 2: //Brightness
emit brightnessReceived(event["value"].toInt());
break;
case 3: //Hue
emit hueReceived(event["value"].toInt());
break;
case 4: //Saturation
emit saturationReceived(event["value"].toInt());
break;
case 5: //Color Temperature
emit colorTemperatureReceived(event["value"].toInt());
break;
case 6: //colorMode
emit colorModeReceived(event["value"].toString());
break;
default:
qCWarning(dcNanoleaf()) << "Unrecognised Event received";
}
}
});
}
QUuid Nanoleaf::setPower(bool power)
{
QUuid requestId = QUuid::createUuid();
@ -387,10 +513,74 @@ QUuid Nanoleaf::setPower(bool power)
return requestId;
}
QUuid Nanoleaf::setHue(QColor color)
QUuid Nanoleaf::setColor(QColor color)
{
Q_UNUSED(color);
QUuid requestId = QUuid::createUuid();
QUrl url;
url.setHost(m_address.toString());
url.setPort(m_port);
url.setScheme("http");
url.setPath(QString("/api/v1/%1/state").arg(m_authToken));
QVariantMap map;
QVariantMap hue;
hue["value"] = color.hue();
map.insert("hue", hue);
QVariantMap sat;
sat["value"] = color.saturation();
map.insert("sat", sat);
QJsonDocument body = QJsonDocument::fromVariant(map);
QNetworkRequest request;
request.setUrl(url);
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
QNetworkReply *reply = m_networkManager->put(request, body.toJson());
qDebug(dcNanoleaf()) << "Sending request" << request.url();
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status != 204 || reply->error() != QNetworkReply::NoError) {
emit requestExecuted(requestId, false);
qCWarning(dcNanoleaf()) << "Request error:" << status << reply->errorString();
return;
}
emit requestExecuted(requestId, true);
});
return requestId;
}
QUuid Nanoleaf::setHue(int hue)
{
QUuid requestId = QUuid::createUuid();
QUrl url;
url.setHost(m_address.toString());
url.setPort(m_port);
url.setScheme("http");
url.setPath(QString("/api/v1/%1/state").arg(m_authToken));
QVariantMap map;
QVariantMap hueMap;
hueMap["value"] = hue;
map.insert("hue", hueMap);
QJsonDocument body = QJsonDocument::fromVariant(map);
QNetworkRequest request;
request.setUrl(url);
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
QNetworkReply *reply = m_networkManager->put(request, body.toJson());
qDebug(dcNanoleaf()) << "Sending request" << request.url();
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status != 204 || reply->error() != QNetworkReply::NoError) {
emit requestExecuted(requestId, false);
qCWarning(dcNanoleaf()) << "Request error:" << status << reply->errorString();
return;
}
emit requestExecuted(requestId, true);
});
return requestId;
}
@ -430,7 +620,6 @@ QUuid Nanoleaf::setBrightness(int percentage)
QUuid Nanoleaf::setSaturation(int percentage)
{
Q_UNUSED(percentage);
QUuid requestId = QUuid::createUuid();
QUrl url;
url.setHost(m_address.toString());
@ -463,10 +652,45 @@ QUuid Nanoleaf::setSaturation(int percentage)
return requestId;
}
QUuid Nanoleaf::setColorTemperature(int mired)
QUuid Nanoleaf::setMired(int mired)
{
//NOTE: this is just a rough estimation
int kelvin = static_cast<int>(mired * 11.12);
QUuid requestId = setKelvin(kelvin);
return requestId;
}
QUuid Nanoleaf::setKelvin(int kelvin)
{
Q_UNUSED(mired);
QUuid requestId = QUuid::createUuid();
QUrl url;
url.setHost(m_address.toString());
url.setPort(m_port);
url.setScheme("http");
url.setPath(QString("/api/v1/%1/state").arg(m_authToken));
QVariantMap map;
QVariantMap value;
value["value"] = kelvin;
map.insert("ct", value);
QJsonDocument body = QJsonDocument::fromVariant(map);
QNetworkRequest request;
request.setUrl(url);
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
QNetworkReply *reply = m_networkManager->put(request, body.toJson());
qDebug(dcNanoleaf()) << "Sending request" << request.url();
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (status != 204 || reply->error() != QNetworkReply::NoError) {
emit requestExecuted(requestId, false);
qCWarning(dcNanoleaf()) << "Request error:" << status << reply->errorString();
return;
}
emit requestExecuted(requestId, true);
});
return requestId;
}
@ -564,5 +788,40 @@ QUuid Nanoleaf::setEffect(const QString &effect)
return requestId;
}
QUuid Nanoleaf::identify()
{
QUuid requestId = QUuid::createUuid();
QUrl url;
url.setHost(m_address.toString());
url.setPort(m_port);
url.setScheme("http");
url.setPath(QString("/api/v1/%1/identify").arg(m_authToken));
QNetworkRequest request;
request.setUrl(url);
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
QNetworkReply *reply = m_networkManager->put(request, "");
qDebug(dcNanoleaf()) << "Sending request" << request.url();
connect(reply, &QNetworkReply::finished, this, [requestId, reply, this] {
reply->deleteLater();
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// Check HTTP status code
if (status < 200 || status > 204 || reply->error() != QNetworkReply::NoError) {
if (reply->error() == QNetworkReply::HostNotFoundError) {
emit connectionChanged(false);
}
if (status >= 400 && status <= 410) {
emit authenticationStatusChanged(false);
}
emit requestExecuted(requestId, false);
qCWarning(dcNanoleaf()) << "Request error:" << status << reply->errorString();
return;
}
emit requestExecuted(requestId, true);
});
return requestId;
}

View File

@ -1,24 +1,30 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* Copyright (C) 2020 Bernhard Trinnes <bernhard.trinnes@nymea.io> *
* *
* 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 *
* <http://www.gnu.org/licenses/>. *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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
* This project may also contain libraries licensed under the open source software license GNU GPL v.3.
* Alternatively, this project may be redistributed and/or modified under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation; version 3.
* this project is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this project.
* If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under contact@nymea.io
* or see our FAQ/Licensing Information on https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef NANOLEAF_H
#define NANOLEAF_H
@ -36,6 +42,22 @@ class Nanoleaf : public QObject
{
Q_OBJECT
public:
struct ControllerInfo {
QString name;
QString serialNumber;
QString manufacturer;
QString firmwareVersion;
QString model;
};
enum GestureID {
SingleTap = 0,
DoubleTap = 1,
SwipeUp = 2,
SwipeDown = 3,
SwipeLeft = 4,
SwipeRight = 5
};
explicit Nanoleaf(NetworkAccessManager *networkManager, const QHostAddress &address, int port = 16021, QObject *parent = nullptr);
void setIpAddress(const QHostAddress &address);
@ -62,27 +84,21 @@ public:
void getColorTemperature();
void getColorMode();
void registerForEvents();
QUuid setPower(bool power);
QUuid setHue(QColor color);
QUuid setColor(QColor color);
QUuid setHue(int hue);
QUuid setBrightness(int percentage);
QUuid setSaturation(int percentage);
QUuid setColorTemperature(int mired);
QUuid setMired(int mired);
QUuid setKelvin(int kelvin);
//EFFECTS
void getEffects();
void getSelectedEffect();
QUuid setEffect(const QString &effect);
//PANEL LAYOUT
//IDENTIFY
//EXTERNAL CONTROL
//RHYTHM
QUuid identify();
private:
NetworkAccessManager *m_networkManager = nullptr;
@ -95,6 +111,7 @@ signals:
void authenticationStatusChanged(bool authenticated);
void requestExecuted(QUuid requestId, bool success);
void controllerInfoReceived(const ControllerInfo &controllerInfo);
void authTokenRecieved(const QString &token);
void powerReceived(bool power);
void brightnessReceived(int percentage);
@ -102,8 +119,11 @@ signals:
void hueReceived(int hue);
void saturationReceived(int percentage);
void effectListReceived(const QStringList &effects);
void colorTemperatureReceived(int mired);
void colorTemperatureReceived(int kelvin);
void selectedEffectReceived(const QString &effect);
//Only supported by Canvas
void touchEventReceived(GestureID gesture);
};
#endif // NANOLEAF_H