Skip to content

Commit dbc214a

Browse files
committed
Update rest_lights.cpp
Support setting `state.bri` for `lumi.curtain.hagl04`(Xiaomi Aqara curtain motor B1) by writing _Present Value_ in the _Analogue Output_ cluster. See #1654.
1 parent 241bad6 commit dbc214a

File tree

1 file changed

+90
-50
lines changed

1 file changed

+90
-50
lines changed

rest_lights.cpp

Lines changed: 90 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -574,11 +574,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp)
574574
}
575575

576576
// FIXME temporary workaround to support window_covering
577-
bool isWindowCoveringDevice = false;
578-
if (taskRef.lightNode->type() == QLatin1String("Window covering device"))
579-
{
580-
isWindowCoveringDevice = true;
581-
}
577+
bool isWindowCoveringDevice = taskRef.lightNode->type() == QLatin1String("Window covering device");
582578

583579
// on/off
584580
if (hasOn)
@@ -598,6 +594,7 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp)
598594
TaskItem task;
599595
copyTaskReq(taskRef, task);
600596
//FIXME workaround window_convering
597+
// FIXME handle lumi.curtain.hagl04
601598
if (isWindowCoveringDevice
602599
&& addTaskWindowCovering(task, isOn ? 0x01 /*down*/ : 0x00 /*up*/, 0, 0))
603600
{
@@ -682,60 +679,103 @@ int DeRestPluginPrivate::setLightState(const ApiRequest &req, ApiResponse &rsp)
682679
//FIXME workaround window_covering
683680
if (isWindowCoveringDevice)
684681
{
685-
if ((map["bri"].type() == QVariant::String) && map["bri"].toString() == "stop")
686-
{
687-
TaskItem task;
688-
copyTaskReq(taskRef, task);
689-
if (addTaskWindowCovering(task, 0x02 /*stop motion*/, 0, 0))
690-
{
691-
QVariantMap rspItem;
692-
QVariantMap rspItemState;
693-
rspItemState[QString("/groups/%1/action/bri").arg(id)] = map["bri"];
694-
rspItem["success"] = rspItemState;
695-
rsp.list.append(rspItem);
696-
taskToLocalData(task);
697-
}
698-
else
699-
{
700-
rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY)));
701-
}
702-
}
703-
else if (ok && (map["bri"].type() == QVariant::Double) && (bri < 256))
704-
{
705-
TaskItem task;
706-
copyTaskReq(taskRef, task);
707-
uint8_t moveToPct = 0x00;
708-
moveToPct = bri * 100 / 254; // Percent 0 - 100 (0x00 - 0x64)
682+
if ((map["bri"].type() == QVariant::String) && map["bri"].toString() == "stop")
683+
{
684+
TaskItem task;
685+
copyTaskReq(taskRef, task);
686+
if (addTaskWindowCovering(task, 0x02 /*stop motion*/, 0, 0))
687+
{
688+
QVariantMap rspItem;
689+
QVariantMap rspItemState;
690+
rspItemState[QString("/groups/%1/action/bri").arg(id)] = map["bri"];
691+
rspItem["success"] = rspItemState;
692+
rsp.list.append(rspItem);
693+
taskToLocalData(task);
694+
}
695+
else
696+
{
697+
rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY)));
698+
}
699+
}
700+
else if (ok && (map["bri"].type() == QVariant::Double) && (bri < 256))
701+
{
702+
bool ok = false;
703+
TaskItem task;
704+
copyTaskReq(taskRef, task);
705+
uint8_t moveToPct = 0x00;
706+
moveToPct = bri * 100 / 254; // Percent 0 - 100 (0x00 - 0x64)
709707
if (taskRef.lightNode->modelId().startsWith(QLatin1String("lumi.curtain")) )
710708
{
711709
moveToPct = 100 - moveToPct;
712710
}
713-
//Legrand invert bri and don't support other value than 0
714711
if (taskRef.lightNode->modelId() == QLatin1String("Shutter switch with neutral"))
715712
{
716-
if (bri == 0)
717-
{
718-
moveToPct = 254;
713+
// Legrand invert bri and don't support other value than 0
714+
moveToPct = (bri == 0) ? 254 : 0;
715+
}
716+
if (taskRef.lightNode->modelId().startsWith(QLatin1String("lumi.curtain"))) // FIXME - used for testing.
717+
// if (taskRef.lightNode->modelId().startsWith(QLatin1String("lumi.curtain.hagl04")))
718+
{
719+
float value = moveToPct;
720+
TaskItem task;
721+
copyTaskReq(taskRef, task);
722+
723+
// FIXME: The following low-level code is needed because ZclAttribute is broken for ZclSingleFloat.
724+
725+
task.taskType = TaskWriteAttribute;
726+
727+
task.req.setClusterId(ANALOG_OUTPUT_CLUSTER_ID);
728+
task.req.setProfileId(HA_PROFILE_ID);
729+
task.zclFrame.setSequenceNumber(zclSeq++);
730+
task.zclFrame.setCommandId(deCONZ::ZclWriteAttributesId);
731+
task.zclFrame.setFrameControl(deCONZ::ZclFCProfileCommand |
732+
deCONZ::ZclFCDirectionClientToServer |
733+
deCONZ::ZclFCDisableDefaultResponse);
734+
735+
DBG_Printf(DBG_INFO, "write attribute of 0x%016llX ep: 0x%02X cluster: 0x%04X: 0x%04X\n", taskRef.lightNode->address().ext(), taskRef.lightNode->haEndpoint().endpoint(), ANALOG_OUTPUT_CLUSTER_ID, 0x0055);
736+
737+
{ // payload
738+
QDataStream stream(&task.zclFrame.payload(), QIODevice::WriteOnly);
739+
stream.setByteOrder(QDataStream::LittleEndian);
740+
stream.setFloatingPointPrecision(QDataStream::SinglePrecision);
741+
742+
stream << (quint16) 0x0055;
743+
stream << (quint8) deCONZ::ZclSingleFloat;
744+
stream << value;
719745
}
720-
else
721-
{
722-
moveToPct = 0;
746+
747+
{ // ZCL frame
748+
QDataStream stream(&task.req.asdu(), QIODevice::WriteOnly);
749+
stream.setByteOrder(QDataStream::LittleEndian);
750+
task.zclFrame.writeToStream(stream);
723751
}
752+
753+
ok = addTask(task);
754+
755+
// FIXME: Use following code once ZclAttribute has been fixed.
756+
757+
// deCONZ::ZclAttribute attr(0x0055, deCONZ::ZclSingleFloat, "value", deCONZ::ZclReadWrite, true);
758+
// attr.setValue(QVariant(value));
759+
// ok = writeAttribute(taskRef.lightNode, taskRef.lightNode->haEndpoint().endpoint(), ANALOG_OUTPUT_CLUSTER_ID, attr);
724760
}
725-
if (addTaskWindowCovering(task, 0x05 /*move to Lift Percent*/, 0, moveToPct))
726-
{
727-
QVariantMap rspItem;
728-
QVariantMap rspItemState;
729-
rspItemState[QString("/lights/%1/state/bri").arg(id)] = map["bri"];
730-
rspItem["success"] = rspItemState;
731-
rsp.list.append(rspItem);
732-
taskToLocalData(task);
733-
}
734-
else
735-
{
736-
rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY)));
737-
}
738-
}
761+
else
762+
{
763+
ok = addTaskWindowCovering(task, 0x05 /*move to Lift Percent*/, 0, moveToPct);
764+
}
765+
if (ok)
766+
{
767+
QVariantMap rspItem;
768+
QVariantMap rspItemState;
769+
rspItemState[QString("/lights/%1/state/bri").arg(id)] = map["bri"];
770+
rspItem["success"] = rspItemState;
771+
rsp.list.append(rspItem);
772+
taskToLocalData(task);
773+
}
774+
else
775+
{
776+
rsp.list.append(errorToMap(ERR_INTERNAL_ERROR, QString("/lights/%1").arg(id), QString("Internal error, %1").arg(ERR_BRIDGE_BUSY)));
777+
}
778+
}
739779
} // FIXME end workaround window_covering
740780
else if (!isOn && !hasOn)
741781
{

0 commit comments

Comments
 (0)