From baa124fda5f7145f18ff82a5981d70aa1f28e922 Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Wed, 21 Aug 2024 18:01:48 +0200 Subject: [PATCH] GH-1645: Add very simple Queue mode support to Leshan Server Demo. --- .../leshan/demo/server/LeshanServerDemo.java | 2 +- .../demo/server/servlet/ClientServlet.java | 179 ++++++++++++------ .../demo/server/servlet/EventServlet.java | 2 +- .../servlet/queuemode/QueueHandler.java | 147 ++++++++++++++ .../CompositeObjectControl.vue | 16 +- .../components/instance/InstanceControl.vue | 16 +- .../src/components/object/ObjectControl.vue | 16 +- .../components/resources/ResourceControl.vue | 16 +- .../resources/ResourceInstanceControl.vue | 16 +- leshan-demo-server/webapp/src/main.js | 18 +- .../webapp/src/views/Client.vue | 15 ++ .../queue/StaticClientAwakeTimeProvider.java | 2 +- 12 files changed, 350 insertions(+), 95 deletions(-) create mode 100644 leshan-demo-server/src/main/java/org/eclipse/leshan/demo/server/servlet/queuemode/QueueHandler.java diff --git a/leshan-demo-server/src/main/java/org/eclipse/leshan/demo/server/LeshanServerDemo.java b/leshan-demo-server/src/main/java/org/eclipse/leshan/demo/server/LeshanServerDemo.java index b1f882f384..6f504f1ddc 100644 --- a/leshan-demo-server/src/main/java/org/eclipse/leshan/demo/server/LeshanServerDemo.java +++ b/leshan-demo-server/src/main/java/org/eclipse/leshan/demo/server/LeshanServerDemo.java @@ -306,7 +306,7 @@ private static Server createJettyServer(LeshanServerDemoCLI cli, LeshanServer lw ServletHolder eventServletHolder = new ServletHolder(eventServlet); root.addServlet(eventServletHolder, "/api/event/*"); - ServletHolder clientServletHolder = new ServletHolder(new ClientServlet(lwServer)); + ServletHolder clientServletHolder = new ServletHolder(new ClientServlet(lwServer, eventServlet)); root.addServlet(clientServletHolder, "/api/clients/*"); ServletHolder securityServletHolder; diff --git a/leshan-demo-server/src/main/java/org/eclipse/leshan/demo/server/servlet/ClientServlet.java b/leshan-demo-server/src/main/java/org/eclipse/leshan/demo/server/servlet/ClientServlet.java index e9dd56a313..95e002a63a 100644 --- a/leshan-demo-server/src/main/java/org/eclipse/leshan/demo/server/servlet/ClientServlet.java +++ b/leshan-demo-server/src/main/java/org/eclipse/leshan/demo/server/servlet/ClientServlet.java @@ -27,6 +27,9 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; @@ -55,11 +58,13 @@ import org.eclipse.leshan.core.request.CreateRequest; import org.eclipse.leshan.core.request.DeleteRequest; import org.eclipse.leshan.core.request.DiscoverRequest; +import org.eclipse.leshan.core.request.DownlinkDeviceManagementRequest; import org.eclipse.leshan.core.request.ExecuteRequest; import org.eclipse.leshan.core.request.ObserveCompositeRequest; import org.eclipse.leshan.core.request.ObserveRequest; import org.eclipse.leshan.core.request.ReadCompositeRequest; import org.eclipse.leshan.core.request.ReadRequest; +import org.eclipse.leshan.core.request.SimpleDownlinkRequest; import org.eclipse.leshan.core.request.WriteAttributesRequest; import org.eclipse.leshan.core.request.WriteCompositeRequest; import org.eclipse.leshan.core.request.WriteRequest; @@ -69,25 +74,13 @@ import org.eclipse.leshan.core.request.exception.InvalidResponseException; import org.eclipse.leshan.core.request.exception.RequestCanceledException; import org.eclipse.leshan.core.request.exception.RequestRejectedException; -import org.eclipse.leshan.core.response.CancelCompositeObservationResponse; -import org.eclipse.leshan.core.response.CancelObservationResponse; -import org.eclipse.leshan.core.response.CreateResponse; -import org.eclipse.leshan.core.response.DeleteResponse; -import org.eclipse.leshan.core.response.DiscoverResponse; -import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.LwM2mResponse; -import org.eclipse.leshan.core.response.ObserveCompositeResponse; -import org.eclipse.leshan.core.response.ObserveResponse; -import org.eclipse.leshan.core.response.ReadCompositeResponse; -import org.eclipse.leshan.core.response.ReadResponse; -import org.eclipse.leshan.core.response.WriteAttributesResponse; -import org.eclipse.leshan.core.response.WriteCompositeResponse; -import org.eclipse.leshan.core.response.WriteResponse; import org.eclipse.leshan.demo.server.servlet.json.JacksonLinkSerializer; import org.eclipse.leshan.demo.server.servlet.json.JacksonLwM2mNodeDeserializer; import org.eclipse.leshan.demo.server.servlet.json.JacksonLwM2mNodeSerializer; import org.eclipse.leshan.demo.server.servlet.json.JacksonRegistrationSerializer; import org.eclipse.leshan.demo.server.servlet.json.JacksonResponseSerializer; +import org.eclipse.leshan.demo.server.servlet.queuemode.QueueHandler; import org.eclipse.leshan.server.LeshanServer; import org.eclipse.leshan.server.registration.Registration; import org.slf4j.Logger; @@ -124,9 +117,13 @@ public class ClientServlet extends HttpServlet { private final LeshanServer server; private final ObjectMapper mapper; private final LwM2mAttributeParser attributeParser; + private final QueueHandler queueHandler; + private final EventServlet eventServlet; - public ClientServlet(LeshanServer server) { + public ClientServlet(LeshanServer server, EventServlet servlet) { this.server = server; + this.queueHandler = new QueueHandler(server); + this.eventServlet = servlet; mapper = new ObjectMapper(); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); @@ -204,8 +201,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se // create & process request ReadCompositeRequest request = new ReadCompositeRequest(pathContentFormat, nodeContentFormat, paths); - ReadCompositeResponse cResponse = server.send(registration, request, extractTimeout(req)); - processDeviceResponse(req, resp, cResponse); + sendRequestAndWriteResponse(registration, request, req, resp); } else { resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); resp.getWriter().format("No registered client with id '%s'", clientEndpoint).flush(); @@ -224,8 +220,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se if (registration != null) { // create & process request DiscoverRequest request = new DiscoverRequest(target); - DiscoverResponse cResponse = server.send(registration, request, extractTimeout(req)); - processDeviceResponse(req, resp, cResponse); + sendRequestAndWriteResponse(registration, request, req, resp); } else { resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); resp.getWriter().format("No registered client with id '%s'", clientEndpoint).flush(); @@ -249,8 +244,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se // create & process request ReadRequest request = new ReadRequest(contentFormat, target); - ReadResponse cResponse = server.send(registration, request, extractTimeout(req)); - processDeviceResponse(req, resp, cResponse); + sendRequestAndWriteResponse(registration, request, req, resp); } else { resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); resp.getWriter().format("No registered client with id '%s'", clientEndpoint).flush(); @@ -314,10 +308,8 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws Se new TypeReference>() { }); // create & process request - WriteCompositeResponse cResponse = server.send(registration, - new WriteCompositeRequest(nodeContentFormat, values, null), extractTimeout(req)); - processDeviceResponse(req, resp, cResponse); - + sendRequestAndWriteResponse(registration, + new WriteCompositeRequest(nodeContentFormat, values, null), req, resp); } else { resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); resp.getWriter().format("no registered client with id '%s'", clientEndpoint).flush(); @@ -344,8 +336,7 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws Se LwM2mAttributeSet attributes = new LwM2mAttributeSet( attributeParser.parseUriQuery(req.getQueryString())); WriteAttributesRequest request = new WriteAttributesRequest(target, attributes); - WriteAttributesResponse cResponse = server.send(registration, request, extractTimeout(req)); - processDeviceResponse(req, resp, cResponse); + sendRequestAndWriteResponse(registration, request, req, resp); } else { // get content format String contentFormatParam = req.getParameter(FORMAT_PARAM); @@ -363,8 +354,7 @@ protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws Se LwM2mNode node = extractLwM2mNode(target, req, new LwM2mPath(target)); WriteRequest request = new WriteRequest(replace ? Mode.REPLACE : Mode.UPDATE, contentFormat, target, node); - WriteResponse cResponse = server.send(registration, request, extractTimeout(req)); - processDeviceResponse(req, resp, cResponse); + sendRequestAndWriteResponse(registration, request, req, resp); } } else { resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); @@ -405,8 +395,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S // create & process request ObserveCompositeRequest request = new ObserveCompositeRequest(pathContentFormat, nodeContentFormat, paths); - ObserveCompositeResponse cResponse = server.send(registration, request, extractTimeout(req)); - processDeviceResponse(req, resp, cResponse); + sendRequestAndWriteResponse(registration, request, req, resp); } else { resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); resp.getWriter().format("No registered client with id '%s'", clientEndpoint).flush(); @@ -431,8 +420,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S // create & process request ObserveRequest request = new ObserveRequest(contentFormat, target); - ObserveResponse cResponse = server.send(registration, request, extractTimeout(req)); - processDeviceResponse(req, resp, cResponse); + sendRequestAndWriteResponse(registration, request, req, resp); } else { resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); resp.getWriter().format("no registered client with id '%s'", clientEndpoint).flush(); @@ -455,8 +443,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S params = IOUtils.toString(req.getInputStream(), StandardCharsets.UTF_8); } ExecuteRequest request = new ExecuteRequest(target, params); - ExecuteResponse cResponse = server.send(registration, request, extractTimeout(req)); - processDeviceResponse(req, resp, cResponse); + sendRequestAndWriteResponse(registration, request, req, resp); } else { resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); resp.getWriter().format("no registered client with id '%s'", clientEndpoint).flush(); @@ -489,8 +476,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S request = new CreateRequest(contentFormat, target, (LwM2mObjectInstance) node); } - CreateResponse cResponse = server.send(registration, request, extractTimeout(req)); - processDeviceResponse(req, resp, cResponse); + sendRequestAndWriteResponse(registration, request, req, resp); } else { throw new IllegalArgumentException("payload must contain an object instance"); } @@ -531,13 +517,15 @@ protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws .getPaths().equals(LwM2mPath.getLwM2mPathList(Arrays.asList(paths)))) .findFirst(); if (observation.isPresent()) { - CancelCompositeObservationResponse response = server.send(registration, + CompletableFuture futureResponse = sendRequestAndWriteResponse(registration, new CancelCompositeObservationRequest((CompositeObservation) observation.get()), - extractTimeout(req)); - processDeviceResponse(req, resp, response); - if (response.isSuccess()) { - server.getObservationService().cancelCompositeObservations(registration, paths); - } + req, resp); + futureResponse.thenApply(r -> { + if (r.isSuccess()) { + server.getObservationService().cancelCompositeObservations(registration, paths); + } + return r; + }); } else { resp.setStatus(HttpServletResponse.SC_NOT_FOUND); resp.getWriter().format("no composite observation for paths %s for client '%s'", @@ -571,13 +559,15 @@ protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws && ((SingleObservation) obs).getPath().equals(new LwM2mPath(target))) .findFirst(); if (observation.isPresent()) { - CancelObservationResponse response = server.send(registration, - new CancelObservationRequest((SingleObservation) observation.get()), - extractTimeout(req)); - processDeviceResponse(req, resp, response); - if (response.isSuccess()) { - server.getObservationService().cancelObservations(registration, target); - } + CompletableFuture futureResponse = sendRequestAndWriteResponse(registration, + new CancelObservationRequest((SingleObservation) observation.get()), req, resp); + + futureResponse.thenApply(r -> { + if (r.isSuccess()) { + server.getObservationService().cancelObservations(registration, target); + } + return r; + }); } else { resp.setStatus(HttpServletResponse.SC_NOT_FOUND); resp.getWriter() @@ -604,8 +594,7 @@ protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws Registration registration = server.getRegistrationService().getByEndpoint(clientEndpoint); if (registration != null) { DeleteRequest request = new DeleteRequest(target); - DeleteResponse cResponse = server.send(registration, request, extractTimeout(req)); - processDeviceResponse(req, resp, cResponse); + sendRequestAndWriteResponse(registration, request, req, resp); } else { resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); resp.getWriter().format("no registered client with id '%s'", clientEndpoint).flush(); @@ -615,17 +604,85 @@ protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws } } - private void processDeviceResponse(HttpServletRequest req, HttpServletResponse resp, LwM2mResponse cResponse) - throws IOException { - if (cResponse == null) { - LOG.warn(String.format("Request %s%s timed out.", req.getServletPath(), req.getPathInfo())); - resp.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT); - resp.getWriter().append("Request timeout").flush(); + private CompletableFuture sendRequestAndWriteResponse(Registration destination, + DownlinkDeviceManagementRequest lwm2mReq, HttpServletRequest httpReq, HttpServletResponse httpResp) + throws InterruptedException, IOException { + + // Send Request + CompletableFuture future = queueHandler.send(destination, lwm2mReq, extractTimeout(httpReq)); + + if (future.isDone()) { + // if we get response now + try { + LwM2mResponse lwm2mResp = future.get(); + processDeviceResponse(httpReq, httpResp, lwm2mResp); + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } else { + throw new RuntimeException(String + .format("Unexpected error when trying to send request %s because of %s", lwm2mReq, cause)); + } + } } else { - String response = this.mapper.writeValueAsString(cResponse); - resp.setContentType("application/json"); - resp.getOutputStream().write(response.getBytes()); - resp.setStatus(HttpServletResponse.SC_OK); + // else response will be receive later (probably because request was delayed as device is not awake) + final String requestId = UUID.randomUUID().toString(); + future.thenApply(lwm2mResp -> { + try { + // when response will be received, send a event with the response + ResponseDelayed responseDelayed = new ResponseDelayed(); + responseDelayed.ep = destination.getEndpoint(); + if (lwm2mReq instanceof SimpleDownlinkRequest) { + responseDelayed.path = ((SimpleDownlinkRequest) lwm2mReq).getPath().toString(); + } + responseDelayed.response = lwm2mResp; + responseDelayed.requestId = requestId; + responseDelayed.delayed = true; + eventServlet.sendEvent("REQUEST_RESPONSE", this.mapper.writeValueAsString(responseDelayed), + destination.getEndpoint()); + } catch (JsonProcessingException e) { + throw new IllegalStateException(e); + } + return lwm2mResp; + }); + + // answer that request is delayed + ResponseDelayed responseDelayed = new ResponseDelayed(); + responseDelayed.ep = destination.getEndpoint(); + if (lwm2mReq instanceof SimpleDownlinkRequest) { + responseDelayed.path = ((SimpleDownlinkRequest) lwm2mReq).getPath().toString(); + } + responseDelayed.response = null; + responseDelayed.requestId = requestId; + responseDelayed.delayed = true; + httpResp.setContentType("application/json"); + httpResp.getOutputStream().write(this.mapper.writeValueAsString(responseDelayed).getBytes()); + httpResp.setStatus(HttpServletResponse.SC_OK); + } + + return future; + } + + public static class ResponseDelayed { + public String ep; + public String path; + public String requestId; + public LwM2mResponse response; // may be null if response is delayed + public boolean delayed; + } + + private void processDeviceResponse(HttpServletRequest httpReq, HttpServletResponse httpResp, + LwM2mResponse lwm2mResp) throws IOException { + if (lwm2mResp == null) { + LOG.warn(String.format("Request %s%s timed out.", httpReq.getServletPath(), httpReq.getPathInfo())); + httpResp.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT); + httpResp.getWriter().append("Request timeout").flush(); + } else { + String response = this.mapper.writeValueAsString(lwm2mResp); + httpResp.setContentType("application/json"); + httpResp.getOutputStream().write(response.getBytes()); + httpResp.setStatus(HttpServletResponse.SC_OK); } } diff --git a/leshan-demo-server/src/main/java/org/eclipse/leshan/demo/server/servlet/EventServlet.java b/leshan-demo-server/src/main/java/org/eclipse/leshan/demo/server/servlet/EventServlet.java index 929fd916dc..9fe7e4d09c 100644 --- a/leshan-demo-server/src/main/java/org/eclipse/leshan/demo/server/servlet/EventServlet.java +++ b/leshan-demo-server/src/main/java/org/eclipse/leshan/demo/server/servlet/EventServlet.java @@ -310,7 +310,7 @@ public EventServlet(LeshanServer server) { this.mapper = mapper; } - private synchronized void sendEvent(String event, String data, String endpoint) { + public synchronized void sendEvent(String event, String data, String endpoint) { if (LOG.isDebugEnabled()) { LOG.debug("Dispatching {} event from endpoint {}", event, endpoint); } diff --git a/leshan-demo-server/src/main/java/org/eclipse/leshan/demo/server/servlet/queuemode/QueueHandler.java b/leshan-demo-server/src/main/java/org/eclipse/leshan/demo/server/servlet/queuemode/QueueHandler.java new file mode 100644 index 0000000000..623246d192 --- /dev/null +++ b/leshan-demo-server/src/main/java/org/eclipse/leshan/demo/server/servlet/queuemode/QueueHandler.java @@ -0,0 +1,147 @@ +/******************************************************************************* + * Copyright (c) 2024 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.demo.server.servlet.queuemode; + +import java.util.Collection; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.request.DownlinkDeviceManagementRequest; +import org.eclipse.leshan.core.request.exception.ClientSleepingException; +import org.eclipse.leshan.core.response.LwM2mResponse; +import org.eclipse.leshan.server.LeshanServer; +import org.eclipse.leshan.server.queue.PresenceListener; +import org.eclipse.leshan.server.registration.Registration; +import org.eclipse.leshan.server.registration.RegistrationListener; +import org.eclipse.leshan.server.registration.RegistrationUpdate; + +/** + * This is a very simple in memory way to store request when device is sleeping. + */ +public class QueueHandler { + + private final LeshanServer server; + private final ConcurrentMap requestsToSend; + + private class QueueRequestData { + private long timeout; + private DownlinkDeviceManagementRequest request; + private CompletableFuture responseFuture; + } + + public QueueHandler(LeshanServer server) { + this.server = server; + this.requestsToSend = new ConcurrentHashMap<>(); + + // Handle Presence Service Event + server.getPresenceService().addListener(new PresenceListener() { + @Override + public void onSleeping(Registration registration) { + } + + @Override + public void onAwake(Registration registration) { + // try to send store request + QueueRequestData data = requestsToSend.remove(registration.getId()); + if (data != null) { + try { + server.send(registration, data.request, data.timeout, // + r -> { + data.responseFuture.complete(r); + }, // + err -> { + data.responseFuture.completeExceptionally(err); + }); + } catch (RuntimeException e) { + data.responseFuture.completeExceptionally(e); + } + } + } + }); + + // Handle Registration Service Event + server.getRegistrationService().addListener(new RegistrationListener() { + @Override + public void updated(RegistrationUpdate update, Registration updatedReg, Registration previousReg) { + } + + @Override + public void unregistered(Registration registration, Collection observations, boolean expired, + Registration newReg) { + QueueRequestData data = requestsToSend.remove(registration.getId()); + if (data != null) { + data.responseFuture.cancel(false); + } + } + + @Override + public void registered(Registration registration, Registration previousReg, + Collection previousObservations) { + } + }); + + } + + public CompletableFuture send(Registration destination, DownlinkDeviceManagementRequest request, + long timeoutInMs) throws InterruptedException { + + // is client awake ? + boolean clientAwake = server.getPresenceService().isClientAwake(destination); + + // client is awake we try to send request now + CompletableFuture future = new CompletableFuture<>(); + if (clientAwake) { + try { + // TODO ideally we should use async way to send request + LwM2mResponse response = server.send(destination, request, timeoutInMs); + future.complete(response); + } catch (ClientSleepingException e) { + clientAwake = false; + } + } + + // client is not awake we queue the request for later. + if (!clientAwake) { + QueueRequestData data = new QueueRequestData(); + data.request = request; + data.responseFuture = future; + data.timeout = timeoutInMs; + QueueRequestData previous = requestsToSend.put(destination.getId(), data); + // Cancel previous future as we store only last request + if (previous != null) { + previous.responseFuture.cancel(false); + } + + // We want to be sure there still a registration for this ID + // The idea of this code is to avoid race condition which could lead to memory leak, + // In case where Registration is removed before we push QueueRequestData. + // Any better idea to handle this is welcomed. + requestsToSend.compute(destination.getId(), (id, currentData) -> { + if (server.getRegistrationService().getById(id) == null) { + // registration was removed, so we don't want to keep data for it + currentData.responseFuture.cancel(false); + return null; + } else { + // registration is still there, we can add it + return currentData; + } + }); + } + return future; + } +} diff --git a/leshan-demo-server/webapp/src/components/compositeOperation/CompositeObjectControl.vue b/leshan-demo-server/webapp/src/components/compositeOperation/CompositeObjectControl.vue index fbdd9ec9a3..7306fdd3e9 100644 --- a/leshan-demo-server/webapp/src/components/compositeOperation/CompositeObjectControl.vue +++ b/leshan-demo-server/webapp/src/components/compositeOperation/CompositeObjectControl.vue @@ -107,12 +107,16 @@ export default { return `?timeout=${timeout.get()}&pathformat=${compositePathFormat.get()}&nodeformat=${compositeNodeFormat.get()}`; }, updateState(content, requestButton) { - let state = !content.valid - ? "warning" - : content.success - ? "success" - : "error"; - requestButton.changeState(state, content.status); + if ("valid" in content || "success" in content) { + let state = !content.valid + ? "warning" + : content.success + ? "success" + : "error"; + requestButton.changeState(state, content.status); + } else { + requestButton.resetState(); + } }, read(requestButton) { this.axios diff --git a/leshan-demo-server/webapp/src/components/instance/InstanceControl.vue b/leshan-demo-server/webapp/src/components/instance/InstanceControl.vue index a889fcdb4f..3e622765c3 100644 --- a/leshan-demo-server/webapp/src/components/instance/InstanceControl.vue +++ b/leshan-demo-server/webapp/src/components/instance/InstanceControl.vue @@ -89,12 +89,16 @@ export default { return `?timeout=${timeout.get()}&format=${format.get()}`; }, updateState(content, requestButton) { - let state = !content.valid - ? "warning" - : content.success - ? "success" - : "error"; - requestButton.changeState(state, content.status); + if ("valid" in content || "success" in content) { + let state = !content.valid + ? "warning" + : content.success + ? "success" + : "error"; + requestButton.changeState(state, content.status); + } else { + requestButton.resetState(); + } }, read(requestButton) { this.axios diff --git a/leshan-demo-server/webapp/src/components/object/ObjectControl.vue b/leshan-demo-server/webapp/src/components/object/ObjectControl.vue index ee1c133d77..1d86441708 100644 --- a/leshan-demo-server/webapp/src/components/object/ObjectControl.vue +++ b/leshan-demo-server/webapp/src/components/object/ObjectControl.vue @@ -82,12 +82,16 @@ export default { return `?timeout=${timeout.get()}&format=${format.get()}`; }, updateState(content, requestButton) { - let state = !content.valid - ? "warning" - : content.success - ? "success" - : "error"; - requestButton.changeState(state, content.status); + if ("valid" in content || "success" in content) { + let state = !content.valid + ? "warning" + : content.success + ? "success" + : "error"; + requestButton.changeState(state, content.status); + } else { + requestButton.resetState(); + } }, openCreateDialog() { this.dialog = true; diff --git a/leshan-demo-server/webapp/src/components/resources/ResourceControl.vue b/leshan-demo-server/webapp/src/components/resources/ResourceControl.vue index 79211b84b2..6f807f6ab3 100644 --- a/leshan-demo-server/webapp/src/components/resources/ResourceControl.vue +++ b/leshan-demo-server/webapp/src/components/resources/ResourceControl.vue @@ -118,12 +118,16 @@ export default { return `?timeout=${timeout.get()}&format=${this.getFormat()}`; }, updateState(content, requestButton) { - let state = !content.valid - ? "warning" - : content.success - ? "success" - : "error"; - requestButton.changeState(state, content.status); + if ("valid" in content || "success" in content) { + let state = !content.valid + ? "warning" + : content.success + ? "success" + : "error"; + requestButton.changeState(state, content.status); + } else { + requestButton.resetState(); + } }, read(requestButton) { this.axios diff --git a/leshan-demo-server/webapp/src/components/resources/ResourceInstanceControl.vue b/leshan-demo-server/webapp/src/components/resources/ResourceInstanceControl.vue index d923052878..d516148549 100644 --- a/leshan-demo-server/webapp/src/components/resources/ResourceInstanceControl.vue +++ b/leshan-demo-server/webapp/src/components/resources/ResourceInstanceControl.vue @@ -105,12 +105,16 @@ export default { }, updateState(content, requestButton) { - let state = !content.valid - ? "warning" - : content.success - ? "success" - : "error"; - requestButton.changeState(state, content.status); + if ("valid" in content || "success" in content) { + let state = !content.valid + ? "warning" + : content.success + ? "success" + : "error"; + requestButton.changeState(state, content.status); + } else { + requestButton.resetState(); + } }, read(requestButton) { this.axios diff --git a/leshan-demo-server/webapp/src/main.js b/leshan-demo-server/webapp/src/main.js index 4aacf5a897..9a66cf6b2f 100644 --- a/leshan-demo-server/webapp/src/main.js +++ b/leshan-demo-server/webapp/src/main.js @@ -32,8 +32,24 @@ Vue.directive("visible", function (el, binding) { el.style.visibility = binding.value ? "visible" : "hidden"; }); -new Vue({ +let v = new Vue({ vuetify, router, render: (h) => h(App), }).$mount("#app"); + +/** Add Leshan Server Demo specific axios interceptor */ +v.$axios.interceptors.response.use(function (response) { + if (response.data.delayed) { + // show request will be delayed + let msg = `Device is not awake +
Request will be delayed until device is awake again. +
Leshan Server Demo is only able to delayed the last request.`; + + Vue.prototype.$dialog.notify.info(msg, { + position: "bottom-right", + timeout: 5000, + }); + } + return response; +}); diff --git a/leshan-demo-server/webapp/src/views/Client.vue b/leshan-demo-server/webapp/src/views/Client.vue index d207b7649d..af09b23f1d 100644 --- a/leshan-demo-server/webapp/src/views/Client.vue +++ b/leshan-demo-server/webapp/src/views/Client.vue @@ -185,6 +185,21 @@ export default { this.$store.newNode(this.$route.params.endpoint, path, msg.val[path]); } }) + .on("REQUEST_RESPONSE", (msg) => { + if (msg.path) { + // we suppose that if we have a path property then this is a single node + this.$store.newNode( + this.$route.params.endpoint, + msg.path, + msg.response.content + ); + } else { + this.$store.newNodes( + this.$route.params.endpoint, + msg.response.content + ); + } + }) .on("NOTIFICATION", (msg) => { if (msg.kind == "composite") { this.$store.newNodes(this.$route.params.endpoint, msg.val); diff --git a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/queue/StaticClientAwakeTimeProvider.java b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/queue/StaticClientAwakeTimeProvider.java index e0bcd2adff..0ec8ab5510 100644 --- a/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/queue/StaticClientAwakeTimeProvider.java +++ b/leshan-lwm2m-server/src/main/java/org/eclipse/leshan/server/queue/StaticClientAwakeTimeProvider.java @@ -22,7 +22,7 @@ public class StaticClientAwakeTimeProvider implements ClientAwakeTimeProvider { private final int clientAwakeTime; /** - * Create a {@link ClientAwakeTimeProvider} which always return 9300ms which is the default CoAP MAX_TRANSMIT_WAIT + * Create a {@link ClientAwakeTimeProvider} which always return 93000ms which is the default CoAP MAX_TRANSMIT_WAIT * value. */ public StaticClientAwakeTimeProvider() {