Skip to content
Closed
128 changes: 128 additions & 0 deletions tests/test_schneiderelectric.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,134 @@ async def test_1gang_shutter_1_lift_percentage_updates(zigpy_device_from_quirk):
assert len(cluster_listener.cluster_commands) == 0


def test_nhpb_shutter_1_signature(assert_signature_matches_quirk):
"""Test signature."""
signature = {
"node_descriptor": (
"NodeDescriptor(logical_type=<LogicalType.Router: 1>, "
"complex_descriptor_available=0, user_descriptor_available=0, reserved=0, "
"aps_flags=0, frequency_band=<FrequencyBand.Freq2400MHz: 8>, "
"mac_capability_flags=<MACCapabilityFlags.FullFunctionDevice|MainsPowered"
"|RxOnWhenIdle|AllocateAddress: 142>, manufacturer_code=4190, "
"maximum_buffer_size=82, maximum_incoming_transfer_size=82, "
"server_mask=10752, maximum_outgoing_transfer_size=82, "
"descriptor_capability_field=<DescriptorCapability.NONE: 0>, "
"*allocate_address=True, *is_alternate_pan_coordinator=False, "
"*is_coordinator=False, *is_end_device=False, "
"*is_full_function_device=True, *is_mains_powered=True, "
"*is_receiver_on_when_idle=True, *is_router=True, "
"*is_security_capable=False)"
),
"endpoints": {
"5": {
"profile_id": 0x0104,
"device_type": "0x0202",
"in_clusters": [
"0x0000",
"0x0003",
"0x0004",
"0x0005",
"0x0102",
"0x0b05",
],
"out_clusters": ["0x0019"],
},
"21": {
"profile_id": 0x0104,
"device_type": "0x0104",
"in_clusters": [
"0x0000",
"0x0003",
"0x0b05",
"0xff17",
],
"out_clusters": [
"0x0003",
"0x0004",
"0x0005",
"0x0006",
"0x0008",
"0x0102",
],
},
"242": {
"profile_id": 0xA1E0,
"device_type": "0x0061",
"in_clusters": [],
"out_clusters": [
"0x0021",
],
},
},
"manufacturer": "Schneider Electric",
"model": "NHPB/SHUTTER/1",
"class": "zigpy.device.Device",
}
assert_signature_matches_quirk(
zhaquirks.schneiderelectric.shutters.NhpbShutter1, signature
)


async def test_nhpb_shutter_1_go_to_lift_percentage_cmd(zigpy_device_from_quirk):
"""Asserts that the go_to_lift_percentage command inverts the percentage value."""

device = zigpy_device_from_quirk(zhaquirks.schneiderelectric.shutters.NhpbShutter1)
window_covering_cluster = device.endpoints[5].window_covering

p = mock.patch.object(window_covering_cluster, "request", mock.AsyncMock())
with p as request_mock:
request_mock.return_value = (foundation.Status.SUCCESS, "done")

await window_covering_cluster.go_to_lift_percentage(58)

assert request_mock.call_count == 1
assert request_mock.call_args[0][1] == (
WindowCovering.ServerCommandDefs.go_to_lift_percentage.id
)
assert request_mock.call_args[0][3] == 42 # 100 - 58


async def test_nhpb_shutter_1_unpatched_cmd(zigpy_device_from_quirk):
"""Asserts that unpatched ZCL commands keep working."""

device = zigpy_device_from_quirk(zhaquirks.schneiderelectric.shutters.NhpbShutter1)
window_covering_cluster = device.endpoints[5].window_covering

p = mock.patch.object(window_covering_cluster, "request", mock.AsyncMock())
with p as request_mock:
request_mock.return_value = (foundation.Status.SUCCESS, "done")

await window_covering_cluster.up_open()

assert request_mock.call_count == 1
assert request_mock.call_args[0][1] == (
WindowCovering.ServerCommandDefs.up_open.id
)


async def test_nhpb_shutter_1_lift_percentage_updates(zigpy_device_from_quirk):
"""Asserts that updates to the ``current_position_lift_percentage`` attribute.

(e.g., by the device) invert the reported percentage value.
"""

device = zigpy_device_from_quirk(zhaquirks.schneiderelectric.shutters.NhpbShutter1)
window_covering_cluster = device.endpoints[5].window_covering
cluster_listener = ClusterListener(window_covering_cluster)

window_covering_cluster.update_attribute(
WindowCovering.AttributeDefs.current_position_lift_percentage.id,
77,
)

assert len(cluster_listener.attribute_updates) == 1
assert cluster_listener.attribute_updates[0] == (
WindowCovering.AttributeDefs.current_position_lift_percentage.id,
23, # 100 - 77
)
assert len(cluster_listener.cluster_commands) == 0


@pytest.mark.parametrize("quirk", (zhaquirks.schneiderelectric.outlet.SocketOutlet,))
async def test_schneider_device_temp(zigpy_device_from_quirk, quirk):
"""Test that instant demand is divided by 1000."""
Expand Down
18 changes: 9 additions & 9 deletions zhaquirks/schneiderelectric/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,47 +180,47 @@ class SEWindowCovering(CustomCluster, WindowCovering):
class AttributeDefs(WindowCovering.AttributeDefs):
"""Attribute definitions."""

