Exemplo n.º 1
0
    def write_attributes(self, attributes, is_report=False):
        args = []
        for attrid, value in attributes.items():
            if isinstance(attrid, str):
                attrid = self._attridx[attrid]
            if attrid not in self.attributes:
                self.error("%d is not a valid attribute id", attrid)
                continue

            if is_report:
                a = foundation.ReadAttributeRecord()
                a.status = 0
            else:
                a = foundation.Attribute()

            a.attrid = t.uint16_t(attrid)
            a.value = foundation.TypeValue()

            try:
                python_type = self.attributes[attrid][1]
                a.value.type = t.uint8_t(foundation.DATA_TYPE_IDX[python_type])
                a.value.value = python_type(value)
                args.append(a)
            except ValueError as e:
                self.error(str(e))

        if is_report:
            schema = foundation.COMMANDS[0x01][1]
            return self.reply(0x01, schema, args)
        else:
            schema = foundation.COMMANDS[0x02][1]
            return self.request(True, 0x02, schema, args)
Exemplo n.º 2
0
def make_attribute(attrid, value, status=0):
    """Make an attribute."""
    attr = zcl_f.Attribute()
    attr.attrid = attrid
    attr.value = zcl_f.TypeValue()
    attr.value.value = value
    return attr
Exemplo n.º 3
0
    def _write_attr_records(
            self, attributes: dict[str | int,
                                   Any]) -> list[foundation.Attribute]:
        args = []
        for attrid, value in attributes.items():
            try:
                attr_def = self.find_attribute(attrid)
            except KeyError:
                self.error("%s is not a valid attribute id", attrid)

                # Throw an error if it's an unknown attribute name, without an ID
                if isinstance(attrid, str):
                    raise

                continue

            attr = foundation.Attribute(attr_def.id, foundation.TypeValue())
            attr.value.type = foundation.DATA_TYPES.pytype_to_datatype_id(
                attr_def.type)

            try:
                attr.value.value = attr_def.type(value)
            except ValueError as e:
                self.error(
                    "Failed to convert attribute 0x%04X from %s (%s) to type %s: %s",
                    attrid,
                    value,
                    type(value),
                    attr_def.type,
                    e,
                )
            else:
                args.append(attr)

        return args
Exemplo n.º 4
0
    def write_attributes(self, attributes, manufacturer=None):
        args = []
        for attrid, value in attributes.items():
            if isinstance(attrid, str):
                attrid = self._attridx[attrid]
            if attrid not in self.attributes:
                self.error("%d is not a valid attribute id", attrid)
                continue

            a = foundation.Attribute(attrid, foundation.TypeValue())

            try:
                python_type = self.attributes[attrid][1]
                a.value.type = t.uint8_t(foundation.DATA_TYPE_IDX[python_type])
                a.value.value = python_type(value)
                args.append(a)
            except ValueError as e:
                self.error(str(e))

        return self._write_attributes(args, manufacturer=manufacturer)
Exemplo n.º 5
0
    def _write_attr_records(
        self, attributes: Dict[Union[str, int], Any]
    ) -> List[foundation.Attribute]:
        args = []
        for attrid, value in attributes.items():
            if isinstance(attrid, str):
                attrid = self.attridx[attrid]
            if attrid not in self.attributes:
                self.error("%d is not a valid attribute id", attrid)
                continue

            a = foundation.Attribute(attrid, foundation.TypeValue())

            try:
                python_type = self.attributes[attrid][1]
                a.value.type = foundation.DATA_TYPES.pytype_to_datatype_id(python_type)
                a.value.value = python_type(value)
                args.append(a)
            except ValueError as e:
                self.error(str(e))
        return args
