[avmfritz] Add support for groups of blinds (#19513)

* Added support for groups of blinds

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
This commit is contained in:
Christoph Weitkamp
2025-10-21 17:50:36 +02:00
committed by GitHub
parent b93e7a01e3
commit 62a0a0580b
10 changed files with 101 additions and 29 deletions
@@ -113,10 +113,11 @@ The FRITZ!Box has to run at least on firmware FRITZ!OS 7.
### FRITZ! Groups
The FRITZ!OS supports two different types of groups.
On the one hand there are groups for heating thermostats on the other hand there are groups for switchable outlets and power meters.
The FRITZ!OS supports three different types of groups.
On the one hand there are groups for heating thermostats on the other hand there are groups for switchable outlets and power meters, a third group supports control of roller shutters and blinds.
The first one provides the same channels and actions like the [FRITZ!DECT 302 / FRITZ!DECT 301 / FRITZ!DECT 300 / Comet DECT](https://www.openhab.org/addons/bindings/avmfritz/#fritz-dect-302-fritz-dect-301-fritz-dect-300-comet-dect) devices.
The latter provides the same channels like the [FRITZ!DECT 200 / FRITZ!DECT 210](https://www.openhab.org/addons/bindings/avmfritz/#fritz-dect-200-fritz-dect-210) / [FRITZ!Powerline 546E](https://www.openhab.org/addons/bindings/avmfritz/#fritz-powerline-546e) devices.
A blind group provides the `rollershutter` channel similar to a `HAN_FUN_BLINDS` thing (e.g. [Becker BoxCTRL](https://becker-antriebe.shop/)).
The FRITZ!Box has to run at least on firmware FRITZ!OS 6.69.
## Discovery
@@ -66,6 +66,7 @@ public class AVMFritzBindingConstants {
public static final String DEVICE_SMART_ENERGY_250 = "FRITZ_Smart_Energy_250";
// List of main group types
public static final String GROUP_BLINDS = "FRITZ_GROUP_BLINDS";
public static final String GROUP_HEATING = "FRITZ_GROUP_HEATING";
public static final String GROUP_SWITCH = "FRITZ_GROUP_SWITCH";
@@ -92,6 +93,7 @@ public class AVMFritzBindingConstants {
DEVICE_HAN_FUN_COLOR_BULB);
public static final ThingTypeUID HAN_FUN_DIMMABLE_BULB_THING_TYPE = new ThingTypeUID(BINDING_ID,
DEVICE_HAN_FUN_DIMMABLE_BULB);
public static final ThingTypeUID GROUP_BLINDS_THING_TYPE = new ThingTypeUID(BINDING_ID, GROUP_BLINDS);
public static final ThingTypeUID GROUP_HEATING_THING_TYPE = new ThingTypeUID(BINDING_ID, GROUP_HEATING);
public static final ThingTypeUID GROUP_SWITCH_THING_TYPE = new ThingTypeUID(BINDING_ID, GROUP_SWITCH);
public static final ThingTypeUID HAN_FUN_SENSOR_THING_TYPE = new ThingTypeUID(BINDING_ID, DEVICE_HAN_FUN_SENSOR);
@@ -208,8 +210,8 @@ public class AVMFritzBindingConstants {
POWERLINE546E_THING_TYPE, HAN_FUN_CONTACT_THING_TYPE, HAN_FUN_ON_OFF_THING_TYPE, HAN_FUN_BLINDS_THING_TYPE,
HAN_FUN_SENSOR_THING_TYPE, HAN_FUN_HOST_THING_TYPE, SMART_ENERGY_250_THING_TYPE);
public static final Set<ThingTypeUID> SUPPORTED_GROUP_THING_TYPES_UIDS = Set.of(GROUP_HEATING_THING_TYPE,
GROUP_SWITCH_THING_TYPE);
public static final Set<ThingTypeUID> SUPPORTED_GROUP_THING_TYPES_UIDS = Set.of(GROUP_BLINDS_THING_TYPE,
GROUP_HEATING_THING_TYPE, GROUP_SWITCH_THING_TYPE);
public static final Set<ThingTypeUID> SUPPORTED_BRIDGE_THING_TYPES_UIDS = Set.of(BRIDGE_THING_TYPE,
POWERLINE546E_STANDALONE_THING_TYPE);
@@ -111,6 +111,17 @@ public abstract class AVMFritzBaseModel implements BatteryModel {
@XmlElement(name = "hkr")
private HeatingModel heatingModel;
@XmlElement(name = "levelcontrol")
private @Nullable LevelControlModel levelControlModel;
public SwitchModel getSwitch() {
return switchModel;
}
public void setSwitch(SwitchModel switchModel) {
this.switchModel = switchModel;
}
public @Nullable SimpleOnOffModel getSimpleOnOffUnit() {
return simpleOnOffUnit;
}
@@ -131,12 +142,12 @@ public abstract class AVMFritzBaseModel implements BatteryModel {
this.heatingModel = heatingModel;
}
public SwitchModel getSwitch() {
return switchModel;
public @Nullable LevelControlModel getLevelControlModel() {
return levelControlModel;
}
public void setSwitch(SwitchModel switchModel) {
this.switchModel = switchModel;
public void setLevelcontrol(LevelControlModel levelControlModel) {
this.levelControlModel = levelControlModel;
}
public String getIdentifier() {
@@ -265,7 +276,8 @@ public abstract class AVMFritzBaseModel implements BatteryModel {
.append(productName).append(",fwversion=").append(firmwareVersion).append(",present=").append(present)
.append(",name=").append(name).append(",battery=").append(getBattery()).append(",batterylow=")
.append(getBatterylow()).append(",").append(getSwitch()).append(",").append(getSimpleOnOffUnit())
.append(",").append(getPowerMeter()).append(",").append(getHkr()).append(",").toString();
.append(",").append(getPowerMeter()).append(",").append(getHkr()).append(",").append(levelControlModel)
.append(",").toString();
}
public transient boolean isLinked;
@@ -34,9 +34,6 @@ public class DeviceModel extends AVMFritzBaseModel {
private HumidityModel humidity;
private AlertModel alert;
@XmlElement(name = "levelcontrol")
private LevelControlModel levelControlModel;
@XmlElement(name = "colorcontrol")
private ColorControlModel colorControlModel;
@@ -69,14 +66,6 @@ public class DeviceModel extends AVMFritzBaseModel {
this.alert = alertModel;
}
public LevelControlModel getLevelControlModel() {
return levelControlModel;
}
public void setLevelControlModel(LevelControlModel levelControlModel) {
this.levelControlModel = levelControlModel;
}
public ColorControlModel getColorControlModel() {
return colorControlModel;
}
@@ -104,8 +93,8 @@ public class DeviceModel extends AVMFritzBaseModel {
@Override
public String toString() {
return new StringBuilder(super.toString()).append(temperature).append(",").append(humidity).append(",")
.append(alert).append(",").append(levelControlModel).append(",").append(colorControlModel).append(",")
.append(getButtons()).append(",").append(etsiunitinfo).append("]").toString();
.append(alert).append(",").append(colorControlModel).append(",").append(getButtons()).append(",")
.append(etsiunitinfo).append("]").toString();
}
@XmlAccessorType(XmlAccessType.FIELD)
@@ -323,7 +323,9 @@ public abstract class AVMFritzBaseBridgeHandler extends BaseBridgeHandler {
*/
public String getThingTypeId(AVMFritzBaseModel device) {
if (device instanceof GroupModel) {
if (device.isHeatingThermostat()) {
if (device.isHANFUNBlinds()) {
return GROUP_BLINDS;
} else if (device.isHeatingThermostat()) {
return GROUP_HEATING;
} else if (device.isSwitchableOutlet()) {
return GROUP_SWITCH;
@@ -145,6 +145,9 @@ public abstract class AVMFritzBaseThingHandler extends BaseThingHandler implemen
if (device.isHeatingThermostat()) {
updateHeatingThermostat(device.getHkr());
}
if (device.isHANFUNBlinds()) {
updateLevelControl(device.getLevelControlModel());
}
if (device instanceof DeviceModel deviceModel) {
if (deviceModel.isTemperatureSensor()) {
updateTemperatureSensor(deviceModel.getTemperature());
@@ -159,9 +162,7 @@ public abstract class AVMFritzBaseThingHandler extends BaseThingHandler implemen
updateHANFUNAlarmSensor(deviceModel.getAlert());
}
}
if (deviceModel.isHANFUNBlinds()) {
updateLevelControl(deviceModel.getLevelControlModel());
} else if (deviceModel.isColorLight()) {
if (deviceModel.isColorLight()) {
updateColorLight(deviceModel.getColorControlModel(), deviceModel.getLevelControlModel(),
deviceModel.getSimpleOnOffUnit());
} else if (deviceModel.isDimmableLight() && !deviceModel.isHANFUNBlinds()) {
@@ -473,7 +474,7 @@ public abstract class AVMFritzBaseThingHandler extends BaseThingHandler implemen
} else if (command instanceof OnOffType) {
fritzBox.setSwitch(ain, OnOffType.ON.equals(command));
} else if (command instanceof IncreaseDecreaseType) {
brightness = ((DeviceModel) currentDevice).getLevelControlModel().getLevelPercentage();
brightness = currentDevice.getLevelControlModel().getLevelPercentage();
if (IncreaseDecreaseType.INCREASE.equals(command)) {
brightness.add(BigDecimal.TEN);
} else {
@@ -47,6 +47,8 @@ thing-type.avmfritz.FRITZ_DECT_500.label = FRITZ!DECT 500
thing-type.avmfritz.FRITZ_DECT_500.description = FRITZ!DECT500 color light.
thing-type.avmfritz.FRITZ_DECT_Repeater_100.label = FRITZ!DECT Repeater 100
thing-type.avmfritz.FRITZ_DECT_Repeater_100.description = FRITZ!DECT Repeater 100 DECT repeater.
thing-type.avmfritz.FRITZ_GROUP_BLINDS.label = Blinds Group
thing-type.avmfritz.FRITZ_GROUP_BLINDS.description = Group for blinds.
thing-type.avmfritz.FRITZ_GROUP_HEATING.label = Heating Group
thing-type.avmfritz.FRITZ_GROUP_HEATING.description = Group for heating thermostats.
thing-type.avmfritz.FRITZ_GROUP_HEATING.channel.actual_temp.label = Current Temperature
@@ -489,6 +489,24 @@
</thing-type>
<!-- Supported FRITZ! groups and features -->
<thing-type id="FRITZ_GROUP_BLINDS">
<supported-bridge-type-refs>
<bridge-type-ref id="fritzbox"/>
<bridge-type-ref id="FRITZ_Powerline_546E_Solo"/>
</supported-bridge-type-refs>
<label>Blinds Group</label>
<description>Group for blinds.</description>
<semantic-equipment-tag>Blinds</semantic-equipment-tag>
<channels>
<channel id="rollershutter" typeId="rollershutter"/>
</channels>
<representation-property>ain</representation-property>
<config-description-ref uri="thing-type:avmfritz:fritzgroup"/>
</thing-type>
<thing-type id="FRITZ_GROUP_HEATING">
<supported-bridge-type-refs>
<bridge-type-ref id="fritzbox"/>
@@ -61,6 +61,7 @@ public class AVMFritzDeviceListModelTest {
final String xml =
"""
<devicelist version="1">\
<group synchronized="0" identifier="grpdb3190-4195D62D5" id="901" functionbitmask="331776" fwversion="1.0" manufacturer="AVM" productname=""><present>1</present><txbusy>0</txbusy><name>Schlafzimmer</name><blind><endpositionsset>1</endpositionsset><mode>manuell</mode></blind><levelcontrol><level>0</level><levelpercentage>0</levelpercentage></levelcontrol><groupinfo><masterdeviceid>0</masterdeviceid><members>2003,2004,2005</members></groupinfo></group>\
<group identifier="F0:A3:7F-900" id="20000" functionbitmask="6784" fwversion="1.0" manufacturer="AVM" productname=""><present>1</present><name>Schlafzimmer</name><switch><state>1</state><mode>manuell</mode><lock>0</lock><devicelock>0</devicelock></switch><powermeter><voltage>230051</voltage><power>0</power><energy>2087</energy></powermeter><groupinfo><masterdeviceid>17</masterdeviceid><members>17,18</members></groupinfo></group>\
<group identifier="F0:A3:7F-901" id="20001" functionbitmask="4160" fwversion="1.0" manufacturer="AVM" productname=""><present>1</present><name>Schlafzimmer</name><temperature><celsius>220</celsius><offset>-10</offset></temperature><hkr><tist>44</tist><tsoll>42</tsoll><absenk>28</absenk><komfort>42</komfort><lock>1</lock><devicelock>1</devicelock><errorcode>0</errorcode><windowopenactiv>0</windowopenactiv><windowopenactiveendtime>0</windowopenactiveendtime><boostactive>0</boostactive><boostactiveendtime>0</boostactiveendtime><batterylow>0</batterylow><battery>100</battery><nextchange><endperiod>1484341200</endperiod><tchange>28</tchange></nextchange></hkr><groupinfo><masterdeviceid>0</masterdeviceid><members>20,21,22</members></groupinfo></group>\
<device identifier="08761 0000434" id="17" functionbitmask="35712" fwversion="03.83" manufacturer="AVM" productname="FRITZ!DECT 200"><present>1</present><name>FRITZ!DECT 200 #1</name><switch><state>1</state><mode>manuell</mode><lock>0</lock><devicelock>0</devicelock></switch><powermeter><voltage>230051</voltage><power>0</power><energy>2087</energy></powermeter><temperature><celsius>255</celsius><offset>0</offset></temperature></device>\
@@ -166,7 +167,7 @@ public class AVMFritzDeviceListModelTest {
@Test
public void validateDeviceListModel() {
assertNotNull(devices);
assertEquals(19, devices.getDevicelist().size());
assertEquals(20, devices.getDevicelist().size());
assertEquals("1", devices.getXmlApiVersion());
}
@@ -916,6 +917,49 @@ public class AVMFritzDeviceListModelTest {
assertNull(device.getLevelControlModel());
}
@Test
public void validateBlindsGroupModel() {
Optional<AVMFritzBaseModel> optionalGroup = findModelByIdentifier("grpdb3190-4195D62D5");
assertTrue(optionalGroup.isPresent());
assertTrue(optionalGroup.get() instanceof GroupModel);
GroupModel group = (GroupModel) optionalGroup.get();
assertEquals("", group.getProductName());
assertEquals("grpdb3190-4195D62D5", group.getIdentifier());
assertEquals("901", group.getDeviceId());
assertEquals("1.0", group.getFirmwareVersion());
assertEquals("AVM", group.getManufacturer());
assertEquals(1, group.getPresent());
assertEquals("Schlafzimmer", group.getName());
assertFalse(group.isButton());
assertFalse(group.isHANFUNButton());
assertFalse(group.isHANFUNAlarmSensor());
assertFalse(group.isDectRepeater());
assertFalse(group.isSwitchableOutlet());
assertFalse(group.isTemperatureSensor());
assertFalse(group.isHumiditySensor());
assertFalse(group.isPowerMeter());
assertFalse(group.isHeatingThermostat());
assertTrue(group.isHANFUNBlinds());
assertNull(group.getSwitch());
assertNull(group.getPowerMeter());
assertNull(group.getHkr());
LevelControlModel levelcontrol = group.getLevelControlModel();
assertNotNull(levelcontrol);
assertEquals(BigDecimal.valueOf(0L), levelcontrol.getLevel());
assertEquals(BigDecimal.valueOf(0L), levelcontrol.getLevelPercentage());
assertNotNull(group.getGroupinfo());
assertEquals("0", group.getGroupinfo().getMasterdeviceid());
assertEquals("2003,2004,2005", group.getGroupinfo().getMembers());
}
@Test
public void validateHeatingGroupModel() {
Optional<AVMFritzBaseModel> optionalGroup = findModelByIdentifier("F0:A3:7F-901");
@@ -95,7 +95,7 @@ public class AVMFritzDiscoveryServiceOSGiTest extends AVMFritzThingHandlerOSGiTe
@Test
public void correctSupportedTypes() {
assertEquals(22, discovery.getSupportedThingTypes().size());
assertEquals(23, discovery.getSupportedThingTypes().size());
assertTrue(discovery.getSupportedThingTypes().contains(DECT100_THING_TYPE));
assertTrue(discovery.getSupportedThingTypes().contains(DECT200_THING_TYPE));
assertTrue(discovery.getSupportedThingTypes().contains(DECT210_THING_TYPE));
@@ -115,6 +115,7 @@ public class AVMFritzDiscoveryServiceOSGiTest extends AVMFritzThingHandlerOSGiTe
assertTrue(discovery.getSupportedThingTypes().contains(HAN_FUN_DIMMABLE_BULB_THING_TYPE));
assertTrue(discovery.getSupportedThingTypes().contains(HAN_FUN_SENSOR_THING_TYPE));
assertTrue(discovery.getSupportedThingTypes().contains(HAN_FUN_HOST_THING_TYPE));
assertTrue(discovery.getSupportedThingTypes().contains(GROUP_BLINDS_THING_TYPE));
assertTrue(discovery.getSupportedThingTypes().contains(GROUP_HEATING_THING_TYPE));
assertTrue(discovery.getSupportedThingTypes().contains(GROUP_SWITCH_THING_TYPE));
assertTrue(discovery.getSupportedThingTypes().contains(SMART_ENERGY_250_THING_TYPE));