unknown_attribute_65533: Final = ZCLAttributeDef(
se_cluster_revision: Final = ZCLAttributeDef(
id=0xFFFD,
type=t.uint16_t,
is_manufacturer_specific=True,
)
lift_duration: Final = ZCLAttributeDef(
se_drive_close_duration: Final = ZCLAttributeDef(
id=0xE000,
type=t.uint16_t,
is_manufacturer_specific=True,
)
unknown_attribute_57360: Final = ZCLAttributeDef(
se_protection_status: Final = ZCLAttributeDef(
id=0xE010,
type=t.bitmap8,
is_manufacturer_specific=True,
)
unknown_attribute_57362: Final = ZCLAttributeDef(
se_sun_protection_illuminance_threshold: Final = ZCLAttributeDef(
id=0xE012,
type=t.uint16_t,
is_manufacturer_specific=True,
)
unknown_attribute_57363: Final = ZCLAttributeDef(
se_protection_sensor: Final = ZCLAttributeDef(
id=0xE013,
type=t.bitmap8,
is_manufacturer_specific=True,
)
unknown_attribute_57364: Final = ZCLAttributeDef(
se_lift_drive_up_time: Final = ZCLAttributeDef(
id=0xE014,
type=t.uint16_t,
is_manufacturer_specific=True,
)
unknown_attribute_57365: Final = ZCLAttributeDef(
se_lift_drive_down_time: Final = ZCLAttributeDef(
id=0xE015,
type=t.uint16_t,
is_manufacturer_specific=True,
)
unknown_attribute_57366: Final = ZCLAttributeDef(
se_tilt_open_close_and_step_time: Final = ZCLAttributeDef(
id=0xE016,
type=t.uint16_t,
is_manufacturer_specific=True,
)
unknown_attribute_57367: Final = ZCLAttributeDef(
se_tilt_position_percentage_after_move_to_level: Final = ZCLAttributeDef(
id=0xE017,
type=t.uint8_t,
is_manufacturer_specific=True,
Expand Down
110 changes: 109 additions & 1 deletion zhaquirks/schneiderelectric/shutters.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"""Quirks for Schneider Electric shutters."""

from zigpy.profiles import zha
from zigpy.profiles import zgp, zha
from zigpy.quirks import CustomDevice
from zigpy.zcl.clusters.closures import WindowCovering
from zigpy.zcl.clusters.general import (
Basic,
GreenPowerProxy,
Groups,
Identify,
LevelControl,
Expand Down Expand Up @@ -115,3 +116,110 @@ class OneGangShutter1(CustomDevice):
},
}
}


class NhpbShutter1(CustomDevice):
"""NHPB/SHUTTER/1 from Schneider Electric."""

signature = {
MODELS_INFO: [
(SE_MANUF_NAME, "NHPB/SHUTTER/1"),
],
ENDPOINTS: {
# <SimpleDescriptor endpoint=5, profile=260, device_type=514,
# device_version=0,
# input_clusters=[0, 3, 4, 5, 258, 2821],
# output_clusters=[25]>
5: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.WINDOW_COVERING_DEVICE,
INPUT_CLUSTERS: [
Basic.cluster_id,
Identify.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
WindowCovering.cluster_id,
Diagnostic.cluster_id,
],
OUTPUT_CLUSTERS: [Ota.cluster_id],
},
# <SimpleDescriptor endpoint=21, profile=260, device_type=260,
# device_version=0,
# input_clusters=[0, 3, 2821, 65303],
# output_clusters=[3, 4, 5, 6, 8, 258]>
21: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.DIMMER_SWITCH,
INPUT_CLUSTERS: [
Basic.cluster_id,
Identify.cluster_id,
Diagnostic.cluster_id,
SESpecific.cluster_id,
],
OUTPUT_CLUSTERS: [
Identify.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
OnOff.cluster_id,
LevelControl.cluster_id,
WindowCovering.cluster_id,
],
},
# <SimpleDescriptor endpoint=242, profile=41440, device_type=97,
# device_version=0,
# input_clusters=[],
# output_clusters=[33]>
242: {
PROFILE_ID: zgp.PROFILE_ID,
DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
INPUT_CLUSTERS: [],
OUTPUT_CLUSTERS: [
GreenPowerProxy.cluster_id,
],
},
},
}

replacement = {
ENDPOINTS: {
5: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.WINDOW_COVERING_DEVICE,
INPUT_CLUSTERS: [
SEBasic,
Identify.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
SEWindowCovering,
Diagnostic.cluster_id,
],
OUTPUT_CLUSTERS: [Ota.cluster_id],
},
21: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.DIMMER_SWITCH,
INPUT_CLUSTERS: [
SEBasic,
Identify.cluster_id,
Diagnostic.cluster_id,
SESpecific,
],
OUTPUT_CLUSTERS: [
Identify.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
OnOff.cluster_id,
LevelControl.cluster_id,
SEWindowCovering,
],
},
242: {
PROFILE_ID: zgp.PROFILE_ID,
DEVICE_TYPE: zgp.DeviceType.PROXY_BASIC,
INPUT_CLUSTERS: [],
OUTPUT_CLUSTERS: [
GreenPowerProxy.cluster_id,
],
},
}
}
Loading