async def test_zigpy_request(application, mocker): app, znp_server = application await app.startup(auto_form=False) TSN = 1 device = app.add_device(ieee=t.EUI64(range(8)), nwk=0xAABB) device.status = zigpy.device.Status.ENDPOINTS_INIT device.initializing = False device.add_endpoint(1).add_input_cluster(6) # Respond to a light turn on request data_req = znp_server.reply_once_to( request=c.AF.DataRequestExt.Req( DstAddrModeAddress=t.AddrModeAddress(mode=t.AddrMode.NWK, address=device.nwk), DstEndpoint=1, SrcEndpoint=1, ClusterId=6, TSN=TSN, Data=bytes([0x01, TSN, 0x01]), partial=True, ), responses=[ c.AF.DataRequestExt.Rsp(Status=t.Status.SUCCESS), c.AF.DataConfirm.Callback( Status=t.Status.SUCCESS, Endpoint=1, TSN=TSN, ), c.ZDO.SrcRtgInd.Callback(DstAddr=device.nwk, Relays=[]), c.AF.IncomingMsg.Callback( GroupId=0x0000, ClusterId=6, SrcAddr=device.nwk, SrcEndpoint=1, DstEndpoint=1, WasBroadcast=False, LQI=63, SecurityUse=False, TimeStamp=1198515, TSN=0, Data=bytes([0x08, TSN, 0x0B, 0x00, 0x00]), MacSrcAddr=device.nwk, MsgResultRadius=29, ), ], ) # Turn on the light await device.endpoints[1].on_off.on() await data_req
async def test_zigpy_request_failure(application, mocker): app, znp_server = application await app.startup(auto_form=False) TSN = 1 device = app.add_device(ieee=t.EUI64(range(8)), nwk=0xAABB) device.status = zigpy.device.Status.ENDPOINTS_INIT device.initializing = False device.add_endpoint(1).add_input_cluster(6) # Fail to respond to a light turn on request znp_server.reply_to( request=c.AF.DataRequestExt.Req( DstAddrModeAddress=t.AddrModeAddress(mode=t.AddrMode.NWK, address=device.nwk), DstEndpoint=1, SrcEndpoint=1, ClusterId=6, TSN=TSN, Data=bytes([0x01, TSN, 0x01]), partial=True, ), responses=[ c.AF.DataRequestExt.Rsp(Status=t.Status.SUCCESS), c.AF.DataConfirm.Callback( Status=t.Status.FAILURE, Endpoint=1, TSN=TSN, ), ], ) mocker.patch.object(app, "_send_request", new=CoroutineMock(wraps=app._send_request)) # Fail to turn on the light with pytest.raises(zigpy.exceptions.DeliveryError): await device.endpoints[1].on_off.on() assert app._send_request.call_count == 1
def generate_device(manufacturer: str, model: str) -> Optional[Device]: """Generate device from quirks. Should be called earlier: zhaquirks.setup() Or direct import: from zhaquirks.xiaomi.mija.sensor_switch import MijaButton Used like a Cluster: hdr, value = device.deserialize(<endpoint_id>, <cluster_id>, data) """ quirks = zigpy.quirks.get_quirk_list(manufacturer, model) if not quirks: return None device = Device(None, None, 0) device.manufacturer = manufacturer device.model = model quirk: zigpy.quirks.CustomDevice = quirks[0] if SIG_ENDPOINTS in quirk.replacement: for endpoint_id in quirk.replacement[SIG_ENDPOINTS].keys(): device.add_endpoint(endpoint_id) return quirks[0](None, None, 0, device)
def _mock_dev( endpoints, ieee="00:0d:6f:00:0a:90:69:e7", manufacturer="FakeManufacturer", model="FakeModel", node_descriptor=b"\x02@\x807\x10\x7fd\x00\x00*d\x00\x00", nwk=0xB79C, patch_cluster=True, quirk=None, ): """Make a fake device using the specified cluster classes.""" device = zigpy.device.Device(zigpy_app_controller, zigpy.types.EUI64.convert(ieee), nwk) device.manufacturer = manufacturer device.model = model device.node_desc = zdo_t.NodeDescriptor.deserialize(node_descriptor)[0] device.last_seen = time.time() for epid, ep in endpoints.items(): endpoint = device.add_endpoint(epid) endpoint.device_type = ep[SIG_EP_TYPE] endpoint.profile_id = ep.get(SIG_EP_PROFILE) endpoint.request = AsyncMock(return_value=[0]) for cluster_id in ep.get(SIG_EP_INPUT, []): endpoint.add_input_cluster(cluster_id) for cluster_id in ep.get(SIG_EP_OUTPUT, []): endpoint.add_output_cluster(cluster_id) if quirk: device = quirk(zigpy_app_controller, device.ieee, device.nwk, device) if patch_cluster: for endpoint in (ep for epid, ep in device.endpoints.items() if epid): endpoint.request = AsyncMock(return_value=[0]) for cluster in itertools.chain(endpoint.in_clusters.values(), endpoint.out_clusters.values()): common.patch_cluster(cluster) return device
def from_signature( cls, device: zigpy.device.Device, model: Optional[str] = None ) -> zigpy.device.Device: """Update device accordingly to quirk signature.""" assert isinstance(cls.signature, dict) if model is None: model = cls.signature[MODEL] manufacturer = cls.signature.get(MANUFACTURER) if manufacturer is None: manufacturer = cls.signature[MODELS_INFO][0][0] device.node_desc = cls.signature[NODE_DESCRIPTOR] endpoints = cls.signature[ENDPOINTS] for ep_id, ep_data in endpoints.items(): endpoint = device.add_endpoint(ep_id) endpoint.profile_id = ep_data[PROFILE_ID] endpoint.device_type = ep_data[DEVICE_TYPE] for cluster_id in ep_data[INPUT_CLUSTERS]: cluster = endpoint.add_input_cluster(cluster_id) if cluster.ep_attribute == "basic": manuf_attr_id = cluster.attridx[MANUFACTURER] cluster._update_attribute( # pylint: disable=W0212 manuf_attr_id, manufacturer ) cluster._update_attribute( # pylint: disable=W0212 cluster.attridx[MODEL], model ) for cluster_id in ep_data[OUTPUT_CLUSTERS]: endpoint.add_output_cluster(cluster_id) endpoint.status = zigpy.endpoint.Status.ZDO_INIT device.status = zigpy.device.Status.ENDPOINTS_INIT device.manufacturer = manufacturer device.model = model return device
def handle_zcl(self, dataPayload: ZclDataPayload): # LOGGER.debug("Received ZCL data %s", dataPayload) # if (dataPayload.frame && dataPayload.frame.Cluster.name === 'touchlink') { # // This is handled by touchlink # return # } nwk = dataPayload.address try: device: zigpy.device.Device = self.get_device(nwk=nwk) except KeyError: LOGGER.debug("Received ZCL frame from unknown device: %s", nwk) return # device.updateLastSeen() # endpoint: zigpy.endpoint.Endpoint # try: endpoint = device.endpoints[dataPayload.endpoint] except KeyError: LOGGER.debug("ZCL data is from unknown endpoint %d from device %s, creating it...", dataPayload.endpoint, nwk) endpoint = device.add_endpoint(dataPayload.endpoint) # cluster = Cluster.from_id(endpoint, dataPayload.clusterid) # hdr, data = cluster.deserialize(dataPayload.data) # LOGGER.info("ZLC: %s %s", hdr.command_id, data) # LOGGER.debug("ZLC: %s", hdr) profile = 0 device.handle_message( profile, dataPayload.clusterid, dataPayload.endpoint, dataPayload.dstendpoint, dataPayload.data, ) # endpoint.handle_message(profile, dataPayload.clusterid, hdr, data) # if hdr.frame_control.is_general(): # if hdr.command_id == Command.Report_Attributes: # pass # else: # pass # # if hdr.command_id == Command.Read_Attributes_rsp or hdr.command_id == Command.Report_Attributes: # # Some device report, e.g. it's modelID through a readResponse or attributeReport # for key, value in data.items(): # property = cluster.discover_attributes.ReportablePropertiesMapping[key] # if property && !device[property.key]: # property.set(value, device) # # endpoint.saveClusterAttributeKeyValue(clusterName, data) ''' // Parse command for event let type: Events.MessagePayloadType = undefined let data: KeyValue let clusterName = undefined const meta: {zclTransactionSequenceNumber?: number} = {} const frame = dataPayload.frame const command = frame.getCommand() clusterName = frame.Cluster.name meta.zclTransactionSequenceNumber = frame.Header.transactionSequenceNumber if (frame.isGlobal()) { if (frame.isCommand('report')) { type = 'attributeReport' data = ZclFrameConverter.attributeKeyValue(dataPayload.frame) } else if (frame.isCommand('read')) { type = 'read' data = ZclFrameConverter.attributeList(dataPayload.frame) } else if (frame.isCommand('write')) { type = 'write' data = ZclFrameConverter.attributeKeyValue(dataPayload.frame) } else { /* istanbul ignore else */ if (frame.isCommand('readRsp')) { type = 'readResponse' data = ZclFrameConverter.attributeKeyValue(dataPayload.frame) } } } else { /* istanbul ignore else */ if (frame.isSpecific()) { if (Events.CommandsLookup[command.name]) { type = Events.CommandsLookup[command.name] data = dataPayload.frame.Payload } else { debug.log("Skipping command '${command.name}' because it is missing from the lookup") } } } if (type === 'readResponse' || type === 'attributeReport') { // Some device report, e.g. it's modelID through a readResponse or attributeReport for (const [key, value] of Object.entries(data)) { const property = Device.ReportablePropertiesMapping[key] if (property && !device[property.key]) { property.set(value, device) } } endpoint.saveClusterAttributeKeyValue(clusterName, data) } if (type && data) { const endpoint = device.getEndpoint(dataPayload.endpoint) const linkquality = dataPayload.linkquality const groupID = dataPayload.groupID const eventData: Events.MessagePayload = { type: type, device, endpoint, data, linkquality, groupID, cluster: clusterName, meta } this.emit(Events.Events.message, eventData) } const frame = dataPayload.frame // Send a default response if necessary. if (!frame.Header.frameControl.disableDefaultResponse) { try { await endpoint.defaultResponse( frame.getCommand().ID, 0, frame.Cluster.ID, frame.Header.transactionSequenceNumber, ) } catch (error) { debug.error("Default response to ${device.ieeeAddr} failed") } } // Reponse to time reads if (frame.isGlobal() && frame.isCluster('genTime') && frame.isCommand('read')) { const time = Math.round(((new Date()).getTime() - OneJanuary2000) / 1000) try { await endpoint.readResponse(frame.Cluster.ID, frame.Header.transactionSequenceNumber, {time}) } catch (error) { debug.error("genTime response to ${device.ieeeAddr} failed") } } ''' pass