diff --git a/libnymea-zigbee/nxp/interface/zigbeeinterface.cpp b/libnymea-zigbee/nxp/interface/zigbeeinterface.cpp index 74d3b24..40e1bd1 100644 --- a/libnymea-zigbee/nxp/interface/zigbeeinterface.cpp +++ b/libnymea-zigbee/nxp/interface/zigbeeinterface.cpp @@ -274,6 +274,11 @@ void ZigbeeInterface::disable() void ZigbeeInterface::sendMessage(const ZigbeeInterfaceMessage &message) { + if (!available()) { + qCWarning(dcZigbeeInterface()) << "Could not send message. The serial port is not available."; + return; + } + quint16 messageTypeValue = static_cast(message.messageType()); quint16 lengthValue = static_cast(message.data().count()); quint8 crcValue = calculateCrc(messageTypeValue, lengthValue, message.data()); diff --git a/libnymea-zigbee/nxp/zigbeebridgecontrollernxp.cpp b/libnymea-zigbee/nxp/zigbeebridgecontrollernxp.cpp index 59aa416..324099c 100644 --- a/libnymea-zigbee/nxp/zigbeebridgecontrollernxp.cpp +++ b/libnymea-zigbee/nxp/zigbeebridgecontrollernxp.cpp @@ -585,6 +585,25 @@ ZigbeeInterfaceReply *ZigbeeBridgeControllerNxp::commandMoveToColourTemperature( return sendRequest(request); } +ZigbeeInterfaceReply *ZigbeeBridgeControllerNxp::commandMoveToColor(quint8 addressMode, quint16 targetShortAddress, quint8 sourceEndpoint, quint8 destinationEndpoint, quint16 x, quint16 y, quint16 transitionTime) +{ + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + stream << addressMode; + stream << targetShortAddress; + stream << sourceEndpoint; + stream << destinationEndpoint; + stream << x << y; + stream << transitionTime; + + ZigbeeInterfaceRequest request(ZigbeeInterfaceMessage(Zigbee::MessageTypeMoveToColor, data)); + //request.setExpectedAdditionalMessageType(Zigbee::MessageTypeDefaultResponse); + request.setDescription(QString("Move to colour %1, %2").arg(x).arg(y)); + request.setTimoutIntervall(5000); + + return sendRequest(request); +} + ZigbeeInterfaceReply *ZigbeeBridgeControllerNxp::commandMoveToHueSaturation(quint8 addressMode, quint16 targetShortAddress, quint8 sourceEndpoint, quint8 destinationEndpoint, quint8 hue, quint8 saturation, quint16 transitionTime) { QByteArray data; diff --git a/libnymea-zigbee/nxp/zigbeebridgecontrollernxp.h b/libnymea-zigbee/nxp/zigbeebridgecontrollernxp.h index a2dfaea..21e1a52 100644 --- a/libnymea-zigbee/nxp/zigbeebridgecontrollernxp.h +++ b/libnymea-zigbee/nxp/zigbeebridgecontrollernxp.h @@ -98,6 +98,7 @@ public: // Level ZigbeeInterfaceReply *commandMoveToColourTemperature(quint8 addressMode, quint16 targetShortAddress, quint8 sourceEndpoint, quint8 destinationEndpoint, quint16 colourTemperature, quint16 transitionTime); + ZigbeeInterfaceReply *commandMoveToColor(quint8 addressMode, quint16 targetShortAddress, quint8 sourceEndpoint, quint8 destinationEndpoint, quint16 x, quint16 y, quint16 transitionTime); ZigbeeInterfaceReply *commandMoveToHueSaturation(quint8 addressMode, quint16 targetShortAddress, quint8 sourceEndpoint, quint8 destinationEndpoint, quint8 hue, quint8 saturation, quint16 transitionTime); ZigbeeInterfaceReply *commandMoveToHue(quint8 addressMode, quint16 targetShortAddress, quint8 sourceEndpoint, quint8 destinationEndpoint, quint8 hue, quint16 transitionTime); ZigbeeInterfaceReply *commandMoveToSaturation(quint8 addressMode, quint16 targetShortAddress, quint8 sourceEndpoint, quint8 destinationEndpoint, quint8 saturation, quint16 transitionTime); diff --git a/libnymea-zigbee/nxp/zigbeenodeendpointnxp.cpp b/libnymea-zigbee/nxp/zigbeenodeendpointnxp.cpp index c221e78..98ee6de 100644 --- a/libnymea-zigbee/nxp/zigbeenodeendpointnxp.cpp +++ b/libnymea-zigbee/nxp/zigbeenodeendpointnxp.cpp @@ -210,6 +210,28 @@ ZigbeeNetworkReply *ZigbeeNodeEndpointNxp::sendMoveToColorTemperature(quint16 co return nullptr; } +ZigbeeNetworkReply *ZigbeeNodeEndpointNxp::sendMoveToColor(double x, double y, quint16 transitionTime) +{ + qCDebug(dcZigbeeNode()) << "Move to color request" << node() << x << y << transitionTime; + + quint16 normalizedX = static_cast(qRound(x * 65536)); + quint16 normalizedY = static_cast(qRound(y * 65536)); + + ZigbeeInterfaceReply *reply = m_controller->commandMoveToColor(0x02, node()->shortAddress(), 0x01, endpointId(), normalizedX, normalizedY, transitionTime); + connect(reply, &ZigbeeInterfaceReply::finished, this, [reply](){ + reply->deleteLater(); + + if (reply->status() != ZigbeeInterfaceReply::Success) { + qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage(); + return; + } + + qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; + }); + + return nullptr; +} + ZigbeeNetworkReply *ZigbeeNodeEndpointNxp::sendMoveToHueSaturation(quint8 hue, quint8 saturation, quint16 transitionTime) { qCDebug(dcZigbeeNode()) << "Move to hue saturation request" << node() << hue << saturation << transitionTime; diff --git a/libnymea-zigbee/nxp/zigbeenodeendpointnxp.h b/libnymea-zigbee/nxp/zigbeenodeendpointnxp.h index aaca5c6..07e20bd 100644 --- a/libnymea-zigbee/nxp/zigbeenodeendpointnxp.h +++ b/libnymea-zigbee/nxp/zigbeenodeendpointnxp.h @@ -50,8 +50,13 @@ public: ZigbeeNetworkReply *bindUnicast(Zigbee::ClusterId clusterId, const ZigbeeAddress &destinationAddress, quint8 destinationEndpoint) override; ZigbeeNetworkReply *sendOnOffClusterCommand(ZigbeeCluster::OnOffClusterCommand command) override; ZigbeeNetworkReply *addGroup(quint8 destinationEndpoint, quint16 groupAddress) override; + + // Level control ZigbeeNetworkReply *sendLevelCommand(ZigbeeCluster::LevelClusterCommand command, quint8 level, bool triggersOnOff, quint16 transitionTime) override; + + // Color control ZigbeeNetworkReply *sendMoveToColorTemperature(quint16 colourTemperature, quint16 transitionTime) override; + ZigbeeNetworkReply *sendMoveToColor(double x, double y, quint16 transitionTime) override; ZigbeeNetworkReply *sendMoveToHueSaturation(quint8 hue, quint8 saturation, quint16 transitionTime) override; ZigbeeNetworkReply *sendMoveToHue(quint8 hue, quint16 transitionTime) override; ZigbeeNetworkReply *sendMoveToSaturation(quint8 saturation, quint16 transitionTime) override; diff --git a/libnymea-zigbee/zigbeenodeendpoint.h b/libnymea-zigbee/zigbeenodeendpoint.h index 40479a7..d9fcd4a 100644 --- a/libnymea-zigbee/zigbeenodeendpoint.h +++ b/libnymea-zigbee/zigbeenodeendpoint.h @@ -95,6 +95,7 @@ public: // Color commands virtual ZigbeeNetworkReply *sendMoveToColorTemperature(quint16 colourTemperature, quint16 transitionTime) = 0; + virtual ZigbeeNetworkReply *sendMoveToColor(double x, double y, quint16 transitionTime) = 0; virtual ZigbeeNetworkReply *sendMoveToHueSaturation(quint8 hue, quint8 saturation, quint16 transitionTime) = 0; virtual ZigbeeNetworkReply *sendMoveToHue(quint8 hue, quint16 transitionTime) = 0; virtual ZigbeeNetworkReply *sendMoveToSaturation(quint8 saturation, quint16 transitionTime) = 0; diff --git a/libnymea-zigbee/zigbeeutils.cpp b/libnymea-zigbee/zigbeeutils.cpp index 0bda1e4..13bb957 100644 --- a/libnymea-zigbee/zigbeeutils.cpp +++ b/libnymea-zigbee/zigbeeutils.cpp @@ -27,10 +27,127 @@ #include "zigbeeutils.h" +#include #include #include #include +#include + +static QList colorTemperatureScale = { + QColor(255, 56, 0), // 1000 K + QColor(255, 71, 0), + QColor(255, 83, 0), + QColor(255, 93, 0), + QColor(255, 101, 0), + QColor(255, 109, 0), + QColor(255, 115, 0), + QColor(255, 121, 0), + QColor(255, 126, 0), + QColor(255, 131, 0), + QColor(255, 138, 18), + QColor(255, 142, 33), + QColor(255, 147, 44), + QColor(255, 152, 54), + QColor(255, 157, 63), + QColor(255, 161, 72), + QColor(255, 165, 79), + QColor(255, 169, 87), + QColor(255, 173, 94), + QColor(255, 177, 101), + QColor(255, 180, 107), + QColor(255, 184, 114), + QColor(255, 187, 120), + QColor(255, 190, 126), + QColor(255, 193, 132), + QColor(255, 196, 137), + QColor(255, 199, 143), + QColor(255, 201, 148), + QColor(255, 204, 153), + QColor(255, 206, 159), + QColor(255, 209, 163), + QColor(255, 211, 168), + QColor(255, 213, 173), + QColor(255, 215, 177), + QColor(255, 217, 182), + QColor(255, 219, 186), + QColor(255, 221, 190), + QColor(255, 223, 194), + QColor(255, 225, 198), + QColor(255, 227, 202), + QColor(255, 228, 206), + QColor(255, 230, 210), + QColor(255, 232, 213), + QColor(255, 233, 217), + QColor(255, 235, 220), + QColor(255, 236, 224), + QColor(255, 238, 227), + QColor(255, 239, 230), + QColor(255, 240, 233), + QColor(255, 242, 236), + QColor(255, 243, 239), + QColor(255, 244, 242), + QColor(255, 245, 245), + QColor(255, 246, 247), + QColor(255, 248, 251), + QColor(255, 249, 253), + QColor(254, 249, 255), + QColor(252, 247, 255), + QColor(249, 246, 255), + QColor(247, 245, 255), + QColor(245, 243, 255), + QColor(243, 242, 255), + QColor(240, 241, 255), + QColor(239, 240, 255), + QColor(237, 239, 255), + QColor(235, 238, 255), + QColor(233, 237, 255), + QColor(231, 236, 255), + QColor(230, 235, 255), + QColor(228, 234, 255), + QColor(227, 233, 255), + QColor(225, 232, 255), + QColor(224, 231, 255), + QColor(222, 230, 255), + QColor(221, 230, 255), + QColor(220, 229, 255), + QColor(218, 229, 255), + QColor(217, 227, 255), + QColor(216, 227, 255), + QColor(215, 226, 255), + QColor(214, 225, 255), + QColor(212, 225, 255), + QColor(211, 224, 255), + QColor(210, 223, 255), + QColor(209, 223, 255), + QColor(208, 222, 255), + QColor(207, 221, 255), + QColor(207, 221, 255), + QColor(206, 220, 255), + QColor(205, 220, 255), + QColor(207, 218, 255), + QColor(207, 218, 255), + QColor(206, 217, 255), + QColor(205, 217, 255), + QColor(204, 216, 255), + QColor(204, 216, 255), + QColor(203, 215, 255), + QColor(202, 215, 255), + QColor(202, 214, 255), + QColor(201, 214, 255), + QColor(200, 213, 255), + QColor(200, 213, 255), + QColor(199, 212, 255), + QColor(198, 212, 255), + QColor(198, 212, 255), + QColor(197, 211, 255), + QColor(197, 211, 255), + QColor(197, 210, 255), + QColor(196, 210, 255), + QColor(195, 210, 255), + QColor(195, 209, 255) // 12000 K +}; + QBitArray ZigbeeUtils::convertByteArrayToBitArray(const QByteArray &byteArray) { QBitArray bitArray(byteArray.count() * 8); @@ -175,5 +292,132 @@ quint64 ZigbeeUtils::generateRandomPanId() QPointF ZigbeeUtils::convertColorToXY(const QColor &color) { - floar r = color.red() + // https://developers.meethue.com/develop/application-design-guidance/color-conversion-formulas-rgb-to-xy-and-back/ + + // Color values between [0, 1] + + // Gamma correction + double redGamma = (color.redF() > 0.04045) ? pow((color.redF() + 0.055) / (1.0 + 0.055), 2.4) : (color.redF() / 12.92); + double greenGamme = (color.greenF() > 0.04045) ? pow((color.greenF() + 0.055) / (1.0 + 0.055), 2.4) : (color.greenF() / 12.92); + double blueGamme = (color.blueF() > 0.04045) ? pow((color.blueF() + 0.055) / (1.0 + 0.055), 2.4) : (color.blueF() / 12.92); + + // Convert the RGB values to XYZ using the Wide RGB D65 conversion formula + double xx = redGamma * 0.664511 + greenGamme * 0.154324 + blueGamme * 0.162028; + double yy = redGamma * 0.283881 + greenGamme * 0.668433 + blueGamme * 0.047685; + double zz = redGamma * 0.000088 + greenGamme * 0.072310 + blueGamme * 0.986039; + + //qWarning() << "xyz" << xx << yy << zz; + + double x = xx / (xx + yy + zz); + double y = yy / (xx + yy + zz); + + // Correct brightness if required + if (y >= 1) y = 1.0; + + //qWarning() << "xy" << x << y; + + // TODO: ceck if this point is within the color gamut triangle of the light, otherwise get closest point + + return QPointF(x, y); +} + +QColor ZigbeeUtils::convertXYToColor(const QPointF &xyColor) +{ + // https://developers.meethue.com/develop/application-design-guidance/color-conversion-formulas-rgb-to-xy-and-back/ + + // TODO: ceck if this point is within the color gamut triangle of the light, otherwise get closest point + + // Extract x y and z + double xx = xyColor.x(); + double yy = xyColor.y(); + double zz = 1.0 - xx - yy; + + // Get x, y and z + double y = 1.0; // assume full brightness for the calculation + double x = (y / yy) * xx; + double z = (y / yy) * zz; + + //qWarning() << "xyz" << x << y << z; + + // Convert to r, g and b according D65 + double r = x * 1.656492 - y * 0.354851 - z * 0.255038; + double g = -x * 0.707196 + y * 1.655397 + z * 0.036152; + double b = x * 0.051713 - y * 0.121364 + z * 1.011530; + + if (r > b && r > g && r > 1.0) { + // red is too big + g = g / r; + b = b / r; + r = 1.0; + } else if (g > b && g > r && g > 1.0) { + // green is too big + r = r / g; + b = b / g; + g = 1.0; + } else if (b > r && b > g && b > 1.0) { + // blue is too big + r = r / b; + g = g / b; + b = 1.0; + } + + // Apply gamma correction + r = (r <= 0.0031308) ? 12.92 * r : (1.0 + 0.055) * pow(r, (1.0 / 2.4)) - 0.055; + g = (g <= 0.0031308) ? 12.92 * g : (1.0 + 0.055) * pow(g, (1.0 / 2.4)) - 0.055; + b = (b <= 0.0031308) ? 12.92 * b : (1.0 + 0.055) * pow(b, (1.0 / 2.4)) - 0.055; + + if (r > b && r > g) { + // red is biggest + if (r > 1.0) { + g = g / r; + b = b / r; + r = 1.0; + } + } else if (g > b && g > r) { + // green is biggest + if (g > 1.0) { + r = r / g; + b = b / g; + g = 1.0; + } + } else if (b > r && b > g) { + // blue is biggest + if (b > 1.0) { + r = r / b; + g = g / b; + b = 1.0; + } + } + + // Make sure we don't have any negative round error values + if (r < 0) r = 0; + if (g < 0) g = 0; + if (b < 0) b = 0; + + //qWarning() << r << g << b; + QColor color; + color.setRedF(r); + color.setGreenF(g); + color.setBlueF(b); + color.setAlphaF(1.0); + + return color; +} + +QColor ZigbeeUtils::convertXYToColor(quint16 x, quint16 y) +{ + QPointF xy(x / 65536.0, y / 65536.0); + return convertXYToColor(xy); +} + +QColor ZigbeeUtils::interpolateColorFromColorTemperature(int colorTemperature, int minValue, int maxValue) +{ + Q_ASSERT_X(colorTemperature >= minValue && colorTemperature <= maxValue, "interpolate colors", "Interpolation value not between min and max value"); + int intervalSize = maxValue - minValue; + int intervalPosition = colorTemperature - minValue; + double percentage = intervalPosition * 1.0 / intervalSize; + //qWarning() << "Interpolate color" << intervalSize << intervalPosition << percentage; + int closestColorIndex = qRound((colorTemperatureScale.count() - 1) * (1.0 - percentage)); + //qWarning() << "Colors size" << colorTemperatureScale.count() << "Color position according to percentage" << closestColorIndex; + return colorTemperatureScale.at(closestColorIndex); } diff --git a/libnymea-zigbee/zigbeeutils.h b/libnymea-zigbee/zigbeeutils.h index 505a1c5..46416da 100644 --- a/libnymea-zigbee/zigbeeutils.h +++ b/libnymea-zigbee/zigbeeutils.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -64,7 +65,13 @@ public: // Generate random data static quint64 generateRandomPanId(); + // Color converter static QPointF convertColorToXY(const QColor &color); + static QColor convertXYToColor(const QPointF &xyColor); + static QColor convertXYToColor(quint16 x, quint16 y); + + // Color temperature interpolation + static QColor interpolateColorFromColorTemperature(int colorTemperature, int minValue, int maxValue); };