async def _read_attribute_raw(attributes, *args, **kwargs): result = [] for attr_id in attributes: value = cluster.PLUGGED_ATTR_READS.get(attr_id) if value is None: # try converting attr_id to attr_name and lookup the plugs again attr_name = cluster.attributes.get(attr_id) value = attr_name and cluster.PLUGGED_ATTR_READS.get( attr_name[0]) if value is not None: result.append( zcl_f.ReadAttributeRecord( attr_id, zcl_f.Status.SUCCESS, zcl_f.TypeValue(python_type=None, value=value), )) else: result.append( zcl_f.ReadAttributeRecord(attr_id, zcl_f.Status.FAILURE)) return (result, )
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)
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
def write_attributes(self, attributes, is_report=False, 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 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(True, 0x01, schema, args, manufacturer=manufacturer) else: schema = foundation.COMMANDS[0x02][1] return self.request(True, 0x02, schema, args, manufacturer=manufacturer)
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
def read_attributes_rsp(self, attributes, manufacturer=None, *, tsn=None): args = [] for attrid, value in attributes.items(): if isinstance(attrid, str): attrid = self.attridx[attrid] a = foundation.ReadAttributeRecord( attrid, foundation.Status.UNSUPPORTED_ATTRIBUTE, foundation.TypeValue() ) args.append(a) if value is None: continue try: a.status = foundation.Status.SUCCESS python_type = self.attributes[attrid][1] a.value.type = foundation.DATA_TYPES.pytype_to_datatype_id(python_type) a.value.value = python_type(value) except ValueError as e: a.status = foundation.Status.UNSUPPORTED_ATTRIBUTE self.error(str(e)) return self._read_attributes_rsp(args, manufacturer=manufacturer, tsn=tsn)
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