Sonoff MINI-ZB1GP

Hello!
I just got these Zigbee power meters. Sonoff MINI-ZB1GP
I managed to pair one of them to my ZHA integration. I can see it online, but the entities that were supposed to show the power consumption and delivered energy never update. Power stays at -1 W and Summation delivered stays at 0 kWh.
On Sonoff website I found this Notice (of course it wasn't on the product page, it was hidden somewhere in Help section):
" Device access is now supported in Z2M v2.11.0. For ZHA integration, using the MINI-ZB1GSP currently requires a specific script. Please refer to: https://github.com/Koenkk/zigbee-herdsman-converters/pull/12336"
I opened that link, but I couldn't make heads or tails out of that. It is way over my understanding...
Can you help me fix this? I would really hate to switch my entire Zigbee network over to MQTT, I have over 100 devices. It would be a pain...
Thanks!

If the update is in a pull request, just watch that. It should eventually be pulled in and show up in a release.
Otherwise it is likely that there is a quirk listed in that PR, it may be possible to load that now.

The "script" they linked to is for Z2M & won't work as-is with ZHA. Basically, that link is wrong & they need to fix the link for the ZHA quirk. If they didn't create one, then you're basically out of luck until someone does.

Thank you for the clarification! I was afraid of that...
I created a ticket with Sonoff support and asked them to clarify this. I hope they can fix the link or provide me with the solution for ZHA.
I wish they wouldn't advertise the product as working with ZHA, when it is clearly not the case.
I'll post whatever reply I get from them.

Spoiler alert, I had a look at the commits from that github user. The only non-Z2M PRs were the ones raised in HA Core (not even in zha-handlers) for their coordinators. Didn't manage to find a single PR for their other devices.

You might get lucky & maybe another user from the company added PRs for the devices, but I wouldn't bet on it. Might be a good time for you to buy a second coordinator & start migrating stuff slowly over to Z2M. Your situation is exactly how I got started with it.

Yeah... This is the reply I got... :man_facepalming:

This support tech has no idea what I am talking about...

i also contacted them and got this reply:

So… good news / bad news situation here :slight_smile:
Good news: I got an update from Sonoff. They sent me script that it is for ZHA. So they say, at least.
Bad news: I can not test it, I have already switched to Z2M. :rofl:
I am attaching the file here, I hope it will help someone, someday.
Thanks!

"""Sonoff MINI-ZB1GSP - Zigbee Smart Plug."""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Final

from zigpy import types
from zigpy.quirks import CustomCluster
from zigpy.quirks.v2 import (
    EntityType,
    NumberDeviceClass,
    QuirkBuilder,
    SensorDeviceClass,
    SensorStateClass,
    EntityPlatform,
)
from zigpy.quirks.v2.homeassistant import (
    UnitOfElectricCurrent,
    UnitOfElectricPotential,
    UnitOfEnergy,
    UnitOfPower,
    UnitOfTime,
    UnitOfFrequency,
)
import zigpy.types as t
from zigpy.zcl import (
    AttributeReadEvent,
    AttributeReportedEvent,
    AttributeUpdatedEvent,
    AttributeWrittenEvent,
    foundation,
)
from zigpy.zcl.foundation import BaseAttributeDefs, Status, ZCLAttributeDef

from zhaquirks import LocalDataCluster

FAST_SCENE_PACKET_MODIFY = 0x00
FAST_SCENE_PACKET_REPORT = 0x01
FAST_SCENE_ARRAY_ITEM_TYPE = foundation.DataTypeId.uint8
FAST_SCENE_VOLTAGE_ENABLE_BIT = 0x80000000
FAST_SCENE_VOLTAGE_VALUE_MASK = 0x7FFFFFFF
PROTECTION_CURRENT_MIN_MA = 100
PROTECTION_CURRENT_MAX_MA = 16000
PROTECTION_POWER_MIN_MW = 2000
PROTECTION_POWER_MAX_MW = 3840000
PROTECTION_VOLTAGE_MIN_MV = 85000
PROTECTION_VOLTAGE_MAX_MV = 277000


def _u32_le(data: bytes) -> int:
    """Decode an unsigned little-endian 32-bit integer."""

    return int.from_bytes(data[:4], "little")


def _put_u32_le(value: int) -> list[int]:
    """Encode an unsigned little-endian 32-bit integer."""

    return list(int(value).to_bytes(4, "little"))


@dataclass
class FastSceneProtection:
    """Electrical protection fast scene."""

    scene_switch: int = 1
    over_current_ma: int = 0
    over_load_mw: int = 0
    only_ext_mode_restore: int = 0
    over_voltage_mv: int = 0
    over_voltage_en: int = 0
    under_voltage_mv: int = 0
    under_voltage_en: int = 0
    auto_recover_en: int = 0
    notify_en: int = 0


@dataclass
class FastSceneState:
    """Decoded Sonoff fast scene state."""

    packet_type: int = FAST_SCENE_PACKET_REPORT
    total_num: int = 1
    current_data_index: int = 1
    protection: FastSceneProtection = field(default_factory=FastSceneProtection)


def fast_scene_array_from_payload(payload: bytes | list[int]) -> foundation.Array:
    """Wrap a fast scene payload in a ZCL array value."""

    return foundation.Array(
        type=FAST_SCENE_ARRAY_ITEM_TYPE,
        value=t.LVList[t.uint8_t, t.uint16_t](payload),
    )


def fast_scene_payload_from_array(value: Any) -> bytes:
    """Extract the fast scene payload bytes from a decoded ZCL array value."""

    if isinstance(value, foundation.Array):
        if value.value is None:
            raise ValueError("Fast scene payload is empty")
        return bytes(value.value)
    if isinstance(value, (bytes, bytearray)):
        if len(value) >= 3 and value[0] == FAST_SCENE_ARRAY_ITEM_TYPE:
            length = int.from_bytes(value[1:3], "little")
            return bytes(value[3 : 3 + length])
        return bytes(value)
    if isinstance(value, list):
        return bytes(value)
    if isinstance(value, t.LVList):
        return bytes(value)
    raise ValueError("Unsupported fast scene payload value")


def _decode_voltage(raw_value: int) -> tuple[int, int]:
    """Split a firmware voltage threshold into value and enable flag."""

    return raw_value & FAST_SCENE_VOLTAGE_VALUE_MASK, int(
        bool(raw_value & FAST_SCENE_VOLTAGE_ENABLE_BIT)
    )


def _encode_voltage(value: int, enabled: int) -> int:
    """Combine a voltage threshold value and enable flag."""

    raw_value = value & FAST_SCENE_VOLTAGE_VALUE_MASK
    if enabled:
        raw_value |= FAST_SCENE_VOLTAGE_ENABLE_BIT
    return raw_value


def decode_fast_scene_payload(payload: bytes | list[int] | foundation.Array) -> FastSceneState:
    """Decode a Sonoff fast scene payload."""

    data = fast_scene_payload_from_array(payload)
    if len(data) < 3:
        raise ValueError("Fast scene payload is too short")

    state = FastSceneState(
        packet_type=data[0],
        total_num=data[1],
        current_data_index=data[2],
    )
    idx = 3

    while idx < len(data):
        if idx + 2 > len(data):
            if all(byte == 0 for byte in data[idx:]):
                break
            raise ValueError("Fast scene TLV header is incomplete")

        scene_type = data[idx]
        scene_len = data[idx + 1]
        idx += 2

        if idx + scene_len > len(data):
            if scene_type == 0 and all(byte == 0 for byte in data[idx - 2 :]):
                break
            raise ValueError("Fast scene TLV length exceeds payload")

        scene_data = data[idx : idx + scene_len]
        idx += scene_len

        if not scene_data:
            continue

        if scene_type == 2 and len(scene_data) >= 20:
            scene_switch = scene_data[0]
            over_voltage_mv, over_voltage_en = _decode_voltage(_u32_le(scene_data[10:14]))
            under_voltage_mv, under_voltage_en = _decode_voltage(
                _u32_le(scene_data[14:18])
            )
            state.protection = FastSceneProtection(
                scene_switch=scene_switch,
                over_current_ma=_u32_le(scene_data[1:5]),
                over_load_mw=_u32_le(scene_data[5:9]),
                only_ext_mode_restore=scene_data[9],
                over_voltage_mv=over_voltage_mv,
                over_voltage_en=over_voltage_en,
                under_voltage_mv=under_voltage_mv,
                under_voltage_en=under_voltage_en,
                auto_recover_en=scene_data[18],
                notify_en=scene_data[19],
            )

    return state


def encode_fast_scene_payload(
    state: FastSceneState,
    packet_type: int = FAST_SCENE_PACKET_MODIFY,
) -> bytes:
    """Encode a Sonoff fast scene payload."""

    payload: list[int] = [
        packet_type,
        state.total_num,
        state.current_data_index,
    ]
    protection = state.protection
    payload.extend(
        [
            0x02,
            20,
            protection.scene_switch,
            *_put_u32_le(protection.over_current_ma),
            *_put_u32_le(protection.over_load_mw),
            protection.only_ext_mode_restore,
            *_put_u32_le(
                _encode_voltage(
                    protection.over_voltage_mv, protection.over_voltage_en
                )
            ),
            *_put_u32_le(
                _encode_voltage(
                    protection.under_voltage_mv, protection.under_voltage_en
                )
            ),
            protection.auto_recover_en,
            protection.notify_en,
        ]
    )

    return bytes(payload)


class SonoffErrorCodeType(types.bitmap32):
    """Fault Code Type."""
    Normal = 0x07020000,
    Overheat = 0x07020001,
    Overload = 0x07020004,
    Overload_And_Overheat = 0x07020005,


class SonoffCluster(CustomCluster):
    """Custom Sonoff cluster."""

    cluster_id = 0xFC11
    ep_attribute = "sonoff_cluster"
    manufacturer_id_override = foundation.ZCLHeader.NO_MANUFACTURER_ID

    class AttributeDefs(BaseAttributeDefs):
        """Attribute definitions."""

        network_led = ZCLAttributeDef(
            name="network_led",
            id=0x0001,
            type=t.Bool,
        )
        fault_code = ZCLAttributeDef(
            id=0x0010,
            type=SonoffErrorCodeType,
            access="rp",
        )
        turbo_mode = ZCLAttributeDef(
            name="turbo_mode",
            id=0x0012,
            type=t.int16s,
        )
        delayed_power_on_state = ZCLAttributeDef(
            name="delayed_power_on_state",
            id=0x0014,
            type=t.Bool,
        )
        delayed_power_on_time = ZCLAttributeDef(
            name="delayed_power_on_time",
            id=0x0015,
            type=t.uint16_t,
        )
        external_trigger_mode = ZCLAttributeDef(
            name="external_trigger_mode",
            id=0x0016,
            type=t.uint8_t,
        )
        detach_relay = ZCLAttributeDef(
            name="detach_relay",
            id=0x0019,
            type=t.bitmap8,
        )
        local_fast_scene_configuration = ZCLAttributeDef(
            name="local_fast_scene_configuration",
            id=0x7016,
            type=foundation.Array,
        )
        accurrent_current_value = ZCLAttributeDef(
            name="accurrent_current_value",
            id=0x7004,
            type=t.uint32_t,
        )
        accurrent_voltage_value = ZCLAttributeDef(
            name="accurrent_voltage_value",
            id=0x7005,
            type=t.uint32_t,
        )
        accurrent_power_value = ZCLAttributeDef(
            name="accurrent_power_value",
            id=0x7006,
            type=t.uint32_t,
        )
        daily_forward_energy = ZCLAttributeDef(
            name="daily_forward_energy",
            id=0x7009,
            type=t.uint32_t,
        )
        monthly_forward_energy = ZCLAttributeDef(
            name="monthly_forward_energy",
            id=0x700A,
            type=t.uint32_t,
        )
        daily_reverse_energy = ZCLAttributeDef(
            name="daily_reverse_energy",
            id=0x7018,
            type=t.uint32_t,
        )
        monthly_reverse_energy = ZCLAttributeDef(
            name="monthly_reverse_energy",
            id=0x7019,
            type=t.uint32_t,
        )
        daily_run_time = ZCLAttributeDef(
            name="daily_run_time",
            id=0x701C,
            type=t.uint32_t,
        )
        total_run_time = ZCLAttributeDef(
            name="total_run_time",
            id=0x701D,
            type=t.uint32_t,
        )
        total_forward_energy = ZCLAttributeDef(
            name="total_forward_energy",
            id=0x701E,
            type=t.uint32_t,
        )
        total_reverse_energy = ZCLAttributeDef(
            name="total_reverse_energy",
            id=0x701F,
            type=t.uint32_t,
        )
        voltage_frequency = ZCLAttributeDef(
            name="voltage_frequency",
            id=0x7029,
            type=t.uint32_t,
        )

    def __init__(self, *args, **kwargs):
        """Init and listen for fast scene attribute changes."""
        super().__init__(*args, **kwargs)
        self._fast_scene_state = FastSceneState()
        self.on_event(AttributeReadEvent.event_type, self._handle_fast_scene_change)
        self.on_event(AttributeReportedEvent.event_type, self._handle_fast_scene_change)
        self.on_event(AttributeUpdatedEvent.event_type, self._handle_fast_scene_change)
        self.on_event(AttributeWrittenEvent.event_type, self._handle_fast_scene_change)

    def _handle_fast_scene_change(
        self,
        event: AttributeReadEvent
        | AttributeReportedEvent
        | AttributeUpdatedEvent
        | AttributeWrittenEvent,
    ) -> None:
        """Sync fast scene state to the local config cluster."""
        if isinstance(event, AttributeWrittenEvent) and event.status != Status.SUCCESS:
            return
        if event.attribute_id != self.AttributeDefs.local_fast_scene_configuration.id:
            return

        values = [event.value]
        if isinstance(event, AttributeReadEvent) and event.raw_value is not event.value:
            values.append(event.raw_value)

        for value in values:
            try:
                self._fast_scene_state = decode_fast_scene_payload(value)
                break
            except (TypeError, ValueError):
                continue
        else:
            return

        if hasattr(self.endpoint, "sonoff_fast_scene_config"):
            self.endpoint.sonoff_fast_scene_config.update_fast_scene_state(
                self._fast_scene_state
            )

    async def apply_custom_configuration(self, *args, **kwargs):
        """Read fast scene configuration during pairing to populate entities."""
        await self.read_attributes([self.AttributeDefs.local_fast_scene_configuration.id])

    @property
    def _is_manuf_specific(self):
        return False


class SonoffFastSceneConfigCluster(LocalDataCluster):
    """Local cluster for individual fast scene configuration entities."""

    cluster_id = 0xFBFD
    ep_attribute = "sonoff_fast_scene_config"

    class AttributeDefs(BaseAttributeDefs):
        """Attribute definitions."""

        protection_over_current_ma: Final = ZCLAttributeDef(
            id=0x0010, type=t.uint32_t
        )
        protection_over_load_mw: Final = ZCLAttributeDef(id=0x0011, type=t.uint32_t)
        protection_only_ext_mode_restore: Final = ZCLAttributeDef(
            id=0x0012, type=t.Bool
        )
        protection_over_voltage_mv: Final = ZCLAttributeDef(
            id=0x0013, type=t.uint32_t
        )
        protection_over_voltage_enabled: Final = ZCLAttributeDef(
            id=0x0014, type=t.Bool
        )
        protection_under_voltage_mv: Final = ZCLAttributeDef(
            id=0x0015, type=t.uint32_t
        )
        protection_under_voltage_enabled: Final = ZCLAttributeDef(
            id=0x0016, type=t.Bool
        )
        protection_auto_recover: Final = ZCLAttributeDef(id=0x0017, type=t.Bool)
        protection_notify: Final = ZCLAttributeDef(id=0x0018, type=t.Bool)

    def __init__(self, *args, **kwargs):
        """Init with conservative default fast scene state."""
        super().__init__(*args, **kwargs)
        self._fast_scene_state = FastSceneState()

    def update_fast_scene_state(self, state: FastSceneState) -> None:
        """Update local attributes from decoded fast scene state."""
        self._fast_scene_state = state
        protection = state.protection

        updates = {
            self.AttributeDefs.protection_over_current_ma.id: protection.over_current_ma,
            self.AttributeDefs.protection_over_load_mw.id: protection.over_load_mw,
            self.AttributeDefs.protection_only_ext_mode_restore.id: bool(
                protection.only_ext_mode_restore
            ),
            self.AttributeDefs.protection_over_voltage_mv.id: protection.over_voltage_mv,
            self.AttributeDefs.protection_over_voltage_enabled.id: bool(
                protection.over_voltage_en
            ),
            self.AttributeDefs.protection_under_voltage_mv.id: protection.under_voltage_mv,
            self.AttributeDefs.protection_under_voltage_enabled.id: bool(
                protection.under_voltage_en
            ),
            self.AttributeDefs.protection_auto_recover.id: bool(
                protection.auto_recover_en
            ),
            self.AttributeDefs.protection_notify.id: bool(protection.notify_en),
        }
        for attr_id, value in updates.items():
            self._update_attribute(attr_id, value)

    async def write_attributes(
        self,
        attributes: dict[str | int | ZCLAttributeDef, Any],
        **kwargs,
    ) -> list:
        """Merge local writes into the real fast scene aggregate attribute."""
        state = self.endpoint.sonoff_cluster._fast_scene_state

        for attr, value in attributes.items():
            attr_def = self.find_attribute(attr)
            attr_id = attr_def.id
            if attr_id == self.AttributeDefs.protection_over_current_ma.id:
                state.protection.over_current_ma = int(value)
            elif attr_id == self.AttributeDefs.protection_over_load_mw.id:
                state.protection.over_load_mw = int(value)
            elif attr_id == self.AttributeDefs.protection_only_ext_mode_restore.id:
                state.protection.only_ext_mode_restore = int(bool(value))
            elif attr_id == self.AttributeDefs.protection_over_voltage_mv.id:
                state.protection.over_voltage_mv = int(value)
            elif attr_id == self.AttributeDefs.protection_over_voltage_enabled.id:
                state.protection.over_voltage_en = int(bool(value))
            elif attr_id == self.AttributeDefs.protection_under_voltage_mv.id:
                state.protection.under_voltage_mv = int(value)
            elif attr_id == self.AttributeDefs.protection_under_voltage_enabled.id:
                state.protection.under_voltage_en = int(bool(value))
            elif attr_id == self.AttributeDefs.protection_auto_recover.id:
                state.protection.auto_recover_en = int(bool(value))
            elif attr_id == self.AttributeDefs.protection_notify.id:
                state.protection.notify_en = int(bool(value))

        payload = encode_fast_scene_payload(state)
        zcl_array = fast_scene_array_from_payload(payload)
        result = await self.endpoint.sonoff_cluster.write_attributes(
            {
                SonoffCluster.AttributeDefs.local_fast_scene_configuration.id: zcl_array
            }
        )
        if self._write_succeeded(result):
            self.update_fast_scene_state(state)
        return result

    @staticmethod
    def _write_succeeded(result: list) -> bool:
        """Return whether a Zigpy write_attributes response succeeded."""
        try:
            records = result[0]
        except (IndexError, TypeError):
            return False
        return all(record.status == Status.SUCCESS for record in records)


class SonoffNetworkLedSetType(types.enum8):
    """network led set type."""

    Off = 0x00
    On = 0x01

class SonoffExternalTriggerMode(types.enum8):
    """External trigger mode attribute values."""

    Edge_trigger = 0x00
    Pulse_trigger = 0x01
    Normally_off_follow_trigger = 0x02
    Normally_on_follow_trigger = 0x82


(
    QuirkBuilder("SONOFF", "MINI-ZB1GSP")
    .replaces(SonoffCluster)
    .adds(SonoffFastSceneConfigCluster)
    .removes(0x0B04)
    .removes(0x0702)
    .enum(
        SonoffCluster.AttributeDefs.network_led.name,
        SonoffNetworkLedSetType,
        SonoffCluster.cluster_id,
        translation_key="network_led",
        fallback_name="Network led",
    )
    .switch(
        SonoffCluster.AttributeDefs.turbo_mode.name,
        SonoffCluster.cluster_id,
        off_value=9,
        on_value=20,
        translation_key="turbo_mode",
        fallback_name="Turbo mode",
    )
    .switch(
        SonoffCluster.AttributeDefs.delayed_power_on_state.name,
        SonoffCluster.cluster_id,
        off_value=0,
        on_value=1,
        translation_key="delayed_power_on_state",
        fallback_name="Delayed power on state",
    )
    .number(
        SonoffCluster.AttributeDefs.delayed_power_on_time.name,
        SonoffCluster.cluster_id,
        min_value=0.5,
        max_value=3599.5,
        step=0.5,
        device_class=NumberDeviceClass.DURATION,
        unit=UnitOfTime.SECONDS,
        multiplier=0.5,
        translation_key="delayed_power_on_time",
        fallback_name="Delayed power on time",
    )
    .enum(
        SonoffCluster.AttributeDefs.external_trigger_mode.name,
        SonoffExternalTriggerMode,
        SonoffCluster.cluster_id,
        translation_key="external_trigger_mode",
        fallback_name="External trigger mode",
    )
    .switch(
        SonoffCluster.AttributeDefs.detach_relay.name,
        SonoffCluster.cluster_id,
        off_value=0,
        on_value=1,
        translation_key="detach_relay",
        fallback_name="Detach relay",
    )
    .number(
        SonoffFastSceneConfigCluster.AttributeDefs.protection_over_current_ma.name,
        SonoffFastSceneConfigCluster.cluster_id,
        min_value=PROTECTION_CURRENT_MIN_MA,
        max_value=PROTECTION_CURRENT_MAX_MA,
        step=1,
        entity_type=EntityType.CONFIG,
        unit="mA",
        translation_key="protection_over_current_ma",
        fallback_name="Protection over-current threshold",
    )
    .number(
        SonoffFastSceneConfigCluster.AttributeDefs.protection_over_load_mw.name,
        SonoffFastSceneConfigCluster.cluster_id,
        min_value=PROTECTION_POWER_MIN_MW,
        max_value=PROTECTION_POWER_MAX_MW,
        step=1,
        entity_type=EntityType.CONFIG,
        unit="mW",
        translation_key="protection_over_load_mw",
        fallback_name="Protection overload threshold",
    )
    .switch(
        SonoffFastSceneConfigCluster.AttributeDefs.protection_only_ext_mode_restore.name,
        SonoffFastSceneConfigCluster.cluster_id,
        entity_type=EntityType.CONFIG,
        translation_key="protection_only_ext_mode_restore",
        fallback_name="Protection external switch restore",
    )
    .number(
        SonoffFastSceneConfigCluster.AttributeDefs.protection_over_voltage_mv.name,
        SonoffFastSceneConfigCluster.cluster_id,
        min_value=PROTECTION_VOLTAGE_MIN_MV,
        max_value=PROTECTION_VOLTAGE_MAX_MV,
        step=1,
        entity_type=EntityType.CONFIG,
        unit="mV",
        translation_key="protection_over_voltage_mv",
        fallback_name="Protection over-voltage threshold",
    )
    .switch(
        SonoffFastSceneConfigCluster.AttributeDefs.protection_over_voltage_enabled.name,
        SonoffFastSceneConfigCluster.cluster_id,
        entity_type=EntityType.CONFIG,
        translation_key="protection_over_voltage_enabled",
        fallback_name="Protection over-voltage enabled",
    )
    .number(
        SonoffFastSceneConfigCluster.AttributeDefs.protection_under_voltage_mv.name,
        SonoffFastSceneConfigCluster.cluster_id,
        min_value=PROTECTION_VOLTAGE_MIN_MV,
        max_value=PROTECTION_VOLTAGE_MAX_MV,
        step=1,
        entity_type=EntityType.CONFIG,
        unit="mV",
        translation_key="protection_under_voltage_mv",
        fallback_name="Protection under-voltage threshold",
    )
    .switch(
        SonoffFastSceneConfigCluster.AttributeDefs.protection_under_voltage_enabled.name,
        SonoffFastSceneConfigCluster.cluster_id,
        entity_type=EntityType.CONFIG,
        translation_key="protection_under_voltage_enabled",
        fallback_name="Protection under-voltage enabled",
    )
    .switch(
        SonoffFastSceneConfigCluster.AttributeDefs.protection_auto_recover.name,
        SonoffFastSceneConfigCluster.cluster_id,
        entity_type=EntityType.CONFIG,
        translation_key="protection_auto_recover",
        fallback_name="Protection auto recover",
    )
    # .switch(
    #     SonoffFastSceneConfigCluster.AttributeDefs.protection_notify.name,
    #     SonoffFastSceneConfigCluster.cluster_id,
    #     entity_type=EntityType.CONFIG,
    #     translation_key="protection_notify",
    #     fallback_name="Protection notification",
    # )
    .sensor(
        attribute_name=SonoffCluster.AttributeDefs.accurrent_current_value.name,
        cluster_id=SonoffCluster.cluster_id,
        device_class=SensorDeviceClass.CURRENT,
        state_class=SensorStateClass.MEASUREMENT,
        unit=UnitOfElectricCurrent.AMPERE,
        translation_key="accurrent_current_value",
        fallback_name="Current",
        divisor=1000,
    )
    .sensor(
        attribute_name=SonoffCluster.AttributeDefs.accurrent_voltage_value.name,
        cluster_id=SonoffCluster.cluster_id,
        device_class=SensorDeviceClass.VOLTAGE,
        state_class=SensorStateClass.MEASUREMENT,
        unit=UnitOfElectricPotential.VOLT,
        divisor=1000,
        translation_key="accurrent_voltage_value",
        fallback_name="Voltage",

    )
    .sensor(
        attribute_name=SonoffCluster.AttributeDefs.accurrent_power_value.name,
        cluster_id=SonoffCluster.cluster_id,
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
        unit=UnitOfPower.WATT,
        divisor=1000,
        translation_key="accurrent_power_value",
        fallback_name="Power",

    )
    .sensor(
        attribute_name=SonoffCluster.AttributeDefs.daily_forward_energy.name,
        cluster_id=SonoffCluster.cluster_id,
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.MEASUREMENT,
        unit=UnitOfEnergy.KILO_WATT_HOUR,
        translation_key="daily_forward_energy",
        fallback_name="daily forward energy",
        divisor=1000,

    )
    .sensor(
        attribute_name=SonoffCluster.AttributeDefs.monthly_forward_energy.name,
        cluster_id=SonoffCluster.cluster_id,
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.MEASUREMENT,
        unit=UnitOfEnergy.KILO_WATT_HOUR,
        divisor=1000,
        translation_key="monthly_forward_energy",
        fallback_name="monthly forward energy",
    )
    .sensor(
        attribute_name=SonoffCluster.AttributeDefs.daily_reverse_energy.name,
        cluster_id=SonoffCluster.cluster_id,
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.MEASUREMENT,
        unit=UnitOfEnergy.KILO_WATT_HOUR,
        divisor=1000,
        translation_key="daily_reverse_energy",
        fallback_name="daily reverse energy",

    )
    .sensor(
        attribute_name=SonoffCluster.AttributeDefs.monthly_reverse_energy.name,
        cluster_id=SonoffCluster.cluster_id,
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.MEASUREMENT,
        unit=UnitOfEnergy.KILO_WATT_HOUR,
        translation_key="monthly_reverse_energy",
        fallback_name="monthly reverse energy",
        divisor=1000,

    )
    .sensor(
        attribute_name=SonoffCluster.AttributeDefs.total_forward_energy.name,
        cluster_id=SonoffCluster.cluster_id,
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.MEASUREMENT,
        unit=UnitOfEnergy.KILO_WATT_HOUR,
        divisor=1000,
        translation_key="total_forward_energy",
        fallback_name="total forward energy",

    )
    .sensor(
        attribute_name=SonoffCluster.AttributeDefs.total_reverse_energy.name,
        cluster_id=SonoffCluster.cluster_id,
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.MEASUREMENT,
        unit=UnitOfEnergy.KILO_WATT_HOUR,
        divisor=1000,
        translation_key="total_reverse_energy",
        fallback_name="total reverse energy",

    )
    .sensor(
        attribute_name=SonoffCluster.AttributeDefs.total_run_time.name,
        cluster_id=SonoffCluster.cluster_id,
        device_class=SensorDeviceClass.DURATION,
        state_class=SensorStateClass.MEASUREMENT,
        unit=UnitOfTime.SECONDS,
        translation_key="total_run_time",
        fallback_name="total run time",
    )
    .sensor(
        attribute_name=SonoffCluster.AttributeDefs.daily_run_time.name,
        cluster_id=SonoffCluster.cluster_id,
        device_class=SensorDeviceClass.DURATION,
        state_class=SensorStateClass.MEASUREMENT,
        unit=UnitOfTime.SECONDS,
        translation_key="daily_run_time",
        fallback_name="daily run time",
    )
    .sensor(
        attribute_name=SonoffCluster.AttributeDefs.voltage_frequency.name,
        cluster_id=SonoffCluster.cluster_id,
        device_class=SensorDeviceClass.FREQUENCY,
        state_class=SensorStateClass.MEASUREMENT,
        unit=UnitOfFrequency.HERTZ,
        translation_key="voltage_frequency",
        fallback_name="voltage frequency",
    )
    .enum(
        SonoffCluster.AttributeDefs.fault_code.name,
        SonoffErrorCodeType,
        SonoffCluster.cluster_id,
        entity_platform=EntityPlatform.SENSOR,
        entity_type=EntityType.DIAGNOSTIC,
        translation_key="fault_code",
        fallback_name="fault code",
    )
    .add_to_registry()
)

I raised this PR for a ZHA quirk for the MINI-ZB1GP: Add SONOFF MINI-ZB1GP quirk by 040medien · Pull Request #5155 · zigpy/zha-device-handlers · GitHub