Exemplo n.º 6
0
    def _iter_parse_attr_report(
            self, data: bytes) -> Iterator[tuple[foundation.Attribute, bytes]]:
        """Yield all interpretations of the first attribute in an Xiaomi report."""

        # Peek at the attribute report
        attr_id, data = t.uint16_t.deserialize(data)
        attr_type, data = t.uint8_t.deserialize(data)

        if (attr_id not in (
                XIAOMI_AQARA_ATTRIBUTE,
                XIAOMI_MIJA_ATTRIBUTE,
                XIAOMI_AQARA_ATTRIBUTE_E1,
        ) or attr_type != 0x42  # "Character String"
            ):
            # Assume other attributes are reported correctly
            data = attr_id.serialize() + attr_type.serialize() + data
            attribute, data = foundation.Attribute.deserialize(data)

            yield attribute, data
            return

        # Length of the "string" can be wrong
        val_len, data = t.uint8_t.deserialize(data)

        # Try every offset. Start with 0 to pass unbroken reports through.
        for offset in (0, -1, 1):
            fixed_len = val_len + offset

            if len(data) < fixed_len:
                continue

            val, final_data = data[:fixed_len], data[fixed_len:]
            attr_val = t.LVBytes(val)
            attr_type = 0x41  # The data type should be "Octet String"

            yield foundation.Attribute(
                attrid=attr_id,
                value=foundation.TypeValue(python_type=attr_type,
                                           value=attr_val),
            ), final_data
