diff --git a/com.zsmartsystems.zigbee.console/src/main/java/com/zsmartsystems/zigbee/console/ZigBeeConsoleOtaUpgradeCommand.java b/com.zsmartsystems.zigbee.console/src/main/java/com/zsmartsystems/zigbee/console/ZigBeeConsoleOtaUpgradeCommand.java index d5575ce1d..0d0acc61d 100644 --- a/com.zsmartsystems.zigbee.console/src/main/java/com/zsmartsystems/zigbee/console/ZigBeeConsoleOtaUpgradeCommand.java +++ b/com.zsmartsystems.zigbee.console/src/main/java/com/zsmartsystems/zigbee/console/ZigBeeConsoleOtaUpgradeCommand.java @@ -22,6 +22,7 @@ import com.zsmartsystems.zigbee.app.otaserver.ZclOtaUpgradeServer; import com.zsmartsystems.zigbee.app.otaserver.ZigBeeOtaFile; import com.zsmartsystems.zigbee.zcl.clusters.ZclOtaUpgradeCluster; +import com.zsmartsystems.zigbee.zcl.clusters.otaupgrade.ImageNotifyCommand; /** * @@ -70,6 +71,12 @@ public void process(ZigBeeNetworkManager networkManager, String[] args, PrintStr } cmdNodeState(networkManager, args[2], out); break; + case "NOTIFY": + if (args.length < 3) { + throw new IllegalArgumentException("Invalid number of arguments: Endpoint required."); + } + cmdOtaKick(networkManager, args[2], out); + break; case "START": if (args.length < 4) { throw new IllegalArgumentException("Invalid number of arguments: Endpoint and Filename required."); @@ -122,12 +129,16 @@ private void cmdDisplayAllNodes(ZigBeeNetworkManager networkManager, PrintStream return; } - out.println("Address IEEE Address State "); + out.println("Address IEEE Address State % Last Request"); for (ZigBeeEndpoint endpoint : applications.values()) { ZclOtaUpgradeServer otaServer = (ZclOtaUpgradeServer) endpoint .getApplication(ZclOtaUpgradeCluster.CLUSTER_ID); - out.println(String.format("%-9s %s %-8s", endpoint.getEndpointAddress(), endpoint.getIeeeAddress(), - otaServer.getServerStatus())); + out.println(String.format("%-9s %s %-31s %3s %s", endpoint.getEndpointAddress(), + endpoint.getIeeeAddress(), + otaServer.getServerStatus(), + otaServer.getPercentageComplete() == null ? " " + : String.format("%3d", otaServer.getPercentageComplete()), + otaServer.getLastImageRequestTime() == null ? "NEVER" : otaServer.getLastImageRequestTime())); } } @@ -173,6 +184,21 @@ private void cmdDisplayFileInfo(String filename, PrintStream out) { out.println("OTA File: " + otaFile); } + private void cmdOtaKick(ZigBeeNetworkManager networkManager, String endpointString, + PrintStream out) { + final ZigBeeEndpoint endpoint = getEndpoint(networkManager, endpointString); + + ZclOtaUpgradeCluster cluster = (ZclOtaUpgradeCluster) endpoint + .getOutputCluster(ZclOtaUpgradeCluster.CLUSTER_ID); + if (cluster == null) { + throw new IllegalArgumentException("OTA Server not supported by " + endpoint.getEndpointAddress() + ""); + } + + cluster.sendCommand(new ImageNotifyCommand(0, 0, 0, 0, 0)); + + out.println("OTA notify sent to " + endpoint.getEndpointAddress() + ""); + } + private void cmdOtaStart(ZigBeeNetworkManager networkManager, String endpointString, String filename, PrintStream out) { final ZigBeeEndpoint endpoint = getEndpoint(networkManager, endpointString); diff --git a/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/app/otaserver/ZclOtaUpgradeServer.java b/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/app/otaserver/ZclOtaUpgradeServer.java index 123c50af0..057a90609 100644 --- a/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/app/otaserver/ZclOtaUpgradeServer.java +++ b/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/app/otaserver/ZclOtaUpgradeServer.java @@ -10,6 +10,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.Random; import java.util.concurrent.CountDownLatch; @@ -131,6 +132,11 @@ public class ZclOtaUpgradeServer implements ZigBeeApplication, ZclCommandListene */ private ZigBeeOtaServerStatus status; + /** + * The time of the last image update request + */ + private Date lastImageRequestTime = null; + /** * The parameter is part of Image Notify Command sent by the upgrade server. The parameter indicates * whether the client receiving Image Notify Command should send in Query Next Image Request @@ -220,7 +226,7 @@ public class ZclOtaUpgradeServer implements ZigBeeApplication, ZclCommandListene /** * The sleep time before trying to request the current firmware version */ - private static final long CURRENT_FW_VERSION_REQUEST_DELAY = 10000; + private static final long CURRENT_FW_VERSION_REQUEST_DELAY = 1500; /** * Field control value of 0x01 (bit 0 set) means that the client’s IEEE address is included in the payload. This @@ -279,6 +285,24 @@ public ZigBeeOtaServerStatus getServerStatus() { return status; } + /** + * Gets the time the last image update request was received + * + * @return the {@link Date} of the last received image request + */ + public Date getLastImageRequestTime() { + return lastImageRequestTime; + } + + /** + * Gets the percentage complete when an update is in progress. Returns null if not upgrade is in progress. + * + * @return the percentage complete, or null if not upgrade is in progress. + */ + public Integer getPercentageComplete() { + return status == ZigBeeOtaServerStatus.OTA_UNINITIALISED ? null : percentComplete; + } + /** * Cancels any upgrade transfers that are in progress and removes the current file. If a transfer is currently in * progress, then the listeners are notified. @@ -455,7 +479,7 @@ public void run() { } ImageUpgradeStatus status = ImageUpgradeStatus.getStatus(statusValue); if (status != ImageUpgradeStatus.DOWNLOAD_COMPLETE - && status != ImageUpgradeStatus.WAITING_TO_UPGRADE) { + && status != ImageUpgradeStatus.WAITING_TO_UPGRADE && status != ImageUpgradeStatus.NORMAL) { // Client is not in correct state to end upgrade switch (status) { case COUNT_DOWN: @@ -467,22 +491,22 @@ public void run() { case WAIT_FOR_MORE: updateStatus(ZigBeeOtaServerStatus.OTA_WAITING); break; - case NORMAL: case UNKNOWN: default: + logger.debug("{}: Unexpected remote transfer status {} - OTA failed.", + status, cluster.getZigBeeAddress()); updateStatus(ZigBeeOtaServerStatus.OTA_UPGRADE_FAILED); break; } return; } - updateStatus(ZigBeeOtaServerStatus.OTA_UPGRADE_FIRMWARE_RESTARTING); - UpgradeEndResponse upgradeEndResponse = new UpgradeEndResponse(otaFile.getManufacturerCode(), otaFile.getImageType(), otaFile.getFileVersion(), 0, 0); + upgradeEndResponse.setDisableDefaultResponse(true); // If we received a UpgradeEndCommand then send the UpgradeEndResponse as a response. Otherwise send - // it as a commands + // it as a command CommandResult response; if (command != null) { response = cluster.sendResponse(command, upgradeEndResponse).get(); @@ -491,9 +515,11 @@ public void run() { } if (!(response.isSuccess() || response.isTimeout())) { + logger.debug("{}: Failed to send UpgradeEnd - OTA failed.", cluster.getZigBeeAddress()); updateStatus(ZigBeeOtaServerStatus.OTA_UPGRADE_FAILED); return; } + updateStatus(ZigBeeOtaServerStatus.OTA_UPGRADE_FIRMWARE_RESTARTING); // Attempt to get the current firmware version. As the device will be restarting, which could take // some time to complete, we retry this a few times. @@ -526,6 +552,9 @@ public void run() { if (fileVersion.equals(otaFile.getFileVersion())) { updateStatus(ZigBeeOtaServerStatus.OTA_UPGRADE_COMPLETE); return; + } else { + updateStatus(ZigBeeOtaServerStatus.OTA_UPGRADE_FAILED); + return; } } else if (attributesResponse.getRecords().get(0) .getStatus() == ZclStatus.UNSUPPORTED_ATTRIBUTE) { @@ -695,6 +724,8 @@ public void run() { * @return true if the handler has, or will send a response to this command */ private boolean handleQueryNextImageCommand(QueryNextImageCommand command) { + lastImageRequestTime = new Date(); + if (otaFile == null) { logger.debug("{} OTA Server: No file set in QueryNextImageCommand.", cluster.getZigBeeAddress()); @@ -756,7 +787,7 @@ private boolean handleQueryNextImageCommand(QueryNextImageCommand command) { } // Update the state as we're starting - updateStatus(ZigBeeOtaServerStatus.OTA_TRANSFER_IN_PROGRESS); + updateStatus(ZigBeeOtaServerStatus.OTA_STARTED); startTransferTimer(); cluster.sendResponse(command, new QueryNextImageResponse( @@ -766,6 +797,8 @@ private boolean handleQueryNextImageCommand(QueryNextImageCommand command) { otaFile.getFileVersion(), otaFile.getImageSize())); + updateStatus(ZigBeeOtaServerStatus.OTA_TRANSFER_IN_PROGRESS); + return true; } @@ -894,7 +927,15 @@ private boolean handleUpgradeEndCommand(UpgradeEndCommand command) { && status != ZigBeeOtaServerStatus.OTA_TRANSFER_COMPLETE) { logger.debug("{} OTA Error: Invalid server state {} when handling UpgradeEndCommand.", cluster.getZigBeeAddress(), status); - cluster.sendDefaultResponse(command, ZclStatus.UNKNOWN); + + UpgradeEndResponse upgradeEndResponse = new UpgradeEndResponse(command.getManufacturerCode(), + command.getImageType(), command.getFileVersion(), 0, 0); + try { + cluster.sendCommand(upgradeEndResponse).get(); + } catch (InterruptedException e) { + } catch (ExecutionException e) { + } + return true; } @@ -987,6 +1028,8 @@ public boolean commandReceived(final ZclCommand command) { private ZigBeeOtaFile notifyUpdateRequestReceived(final QueryNextImageCommand command) { CountDownLatch latch; List otaFiles = new ArrayList<>(); + logger.debug("{}: ZigBeeOtaServer.notifyUpdateRequestReceived ({} listeners)", cluster.getZigBeeAddress(), + statusListeners.size()); synchronized (this) { // Notify the listeners @@ -1043,7 +1086,7 @@ public void run() { @Override public String toString() { - return "ZigBeeOtaServer [status=" + status + ", cluster=" + cluster + ", otaFile=" + otaFile + "]"; + return "ZigBeeOtaServer [status=" + status + ", listeners=" + statusListeners.size() + ", otaFile=" + otaFile + + "]"; } - } diff --git a/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/app/otaserver/ZigBeeOtaServerStatus.java b/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/app/otaserver/ZigBeeOtaServerStatus.java index 2d4da3558..75fbcd076 100644 --- a/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/app/otaserver/ZigBeeOtaServerStatus.java +++ b/com.zsmartsystems.zigbee/src/main/java/com/zsmartsystems/zigbee/app/otaserver/ZigBeeOtaServerStatus.java @@ -24,6 +24,11 @@ public enum ZigBeeOtaServerStatus { */ OTA_WAITING, + /** + * The transfer has commenced + */ + OTA_STARTED, + /** * The OTA server is currently progressing a transfer */ diff --git a/com.zsmartsystems.zigbee/src/test/java/com/zsmartsystems/zigbee/app/otaupgrade/ZclOtaUpgradeServerTest.java b/com.zsmartsystems.zigbee/src/test/java/com/zsmartsystems/zigbee/app/otaupgrade/ZclOtaUpgradeServerTest.java index 9f4f88ce1..9e3dce4e5 100644 --- a/com.zsmartsystems.zigbee/src/test/java/com/zsmartsystems/zigbee/app/otaupgrade/ZclOtaUpgradeServerTest.java +++ b/com.zsmartsystems.zigbee/src/test/java/com/zsmartsystems/zigbee/app/otaupgrade/ZclOtaUpgradeServerTest.java @@ -177,8 +177,8 @@ public void cancelUpgrade() throws Exception { server.commandReceived(query); await().atMost(1, SECONDS) - .until(() -> otaStatusCapture.contains(ZigBeeOtaServerStatus.OTA_TRANSFER_IN_PROGRESS)); - assertTrue(otaStatusCapture.contains(ZigBeeOtaServerStatus.OTA_TRANSFER_IN_PROGRESS)); + .until(() -> otaStatusCapture.contains(ZigBeeOtaServerStatus.OTA_STARTED)); + assertTrue(otaStatusCapture.contains(ZigBeeOtaServerStatus.OTA_STARTED)); otaStatusCapture.clear(); server.cancelUpgrade(); diff --git a/com.zsmartsystems.zigbee/src/test/java/com/zsmartsystems/zigbee/zcl/ZclClusterTest.java b/com.zsmartsystems.zigbee/src/test/java/com/zsmartsystems/zigbee/zcl/ZclClusterTest.java index 7312ac5c3..f5a227b6c 100644 --- a/com.zsmartsystems.zigbee/src/test/java/com/zsmartsystems/zigbee/zcl/ZclClusterTest.java +++ b/com.zsmartsystems.zigbee/src/test/java/com/zsmartsystems/zigbee/zcl/ZclClusterTest.java @@ -306,22 +306,6 @@ public void handleAttributeReport() throws Exception { List updatedAttributes = attributeCapture.getAllValues(); assertEquals(2, updatedAttributes.size()); - - ZclAttribute attribute1 = updatedAttributes.get(0); - ZclAttribute attribute2 = updatedAttributes.get(1); - assertTrue(attribute1.getLastValue() instanceof Boolean); - assertEquals(ZclDataType.BOOLEAN, attribute1.getDataType()); - assertEquals(ZclOnOffCluster.ATTR_ONOFF, attribute1.getId()); - assertEquals(true, attribute1.getLastValue()); - assertTrue(attribute2.getLastValue() instanceof Integer); - assertEquals(ZclDataType.UNSIGNED_16_BIT_INTEGER, attribute2.getDataType()); - assertEquals(ZclOnOffCluster.ATTR_ONTIME, attribute2.getId()); - assertEquals(1, attribute2.getLastValue()); - - assertEquals(attribute1.getLastReportTime(), attribute2.getLastReportTime()); - - cluster.removeAttributeListener(listenerMock); - assertEquals(0, attributeListeners.size()); } @Test