diff --git a/com.zsmartsystems.zigbee.dongle.ember/src/main/java/com/zsmartsystems/zigbee/dongle/ember/ZigBeeDongleEzsp.java b/com.zsmartsystems.zigbee.dongle.ember/src/main/java/com/zsmartsystems/zigbee/dongle/ember/ZigBeeDongleEzsp.java index 1acb9701f..60f55c09d 100644 --- a/com.zsmartsystems.zigbee.dongle.ember/src/main/java/com/zsmartsystems/zigbee/dongle/ember/ZigBeeDongleEzsp.java +++ b/com.zsmartsystems.zigbee.dongle.ember/src/main/java/com/zsmartsystems/zigbee/dongle/ember/ZigBeeDongleEzsp.java @@ -860,6 +860,9 @@ public void handlePacket(EzspFrame response) { apsFrame.setSourceAddress(incomingMessage.getSender()); apsFrame.setSourceEndpoint(emberApsFrame.getSourceEndpoint()); + apsFrame.setReceivedLqi(incomingMessage.getLastHopLqi()); + apsFrame.setReceivedRssi(incomingMessage.getLastHopRssi()); + apsFrame.setPayload(incomingMessage.getMessageContents()); zigbeeTransportReceive.receiveCommand(apsFrame); diff --git a/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/ZigBeeLinkQualityStatistics.java b/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/ZigBeeLinkQualityStatistics.java new file mode 100644 index 000000000..d2d405e6d --- /dev/null +++ b/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/ZigBeeLinkQualityStatistics.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2016-2020 by the respective copyright holders. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package com.zsmartsystems.zigbee; + +/** + * An interface that allows clients to retrieve link quality information available from a node + * + * @author Chris Jackson + * + */ +public interface ZigBeeLinkQualityStatistics { + /** + * Returns the LQI value from the last recieved packet + * + * @return the last received LQI value + */ + public Integer getLastReceivedLqi(); + + /** + * Returns the RSSI value from the last recieved packet + * + * @return the last received RSSI value in dBm + */ + public Integer getLastReceivedRssi(); + +} diff --git a/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/ZigBeeNetworkManager.java b/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/ZigBeeNetworkManager.java index 036667c19..883f242ea 100644 --- a/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/ZigBeeNetworkManager.java +++ b/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/ZigBeeNetworkManager.java @@ -959,16 +959,27 @@ public void run() { // Pass the command to the transaction manager for processing // If the transaction manager wants to drop this command, it returns null - command = transactionManager.receive(command); - if (command == null) { + final ZigBeeCommand finalCommand = transactionManager.receive(command); + if (finalCommand == null) { return; } // Ignore the DefaultResponse - if (command instanceof DefaultResponse) { + if (finalCommand instanceof DefaultResponse) { return; } + // Directly distribute commands to nodes + ZigBeeNode node = getNode(command.getSourceAddress().getAddress()); + if (node != null) { + NotificationService.execute(new Runnable() { + @Override + public void run() { + node.commandReceived(finalCommand, apsFrame.getReceivedRssi(), apsFrame.getReceivedLqi()); + } + }); + } + // Notify the listeners commandNotifier.notifyCommandListeners(command); } @@ -1585,7 +1596,6 @@ public void removeNode(final ZigBeeNode node) { return; } networkNodes.remove(node.getIeeeAddress()); - removeCommandListener(node); synchronized (this) { if (networkState != ZigBeeNetworkState.ONLINE) { @@ -1628,7 +1638,6 @@ public void updateNode(final ZigBeeNode node) { return; } networkNodes.put(node.getIeeeAddress(), node); - addCommandListener(node); synchronized (this) { if (networkState != ZigBeeNetworkState.ONLINE) { diff --git a/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/ZigBeeNode.java b/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/ZigBeeNode.java index 1310fe744..cbb2aadbb 100644 --- a/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/ZigBeeNode.java +++ b/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/ZigBeeNode.java @@ -27,6 +27,7 @@ import com.zsmartsystems.zigbee.database.ZigBeeEndpointDao; import com.zsmartsystems.zigbee.database.ZigBeeNodeDao; import com.zsmartsystems.zigbee.internal.NotificationService; +import com.zsmartsystems.zigbee.internal.ZigBeeNodeLinkQualityHandler; import com.zsmartsystems.zigbee.transaction.ZigBeeTransactionMatcher; import com.zsmartsystems.zigbee.zcl.ZclCluster; import com.zsmartsystems.zigbee.zcl.ZclCommand; @@ -53,7 +54,7 @@ * @author Chris Jackson * */ -public class ZigBeeNode implements ZigBeeCommandListener { +public class ZigBeeNode { /** * The {@link Logger}. */ @@ -143,6 +144,11 @@ public enum ZigBeeNodeState { OFFLINE } + /** + * A handler to manage the {@link ZigBeeLinkQualityStatistics} + */ + private ZigBeeNodeLinkQualityHandler linkQualityStatistics = new ZigBeeNodeLinkQualityHandler(); + /** * Constructor * @@ -319,6 +325,11 @@ public boolean isReducedFuntionDevice() { return nodeDescriptor.getMacCapabilities().contains(MacCapabilitiesType.REDUCED_FUNCTION_DEVICE); } + /** + * Returns true if the node is capable of supporting security. This tests the {@link NodeDescriptor}. + * + * @return true if the node is capable of supporting security + */ public boolean isSecurityCapable() { if (nodeDescriptor == null) { return false; @@ -326,6 +337,11 @@ public boolean isSecurityCapable() { return nodeDescriptor.getMacCapabilities().contains(MacCapabilitiesType.SECURITY_CAPABLE); } + /** + * Returns true if the node is the primary trust centre. This tests the {@link NodeDescriptor}. + * + * @return true if the node is the primary trust centre + */ public boolean isPrimaryTrustCenter() { if (nodeDescriptor == null) { return false; @@ -670,8 +686,15 @@ public Date getLastUpdateTime() { return lastUpdateTime; } - @Override - public void commandReceived(ZigBeeCommand command) { + /** + * Incoming command handler. The node will process any commands addressed to this node ID and pass to + * the appropriate endpoint. + * + * @param command the {@link ZigBeeCommand} received + * @param rssi the Received Signal Strength Indicator of the received packet, or null + * @param lqi the Link Quality Indicator of the received packet, or null + */ + public void commandReceived(ZigBeeCommand command, Integer rssi, Integer lqi) { // This gets called for all received commands // Check if it's our address if (!(command instanceof ZclCommand) || networkAddress == null @@ -681,6 +704,9 @@ public void commandReceived(ZigBeeCommand command) { logger.trace("{}: ZigBeeNode.commandReceived({})", ieeeAddress, command); + linkQualityStatistics.updateReceivedLqi(lqi); + linkQualityStatistics.updateReceivedRssi(rssi); + ZclCommand zclCommand = (ZclCommand) command; ZigBeeEndpointAddress endpointAddress = (ZigBeeEndpointAddress) zclCommand.getSourceAddress(); @@ -888,6 +914,15 @@ public ZigBeeNodeState getNodeState() { return nodeState; } + /** + * Retrieves the {@link ZigBeeLinkQualityStatistics} for the node + * + * @return the {@link ZigBeeLinkQualityStatistics} for the node + */ + public ZigBeeLinkQualityStatistics getLinkQualityStatistics() { + return linkQualityStatistics; + } + @Override public String toString() { StringBuilder builder = new StringBuilder(100); diff --git a/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/aps/ZigBeeApsFrame.java b/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/aps/ZigBeeApsFrame.java index 65b2dbd18..37e9abde8 100644 --- a/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/aps/ZigBeeApsFrame.java +++ b/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/aps/ZigBeeApsFrame.java @@ -132,6 +132,9 @@ public class ZigBeeApsFrame { */ private int apsCounter = -1; + private Integer rssi; + private Integer lqi; + protected int fragmentSize; protected int fragmentBase = 0; protected int fragmentTotal = 0; @@ -394,6 +397,62 @@ public int[] getPayload() { return payload; } + /** + * Calling this method indicates that transmission of a fragment has completed. + * It moves the fragment base and decrease outstanding fragments counter. + */ + public void oneFragmentCompleted() { + fragmentBase++; + if (fragmentOutstanding > 0) { + fragmentOutstanding--; + } + } + + /** + * Calling this method indicates that a fragment has been sent. It increases outstanding fragments counter. + */ + public void oneFragmentSent() { + if (fragmentOutstanding <= fragmentTotal) { + this.fragmentOutstanding++; + } + } + + /** + * Gets the RSSI from the packet. If this is unknown the method will return null + * + * @return the RSSI of the packet + */ + public Integer getReceivedRssi() { + return rssi; + } + + /** + * Sets the RSSI from the packet. If this is unknown the method will return null + * + * @param the RSSI of the packet + */ + public void setReceivedRssi(int rssi) { + this.rssi = rssi; + } + + /** + * Gets the LQI from the packet. If this is unknown the method will return null + * + * @return the LQI of the packet + */ + public Integer getReceivedLqi() { + return lqi; + } + + /** + * Sets the LQI from the packet. If this is unknown the method will return null + * + * @param the LQI of the packet + */ + public void setReceivedLqi(int lqi) { + this.lqi = lqi; + } + @Override public String toString() { StringBuilder builder = new StringBuilder(164); @@ -413,6 +472,20 @@ public String toString() { } else { builder.append(String.format("%02X", apsCounter)); } + + builder.append(", rssi="); + if (rssi == null) { + builder.append("--"); + } else { + builder.append(rssi); + } + builder.append(", lqi="); + if (lqi == null) { + builder.append("--"); + } else { + builder.append(String.format("%02X", lqi)); + } + builder.append(", payload="); if (payload != null) { for (int c = 0; c < payload.length; c++) { @@ -425,24 +498,4 @@ public String toString() { builder.append(']'); return builder.toString(); } - - /** - * Calling this method indicates that transmission of a fragment has completed. - * It moves the fragment base and decrease outstanding fragments counter. - */ - public void oneFragmentCompleted() { - fragmentBase++; - if (fragmentOutstanding > 0) { - fragmentOutstanding--; - } - } - - /** - * Calling this method indicates that a fragment has been sent. It increases outstanding fragments counter. - */ - public void oneFragmentSent() { - if (fragmentOutstanding <= fragmentTotal) { - this.fragmentOutstanding++; - } - } } diff --git a/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/internal/ZigBeeNodeLinkQualityHandler.java b/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/internal/ZigBeeNodeLinkQualityHandler.java new file mode 100644 index 000000000..62fa589ef --- /dev/null +++ b/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/internal/ZigBeeNodeLinkQualityHandler.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2016-2020 by the respective copyright holders. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package com.zsmartsystems.zigbee.internal; + +import com.zsmartsystems.zigbee.ZigBeeLinkQualityStatistics; + +/** + * Handler to record and manage link quality information for a node + * + * @author Chris Jackson + * + */ +public class ZigBeeNodeLinkQualityHandler implements ZigBeeLinkQualityStatistics { + Integer lastRssi; + Integer lastLqi; + + /** + * Updates the Link Quality Indicator value with the value from the latest received packet + * + * @param lqi the last received LQI value + */ + public void updateReceivedLqi(Integer lqi) { + if (lqi == null) { + return; + } + lastLqi = lqi; + } + + /** + * Updates the Received Signal Strength Indicator value with the value from the latest received packet + * + * @param rssi the last received RSSI value in dBm + */ + public void updateReceivedRssi(Integer rssi) { + if (rssi == null) { + return; + } + lastRssi = rssi; + } + + @Override + public Integer getLastReceivedLqi() { + return lastLqi; + } + + @Override + public Integer getLastReceivedRssi() { + return lastRssi; + } +} diff --git a/com.zsmartsystems.zigbee/src/test/java/com/zsmartsystems/zigbee/ZigBeeNetworkManagerTest.java b/com.zsmartsystems.zigbee/src/test/java/com/zsmartsystems/zigbee/ZigBeeNetworkManagerTest.java index e0de3238f..cd0b8e481 100644 --- a/com.zsmartsystems.zigbee/src/test/java/com/zsmartsystems/zigbee/ZigBeeNetworkManagerTest.java +++ b/com.zsmartsystems.zigbee/src/test/java/com/zsmartsystems/zigbee/ZigBeeNetworkManagerTest.java @@ -944,7 +944,8 @@ private ZigBeeCommand getZigBeeCommand(ZigBeeApsFrame apsFrame) throws Exception TestUtilities.setField(ZigBeeNetworkManager.class, networkManager, "networkState", ZigBeeNetworkState.ONLINE); networkManager.receiveCommand(apsFrame); Mockito.verify(node, Mockito.timeout(TIMEOUT).times(1)) - .commandReceived(ArgumentMatchers.any(ZigBeeCommand.class)); + .commandReceived(ArgumentMatchers.any(ZigBeeCommand.class), ArgumentMatchers.any(), + ArgumentMatchers.any()); Awaitility.await().until(() -> commandListenerUpdated()); if (commandListenerCapture.size() == 0) { return null; diff --git a/com.zsmartsystems.zigbee/src/test/java/com/zsmartsystems/zigbee/ZigBeeNodeTest.java b/com.zsmartsystems.zigbee/src/test/java/com/zsmartsystems/zigbee/ZigBeeNodeTest.java index c5d8f33a6..5b88e6a7c 100644 --- a/com.zsmartsystems.zigbee/src/test/java/com/zsmartsystems/zigbee/ZigBeeNodeTest.java +++ b/com.zsmartsystems.zigbee/src/test/java/com/zsmartsystems/zigbee/ZigBeeNodeTest.java @@ -516,7 +516,7 @@ public void commandReceived() { Mockito.when(invalidSourceAddress.getEndpoint()).thenReturn(1); ZclCommand zigbeeCommand = Mockito.mock(ZclCommand.class); Mockito.when(zigbeeCommand.getSourceAddress()).thenReturn(invalidSourceAddress); - node.commandReceived(zigbeeCommand); + node.commandReceived(zigbeeCommand, null, null); Mockito.verify(endpoint1, Mockito.times(0)).commandReceived(ArgumentMatchers.any(ZclCommand.class)); Mockito.verify(endpoint2, Mockito.times(0)).commandReceived(ArgumentMatchers.any(ZclCommand.class)); @@ -524,7 +524,7 @@ public void commandReceived() { Mockito.when(zigbeeAddress.getAddress()).thenReturn(124); ZigBeeCommand zigbeeCommandInvalidAddressCmd = Mockito.mock(ZigBeeCommand.class); Mockito.when(zigbeeCommandInvalidAddressCmd.getSourceAddress()).thenReturn(zigbeeAddress); - node.commandReceived(zigbeeCommandInvalidAddressCmd); + node.commandReceived(zigbeeCommandInvalidAddressCmd, null, null); Mockito.verify(endpoint1, Mockito.times(0)).commandReceived(ArgumentMatchers.any(ZclCommand.class)); Mockito.verify(endpoint2, Mockito.times(0)).commandReceived(ArgumentMatchers.any(ZclCommand.class)); @@ -538,12 +538,15 @@ public void commandReceived() { Mockito.when(unicast.getTransactionId()).thenReturn(123); Mockito.when(unicast.getCommandId()).thenReturn(99); - node.commandReceived(unicast); + node.commandReceived(unicast, null, null); Mockito.verify(endpoint1, Mockito.times(1)).commandReceived(unicast); Mockito.verify(endpoint2, Mockito.times(0)).commandReceived(unicast); + assertNull(node.getLinkQualityStatistics().getLastReceivedLqi()); + assertNull(node.getLinkQualityStatistics().getLastReceivedRssi()); + Mockito.when(sourceAddress.getEndpoint()).thenReturn(10); - node.commandReceived(unicast); + node.commandReceived(unicast, 1, 2); ArgumentCaptor commandCapture = ArgumentCaptor.forClass(ZigBeeCommand.class); Mockito.verify(networkManager, Mockito.timeout(TIMEOUT).times(1)).sendTransaction(commandCapture.capture()); ZigBeeCommand response = commandCapture.getValue(); @@ -555,11 +558,14 @@ public void commandReceived() { assertEquals(ZclStatus.UNSUPPORTED_CLUSTER, defaultResponse.getStatusCode()); assertEquals(Integer.valueOf(123), defaultResponse.getTransactionId()); + assertEquals(Integer.valueOf(2), node.getLinkQualityStatistics().getLastReceivedLqi()); + assertEquals(Integer.valueOf(1), node.getLinkQualityStatistics().getLastReceivedRssi()); + ZdoCommand zdoCommand = Mockito.mock(ZdoCommand.class); ZigBeeAddress zdoSource = Mockito.mock(ZigBeeAddress.class); Mockito.when(zdoSource.getAddress()).thenReturn(12345); Mockito.when(zdoCommand.getSourceAddress()).thenReturn(zdoSource); - node.commandReceived(zdoCommand); + node.commandReceived(zdoCommand, null, null); } @Test diff --git a/com.zsmartsystems.zigbee/src/test/resource/logs/zdo.txt b/com.zsmartsystems.zigbee/src/test/resource/logs/zdo.txt index d015997d6..102892a56 100644 --- a/com.zsmartsystems.zigbee/src/test/resource/logs/zdo.txt +++ b/com.zsmartsystems.zigbee/src/test/resource/logs/zdo.txt @@ -42,7 +42,7 @@ NodeDescriptorRequest [0000/0 -> 0000/0, cluster=0002, TID=1B, nwkAddrOfInterest ZigBeeApsFrame [sourceAddress=0000/0, destinationAddress=0000/0, profile=0000, cluster=0005, addressMode=DEVICE, radius=31, apsSecurity=false, apsCounter=1D, payload=00 1B 7A] ActiveEndpointsRequest [0000/0 -> 0000/0, cluster=0005, TID=1D, nwkAddrOfInterest=31259] -ZigBeeApsFrame [sourceAddress=61585/0, destinationAddress=0000/0, profile=0000, cluster=8005, addressMode=null, radius=0, apsSecurity=false, apsCounter=3F, payload=00 00 91 F0 07 01 02 03 04 05 C8 E8] +ZigBeeApsFrame [sourceAddress=0000/0, destinationAddress=0000/0, profile=0000, cluster=8005, addressMode=null, radius=0, apsSecurity=false, apsCounter=3F, payload=00 00 91 F0 07 01 02 03 04 05 C8 E8] ActiveEndpointsResponse [61585/0 -> 0000/0, cluster=8005, TID=--, status=SUCCESS, nwkAddrOfInterest=61585, activeEpList=[1, 2, 3, 4, 5, 200, 232]] ZigBeeApsFrame [sourceAddress=0000/0, destinationAddress=0000/0, profile=0000, cluster=0004, addressMode=DEVICE, radius=31, apsSecurity=false, apsCounter=1E, payload=00 1B 7A 03]