Exemplo n.º 7
0
async def attr_write(  # noqa: C901
        app, listener, ieee, cmd, data, service, params, event_data):
    success = True

    dev = app.get_device(ieee=ieee)

    # Decode endpoint
    if params[p.EP_ID] is None or params[p.EP_ID] == "":
        params[p.EP_ID] = u.find_endpoint(dev, params[p.CLUSTER_ID])

    if params[p.EP_ID] not in dev.endpoints:
        msg = f"Endpoint {params[p.EP_ID]} not found for '{ieee!r}"
        LOGGER.error(msg)
        raise Exception(msg)

    if params[p.CLUSTER_ID] not in dev.endpoints[params[p.EP_ID]].in_clusters:
        msg = "InCluster 0x{:04X} not found for '{}', endpoint {}".format(
            params[p.CLUSTER_ID], repr(ieee), params[p.EP_ID])
        if params[p.CLUSTER_ID] in dev.endpoints[params[p.EP_ID]].out_clusters:
            msg = f'{cmd}: "Using" OutCluster. {msg}'
            LOGGER.warning(msg)
            if "warnings" not in event_data:
                event_data["warnings"] = []
            event_data["warnings"].append(msg)
        else:
            LOGGER.error(msg)
            raise Exception(msg)

    cluster = dev.endpoints[params[p.EP_ID]].in_clusters[params[p.CLUSTER_ID]]

    # Prepare read and write lists
    attr_write_list = []
    attr_read_list = []

    # Decode attribute(s)
    #  Currently only one attribute is possible, but the parameter
    #  format could allow for multiple attributes for instance by
    #  adding a split character such as ':' for attr_id, attr_type
    #  and attr_value
    # Then the match should be in a loop

    # Decode attribute id
    # Could accept name for attribute, but extra code to check
    attr_id = params[p.ATTR_ID]

    attr_read_list.append(attr_id)  # Read before write list

    compare_val = None

    if cmd == "attr_write":
        attr_type = params[p.ATTR_TYPE]
        attr_val_str = params[p.ATTR_VAL]

        # Type only needed for write
        if attr_type is None or attr_val_str is None:
            event_data["errors"].append(
                "attr_type and attr_val must be set for attr_write")
        else:
            # Convert attribute value (provided as a string)
            # to appropriate attribute value.
            # If the attr_type is not set, only read the attribute.
            attr_val = None
            if attr_type == 0x10:
                compare_val = u.str2int(attr_val_str)
                attr_val = f.TypeValue(attr_type, t.Bool(compare_val))
            elif attr_type == 0x20:
                compare_val = u.str2int(attr_val_str)
                attr_val = f.TypeValue(attr_type, t.uint8_t(compare_val))
            elif attr_type == 0x21:
                compare_val = u.str2int(attr_val_str)
                attr_val = f.TypeValue(attr_type, t.uint16_t(compare_val))
            elif attr_type == 0x22:
                compare_val = u.str2int(attr_val_str)
                attr_val = f.TypeValue(attr_type, t.uint24_t(compare_val))
            elif attr_type == 0x23:
                compare_val = u.str2int(attr_val_str)
                attr_val = f.TypeValue(attr_type, t.uint32_t(compare_val))
            elif attr_type == 0x24:
                compare_val = u.str2int(attr_val_str)
                attr_val = f.TypeValue(attr_type, t.uint32_t(compare_val))
            elif attr_type == 0x25:
                compare_val = u.str2int(attr_val_str)
                attr_val = f.TypeValue(attr_type, t.uint48_t(compare_val))
            elif attr_type == 0x26:
                compare_val = u.str2int(attr_val_str)
                attr_val = f.TypeValue(attr_type, t.uint56_t(compare_val))
            elif attr_type == 0x27:
                compare_val = u.str2int(attr_val_str)
                attr_val = f.TypeValue(attr_type, t.uint64_t(compare_val))
            elif attr_type == 0x28:
                compare_val = u.str2int(attr_val_str)
                attr_val = f.TypeValue(attr_type, t.int8_t(compare_val))
            elif attr_type == 0x29:
                compare_val = u.str2int(attr_val_str)
                attr_val = f.TypeValue(attr_type, t.int16_t(compare_val))
            elif attr_type == 0x2A:
                compare_val = u.str2int(attr_val_str)
                attr_val = f.TypeValue(attr_type, t.int24_t(compare_val))
            elif attr_type == 0x2B:
                compare_val = u.str2int(attr_val_str)
                attr_val = f.TypeValue(attr_type, t.int32_t(compare_val))
            elif attr_type == 0x2C:
                compare_val = u.str2int(attr_val_str)
                attr_val = f.TypeValue(attr_type, t.int32_t(compare_val))
            elif attr_type == 0x2D:
                compare_val = u.str2int(attr_val_str)
                attr_val = f.TypeValue(attr_type, t.int48_t(compare_val))
            elif attr_type == 0x2E:
                compare_val = u.str2int(attr_val_str)
                attr_val = f.TypeValue(attr_type, t.int56_t(compare_val))
            elif attr_type == 0x2F:
                compare_val = u.str2int(attr_val_str)
                attr_val = f.TypeValue(attr_type, t.int64_t(compare_val))
            elif attr_type <= 0x31 and attr_type >= 0x08:
                compare_val = u.str2int(attr_val_str)
                # uint, int, bool, bitmap and enum
                attr_val = f.TypeValue(attr_type, t.FixedIntType(compare_val))
            elif attr_type in [0x41, 0x42]:  # Octet string
                # Octet string requires length -> LVBytes
                compare_val = attr_val_str

                event_data["str"] = attr_val_str

                if type(attr_val_str) == str:
                    attr_val_str = bytes(attr_val_str, "utf-8")

                if isinstance(attr_val_str, list):
                    # Convert list to List of uint8_t
                    attr_val_str = t.List[t.uint8_t](
                        [t.uint8_t(i) for i in attr_val_str])

                attr_val = f.TypeValue(attr_type, t.LVBytes(attr_val_str))

            if attr_val is not None:
                attr = f.Attribute(attr_id, value=attr_val)
                attr_write_list.append(attr)  # Write list
            else:
                msg = ("attr_type {} not supported, " +
                       "or incorrect parameters (attr_val={})").format(
                           params[p.ATTR_TYPE], params[p.ATTR_VAL])
                event_data["errors"].append(msg)
                LOGGER.debug(msg)
            LOGGER.debug(
                "ATTR TYPE %s, attr_val %s",
                params[p.ATTR_TYPE],
                params[p.ATTR_VAL],
            )

    result_read = None
    if (params[p.READ_BEFORE_WRITE] or (len(attr_write_list) == 0)
            or (cmd != S.ATTR_WRITE)):
        LOGGER.debug("Request attr read %s", attr_read_list)
        result_read = await cluster_read_attributes(
            cluster,
            attr_read_list,
            manufacturer=params[p.MANF],
            tries=params[p.TRIES],
        )
        LOGGER.debug("Reading attr result (attrs, status): %s", result_read)
        success = (len(result_read[1]) == 0) and (len(result_read[0]) == 1)

    # True if value that should be written is the equal to the read one
    write_is_equal = ((params[p.READ_BEFORE_WRITE])
                      and (len(attr_write_list) != 0)
                      and ((attr_id in result_read[0]) and
                           (result_read[0][attr_id] == compare_val)))

    event_data["write_is_equal"] = write_is_equal
    if write_is_equal and (cmd == "attr_write"):
        event_data["info"] = "Data read is equal to data to write"

    if (len(attr_write_list) != 0
            and (not (params[p.READ_BEFORE_WRITE]) or params[p.WRITE_IF_EQUAL]
                 or not (write_is_equal)) and cmd == "attr_write"):

        if result_read is not None:
            event_data["read_before"] = result_read
            result_read = None

        LOGGER.debug("Request attr write %s", attr_write_list)
        result_write = await cluster__write_attributes(
            cluster,
            attr_write_list,
            manufacturer=params[p.MANF],
            tries=params[p.TRIES],
        )
        LOGGER.debug("Write attr status: %s", result_write)
        event_data["result_write"] = result_write
        success = False
        try:
            # LOGGER.debug("Write attr status: %s", result_write[0][0].status)
            success = result_write[0][0].status == f.Status.SUCCESS
            LOGGER.debug(f"Write success: {success}")
        except Exception as e:
            event_data["errors"].append(repr(e))
            success = False

        # success = (len(result_write[1])==0)

        if params[p.READ_AFTER_WRITE]:
            LOGGER.debug(f"Request attr read {attr_read_list!r}")
            result_read = await cluster_read_attributes(
                cluster,
                attr_read_list,
                manufacturer=params[p.MANF],
                tries=params[p.TRIES],
            )
            LOGGER.debug(
                f"Reading attr result (attrs, status): {result_read!r}")
            # read_is_equal = (result_read[0][attr_id] == compare_val)
            success = (success and
                       (len(result_read[1]) == 0 and len(result_read[0]) == 1)
                       and (result_read[0][attr_id] == compare_val))

    if result_read is not None:
        event_data["result_read"] = result_read
        if len(result_read[1]) == 0:
            read_val = result_read[0][attr_id]
        else:
            msg = (f"Result: {result_read[1]}" +
                   f" - Attribute {attr_id} not in read {result_read!r}")
            LOGGER.warning(msg)
            if "warnings" not in event_data:
                event_data["warnings"] = []
            event_data["warnings"].append(msg)
            success = False
    else:
        read_val = None

    event_data["success"] = success

    # Write value to provided state or state attribute
    if params[p.STATE_ID] is not None:
        if len(result_read[1]) == 0 and len(result_read[0]) == 1:
            # No error and one result
            for id, val in result_read[0].items():
                if params[p.STATE_ATTR] is not None:
                    LOGGER.debug(
                        "Set state %s[%s] -> %s from attr_id %s",
                        params[p.STATE_ID],
                        params[p.STATE_ATTR],
                        val,
                        id,
                    )
                else:
                    LOGGER.debug(
                        "Set state %s -> %s from attr_id %s",
                        params[p.STATE_ID],
                        val,
                        id,
                    )
                u.set_state(
                    listener._hass,
                    params[p.STATE_ID],
                    val,
                    key=params[p.STATE_ATTR],
                    allow_create=params[p.ALLOW_CREATE],
                )
                LOGGER.debug("STATE is set")

    if success and params[p.CSV_FILE] is not None:
        fields = []
        if params[p.CSV_LABEL] is not None:
            attr_name = params[p.CSV_LABEL]
        else:
            try:
                attr_name = cluster.attributes.get(attr_id,
                                                   (str(attr_id), None))[0]
            except Exception:
                attr_name = attr_id

        fields.append(dt_util.utcnow().isoformat())
        fields.append(attr_name)
        fields.append(read_val)
        fields.append("0x%04X" % (attr_id)),
        fields.append("0x%04X" % (cluster.cluster_id)),
        fields.append(cluster.endpoint.endpoint_id)
        fields.append(str(cluster.endpoint.device.ieee))
        fields.append(("0x%04X" %
                       (params[p.MANF])) if params[p.MANF] is not None else "")
        u.append_to_csvfile(
            fields,
            "csv",
            params[p.CSV_FILE],
            f"{attr_name}={read_val}",
            listener=listener,
        )

    importlib.reload(u)
    if "result_read" in event_data and not u.isJsonable(
            event_data["result_read"]):
        event_data["result_read"] = repr(event_data["result_read"])

    # For internal use
    return result_read