176 changed files with 7851 additions and 4250 deletions
@ -0,0 +1,30 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.common.data.device.data.lwm2m; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import java.util.Map; |
|||
|
|||
@Data |
|||
public class BootstrapConfiguration { |
|||
|
|||
//TODO: define the objects;
|
|||
private Map<String, Object> servers; |
|||
private Map<String, Object> lwm2mServer; |
|||
private Map<String, Object> bootstrapServer; |
|||
|
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.common.data.device.data.lwm2m; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonInclude; |
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
@JsonInclude(JsonInclude.Include.NON_NULL) |
|||
public class ObjectAttributes { |
|||
|
|||
private Long dim; |
|||
private String ver; |
|||
private Long pmin; |
|||
private Long pmax; |
|||
private Double gt; |
|||
private Double lt; |
|||
private Double st; |
|||
|
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.common.data.device.data.lwm2m; |
|||
|
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
public class OtherConfiguration { |
|||
|
|||
private Integer fwUpdateStrategy; |
|||
private Integer swUpdateStrategy; |
|||
private Integer clientOnlyObserveAfterConnect; |
|||
private String fwUpdateRecourse; |
|||
private String swUpdateRecourse; |
|||
|
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.common.data.device.data.lwm2m; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import java.util.Map; |
|||
import java.util.Set; |
|||
|
|||
@Data |
|||
public class TelemetryMappingConfiguration { |
|||
|
|||
private Map<String, String> keyName; |
|||
private Set<String> observe; |
|||
private Set<String> attribute; |
|||
private Set<String> telemetry; |
|||
private Map<String, ObjectAttributes> attributeLwm2m; |
|||
|
|||
} |
|||
@ -0,0 +1,211 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.queue.common; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.junit.After; |
|||
import org.junit.Before; |
|||
import org.junit.Test; |
|||
import org.junit.runner.RunWith; |
|||
import org.mockito.ArgumentCaptor; |
|||
import org.mockito.Mock; |
|||
import org.mockito.junit.MockitoJUnitRunner; |
|||
import org.thingsboard.server.queue.TbQueueAdmin; |
|||
import org.thingsboard.server.queue.TbQueueConsumer; |
|||
import org.thingsboard.server.queue.TbQueueMsg; |
|||
import org.thingsboard.server.queue.TbQueueProducer; |
|||
|
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.CountDownLatch; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.concurrent.atomic.AtomicLong; |
|||
|
|||
import static org.hamcrest.Matchers.equalTo; |
|||
import static org.hamcrest.Matchers.greaterThanOrEqualTo; |
|||
import static org.hamcrest.Matchers.is; |
|||
import static org.hamcrest.Matchers.lessThan; |
|||
import static org.mockito.ArgumentMatchers.any; |
|||
import static org.mockito.ArgumentMatchers.anyLong; |
|||
import static org.mockito.BDDMockito.willAnswer; |
|||
import static org.mockito.BDDMockito.willDoNothing; |
|||
import static org.mockito.BDDMockito.willReturn; |
|||
import static org.mockito.Mockito.RETURNS_DEEP_STUBS; |
|||
import static org.mockito.Mockito.atLeastOnce; |
|||
import static org.mockito.Mockito.mock; |
|||
import static org.mockito.Mockito.never; |
|||
import static org.mockito.Mockito.spy; |
|||
import static org.mockito.Mockito.times; |
|||
import static org.mockito.Mockito.verify; |
|||
|
|||
import static org.hamcrest.MatcherAssert.assertThat; |
|||
import static org.mockito.hamcrest.MockitoHamcrest.longThat; |
|||
|
|||
@Slf4j |
|||
@RunWith(MockitoJUnitRunner.class) |
|||
public class DefaultTbQueueRequestTemplateTest { |
|||
|
|||
@Mock |
|||
TbQueueAdmin queueAdmin; |
|||
@Mock |
|||
TbQueueProducer<TbQueueMsg> requestTemplate; |
|||
@Mock |
|||
TbQueueConsumer<TbQueueMsg> responseTemplate; |
|||
@Mock |
|||
ExecutorService executorMock; |
|||
|
|||
ExecutorService executor; |
|||
String topic = "js-responses-tb-node-0"; |
|||
long maxRequestTimeout = 10; |
|||
long maxPendingRequests = 32; |
|||
long pollInterval = 5; |
|||
|
|||
DefaultTbQueueRequestTemplate inst; |
|||
|
|||
@Before |
|||
public void setUp() throws Exception { |
|||
willReturn(topic).given(responseTemplate).getTopic(); |
|||
inst = spy(new DefaultTbQueueRequestTemplate( |
|||
queueAdmin, requestTemplate, responseTemplate, |
|||
maxRequestTimeout, maxPendingRequests, pollInterval, executorMock)); |
|||
|
|||
} |
|||
|
|||
@After |
|||
public void tearDown() throws Exception { |
|||
if (executor != null) { |
|||
executor.shutdownNow(); |
|||
} |
|||
} |
|||
|
|||
@Test |
|||
public void givenInstance_whenVerifyInitialParameters_thenOK() { |
|||
assertThat(inst.maxPendingRequests, equalTo(maxPendingRequests)); |
|||
assertThat(inst.maxRequestTimeoutNs, equalTo(TimeUnit.MILLISECONDS.toNanos(maxRequestTimeout))); |
|||
assertThat(inst.pollInterval, equalTo(pollInterval)); |
|||
assertThat(inst.executor, is(executorMock)); |
|||
assertThat(inst.stopped, is(false)); |
|||
assertThat(inst.internalExecutor, is(false)); |
|||
} |
|||
|
|||
@Test |
|||
public void givenExternalExecutor_whenInitStop_thenOK() { |
|||
inst.init(); |
|||
assertThat(inst.nextCleanupNs, equalTo(0L)); |
|||
verify(queueAdmin, times(1)).createTopicIfNotExists(topic); |
|||
verify(requestTemplate, times(1)).init(); |
|||
verify(responseTemplate, times(1)).subscribe(); |
|||
verify(executorMock, times(1)).submit(any(Runnable.class)); |
|||
|
|||
inst.stop(); |
|||
assertThat(inst.stopped, is(true)); |
|||
verify(responseTemplate, times(1)).unsubscribe(); |
|||
verify(requestTemplate, times(1)).stop(); |
|||
verify(executorMock, never()).shutdownNow(); |
|||
} |
|||
|
|||
@Test |
|||
public void givenMainLoop_whenLoopFewTimes_thenVerifyInvocationCount() throws InterruptedException { |
|||
executor = inst.createExecutor(); |
|||
CountDownLatch latch = new CountDownLatch(5); |
|||
willDoNothing().given(inst).sleep(anyLong()); |
|||
willAnswer(invocation -> { |
|||
if (latch.getCount() == 1) { |
|||
inst.stop(); //stop the loop in natural way
|
|||
} |
|||
if (latch.getCount() == 3 || latch.getCount() == 4) { |
|||
latch.countDown(); |
|||
throw new RuntimeException("test catch block"); |
|||
} |
|||
latch.countDown(); |
|||
return null; |
|||
}).given(inst).fetchAndProcessResponses(); |
|||
|
|||
executor.submit(inst::mainLoop); |
|||
latch.await(10, TimeUnit.SECONDS); |
|||
|
|||
verify(inst, times(5)).fetchAndProcessResponses(); |
|||
verify(inst, times(2)).sleep(longThat(lessThan(TimeUnit.MILLISECONDS.toNanos(inst.pollInterval)))); |
|||
} |
|||
|
|||
@Test |
|||
public void givenMessages_whenSend_thenOK() { |
|||
willDoNothing().given(inst).sendToRequestTemplate(any(), any(), any(), any()); |
|||
inst.init(); |
|||
final int msgCount = 10; |
|||
for (int i = 0; i < msgCount; i++) { |
|||
inst.send(getRequestMsgMock()); |
|||
} |
|||
assertThat(inst.pendingRequests.mappingCount(), equalTo((long) msgCount)); |
|||
verify(inst, times(msgCount)).sendToRequestTemplate(any(), any(), any(), any()); |
|||
} |
|||
|
|||
@Test |
|||
public void givenMessagesOverMaxPendingRequests_whenSend_thenImmediateFailedFutureForTheOfRequests() { |
|||
willDoNothing().given(inst).sendToRequestTemplate(any(), any(), any(), any()); |
|||
inst.init(); |
|||
int msgOverflowCount = 10; |
|||
for (int i = 0; i < inst.maxPendingRequests; i++) { |
|||
assertThat(inst.send(getRequestMsgMock()).isDone(), is(false)); //SettableFuture future - pending only
|
|||
} |
|||
for (int i = 0; i < msgOverflowCount; i++) { |
|||
assertThat("max pending requests overflow", inst.send(getRequestMsgMock()).isDone(), is(true)); //overflow, immediate failed future
|
|||
} |
|||
assertThat(inst.pendingRequests.mappingCount(), equalTo(inst.maxPendingRequests)); |
|||
verify(inst, times((int) inst.maxPendingRequests)).sendToRequestTemplate(any(), any(), any(), any()); |
|||
} |
|||
|
|||
@Test |
|||
public void givenNothing_whenSendAndFetchAndProcessResponsesWithTimeout_thenFail() { |
|||
//given
|
|||
AtomicLong currentTime = new AtomicLong(); |
|||
willAnswer(x -> { |
|||
log.info("currentTime={}", currentTime.get()); |
|||
return currentTime.get(); |
|||
}).given(inst).getCurrentClockNs(); |
|||
inst.init(); |
|||
inst.setupNextCleanup(); |
|||
willReturn(Collections.emptyList()).given(inst).doPoll(); |
|||
|
|||
//when
|
|||
long stepNs = TimeUnit.MILLISECONDS.toNanos(1); |
|||
for (long i = 0; i <= inst.maxRequestTimeoutNs * 2; i = i + stepNs) { |
|||
currentTime.addAndGet(stepNs); |
|||
assertThat(inst.send(getRequestMsgMock()).isDone(), is(false)); //SettableFuture future - pending only
|
|||
if (i % (inst.maxRequestTimeoutNs * 3 / 2) == 0) { |
|||
inst.fetchAndProcessResponses(); |
|||
} |
|||
} |
|||
|
|||
//then
|
|||
ArgumentCaptor<DefaultTbQueueRequestTemplate.ResponseMetaData> argumentCaptorResp = ArgumentCaptor.forClass(DefaultTbQueueRequestTemplate.ResponseMetaData.class); |
|||
ArgumentCaptor<UUID> argumentCaptorUUID = ArgumentCaptor.forClass(UUID.class); |
|||
ArgumentCaptor<Long> argumentCaptorLong = ArgumentCaptor.forClass(Long.class); |
|||
verify(inst, atLeastOnce()).setTimeoutException(argumentCaptorUUID.capture(), argumentCaptorResp.capture(), argumentCaptorLong.capture()); |
|||
|
|||
List<DefaultTbQueueRequestTemplate.ResponseMetaData> responseMetaDataList = argumentCaptorResp.getAllValues(); |
|||
List<Long> tickTsList = argumentCaptorLong.getAllValues(); |
|||
for (int i = 0; i < responseMetaDataList.size(); i++) { |
|||
assertThat("tickTs >= calculatedExpTime", tickTsList.get(i), greaterThanOrEqualTo(responseMetaDataList.get(i).getSubmitTime() + responseMetaDataList.get(i).getTimeout())); |
|||
} |
|||
} |
|||
|
|||
TbQueueMsg getRequestMsgMock() { |
|||
return mock(TbQueueMsg.class, RETURNS_DEEP_STUBS); |
|||
} |
|||
} |
|||
@ -0,0 +1,181 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.coap; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.eclipse.californium.core.coap.CoAP; |
|||
import org.eclipse.californium.core.coap.Request; |
|||
import org.eclipse.californium.core.coap.Response; |
|||
import org.eclipse.californium.core.network.Exchange; |
|||
import org.eclipse.californium.core.observe.ObserveRelation; |
|||
import org.eclipse.californium.core.server.resources.CoapExchange; |
|||
import org.eclipse.californium.core.server.resources.Resource; |
|||
import org.eclipse.californium.core.server.resources.ResourceObserver; |
|||
import org.thingsboard.server.common.data.DeviceTransportType; |
|||
import org.thingsboard.server.common.data.StringUtils; |
|||
import org.thingsboard.server.common.data.ota.OtaPackageType; |
|||
import org.thingsboard.server.common.data.security.DeviceTokenCredentials; |
|||
import org.thingsboard.server.common.transport.TransportServiceCallback; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
|
|||
import java.util.List; |
|||
import java.util.Optional; |
|||
import java.util.UUID; |
|||
|
|||
@Slf4j |
|||
public class OtaPackageTransportResource extends AbstractCoapTransportResource { |
|||
private static final int ACCESS_TOKEN_POSITION = 2; |
|||
|
|||
private final OtaPackageType otaPackageType; |
|||
|
|||
public OtaPackageTransportResource(CoapTransportContext ctx, OtaPackageType otaPackageType) { |
|||
super(ctx, otaPackageType.getKeyPrefix()); |
|||
this.setObservable(true); |
|||
this.addObserver(new OtaPackageTransportResource.CoapResourceObserver()); |
|||
this.otaPackageType = otaPackageType; |
|||
} |
|||
|
|||
@Override |
|||
protected void processHandleGet(CoapExchange exchange) { |
|||
log.trace("Processing {}", exchange.advanced().getRequest()); |
|||
exchange.accept(); |
|||
Exchange advanced = exchange.advanced(); |
|||
Request request = advanced.getRequest(); |
|||
processAccessTokenRequest(exchange, request); |
|||
} |
|||
|
|||
@Override |
|||
protected void processHandlePost(CoapExchange exchange) { |
|||
exchange.respond(CoAP.ResponseCode.METHOD_NOT_ALLOWED); |
|||
} |
|||
|
|||
private void processAccessTokenRequest(CoapExchange exchange, Request request) { |
|||
Optional<DeviceTokenCredentials> credentials = decodeCredentials(request); |
|||
if (credentials.isEmpty()) { |
|||
exchange.respond(CoAP.ResponseCode.UNAUTHORIZED); |
|||
return; |
|||
} |
|||
transportService.process(DeviceTransportType.COAP, TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(credentials.get().getCredentialsId()).build(), |
|||
new CoapDeviceAuthCallback(transportContext, exchange, (sessionInfo, deviceProfile) -> { |
|||
getOtaPackageCallback(sessionInfo, exchange, otaPackageType); |
|||
})); |
|||
} |
|||
|
|||
private void getOtaPackageCallback(TransportProtos.SessionInfoProto sessionInfo, CoapExchange exchange, OtaPackageType firmwareType) { |
|||
TransportProtos.GetOtaPackageRequestMsg requestMsg = TransportProtos.GetOtaPackageRequestMsg.newBuilder() |
|||
.setTenantIdMSB(sessionInfo.getTenantIdMSB()) |
|||
.setTenantIdLSB(sessionInfo.getTenantIdLSB()) |
|||
.setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) |
|||
.setDeviceIdLSB(sessionInfo.getDeviceIdLSB()) |
|||
.setType(firmwareType.name()).build(); |
|||
transportContext.getTransportService().process(sessionInfo, requestMsg, new OtaPackageCallback(exchange)); |
|||
} |
|||
|
|||
private Optional<DeviceTokenCredentials> decodeCredentials(Request request) { |
|||
List<String> uriPath = request.getOptions().getUriPath(); |
|||
if (uriPath.size() == ACCESS_TOKEN_POSITION) { |
|||
return Optional.of(new DeviceTokenCredentials(uriPath.get(ACCESS_TOKEN_POSITION - 1))); |
|||
} else { |
|||
return Optional.empty(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public Resource getChild(String name) { |
|||
return this; |
|||
} |
|||
|
|||
private class OtaPackageCallback implements TransportServiceCallback<TransportProtos.GetOtaPackageResponseMsg> { |
|||
private final CoapExchange exchange; |
|||
|
|||
OtaPackageCallback(CoapExchange exchange) { |
|||
this.exchange = exchange; |
|||
} |
|||
|
|||
@Override |
|||
public void onSuccess(TransportProtos.GetOtaPackageResponseMsg msg) { |
|||
String title = exchange.getQueryParameter("title"); |
|||
String version = exchange.getQueryParameter("version"); |
|||
if (msg.getResponseStatus().equals(TransportProtos.ResponseStatus.SUCCESS)) { |
|||
String firmwareId = new UUID(msg.getOtaPackageIdMSB(), msg.getOtaPackageIdLSB()).toString(); |
|||
if ((title == null || msg.getTitle().equals(title)) && (version == null || msg.getVersion().equals(version))) { |
|||
String strChunkSize = exchange.getQueryParameter("size"); |
|||
String strChunk = exchange.getQueryParameter("chunk"); |
|||
int chunkSize = StringUtils.isEmpty(strChunkSize) ? 0 : Integer.parseInt(strChunkSize); |
|||
int chunk = StringUtils.isEmpty(strChunk) ? 0 : Integer.parseInt(strChunk); |
|||
respondOtaPackage(exchange, transportContext.getOtaPackageDataCache().get(firmwareId, chunkSize, chunk)); |
|||
} else { |
|||
exchange.respond(CoAP.ResponseCode.BAD_REQUEST); |
|||
} |
|||
} else { |
|||
exchange.respond(CoAP.ResponseCode.NOT_FOUND); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onError(Throwable e) { |
|||
log.warn("Failed to process request", e); |
|||
exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR); |
|||
} |
|||
} |
|||
|
|||
private void respondOtaPackage(CoapExchange exchange, byte[] data) { |
|||
Response response = new Response(CoAP.ResponseCode.CONTENT); |
|||
if (data != null && data.length > 0) { |
|||
response.setPayload(data); |
|||
if (exchange.getRequestOptions().getBlock2() != null) { |
|||
int chunkSize = exchange.getRequestOptions().getBlock2().getSzx(); |
|||
boolean lastFlag = data.length > chunkSize; |
|||
response.getOptions().setUriPath(exchange.getRequestOptions().getUriPathString()); |
|||
response.getOptions().setBlock2(chunkSize, lastFlag, 0); |
|||
} |
|||
exchange.respond(response); |
|||
} |
|||
} |
|||
|
|||
public class CoapResourceObserver implements ResourceObserver { |
|||
@Override |
|||
public void changedName(String old) { |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public void changedPath(String old) { |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public void addedChild(Resource child) { |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public void removedChild(Resource child) { |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public void addedObserveRelation(ObserveRelation relation) { |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public void removedObserveRelation(ObserveRelation relation) { |
|||
|
|||
} |
|||
} |
|||
|
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,48 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server; |
|||
|
|||
public enum LwM2MFirmwareUpdateStrategy { |
|||
OBJ_5_BINARY(1, "ObjectId 5, Binary"), |
|||
OBJ_5_TEMP_URL(2, "ObjectId 5, URI"), |
|||
OBJ_19_BINARY(3, "ObjectId 19, Binary"); |
|||
|
|||
public int code; |
|||
public String type; |
|||
|
|||
LwM2MFirmwareUpdateStrategy(int code, String type) { |
|||
this.code = code; |
|||
this.type = type; |
|||
} |
|||
|
|||
public static LwM2MFirmwareUpdateStrategy fromStrategyFwByType(String type) { |
|||
for (LwM2MFirmwareUpdateStrategy to : LwM2MFirmwareUpdateStrategy.values()) { |
|||
if (to.type.equals(type)) { |
|||
return to; |
|||
} |
|||
} |
|||
throw new IllegalArgumentException(String.format("Unsupported FW State type : %s", type)); |
|||
} |
|||
|
|||
public static LwM2MFirmwareUpdateStrategy fromStrategyFwByCode(int code) { |
|||
for (LwM2MFirmwareUpdateStrategy to : LwM2MFirmwareUpdateStrategy.values()) { |
|||
if (to.code == code) { |
|||
return to; |
|||
} |
|||
} |
|||
throw new IllegalArgumentException(String.format("Unsupported FW Strategy code : %s", code)); |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server; |
|||
|
|||
public enum LwM2MSoftwareUpdateStrategy { |
|||
BINARY(1, "ObjectId 9, Binary"), |
|||
TEMP_URL(2, "ObjectId 9, URI"); |
|||
|
|||
public int code; |
|||
public String type; |
|||
|
|||
LwM2MSoftwareUpdateStrategy(int code, String type) { |
|||
this.code = code; |
|||
this.type = type; |
|||
} |
|||
|
|||
public static LwM2MSoftwareUpdateStrategy fromStrategySwByType(String type) { |
|||
for (LwM2MSoftwareUpdateStrategy to : LwM2MSoftwareUpdateStrategy.values()) { |
|||
if (to.type.equals(type)) { |
|||
return to; |
|||
} |
|||
} |
|||
throw new IllegalArgumentException(String.format("Unsupported SW Strategy type : %s", type)); |
|||
} |
|||
|
|||
public static LwM2MSoftwareUpdateStrategy fromStrategySwByCode(int code) { |
|||
for (LwM2MSoftwareUpdateStrategy to : LwM2MSoftwareUpdateStrategy.values()) { |
|||
if (to.code == code) { |
|||
return to; |
|||
} |
|||
} |
|||
throw new IllegalArgumentException(String.format("Unsupported SW Strategy code : %s", code)); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server; |
|||
|
|||
import lombok.Getter; |
|||
|
|||
/** |
|||
* Define the behavior of a write request. |
|||
*/ |
|||
public enum LwM2mOperationType { |
|||
|
|||
READ(0, "Read", true), |
|||
DISCOVER(1, "Discover", true), |
|||
DISCOVER_ALL(2, "DiscoverAll", false), |
|||
OBSERVE_READ_ALL(3, "ObserveReadAll", false), |
|||
|
|||
OBSERVE(4, "Observe", true), |
|||
OBSERVE_CANCEL(5, "ObserveCancel", true), |
|||
OBSERVE_CANCEL_ALL(6, "ObserveCancelAll", false), |
|||
EXECUTE(7, "Execute", true), |
|||
/** |
|||
* Replaces the Object Instance or the Resource(s) with the new value provided in the “Write” operation. (see |
|||
* section 5.3.3 of the LW M2M spec). |
|||
* if all resources are to be replaced |
|||
*/ |
|||
WRITE_REPLACE(8, "WriteReplace", true), |
|||
|
|||
/** |
|||
* Adds or updates Resources provided in the new value and leaves other existing Resources unchanged. (see section |
|||
* 5.3.3 of the LW M2M spec). |
|||
* if this is a partial update request |
|||
*/ |
|||
WRITE_UPDATE(9, "WriteUpdate", true), |
|||
WRITE_ATTRIBUTES(10, "WriteAttributes", true), |
|||
DELETE(11, "Delete", true), |
|||
|
|||
// only for RPC
|
|||
FW_UPDATE(12, "FirmwareUpdate", false); |
|||
|
|||
// FW_READ_INFO(12, "FirmwareReadInfo"),
|
|||
// SW_READ_INFO(15, "SoftwareReadInfo"),
|
|||
// SW_UPDATE(16, "SoftwareUpdate"),
|
|||
// SW_UNINSTALL(18, "SoftwareUninstall");
|
|||
|
|||
@Getter |
|||
private final int code; |
|||
@Getter |
|||
private final String type; |
|||
@Getter |
|||
private final boolean hasObjectId; |
|||
|
|||
LwM2mOperationType(int code, String type, boolean hasObjectId) { |
|||
this.code = code; |
|||
this.type = type; |
|||
this.hasObjectId = hasObjectId; |
|||
} |
|||
|
|||
public static LwM2mOperationType fromType(String type) { |
|||
for (LwM2mOperationType to : LwM2mOperationType.values()) { |
|||
if (to.type.equals(type)) { |
|||
return to; |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
} |
|||
@ -1,613 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.eclipse.californium.core.coap.CoAP; |
|||
import org.eclipse.californium.core.coap.Response; |
|||
import org.eclipse.leshan.core.Link; |
|||
import org.eclipse.leshan.core.model.ResourceModel; |
|||
import org.eclipse.leshan.core.node.LwM2mNode; |
|||
import org.eclipse.leshan.core.node.LwM2mObject; |
|||
import org.eclipse.leshan.core.node.LwM2mObjectInstance; |
|||
import org.eclipse.leshan.core.node.LwM2mPath; |
|||
import org.eclipse.leshan.core.node.LwM2mResource; |
|||
import org.eclipse.leshan.core.node.LwM2mSingleResource; |
|||
import org.eclipse.leshan.core.node.ObjectLink; |
|||
import org.eclipse.leshan.core.observation.Observation; |
|||
import org.eclipse.leshan.core.request.ContentFormat; |
|||
import org.eclipse.leshan.core.request.DeleteRequest; |
|||
import org.eclipse.leshan.core.request.DiscoverRequest; |
|||
import org.eclipse.leshan.core.request.ExecuteRequest; |
|||
import org.eclipse.leshan.core.request.ObserveRequest; |
|||
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.WriteRequest; |
|||
import org.eclipse.leshan.core.request.exception.ClientSleepingException; |
|||
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.ReadResponse; |
|||
import org.eclipse.leshan.core.response.ResponseCallback; |
|||
import org.eclipse.leshan.core.response.WriteAttributesResponse; |
|||
import org.eclipse.leshan.core.response.WriteResponse; |
|||
import org.eclipse.leshan.core.util.Hex; |
|||
import org.eclipse.leshan.core.util.NamedThreadFactory; |
|||
import org.eclipse.leshan.server.registration.Registration; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; |
|||
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientRpcRequest; |
|||
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import java.util.Arrays; |
|||
import java.util.Collection; |
|||
import java.util.Date; |
|||
import java.util.Set; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.stream.Collectors; |
|||
|
|||
import static org.eclipse.californium.core.coap.CoAP.ResponseCode.CONTENT; |
|||
import static org.eclipse.leshan.core.ResponseCode.BAD_REQUEST; |
|||
import static org.eclipse.leshan.core.ResponseCode.NOT_FOUND; |
|||
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADED; |
|||
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.FAILED; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper.getContentFormatByResourceModelType; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.DEFAULT_TIMEOUT; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_PACKAGE_5_ID; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_UPDATE_ID; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_ERROR; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_VALUE; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.DISCOVER; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.EXECUTE; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_CANCEL; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_READ_ALL; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_ATTRIBUTES; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_REPLACE; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_UPDATE; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.RESPONSE_REQUEST_CHANNEL; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_INSTALL_ID; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_PACKAGE_ID; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromIdVerToObjectId; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.createWriteAttributeRequest; |
|||
|
|||
@Slf4j |
|||
@Service |
|||
@TbLwM2mTransportComponent |
|||
@RequiredArgsConstructor |
|||
public class LwM2mTransportRequest { |
|||
private ExecutorService responseRequestExecutor; |
|||
|
|||
public LwM2mValueConverterImpl converter; |
|||
|
|||
private final LwM2mTransportContext context; |
|||
private final LwM2MTransportServerConfig config; |
|||
private final LwM2mClientContext lwM2mClientContext; |
|||
private final DefaultLwM2MTransportMsgHandler handler; |
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
this.converter = LwM2mValueConverterImpl.getInstance(); |
|||
responseRequestExecutor = Executors.newFixedThreadPool(this.config.getResponsePoolSize(), |
|||
new NamedThreadFactory(String.format("LwM2M %s channel response after request", RESPONSE_REQUEST_CHANNEL))); |
|||
} |
|||
|
|||
public void sendAllRequest(LwM2mClient lwM2MClient, String targetIdVer, LwM2mTypeOper typeOper, Object params, long timeoutInMs, LwM2mClientRpcRequest lwm2mClientRpcRequest) { |
|||
sendAllRequest(lwM2MClient, targetIdVer, typeOper, lwM2MClient.getDefaultContentFormat(), params, timeoutInMs, lwm2mClientRpcRequest); |
|||
} |
|||
|
|||
|
|||
public void sendAllRequest(LwM2mClient lwM2MClient, String targetIdVer, LwM2mTypeOper typeOper, |
|||
ContentFormat contentFormat, Object params, long timeoutInMs, LwM2mClientRpcRequest lwm2mClientRpcRequest) { |
|||
Registration registration = lwM2MClient.getRegistration(); |
|||
try { |
|||
String target = convertPathFromIdVerToObjectId(targetIdVer); |
|||
if(contentFormat == null){ |
|||
contentFormat = ContentFormat.DEFAULT; |
|||
} |
|||
LwM2mPath resultIds = target != null ? new LwM2mPath(target) : null; |
|||
if (!OBSERVE_CANCEL.name().equals(typeOper.name()) && resultIds != null && registration != null && resultIds.getObjectId() >= 0 && lwM2MClient != null) { |
|||
if (lwM2MClient.isValidObjectVersion(targetIdVer)) { |
|||
timeoutInMs = timeoutInMs > 0 ? timeoutInMs : DEFAULT_TIMEOUT; |
|||
SimpleDownlinkRequest request = createRequest(registration, lwM2MClient, typeOper, contentFormat, target, |
|||
targetIdVer, resultIds, params, lwm2mClientRpcRequest); |
|||
if (request != null) { |
|||
try { |
|||
this.sendRequest(registration, lwM2MClient, request, timeoutInMs, lwm2mClientRpcRequest); |
|||
} catch (ClientSleepingException e) { |
|||
SimpleDownlinkRequest finalRequest = request; |
|||
long finalTimeoutInMs = timeoutInMs; |
|||
LwM2mClientRpcRequest finalRpcRequest = lwm2mClientRpcRequest; |
|||
lwM2MClient.getQueuedRequests().add(() -> sendRequest(registration, lwM2MClient, finalRequest, finalTimeoutInMs, finalRpcRequest)); |
|||
} catch (Exception e) { |
|||
log.error("[{}] [{}] [{}] Failed to send downlink.", registration.getEndpoint(), targetIdVer, typeOper.name(), e); |
|||
} |
|||
} else if (WRITE_UPDATE.name().equals(typeOper.name())) { |
|||
if (lwm2mClientRpcRequest != null) { |
|||
String errorMsg = String.format("Path %s params is not valid", targetIdVer); |
|||
handler.sentRpcResponse(lwm2mClientRpcRequest, BAD_REQUEST.getName(), errorMsg, LOG_LW2M_ERROR); |
|||
} |
|||
} else if (WRITE_REPLACE.name().equals(typeOper.name()) || EXECUTE.name().equals(typeOper.name())) { |
|||
if (lwm2mClientRpcRequest != null) { |
|||
String errorMsg = String.format("Path %s object model is absent", targetIdVer); |
|||
handler.sentRpcResponse(lwm2mClientRpcRequest, BAD_REQUEST.getName(), errorMsg, LOG_LW2M_ERROR); |
|||
} |
|||
} else if (!OBSERVE_CANCEL.name().equals(typeOper.name())) { |
|||
log.error("[{}], [{}] - [{}] error SendRequest", registration.getEndpoint(), typeOper.name(), targetIdVer); |
|||
if (lwm2mClientRpcRequest != null) { |
|||
ResourceModel resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.config.getModelProvider()); |
|||
String errorMsg = resourceModel == null ? String.format("Path %s not found in object version", targetIdVer) : "SendRequest - null"; |
|||
handler.sentRpcResponse(lwm2mClientRpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR); |
|||
} |
|||
} |
|||
} else if (lwm2mClientRpcRequest != null) { |
|||
String errorMsg = String.format("Path %s not found in object version", targetIdVer); |
|||
handler.sentRpcResponse(lwm2mClientRpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR); |
|||
} |
|||
} else { |
|||
switch (typeOper) { |
|||
case OBSERVE_READ_ALL: |
|||
case DISCOVER_ALL: |
|||
Set<String> paths; |
|||
if (OBSERVE_READ_ALL.name().equals(typeOper.name())) { |
|||
Set<Observation> observations = context.getServer().getObservationService().getObservations(registration); |
|||
paths = observations.stream().map(observation -> observation.getPath().toString()).collect(Collectors.toUnmodifiableSet()); |
|||
} else { |
|||
assert registration != null; |
|||
Link[] objectLinks = registration.getSortedObjectLinks(); |
|||
paths = Arrays.stream(objectLinks).map(Link::toString).collect(Collectors.toUnmodifiableSet()); |
|||
} |
|||
String msg = String.format("%s: type operation %s paths - %s", LOG_LW2M_INFO, |
|||
typeOper.name(), paths); |
|||
this.handler.sendLogsToThingsboard(lwM2MClient, msg); |
|||
if (lwm2mClientRpcRequest != null) { |
|||
String valueMsg = String.format("Paths - %s", paths); |
|||
handler.sentRpcResponse(lwm2mClientRpcRequest, CONTENT.name(), valueMsg, LOG_LW2M_VALUE); |
|||
} |
|||
break; |
|||
case OBSERVE_CANCEL: |
|||
case OBSERVE_CANCEL_ALL: |
|||
int observeCancelCnt = 0; |
|||
String observeCancelMsg = null; |
|||
if (OBSERVE_CANCEL.name().equals(typeOper)) { |
|||
observeCancelCnt = context.getServer().getObservationService().cancelObservations(registration, target); |
|||
observeCancelMsg = String.format("%s: type operation %s paths: %s count: %d", LOG_LW2M_INFO, |
|||
OBSERVE_CANCEL.name(), target, observeCancelCnt); |
|||
} else { |
|||
observeCancelCnt = context.getServer().getObservationService().cancelObservations(registration); |
|||
observeCancelMsg = String.format("%s: type operation %s paths: All count: %d", LOG_LW2M_INFO, |
|||
OBSERVE_CANCEL.name(), observeCancelCnt); |
|||
} |
|||
this.afterObserveCancel(lwM2MClient, observeCancelCnt, observeCancelMsg, lwm2mClientRpcRequest); |
|||
break; |
|||
// lwm2mClientRpcRequest != null
|
|||
case FW_UPDATE: |
|||
handler.getInfoFirmwareUpdate(lwM2MClient, lwm2mClientRpcRequest); |
|||
break; |
|||
} |
|||
} |
|||
} catch (Exception e) { |
|||
String msg = String.format("%s: type operation %s %s", LOG_LW2M_ERROR, |
|||
typeOper.name(), e.getMessage()); |
|||
handler.sendLogsToThingsboard(lwM2MClient, msg); |
|||
if (lwm2mClientRpcRequest != null) { |
|||
String errorMsg = String.format("Path %s type operation %s %s", targetIdVer, typeOper.name(), e.getMessage()); |
|||
handler.sentRpcResponse(lwm2mClientRpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private SimpleDownlinkRequest createRequest(Registration registration, LwM2mClient lwM2MClient, LwM2mTypeOper typeOper, |
|||
ContentFormat contentFormat, String target, String targetIdVer, |
|||
LwM2mPath resultIds, Object params, LwM2mClientRpcRequest rpcRequest) { |
|||
SimpleDownlinkRequest request = null; |
|||
switch (typeOper) { |
|||
case READ: |
|||
request = new ReadRequest(contentFormat, target); |
|||
break; |
|||
case DISCOVER: |
|||
request = new DiscoverRequest(target); |
|||
break; |
|||
case OBSERVE: |
|||
String msg = String.format("%s: Send Observation %s.", LOG_LW2M_INFO, targetIdVer); |
|||
log.warn(msg); |
|||
if (resultIds.isResource()) { |
|||
Set<Observation> observations = context.getServer().getObservationService().getObservations(registration); |
|||
Set<Observation> paths = observations.stream().filter(observation -> observation.getPath().equals(resultIds)).collect(Collectors.toSet()); |
|||
if (paths.size() == 0) { |
|||
request = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId()); |
|||
} else { |
|||
request = new ReadRequest(contentFormat, target); |
|||
} |
|||
} else if (resultIds.isObjectInstance()) { |
|||
request = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId()); |
|||
} else if (resultIds.getObjectId() >= 0) { |
|||
request = new ObserveRequest(contentFormat, resultIds.getObjectId()); |
|||
} |
|||
break; |
|||
case EXECUTE: |
|||
ResourceModel resourceModelExecute = lwM2MClient.getResourceModel(targetIdVer, this.config.getModelProvider()); |
|||
if (resourceModelExecute != null) { |
|||
if (params != null && !resourceModelExecute.multiple) { |
|||
request = new ExecuteRequest(target, (String) this.converter.convertValue(params, resourceModelExecute.type, ResourceModel.Type.STRING, resultIds)); |
|||
} else { |
|||
request = new ExecuteRequest(target); |
|||
} |
|||
} |
|||
break; |
|||
case WRITE_REPLACE: |
|||
/** |
|||
* Request to write a <b>String Single-Instance Resource</b> using the TLV content format. |
|||
* Type from resourceModel -> STRING, INTEGER, FLOAT, BOOLEAN, OPAQUE, TIME, OBJLNK |
|||
* contentFormat -> TLV, TLV, TLV, TLV, OPAQUE, TLV, LINK |
|||
* JSON, TEXT; |
|||
**/ |
|||
ResourceModel resourceModelWrite = lwM2MClient.getResourceModel(targetIdVer, this.config.getModelProvider()); |
|||
if (resourceModelWrite != null) { |
|||
contentFormat = getContentFormatByResourceModelType(resourceModelWrite, contentFormat); |
|||
request = this.getWriteRequestSingleResource(contentFormat, resultIds.getObjectId(), |
|||
resultIds.getObjectInstanceId(), resultIds.getResourceId(), params, resourceModelWrite.type, |
|||
lwM2MClient, rpcRequest); |
|||
} |
|||
break; |
|||
case WRITE_UPDATE: |
|||
if (resultIds.isResource()) { |
|||
/** |
|||
* send request: path = '/3/0' node == wM2mObjectInstance |
|||
* with params == "\"resources\": {15: resource:{id:15. value:'+01'...}} |
|||
**/ |
|||
Collection<LwM2mResource> resources = lwM2MClient.getNewResourceForInstance( |
|||
targetIdVer, params, |
|||
this.config.getModelProvider(), |
|||
this.converter); |
|||
contentFormat = getContentFormatByResourceModelType(lwM2MClient.getResourceModel(targetIdVer, this.config.getModelProvider()), |
|||
contentFormat); |
|||
request = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), |
|||
resultIds.getObjectInstanceId(), resources); |
|||
} |
|||
/** |
|||
* params = "{\"id\":0,\"resources\":[{\"id\":14,\"value\":\"+5\"},{\"id\":15,\"value\":\"+9\"}]}" |
|||
* int rscId = resultIds.getObjectInstanceId(); |
|||
* contentFormat – Format of the payload (TLV or JSON). |
|||
*/ |
|||
else if (resultIds.isObjectInstance()) { |
|||
if (((ConcurrentHashMap) params).size() > 0) { |
|||
Collection<LwM2mResource> resources = lwM2MClient.getNewResourcesForInstance( |
|||
targetIdVer, params, |
|||
this.config.getModelProvider(), |
|||
this.converter); |
|||
if (resources.size() > 0) { |
|||
contentFormat = contentFormat.equals(ContentFormat.JSON) ? contentFormat : ContentFormat.TLV; |
|||
request = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), |
|||
resultIds.getObjectInstanceId(), resources); |
|||
} |
|||
} |
|||
} else if (resultIds.getObjectId() >= 0) { |
|||
request = new ObserveRequest(resultIds.getObjectId()); |
|||
} |
|||
break; |
|||
case WRITE_ATTRIBUTES: |
|||
request = createWriteAttributeRequest(target, params, this.handler); |
|||
break; |
|||
case DELETE: |
|||
request = new DeleteRequest(target); |
|||
break; |
|||
} |
|||
return request; |
|||
} |
|||
|
|||
/** |
|||
* @param registration - |
|||
* @param request - |
|||
* @param timeoutInMs - |
|||
*/ |
|||
|
|||
@SuppressWarnings({"error sendRequest"}) |
|||
private void sendRequest(Registration registration, LwM2mClient lwM2MClient, SimpleDownlinkRequest request, |
|||
long timeoutInMs, LwM2mClientRpcRequest rpcRequest) { |
|||
context.getServer().send(registration, request, timeoutInMs, (ResponseCallback<?>) response -> { |
|||
|
|||
if (!lwM2MClient.isInit()) { |
|||
lwM2MClient.initReadValue(this.handler, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); |
|||
} |
|||
if (CoAP.ResponseCode.isSuccess(((Response) response.getCoapResponse()).getCode())) { |
|||
this.handleResponse(lwM2MClient, request.getPath().toString(), response, request, rpcRequest); |
|||
} else { |
|||
String msg = String.format("%s: SendRequest %s: CoapCode - %s Lwm2m code - %d name - %s Resource path - %s", LOG_LW2M_ERROR, request.getClass().getName().toString(), |
|||
((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString()); |
|||
handler.sendLogsToThingsboard(lwM2MClient, msg); |
|||
log.error("[{}] [{}], [{}] - [{}] [{}] error SendRequest", request.getClass().getName().toString(), registration.getEndpoint(), |
|||
((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString()); |
|||
if (!lwM2MClient.isInit()) { |
|||
lwM2MClient.initReadValue(this.handler, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); |
|||
} |
|||
/** Not Found */ |
|||
if (rpcRequest != null) { |
|||
handler.sentRpcResponse(rpcRequest, response.getCode().getName(), response.getErrorMessage(), LOG_LW2M_ERROR); |
|||
} |
|||
/** Not Found |
|||
set setClient_fw_info... = empty |
|||
**/ |
|||
if (lwM2MClient.getFwUpdate() != null && lwM2MClient.getFwUpdate().isInfoFwSwUpdate()) { |
|||
lwM2MClient.getFwUpdate().initReadValue(handler, this, request.getPath().toString()); |
|||
} |
|||
if (lwM2MClient.getSwUpdate() != null && lwM2MClient.getSwUpdate().isInfoFwSwUpdate()) { |
|||
lwM2MClient.getSwUpdate().initReadValue(handler, this, request.getPath().toString()); |
|||
} |
|||
if (request.getPath().toString().equals(FW_PACKAGE_5_ID) || request.getPath().toString().equals(SW_PACKAGE_ID)) { |
|||
this.afterWriteFwSWUpdateError(registration, request, response.getErrorMessage()); |
|||
} |
|||
if (request.getPath().toString().equals(FW_UPDATE_ID) || request.getPath().toString().equals(SW_INSTALL_ID)) { |
|||
this.afterExecuteFwSwUpdateError(registration, request, response.getErrorMessage()); |
|||
} |
|||
} |
|||
}, e -> { |
|||
/** version == null |
|||
set setClient_fw_info... = empty |
|||
**/ |
|||
if (lwM2MClient.getFwUpdate() != null && lwM2MClient.getFwUpdate().isInfoFwSwUpdate()) { |
|||
lwM2MClient.getFwUpdate().initReadValue(handler, this, request.getPath().toString()); |
|||
} |
|||
if (lwM2MClient.getSwUpdate() != null && lwM2MClient.getSwUpdate().isInfoFwSwUpdate()) { |
|||
lwM2MClient.getSwUpdate().initReadValue(handler, this, request.getPath().toString()); |
|||
} |
|||
if (request.getPath().toString().equals(FW_PACKAGE_5_ID) || request.getPath().toString().equals(SW_PACKAGE_ID)) { |
|||
this.afterWriteFwSWUpdateError(registration, request, e.getMessage()); |
|||
} |
|||
if (request.getPath().toString().equals(FW_UPDATE_ID) || request.getPath().toString().equals(SW_INSTALL_ID)) { |
|||
this.afterExecuteFwSwUpdateError(registration, request, e.getMessage()); |
|||
} |
|||
if (!lwM2MClient.isInit()) { |
|||
lwM2MClient.initReadValue(this.handler, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); |
|||
} |
|||
String msg = String.format("%s: SendRequest %s: Resource path - %s msg error - %s", |
|||
LOG_LW2M_ERROR, request.getClass().getName().toString(), request.getPath().toString(), e.getMessage()); |
|||
handler.sendLogsToThingsboard(lwM2MClient, msg); |
|||
log.error("[{}] [{}] - [{}] error SendRequest", request.getClass().getName().toString(), request.getPath().toString(), e.toString()); |
|||
if (rpcRequest != null) { |
|||
handler.sentRpcResponse(rpcRequest, CoAP.CodeClass.ERROR_RESPONSE.name(), e.getMessage(), LOG_LW2M_ERROR); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private WriteRequest getWriteRequestSingleResource(ContentFormat contentFormat, Integer objectId, Integer instanceId, |
|||
Integer resourceId, Object value, ResourceModel.Type type, |
|||
LwM2mClient client, LwM2mClientRpcRequest rpcRequest) { |
|||
try { |
|||
if (type != null) { |
|||
switch (type) { |
|||
case STRING: // String
|
|||
return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, value.toString()) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, value.toString()); |
|||
case INTEGER: // Long
|
|||
final long valueInt = Integer.toUnsignedLong(Integer.parseInt(value.toString())); |
|||
return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, valueInt) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, valueInt); |
|||
case OBJLNK: // ObjectLink
|
|||
return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, ObjectLink.fromPath(value.toString())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, ObjectLink.fromPath(value.toString())); |
|||
case BOOLEAN: // Boolean
|
|||
return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Boolean.parseBoolean(value.toString())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Boolean.parseBoolean(value.toString())); |
|||
case FLOAT: // Double
|
|||
return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Double.parseDouble(value.toString())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Double.parseDouble(value.toString())); |
|||
case TIME: // Date
|
|||
Date date = new Date(Long.decode(value.toString())); |
|||
return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, date) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, date); |
|||
case OPAQUE: // byte[] value, base64
|
|||
byte[] valueRequest = value instanceof byte[] ? (byte[]) value : Hex.decodeHex(value.toString().toCharArray()); |
|||
return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, valueRequest) : |
|||
new WriteRequest(contentFormat, objectId, instanceId, resourceId, valueRequest); |
|||
default: |
|||
} |
|||
} |
|||
if (rpcRequest != null) { |
|||
String patn = "/" + objectId + "/" + instanceId + "/" + resourceId; |
|||
String errorMsg = String.format("Bad ResourceModel Operations (E): Resource path - %s ResourceModel type - %s", patn, type); |
|||
rpcRequest.setErrorMsg(errorMsg); |
|||
} |
|||
return null; |
|||
} catch (NumberFormatException e) { |
|||
String patn = "/" + objectId + "/" + instanceId + "/" + resourceId; |
|||
String msg = String.format(LOG_LW2M_ERROR + ": NumberFormatException: Resource path - %s type - %s value - %s msg error - %s SendRequest to Client", |
|||
patn, type, value, e.toString()); |
|||
handler.sendLogsToThingsboard(client, msg); |
|||
log.error("Path: [{}] type: [{}] value: [{}] errorMsg: [{}]]", patn, type, value, e.toString()); |
|||
if (rpcRequest != null) { |
|||
String errorMsg = String.format("NumberFormatException: Resource path - %s type - %s value - %s", patn, type, value); |
|||
handler.sentRpcResponse(rpcRequest, BAD_REQUEST.getName(), errorMsg, LOG_LW2M_ERROR); |
|||
} |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private void handleResponse(LwM2mClient lwM2mClient, final String path, LwM2mResponse response, |
|||
SimpleDownlinkRequest request, LwM2mClientRpcRequest rpcRequest) { |
|||
responseRequestExecutor.submit(() -> { |
|||
try { |
|||
this.sendResponse(lwM2mClient, path, response, request, rpcRequest); |
|||
} catch (Exception e) { |
|||
log.error("[{}] endpoint [{}] path [{}] Exception Unable to after send response.", lwM2mClient.getRegistration().getEndpoint(), path, e); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* processing a response from a client |
|||
* |
|||
* @param path - |
|||
* @param response - |
|||
*/ |
|||
private void sendResponse(LwM2mClient lwM2mClient, String path, LwM2mResponse response, |
|||
SimpleDownlinkRequest request, LwM2mClientRpcRequest rpcRequest) { |
|||
Registration registration = lwM2mClient.getRegistration(); |
|||
String pathIdVer = convertPathFromObjectIdToIdVer(path, registration); |
|||
String msgLog = ""; |
|||
if (response instanceof ReadResponse) { |
|||
handler.onUpdateValueAfterReadResponse(registration, pathIdVer, (ReadResponse) response, rpcRequest); |
|||
} else if (response instanceof DeleteResponse) { |
|||
log.warn("11) [{}] Path [{}] DeleteResponse", pathIdVer, response); |
|||
if (rpcRequest != null) { |
|||
rpcRequest.setInfoMsg(null); |
|||
handler.sentRpcResponse(rpcRequest, response.getCode().getName(), null, null); |
|||
} |
|||
} else if (response instanceof DiscoverResponse) { |
|||
String discoverValue = Link.serialize(((DiscoverResponse) response).getObjectLinks()); |
|||
msgLog = String.format("%s: type operation: %s path: %s value: %s", |
|||
LOG_LW2M_INFO, DISCOVER.name(), request.getPath().toString(), discoverValue); |
|||
handler.sendLogsToThingsboard(lwM2mClient, msgLog); |
|||
log.warn("DiscoverResponse: [{}]", (DiscoverResponse) response); |
|||
if (rpcRequest != null) { |
|||
handler.sentRpcResponse(rpcRequest, response.getCode().getName(), discoverValue, LOG_LW2M_VALUE); |
|||
} |
|||
} else if (response instanceof ExecuteResponse) { |
|||
msgLog = String.format("%s: type operation: %s path: %s", |
|||
LOG_LW2M_INFO, EXECUTE.name(), request.getPath().toString()); |
|||
log.warn("9) [{}] ", msgLog); |
|||
handler.sendLogsToThingsboard(lwM2mClient, msgLog); |
|||
if (rpcRequest != null) { |
|||
msgLog = String.format("Start %s path: %S. Preparation finished: %s", EXECUTE.name(), path, rpcRequest.getInfoMsg()); |
|||
rpcRequest.setInfoMsg(msgLog); |
|||
handler.sentRpcResponse(rpcRequest, response.getCode().getName(), path, LOG_LW2M_INFO); |
|||
} |
|||
|
|||
} else if (response instanceof WriteAttributesResponse) { |
|||
msgLog = String.format("%s: type operation: %s path: %s value: %s", |
|||
LOG_LW2M_INFO, WRITE_ATTRIBUTES.name(), request.getPath().toString(), ((WriteAttributesRequest) request).getAttributes().toString()); |
|||
handler.sendLogsToThingsboard(lwM2mClient, msgLog); |
|||
log.warn("12) [{}] Path [{}] WriteAttributesResponse", pathIdVer, response); |
|||
if (rpcRequest != null) { |
|||
handler.sentRpcResponse(rpcRequest, response.getCode().getName(), response.toString(), LOG_LW2M_VALUE); |
|||
} |
|||
} else if (response instanceof WriteResponse) { |
|||
msgLog = String.format("Type operation: Write path: %s", pathIdVer); |
|||
log.warn("10) [{}] response: [{}]", msgLog, response); |
|||
this.infoWriteResponse(lwM2mClient, response, request, rpcRequest); |
|||
handler.onWriteResponseOk(registration, pathIdVer, (WriteRequest) request); |
|||
} |
|||
} |
|||
|
|||
private void infoWriteResponse(LwM2mClient lwM2mClient, LwM2mResponse response, SimpleDownlinkRequest request, LwM2mClientRpcRequest rpcRequest) { |
|||
try { |
|||
Registration registration = lwM2mClient.getRegistration(); |
|||
LwM2mNode node = ((WriteRequest) request).getNode(); |
|||
String msg = null; |
|||
Object value; |
|||
if (node instanceof LwM2mObject) { |
|||
msg = String.format("%s: Update finished successfully: Lwm2m code - %d Source path: %s value: %s", |
|||
LOG_LW2M_INFO, response.getCode().getCode(), request.getPath().toString(), ((LwM2mObject) node).toString()); |
|||
} else if (node instanceof LwM2mObjectInstance) { |
|||
msg = String.format("%s: Update finished successfully: Lwm2m code - %d Source path: %s value: %s", |
|||
LOG_LW2M_INFO, response.getCode().getCode(), request.getPath().toString(), ((LwM2mObjectInstance) node).prettyPrint()); |
|||
} else if (node instanceof LwM2mSingleResource) { |
|||
LwM2mSingleResource singleResource = (LwM2mSingleResource) node; |
|||
if (singleResource.getType() == ResourceModel.Type.STRING || singleResource.getType() == ResourceModel.Type.OPAQUE) { |
|||
int valueLength; |
|||
if (singleResource.getType() == ResourceModel.Type.STRING) { |
|||
valueLength = ((String) singleResource.getValue()).length(); |
|||
value = ((String) singleResource.getValue()) |
|||
.substring(Math.min(valueLength, config.getLogMaxLength())).trim(); |
|||
|
|||
} else { |
|||
valueLength = ((byte[]) singleResource.getValue()).length; |
|||
value = new String(Arrays.copyOf(((byte[]) singleResource.getValue()), |
|||
Math.min(valueLength, config.getLogMaxLength()))).trim(); |
|||
} |
|||
value = valueLength > config.getLogMaxLength() ? value + "..." : value; |
|||
msg = String.format("%s: Update finished successfully: Lwm2m code - %d Resource path: %s length: %s value: %s", |
|||
LOG_LW2M_INFO, response.getCode().getCode(), request.getPath().toString(), valueLength, value); |
|||
} else { |
|||
value = this.converter.convertValue(singleResource.getValue(), |
|||
singleResource.getType(), ResourceModel.Type.STRING, request.getPath()); |
|||
msg = String.format("%s: Update finished successfully. Lwm2m code: %d Resource path: %s value: %s", |
|||
LOG_LW2M_INFO, response.getCode().getCode(), request.getPath().toString(), value); |
|||
} |
|||
} |
|||
if (msg != null) { |
|||
handler.sendLogsToThingsboard(lwM2mClient, msg); |
|||
if (request.getPath().toString().equals(FW_PACKAGE_5_ID) || request.getPath().toString().equals(SW_PACKAGE_ID)) { |
|||
this.afterWriteSuccessFwSwUpdate(registration, request); |
|||
if (rpcRequest != null) { |
|||
rpcRequest.setInfoMsg(msg); |
|||
} |
|||
} |
|||
else if (rpcRequest != null) { |
|||
handler.sentRpcResponse(rpcRequest, response.getCode().getName(), msg, LOG_LW2M_INFO); |
|||
} |
|||
} |
|||
} catch (Exception e) { |
|||
log.trace("Fail convert value from request to string. ", e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* After finish operation FwSwUpdate Write (success): |
|||
* fw_state/sw_state = DOWNLOADED |
|||
* send operation Execute |
|||
*/ |
|||
private void afterWriteSuccessFwSwUpdate(Registration registration, SimpleDownlinkRequest request) { |
|||
LwM2mClient lwM2MClient = this.lwM2mClientContext.getClientByRegistrationId(registration.getId()); |
|||
if (request.getPath().toString().equals(FW_PACKAGE_5_ID) && lwM2MClient.getFwUpdate() != null) { |
|||
lwM2MClient.getFwUpdate().setStateUpdate(DOWNLOADED.name()); |
|||
lwM2MClient.getFwUpdate().sendLogs(this.handler, WRITE_REPLACE.name(), LOG_LW2M_INFO, null); |
|||
} |
|||
if (request.getPath().toString().equals(SW_PACKAGE_ID) && lwM2MClient.getSwUpdate() != null) { |
|||
lwM2MClient.getSwUpdate().setStateUpdate(DOWNLOADED.name()); |
|||
lwM2MClient.getSwUpdate().sendLogs(this.handler, WRITE_REPLACE.name(), LOG_LW2M_INFO, null); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* After finish operation FwSwUpdate Write (error): fw_state = FAILED |
|||
*/ |
|||
private void afterWriteFwSWUpdateError(Registration registration, SimpleDownlinkRequest request, String msgError) { |
|||
LwM2mClient lwM2MClient = this.lwM2mClientContext.getClientByRegistrationId(registration.getId()); |
|||
if (request.getPath().toString().equals(FW_PACKAGE_5_ID) && lwM2MClient.getFwUpdate() != null) { |
|||
lwM2MClient.getFwUpdate().setStateUpdate(FAILED.name()); |
|||
lwM2MClient.getFwUpdate().sendLogs(this.handler, WRITE_REPLACE.name(), LOG_LW2M_ERROR, msgError); |
|||
} |
|||
if (request.getPath().toString().equals(SW_PACKAGE_ID) && lwM2MClient.getSwUpdate() != null) { |
|||
lwM2MClient.getSwUpdate().setStateUpdate(FAILED.name()); |
|||
lwM2MClient.getSwUpdate().sendLogs(this.handler, WRITE_REPLACE.name(), LOG_LW2M_ERROR, msgError); |
|||
} |
|||
} |
|||
|
|||
private void afterExecuteFwSwUpdateError(Registration registration, SimpleDownlinkRequest request, String msgError) { |
|||
LwM2mClient lwM2MClient = this.lwM2mClientContext.getClientByRegistrationId(registration.getId()); |
|||
if (request.getPath().toString().equals(FW_UPDATE_ID) && lwM2MClient.getFwUpdate() != null) { |
|||
lwM2MClient.getFwUpdate().sendLogs(this.handler, EXECUTE.name(), LOG_LW2M_ERROR, msgError); |
|||
} |
|||
if (request.getPath().toString().equals(SW_INSTALL_ID) && lwM2MClient.getSwUpdate() != null) { |
|||
lwM2MClient.getSwUpdate().sendLogs(this.handler, EXECUTE.name(), LOG_LW2M_ERROR, msgError); |
|||
} |
|||
} |
|||
|
|||
private void afterObserveCancel(LwM2mClient lwM2mClient, int observeCancelCnt, String observeCancelMsg, LwM2mClientRpcRequest rpcRequest) { |
|||
handler.sendLogsToThingsboard(lwM2mClient, observeCancelMsg); |
|||
log.warn("[{}]", observeCancelMsg); |
|||
if (rpcRequest != null) { |
|||
rpcRequest.setInfoMsg(String.format("Count: %d", observeCancelCnt)); |
|||
handler.sentRpcResponse(rpcRequest, CONTENT.name(), null, LOG_LW2M_INFO); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server; |
|||
|
|||
import lombok.Getter; |
|||
|
|||
/** |
|||
* FW Update Result |
|||
* 0: Initial value. Once the updating process is initiated (Download /Update), this Resource MUST be reset to Initial value. |
|||
* 1: Firmware updated successfully. |
|||
* 2: Not enough flash memory for the new firmware package. |
|||
* 3: Out of RAM during downloading process. |
|||
* 4: Connection lost during downloading process. |
|||
* 5: Integrity check failure for new downloaded package. |
|||
* 6: Unsupported package type. |
|||
* 7: Invalid URI. |
|||
* 8: Firmware update failed. |
|||
* 9: Unsupported protocol. |
|||
*/ |
|||
public enum UpdateResultFw { |
|||
INITIAL(0, "Initial value", false), |
|||
UPDATE_SUCCESSFULLY(1, "Firmware updated successfully", false), |
|||
NOT_ENOUGH(2, "Not enough flash memory for the new firmware package", false), |
|||
OUT_OFF_MEMORY(3, "Out of RAM during downloading process", false), |
|||
CONNECTION_LOST(4, "Connection lost during downloading process", true), |
|||
INTEGRITY_CHECK_FAILURE(5, "Integrity check failure for new downloaded package", true), |
|||
UNSUPPORTED_TYPE(6, "Unsupported package type", false), |
|||
INVALID_URI(7, "Invalid URI", false), |
|||
UPDATE_FAILED(8, "Firmware update failed", false), |
|||
UNSUPPORTED_PROTOCOL(9, "Unsupported protocol", false); |
|||
|
|||
@Getter |
|||
private int code; |
|||
@Getter |
|||
private String type; |
|||
@Getter |
|||
private boolean again; |
|||
|
|||
UpdateResultFw(int code, String type, boolean isAgain) { |
|||
this.code = code; |
|||
this.type = type; |
|||
this.again = isAgain; |
|||
} |
|||
|
|||
public static UpdateResultFw fromUpdateResultFwByType(String type) { |
|||
for (UpdateResultFw to : UpdateResultFw.values()) { |
|||
if (to.type.equals(type)) { |
|||
return to; |
|||
} |
|||
} |
|||
throw new IllegalArgumentException(String.format("Unsupported FW Update Result type : %s", type)); |
|||
} |
|||
|
|||
public static UpdateResultFw fromUpdateResultFwByCode(int code) { |
|||
for (UpdateResultFw to : UpdateResultFw.values()) { |
|||
if (to.code == code) { |
|||
return to; |
|||
} |
|||
} |
|||
throw new IllegalArgumentException(String.format("Unsupported FW Update Result code : %s", code)); |
|||
} |
|||
} |
|||
@ -0,0 +1,56 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server; |
|||
|
|||
/** |
|||
* /** State R |
|||
* 0: Idle (before downloading or after successful updating) |
|||
* 1: Downloading (The data sequence is on the way) |
|||
* 2: Downloaded |
|||
* 3: Updating |
|||
*/ |
|||
public enum UpdateStateFw { |
|||
IDLE(0, "Idle"), |
|||
DOWNLOADING(1, "Downloading"), |
|||
DOWNLOADED(2, "Downloaded"), |
|||
UPDATING(3, "Updating"); |
|||
|
|||
public int code; |
|||
public String type; |
|||
|
|||
UpdateStateFw(int code, String type) { |
|||
this.code = code; |
|||
this.type = type; |
|||
} |
|||
|
|||
public static UpdateStateFw fromStateFwByType(String type) { |
|||
for (UpdateStateFw to : UpdateStateFw.values()) { |
|||
if (to.type.equals(type)) { |
|||
return to; |
|||
} |
|||
} |
|||
throw new IllegalArgumentException(String.format("Unsupported FW State type : %s", type)); |
|||
} |
|||
|
|||
public static UpdateStateFw fromStateFwByCode(int code) { |
|||
for (UpdateStateFw to : UpdateStateFw.values()) { |
|||
if (to.code == code) { |
|||
return to; |
|||
} |
|||
} |
|||
throw new IllegalArgumentException(String.format("Unsupported FW State code : %s", code)); |
|||
} |
|||
} |
|||
@ -0,0 +1,223 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.attributes; |
|||
|
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import com.google.common.util.concurrent.SettableFuture; |
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.eclipse.leshan.core.model.ResourceModel; |
|||
import org.eclipse.leshan.core.node.LwM2mPath; |
|||
import org.eclipse.leshan.core.node.LwM2mResource; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.ota.OtaPackageKey; |
|||
import org.thingsboard.server.common.data.ota.OtaPackageType; |
|||
import org.thingsboard.server.common.data.ota.OtaPackageUtil; |
|||
import org.thingsboard.server.common.transport.TransportService; |
|||
import org.thingsboard.server.common.transport.TransportServiceCallback; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; |
|||
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; |
|||
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.LwM2mDownlinkMsgHandler; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback; |
|||
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; |
|||
import org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService; |
|||
import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MOtaUpdateService; |
|||
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; |
|||
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Collection; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Optional; |
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
|
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper.getValueFromKvProto; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_ERROR; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; |
|||
|
|||
@Slf4j |
|||
@Service |
|||
@TbLwM2mTransportComponent |
|||
@RequiredArgsConstructor |
|||
public class DefaultLwM2MAttributesService implements LwM2MAttributesService { |
|||
|
|||
//TODO: add timeout logic
|
|||
private final AtomicInteger reqIdSeq = new AtomicInteger(); |
|||
private final Map<Integer, SettableFuture<List<TransportProtos.TsKvProto>>> futures; |
|||
|
|||
private final TransportService transportService; |
|||
private final LwM2mTransportServerHelper helper; |
|||
private final LwM2mClientContext clientContext; |
|||
private final LwM2MTransportServerConfig config; |
|||
private final LwM2mUplinkMsgHandler uplinkHandler; |
|||
private final LwM2mDownlinkMsgHandler downlinkHandler; |
|||
private final LwM2MTelemetryLogService logService; |
|||
private final LwM2MOtaUpdateService otaUpdateService; |
|||
|
|||
@Override |
|||
public ListenableFuture<List<TransportProtos.TsKvProto>> getSharedAttributes(LwM2mClient client, Collection<String> keys) { |
|||
SettableFuture<List<TransportProtos.TsKvProto>> future = SettableFuture.create(); |
|||
int requestId = reqIdSeq.incrementAndGet(); |
|||
futures.put(requestId, future); |
|||
transportService.process(client.getSession(), TransportProtos.GetAttributeRequestMsg.newBuilder().setRequestId(requestId). |
|||
addAllSharedAttributeNames(keys).build(), new TransportServiceCallback<Void>() { |
|||
@Override |
|||
public void onSuccess(Void msg) { |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public void onError(Throwable e) { |
|||
SettableFuture<List<TransportProtos.TsKvProto>> callback = futures.remove(requestId); |
|||
if (callback != null) { |
|||
callback.setException(e); |
|||
} |
|||
} |
|||
}); |
|||
return future; |
|||
} |
|||
|
|||
@Override |
|||
public void onGetAttributesResponse(GetAttributeResponseMsg getAttributesResponse, TransportProtos.SessionInfoProto sessionInfo) { |
|||
var callback = futures.remove(getAttributesResponse.getRequestId()); |
|||
if (callback != null) { |
|||
callback.set(getAttributesResponse.getSharedAttributeListList()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Update - send request in change value resources in Client |
|||
* 1. FirmwareUpdate: |
|||
* - If msg.getSharedUpdatedList().forEach(tsKvProto -> {tsKvProto.getKv().getKey().indexOf(FIRMWARE_UPDATE_PREFIX, 0) == 0 |
|||
* 2. Shared Other AttributeUpdate |
|||
* -- Path to resources from profile equal keyName or from ModelObject equal name |
|||
* -- Only for resources: isWritable && isPresent as attribute in profile -> LwM2MClientProfile (format: CamelCase) |
|||
* 3. Delete - nothing |
|||
* |
|||
* @param msg - |
|||
*/ |
|||
@Override |
|||
public void onAttributesUpdate(TransportProtos.AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo) { |
|||
LwM2mClient lwM2MClient = clientContext.getClientBySessionInfo(sessionInfo); |
|||
if (msg.getSharedUpdatedCount() > 0 && lwM2MClient != null) { |
|||
String newFirmwareTitle = null; |
|||
String newFirmwareVersion = null; |
|||
String newFirmwareUrl = null; |
|||
String newSoftwareTitle = null; |
|||
String newSoftwareVersion = null; |
|||
List<TransportProtos.TsKvProto> otherAttributes = new ArrayList<>(); |
|||
for (TransportProtos.TsKvProto tsKvProto : msg.getSharedUpdatedList()) { |
|||
String attrName = tsKvProto.getKv().getKey(); |
|||
if (DefaultLwM2MOtaUpdateService.FIRMWARE_TITLE.equals(attrName)) { |
|||
newFirmwareTitle = getStrValue(tsKvProto); |
|||
} else if (DefaultLwM2MOtaUpdateService.FIRMWARE_VERSION.equals(attrName)) { |
|||
newFirmwareVersion = getStrValue(tsKvProto); |
|||
} else if (DefaultLwM2MOtaUpdateService.FIRMWARE_URL.equals(attrName)) { |
|||
newFirmwareUrl = getStrValue(tsKvProto); |
|||
} else if (DefaultLwM2MOtaUpdateService.SOFTWARE_TITLE.equals(attrName)) { |
|||
newSoftwareTitle = getStrValue(tsKvProto); |
|||
} else if (DefaultLwM2MOtaUpdateService.SOFTWARE_VERSION.equals(attrName)) { |
|||
newSoftwareVersion = getStrValue(tsKvProto); |
|||
} else { |
|||
otherAttributes.add(tsKvProto); |
|||
} |
|||
} |
|||
if (newFirmwareTitle != null || newFirmwareVersion != null) { |
|||
otaUpdateService.onTargetFirmwareUpdate(lwM2MClient, newFirmwareTitle, newFirmwareVersion, Optional.ofNullable(newFirmwareUrl)); |
|||
} |
|||
if (newSoftwareTitle != null || newSoftwareVersion != null) { |
|||
otaUpdateService.onTargetSoftwareUpdate(lwM2MClient, newSoftwareTitle, newSoftwareVersion); |
|||
} |
|||
if (!otherAttributes.isEmpty()) { |
|||
onAttributesUpdate(lwM2MClient, otherAttributes); |
|||
} |
|||
} else if (lwM2MClient == null) { |
|||
log.error("OnAttributeUpdate, lwM2MClient is null"); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* #1.1 If two names have equal path => last time attribute |
|||
* #2.1 if there is a difference in values between the current resource values and the shared attribute values |
|||
* => send to client Request Update of value (new value from shared attribute) |
|||
* and LwM2MClient.delayedRequests.add(path) |
|||
* #2.1 if there is not a difference in values between the current resource values and the shared attribute values |
|||
* |
|||
*/ |
|||
@Override |
|||
public void onAttributesUpdate(LwM2mClient lwM2MClient, List<TransportProtos.TsKvProto> tsKvProtos) { |
|||
log.trace("[{}] onAttributesUpdate [{}]", lwM2MClient.getEndpoint(), tsKvProtos); |
|||
tsKvProtos.forEach(tsKvProto -> { |
|||
String pathIdVer = clientContext.getObjectIdByKeyNameFromProfile(lwM2MClient, tsKvProto.getKv().getKey()); |
|||
if (pathIdVer != null) { |
|||
// #1.1
|
|||
if (lwM2MClient.getSharedAttributes().containsKey(pathIdVer)) { |
|||
if (tsKvProto.getTs() > lwM2MClient.getSharedAttributes().get(pathIdVer).getTs()) { |
|||
lwM2MClient.getSharedAttributes().put(pathIdVer, tsKvProto); |
|||
} |
|||
} else { |
|||
lwM2MClient.getSharedAttributes().put(pathIdVer, tsKvProto); |
|||
} |
|||
} |
|||
}); |
|||
// #2.1
|
|||
lwM2MClient.getSharedAttributes().forEach((pathIdVer, tsKvProto) -> { |
|||
this.pushUpdateToClientIfNeeded(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), |
|||
getValueFromKvProto(tsKvProto.getKv()), pathIdVer); |
|||
}); |
|||
} |
|||
|
|||
private void pushUpdateToClientIfNeeded(LwM2mClient lwM2MClient, Object valueOld, Object newValue, String versionedId) { |
|||
if (newValue != null && (valueOld == null || !newValue.toString().equals(valueOld.toString()))) { |
|||
TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId).value(newValue).timeout(this.config.getTimeout()).build(); |
|||
downlinkHandler.sendWriteReplaceRequest(lwM2MClient, request, new TbLwM2MWriteResponseCallback(uplinkHandler, logService, lwM2MClient, versionedId)); |
|||
} else { |
|||
log.error("Failed update resource [{}] [{}]", versionedId, newValue); |
|||
String logMsg = String.format("%s: Failed update resource versionedId - %s value - %s. Value is not changed or bad", |
|||
LOG_LWM2M_ERROR, versionedId, newValue); |
|||
logService.log(lwM2MClient, logMsg); |
|||
log.info("Failed update resource [{}] [{}]", versionedId, newValue); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @param pathIdVer - path resource |
|||
* @return - value of Resource into format KvProto or null |
|||
*/ |
|||
private Object getResourceValueFormatKv(LwM2mClient lwM2MClient, String pathIdVer) { |
|||
LwM2mResource resourceValue = LwM2mTransportUtil.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer); |
|||
if (resourceValue != null) { |
|||
ResourceModel.Type currentType = resourceValue.getType(); |
|||
ResourceModel.Type expectedType = helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer); |
|||
return LwM2mValueConverterImpl.getInstance().convertValue(resourceValue.getValue(), currentType, expectedType, |
|||
new LwM2mPath(fromVersionedIdToObjectId(pathIdVer))); |
|||
} else { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private String getStrValue(TransportProtos.TsKvProto tsKvProto) { |
|||
return tsKvProto.getKv().getStringV(); |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.attributes; |
|||
|
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|||
|
|||
import java.util.Collection; |
|||
import java.util.List; |
|||
|
|||
public interface LwM2MAttributesService { |
|||
|
|||
ListenableFuture<List<TransportProtos.TsKvProto>> getSharedAttributes(LwM2mClient client, Collection<String> keys); |
|||
|
|||
void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg getAttributesResponse, TransportProtos.SessionInfoProto sessionInfo); |
|||
|
|||
void onAttributesUpdate(TransportProtos.AttributeUpdateNotificationMsg attributeUpdateNotification, TransportProtos.SessionInfoProto sessionInfo); |
|||
|
|||
void onAttributesUpdate(LwM2mClient lwM2MClient, List<TransportProtos.TsKvProto> tsKvProtos); |
|||
} |
|||
@ -1,113 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.client; |
|||
|
|||
import com.google.gson.Gson; |
|||
import com.google.gson.JsonArray; |
|||
import com.google.gson.JsonObject; |
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2MClientStrategy; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2MSoftwareUpdateStrategy; |
|||
|
|||
@Data |
|||
public class LwM2mClientProfile { |
|||
private final String clientStrategyStr = "clientStrategy"; |
|||
private final String fwUpdateStrategyStr = "fwUpdateStrategy"; |
|||
private final String swUpdateStrategyStr = "swUpdateStrategy"; |
|||
|
|||
private TenantId tenantId; |
|||
/** |
|||
* "clientLwM2mSettings": { |
|||
* "fwUpdateStrategy": "1", |
|||
* "swUpdateStrategy": "1", |
|||
* "clientStrategy": "1" |
|||
* } |
|||
**/ |
|||
private JsonObject postClientLwM2mSettings; |
|||
|
|||
/** |
|||
* {"keyName": { |
|||
* "/3_1.0/0/1": "modelNumber", |
|||
* "/3_1.0/0/0": "manufacturer", |
|||
* "/3_1.0/0/2": "serialNumber" |
|||
* } |
|||
**/ |
|||
private JsonObject postKeyNameProfile; |
|||
|
|||
/** |
|||
* [ "/3_1.0/0/0", "/3_1.0/0/1"] |
|||
*/ |
|||
private JsonArray postAttributeProfile; |
|||
|
|||
/** |
|||
* [ "/3_1.0/0/0", "/3_1.0/0/2"] |
|||
*/ |
|||
private JsonArray postTelemetryProfile; |
|||
|
|||
/** |
|||
* [ "/3_1.0/0", "/3_1.0/0/1, "/3_1.0/0/2"] |
|||
*/ |
|||
private JsonArray postObserveProfile; |
|||
|
|||
/** |
|||
* "attributeLwm2m": {"/3_1.0": {"ver": "currentTimeTest11"}, |
|||
* "/3_1.0/0": {"gt": 17}, |
|||
* "/3_1.0/0/9": {"pmax": 45}, "/3_1.2": {ver": "3_1.2"}} |
|||
*/ |
|||
private JsonObject postAttributeLwm2mProfile; |
|||
|
|||
public LwM2mClientProfile clone() { |
|||
LwM2mClientProfile lwM2mClientProfile = new LwM2mClientProfile(); |
|||
lwM2mClientProfile.postClientLwM2mSettings = this.deepCopy(this.postClientLwM2mSettings, JsonObject.class); |
|||
lwM2mClientProfile.postKeyNameProfile = this.deepCopy(this.postKeyNameProfile, JsonObject.class); |
|||
lwM2mClientProfile.postAttributeProfile = this.deepCopy(this.postAttributeProfile, JsonArray.class); |
|||
lwM2mClientProfile.postTelemetryProfile = this.deepCopy(this.postTelemetryProfile, JsonArray.class); |
|||
lwM2mClientProfile.postObserveProfile = this.deepCopy(this.postObserveProfile, JsonArray.class); |
|||
lwM2mClientProfile.postAttributeLwm2mProfile = this.deepCopy(this.postAttributeLwm2mProfile, JsonObject.class); |
|||
return lwM2mClientProfile; |
|||
} |
|||
|
|||
|
|||
private <T> T deepCopy(T elements, Class<T> type) { |
|||
try { |
|||
Gson gson = new Gson(); |
|||
return gson.fromJson(gson.toJson(elements), type); |
|||
} catch (Exception e) { |
|||
e.printStackTrace(); |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
public int getClientStrategy() { |
|||
return this.postClientLwM2mSettings.getAsJsonObject().has(this.clientStrategyStr) ? |
|||
Integer.parseInt(this.postClientLwM2mSettings.getAsJsonObject().get(this.clientStrategyStr).getAsString()) : |
|||
LwM2MClientStrategy.CLIENT_STRATEGY_1.code; |
|||
} |
|||
|
|||
public int getFwUpdateStrategy() { |
|||
return this.postClientLwM2mSettings.getAsJsonObject().has(this.fwUpdateStrategyStr) ? |
|||
Integer.parseInt(this.postClientLwM2mSettings.getAsJsonObject().get(this.fwUpdateStrategyStr).getAsString()) : |
|||
LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY.code; |
|||
} |
|||
|
|||
public int getSwUpdateStrategy() { |
|||
return this.postClientLwM2mSettings.getAsJsonObject().has(this.swUpdateStrategyStr) ? |
|||
Integer.parseInt(this.postClientLwM2mSettings.getAsJsonObject().get(this.swUpdateStrategyStr).getAsString()) : |
|||
LwM2MSoftwareUpdateStrategy.BINARY.code; |
|||
} |
|||
} |
|||
@ -1,281 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.client; |
|||
|
|||
import com.google.gson.Gson; |
|||
import com.google.gson.JsonObject; |
|||
import com.google.gson.reflect.TypeToken; |
|||
import lombok.Data; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.eclipse.leshan.core.node.LwM2mPath; |
|||
import org.eclipse.leshan.server.registration.Registration; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2MTransportMsgHandler; |
|||
|
|||
import java.util.Map; |
|||
import java.util.Objects; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.TimeoutException; |
|||
|
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.ERROR_KEY; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FINISH_JSON_KEY; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FINISH_VALUE_KEY; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.INFO_KEY; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.KEY_NAME_KEY; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.DISCOVER_ALL; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.EXECUTE; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.FW_UPDATE; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_CANCEL; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_READ_ALL; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_ATTRIBUTES; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_REPLACE; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_UPDATE; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.METHOD_KEY; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.PARAMS_KEY; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.RESULT_KEY; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SEPARATOR_KEY; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.START_JSON_KEY; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.TARGET_ID_VER_KEY; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.VALUE_KEY; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromIdVerToObjectId; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.validPathIdVer; |
|||
|
|||
@Slf4j |
|||
@Data |
|||
public class LwM2mClientRpcRequest { |
|||
|
|||
private Registration registration; |
|||
private TransportProtos.SessionInfoProto sessionInfo; |
|||
private String bodyParams; |
|||
private int requestId; |
|||
|
|||
private LwM2mTypeOper typeOper; |
|||
private String key; |
|||
private String targetIdVer; |
|||
private Object value; |
|||
private Map<String, Object> params; |
|||
|
|||
private String errorMsg; |
|||
private String valueMsg; |
|||
private String infoMsg; |
|||
private String responseCode; |
|||
|
|||
public LwM2mClientRpcRequest() { |
|||
} |
|||
|
|||
public LwM2mClientRpcRequest(LwM2mTypeOper lwM2mTypeOper, String bodyParams, int requestId, |
|||
TransportProtos.SessionInfoProto sessionInfo, Registration registration, DefaultLwM2MTransportMsgHandler handler) { |
|||
this.registration = registration; |
|||
this.sessionInfo = sessionInfo; |
|||
this.requestId = requestId; |
|||
if (lwM2mTypeOper != null) { |
|||
this.typeOper = lwM2mTypeOper; |
|||
} else { |
|||
this.errorMsg = METHOD_KEY + " - " + typeOper + " is not valid."; |
|||
} |
|||
if (this.errorMsg == null && !bodyParams.equals("null")) { |
|||
this.bodyParams = bodyParams; |
|||
this.init(handler); |
|||
} |
|||
} |
|||
|
|||
public TransportProtos.ToDeviceRpcResponseMsg getDeviceRpcResponseResultMsg() { |
|||
JsonObject payloadResp = new JsonObject(); |
|||
payloadResp.addProperty(RESULT_KEY, this.responseCode); |
|||
if (this.errorMsg != null) { |
|||
payloadResp.addProperty(ERROR_KEY, this.errorMsg); |
|||
} else if (this.valueMsg != null) { |
|||
payloadResp.addProperty(VALUE_KEY, this.valueMsg); |
|||
} else if (this.infoMsg != null) { |
|||
payloadResp.addProperty(INFO_KEY, this.infoMsg); |
|||
} |
|||
return TransportProtos.ToDeviceRpcResponseMsg.newBuilder() |
|||
.setPayload(payloadResp.getAsJsonObject().toString()) |
|||
.setRequestId(this.requestId) |
|||
.build(); |
|||
} |
|||
|
|||
private void init(DefaultLwM2MTransportMsgHandler handler) { |
|||
try { |
|||
// #1
|
|||
if (this.bodyParams.contains(KEY_NAME_KEY)) { |
|||
String targetIdVerStr = this.getValueKeyFromBody(KEY_NAME_KEY); |
|||
if (targetIdVerStr != null) { |
|||
String targetIdVer = handler.getPresentPathIntoProfile(sessionInfo, targetIdVerStr); |
|||
if (targetIdVer != null) { |
|||
this.targetIdVer = targetIdVer; |
|||
this.setInfoMsg(String.format("Changed by: key - %s, pathIdVer - %s", |
|||
targetIdVerStr, targetIdVer)); |
|||
} |
|||
} |
|||
} |
|||
if (this.getTargetIdVer() == null && this.bodyParams.contains(TARGET_ID_VER_KEY)) { |
|||
this.setValidTargetIdVerKey(); |
|||
} |
|||
if (this.bodyParams.contains(VALUE_KEY)) { |
|||
this.value = this.getValueKeyFromBody(VALUE_KEY); |
|||
} |
|||
try { |
|||
if (this.bodyParams.contains(PARAMS_KEY)) { |
|||
this.setValidParamsKey(handler); |
|||
} |
|||
} catch (Exception e) { |
|||
this.setErrorMsg(String.format("Params of request is bad Json format. %s", e.getMessage())); |
|||
} |
|||
|
|||
if (this.getTargetIdVer() == null |
|||
&& !(OBSERVE_READ_ALL == this.getTypeOper() |
|||
|| DISCOVER_ALL == this.getTypeOper() |
|||
|| OBSERVE_CANCEL == this.getTypeOper() |
|||
|| FW_UPDATE == this.getTypeOper())) { |
|||
this.setErrorMsg(TARGET_ID_VER_KEY + " and " + |
|||
KEY_NAME_KEY + " is null or bad format"); |
|||
} |
|||
/** |
|||
* EXECUTE && WRITE_REPLACE - only for Resource or ResourceInstance |
|||
*/ |
|||
else if (this.getTargetIdVer() != null |
|||
&& (EXECUTE == this.getTypeOper() |
|||
|| WRITE_REPLACE == this.getTypeOper()) |
|||
&& !(new LwM2mPath(Objects.requireNonNull(convertPathFromIdVerToObjectId(this.getTargetIdVer()))).isResource() |
|||
|| new LwM2mPath(Objects.requireNonNull(convertPathFromIdVerToObjectId(this.getTargetIdVer()))).isResourceInstance())) { |
|||
this.setErrorMsg("Invalid parameter " + TARGET_ID_VER_KEY |
|||
+ ". Only Resource or ResourceInstance can be this operation"); |
|||
} |
|||
} catch (Exception e) { |
|||
this.setErrorMsg(String.format("Bad format request. %s", e.getMessage())); |
|||
} |
|||
|
|||
} |
|||
|
|||
private void setValidTargetIdVerKey() { |
|||
String targetIdVerStr = this.getValueKeyFromBody(TARGET_ID_VER_KEY); |
|||
// targetIdVer without ver - ok
|
|||
try { |
|||
// targetIdVer with/without ver - ok
|
|||
this.targetIdVer = validPathIdVer(targetIdVerStr, this.registration); |
|||
if (this.targetIdVer != null) { |
|||
this.infoMsg = String.format("Changed by: pathIdVer - %s", this.targetIdVer); |
|||
} |
|||
} catch (Exception e) { |
|||
if (this.targetIdVer == null) { |
|||
this.errorMsg = TARGET_ID_VER_KEY + " - " + targetIdVerStr + " is not valid."; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void setValidParamsKey(DefaultLwM2MTransportMsgHandler handler) { |
|||
String paramsStr = this.getValueKeyFromBody(PARAMS_KEY); |
|||
if (paramsStr != null) { |
|||
String params2Json = |
|||
START_JSON_KEY |
|||
+ "\"" |
|||
+ paramsStr |
|||
.replaceAll(SEPARATOR_KEY, "\"" + SEPARATOR_KEY + "\"") |
|||
.replaceAll(FINISH_VALUE_KEY, "\"" + FINISH_VALUE_KEY + "\"") |
|||
+ "\"" |
|||
+ FINISH_JSON_KEY; |
|||
// jsonObject
|
|||
Map<String, Object> params = new Gson().fromJson(params2Json, new TypeToken<ConcurrentHashMap<String, Object>>() { |
|||
}.getType()); |
|||
if (WRITE_UPDATE == this.getTypeOper()) { |
|||
if (this.targetIdVer != null) { |
|||
Map<String, Object> paramsResourceId = this.convertParamsToResourceId((ConcurrentHashMap<String, Object>) params, handler); |
|||
if (paramsResourceId.size() > 0) { |
|||
this.setParams(paramsResourceId); |
|||
} |
|||
} |
|||
} else if (WRITE_ATTRIBUTES == this.getTypeOper()) { |
|||
this.setParams(params); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private String getValueKeyFromBody(String key) { |
|||
String valueKey = null; |
|||
int startInd = -1; |
|||
int finishInd = -1; |
|||
try { |
|||
switch (key) { |
|||
case KEY_NAME_KEY: |
|||
case TARGET_ID_VER_KEY: |
|||
case VALUE_KEY: |
|||
startInd = this.bodyParams.indexOf(SEPARATOR_KEY, this.bodyParams.indexOf(key)); |
|||
finishInd = this.bodyParams.indexOf(FINISH_VALUE_KEY, this.bodyParams.indexOf(key)); |
|||
if (startInd >= 0 && finishInd < 0) { |
|||
finishInd = this.bodyParams.indexOf(FINISH_JSON_KEY, this.bodyParams.indexOf(key)); |
|||
} |
|||
break; |
|||
case PARAMS_KEY: |
|||
startInd = this.bodyParams.indexOf(START_JSON_KEY, this.bodyParams.indexOf(key)); |
|||
finishInd = this.bodyParams.indexOf(FINISH_JSON_KEY, this.bodyParams.indexOf(key)); |
|||
} |
|||
if (startInd >= 0 && finishInd > 0) { |
|||
valueKey = this.bodyParams.substring(startInd + 1, finishInd); |
|||
} |
|||
} catch (Exception e) { |
|||
log.error("", new TimeoutException()); |
|||
} |
|||
/** |
|||
* ReplaceAll "\"" |
|||
*/ |
|||
if (StringUtils.trimToNull(valueKey) != null) { |
|||
char[] chars = valueKey.toCharArray(); |
|||
for (int i = 0; i < chars.length; i++) { |
|||
if (chars[i] == 92 || chars[i] == 34) chars[i] = 32; |
|||
} |
|||
return key.equals(PARAMS_KEY) ? String.valueOf(chars) : String.valueOf(chars).replaceAll(" ", ""); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
private ConcurrentHashMap<String, Object> convertParamsToResourceId(ConcurrentHashMap<String, Object> params, |
|||
DefaultLwM2MTransportMsgHandler serviceImpl) { |
|||
Map<String, Object> paramsIdVer = new ConcurrentHashMap<>(); |
|||
LwM2mPath targetId = new LwM2mPath(Objects.requireNonNull(convertPathFromIdVerToObjectId(this.targetIdVer))); |
|||
if (targetId.isObjectInstance()) { |
|||
params.forEach((k, v) -> { |
|||
try { |
|||
int id = Integer.parseInt(k); |
|||
paramsIdVer.put(String.valueOf(id), v); |
|||
} catch (NumberFormatException e) { |
|||
String targetIdVer = serviceImpl.getPresentPathIntoProfile(sessionInfo, k); |
|||
if (targetIdVer != null) { |
|||
LwM2mPath lwM2mPath = new LwM2mPath(Objects.requireNonNull(convertPathFromIdVerToObjectId(targetIdVer))); |
|||
paramsIdVer.put(String.valueOf(lwM2mPath.getResourceId()), v); |
|||
} |
|||
/** WRITE_UPDATE*/ |
|||
else { |
|||
String rezId = this.getRezIdByResourceNameAndObjectInstanceId(k, serviceImpl); |
|||
if (rezId != null) { |
|||
paramsIdVer.put(rezId, v); |
|||
} |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
return (ConcurrentHashMap<String, Object>) paramsIdVer; |
|||
} |
|||
|
|||
private String getRezIdByResourceNameAndObjectInstanceId(String resourceName, DefaultLwM2MTransportMsgHandler handler) { |
|||
LwM2mClient lwM2mClient = handler.clientContext.getClientBySessionInfo(this.sessionInfo); |
|||
return lwM2mClient != null ? |
|||
lwM2mClient.getRezIdByResourceNameAndObjectInstanceId(resourceName, this.targetIdVer, handler.config.getModelProvider()) : |
|||
null; |
|||
} |
|||
} |
|||
@ -1,474 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.client; |
|||
|
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.eclipse.leshan.core.request.ContentFormat; |
|||
import org.eclipse.leshan.server.registration.Registration; |
|||
import org.thingsboard.server.common.data.ota.OtaPackageType; |
|||
import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2MTransportMsgHandler; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportRequest; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.CopyOnWriteArrayList; |
|||
|
|||
import static org.eclipse.californium.core.coap.CoAP.ResponseCode.CONTENT; |
|||
import static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE; |
|||
import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; |
|||
import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; |
|||
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADED; |
|||
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.FAILED; |
|||
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.INITIATED; |
|||
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATED; |
|||
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATING; |
|||
import static org.thingsboard.server.common.data.ota.OtaPackageUtil.getAttributeKey; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FIRMWARE_UPDATE_COAP_RECOURSE; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_3_VER_ID; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_5_VER_ID; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_NAME_ID; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_PACKAGE_19_ID; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_PACKAGE_5_ID; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_PACKAGE_URI_ID; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_RESULT_ID; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_STATE_ID; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_UPDATE; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_UPDATE_ID; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_ERROR; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_19_BINARY; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.EXECUTE; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_REPLACE; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_INSTALL_ID; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_NAME_ID; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_PACKAGE_ID; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_RESULT_ID; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_UN_INSTALL_ID; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_UPDATE; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_UPDATE_STATE_ID; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_VER_ID; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.equalsFwSateToFirmwareUpdateStatus; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.splitCamelCaseString; |
|||
|
|||
@Slf4j |
|||
public class LwM2mFwSwUpdate { |
|||
// 5/0/6 PkgName
|
|||
// 9/0/0 PkgName
|
|||
@Getter |
|||
@Setter |
|||
private volatile String currentTitle; |
|||
// 5/0/7 PkgVersion
|
|||
// 9/0/1 PkgVersion
|
|||
@Getter |
|||
@Setter |
|||
private volatile String currentVersion; |
|||
@Getter |
|||
@Setter |
|||
private volatile UUID currentId; |
|||
@Getter |
|||
@Setter |
|||
private volatile String stateUpdate; |
|||
@Getter |
|||
private String pathPackageId; |
|||
@Getter |
|||
private String pathStateId; |
|||
@Getter |
|||
private String pathResultId; |
|||
@Getter |
|||
private String pathNameId; |
|||
@Getter |
|||
private String pathVerId; |
|||
@Getter |
|||
private String pathInstallId; |
|||
@Getter |
|||
private String pathUnInstallId; |
|||
@Getter |
|||
private String wUpdate; |
|||
@Getter |
|||
@Setter |
|||
private volatile boolean infoFwSwUpdate = false; |
|||
private final OtaPackageType type; |
|||
@Getter |
|||
LwM2mClient lwM2MClient; |
|||
@Getter |
|||
@Setter |
|||
private final List<String> pendingInfoRequestsStart; |
|||
@Getter |
|||
@Setter |
|||
private volatile LwM2mClientRpcRequest rpcRequest; |
|||
@Getter |
|||
@Setter |
|||
private volatile int updateStrategy; |
|||
|
|||
public LwM2mFwSwUpdate(LwM2mClient lwM2MClient, OtaPackageType type, int updateStrategy) { |
|||
this.lwM2MClient = lwM2MClient; |
|||
this.pendingInfoRequestsStart = new CopyOnWriteArrayList<>(); |
|||
this.type = type; |
|||
this.stateUpdate = null; |
|||
this.updateStrategy = updateStrategy; |
|||
this.initPathId(); |
|||
} |
|||
|
|||
private void initPathId() { |
|||
if (FIRMWARE.equals(this.type)) { |
|||
this.pathPackageId = LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY.code == this.updateStrategy ? |
|||
FW_PACKAGE_5_ID : LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL.code == this.updateStrategy ? |
|||
FW_PACKAGE_URI_ID : FW_PACKAGE_19_ID; |
|||
this.pathStateId = FW_STATE_ID; |
|||
this.pathResultId = FW_RESULT_ID; |
|||
this.pathNameId = FW_NAME_ID; |
|||
this.pathVerId = FW_5_VER_ID; |
|||
this.pathInstallId = FW_UPDATE_ID; |
|||
this.wUpdate = FW_UPDATE; |
|||
} else if (SOFTWARE.equals(this.type)) { |
|||
this.pathPackageId = SW_PACKAGE_ID; |
|||
this.pathStateId = SW_UPDATE_STATE_ID; |
|||
this.pathResultId = SW_RESULT_ID; |
|||
this.pathNameId = SW_NAME_ID; |
|||
this.pathVerId = SW_VER_ID; |
|||
this.pathInstallId = SW_INSTALL_ID; |
|||
this.pathUnInstallId = SW_UN_INSTALL_ID; |
|||
this.wUpdate = SW_UPDATE; |
|||
} |
|||
} |
|||
|
|||
public void initReadValue(DefaultLwM2MTransportMsgHandler handler, LwM2mTransportRequest request, String pathIdVer) { |
|||
if (pathIdVer != null) { |
|||
this.pendingInfoRequestsStart.remove(pathIdVer); |
|||
} |
|||
if (this.pendingInfoRequestsStart.size() == 0) { |
|||
this.infoFwSwUpdate = false; |
|||
// if (!FAILED.name().equals(this.stateUpdate)) {
|
|||
boolean conditionalStart = this.type.equals(FIRMWARE) ? this.conditionalFwUpdateStart(handler) : |
|||
this.conditionalSwUpdateStart(handler); |
|||
if (conditionalStart) { |
|||
this.writeFwSwWare(handler, request); |
|||
} |
|||
// }
|
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Send FsSw to Lwm2mClient: |
|||
* before operation Write: fw_state = DOWNLOADING |
|||
*/ |
|||
public void writeFwSwWare(DefaultLwM2MTransportMsgHandler handler, LwM2mTransportRequest request) { |
|||
if (this.currentId != null) { |
|||
this.stateUpdate = OtaPackageUpdateStatus.INITIATED.name(); |
|||
this.sendLogs(handler, WRITE_REPLACE.name(), LOG_LW2M_INFO, null); |
|||
String targetIdVer = convertPathFromObjectIdToIdVer(this.pathPackageId, this.lwM2MClient.getRegistration()); |
|||
String fwMsg = String.format("%s: Start type operation %s paths: %s", LOG_LW2M_INFO, |
|||
LwM2mTransportUtil.LwM2mTypeOper.FW_UPDATE.name(), this.pathPackageId); |
|||
handler.sendLogsToThingsboard(fwMsg, lwM2MClient.getRegistration().getId()); |
|||
log.warn("8) Start firmware Update. Send save to: [{}] ver: [{}] path: [{}]", this.lwM2MClient.getDeviceName(), this.currentVersion, targetIdVer); |
|||
if (LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY.code == this.updateStrategy) { |
|||
int chunkSize = 0; |
|||
int chunk = 0; |
|||
byte[] firmwareChunk = handler.otaPackageDataCache.get(this.currentId.toString(), chunkSize, chunk); |
|||
request.sendAllRequest(this.lwM2MClient, targetIdVer, WRITE_REPLACE, ContentFormat.OPAQUE, |
|||
firmwareChunk, handler.config.getTimeout(), this.rpcRequest); |
|||
} else if (LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL.code == this.updateStrategy) { |
|||
String apiFont = "coap://176.36.143.9:5685"; |
|||
String uri = apiFont + "/" + FIRMWARE_UPDATE_COAP_RECOURSE + "/" + this.currentId.toString(); |
|||
log.warn("89) coapUri: [{}]", uri); |
|||
request.sendAllRequest(this.lwM2MClient, targetIdVer, WRITE_REPLACE, null, |
|||
uri, handler.config.getTimeout(), this.rpcRequest); |
|||
} else if (LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_19_BINARY.code == this.updateStrategy) { |
|||
|
|||
} |
|||
} else { |
|||
String msgError = "FirmWareId is null."; |
|||
log.warn("6) [{}]", msgError); |
|||
if (this.rpcRequest != null) { |
|||
handler.sentRpcResponse(this.rpcRequest, CONTENT.name(), msgError, LOG_LW2M_ERROR); |
|||
} |
|||
log.error(msgError); |
|||
this.sendLogs(handler, WRITE_REPLACE.name(), LOG_LW2M_ERROR, msgError); |
|||
} |
|||
} |
|||
|
|||
public void sendLogs(DefaultLwM2MTransportMsgHandler handler, String typeOper, String typeInfo, String msgError) { |
|||
// this.sendSateOnThingsBoard(handler);
|
|||
String msg = String.format("%s: %s, %s, pkgVer: %s: pkgName - %s state - %s.", |
|||
typeInfo, this.wUpdate, typeOper, this.currentVersion, this.currentTitle, this.stateUpdate); |
|||
if (LOG_LW2M_ERROR.equals(typeInfo)) { |
|||
msg = String.format("%s Error: %s", msg, msgError); |
|||
} |
|||
handler.sendLogsToThingsboard(lwM2MClient, msg); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* After inspection Update Result |
|||
* fw_state/sw_state = UPDATING |
|||
* send execute |
|||
*/ |
|||
public void executeFwSwWare(DefaultLwM2MTransportMsgHandler handler, LwM2mTransportRequest request) { |
|||
this.sendLogs(handler, EXECUTE.name(), LOG_LW2M_INFO, null); |
|||
request.sendAllRequest(this.lwM2MClient, this.pathInstallId, EXECUTE, null, 0, this.rpcRequest); |
|||
} |
|||
|
|||
/** |
|||
* Firmware start: Check if the version has changed and launch a new update. |
|||
* -ObjectId 5, Binary or ObjectId 5, URI |
|||
* -- If the result of the update - errors (more than 1) - This means that the previous. the update failed. |
|||
* - We launch the update regardless of the state of the firmware and its version. |
|||
* -- If the result of the update - errors (more than 1) - This means that the previous. the update failed. |
|||
* * ObjectId 5, Binary |
|||
* -- If the result of the update is not errors (equal to 1 or 0) and ver in Object 5 is not empty - it means that the previous update has passed. |
|||
* Compare current versions by equals. |
|||
* * ObjectId 5, URI |
|||
* -- If the result of the update is not errors (equal to 1 or 0) and ver in Object 5 is not empty - it means that the previous update has passed. |
|||
* Compare current versions by contains. |
|||
*/ |
|||
private boolean conditionalFwUpdateStart(DefaultLwM2MTransportMsgHandler handler) { |
|||
Long updateResultFw = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); |
|||
String ver5 = (String) this.lwM2MClient.getResourceValue(null, this.pathVerId); |
|||
String pathName = (String) this.lwM2MClient.getResourceValue(null, this.pathNameId); |
|||
String ver3 = (String) this.lwM2MClient.getResourceValue(null, FW_3_VER_ID); |
|||
// #1/#2
|
|||
String fwMsg = null; |
|||
if ((this.currentVersion != null && ( |
|||
ver5 != null && ver5.equals(this.currentVersion) || |
|||
ver3 != null && ver3.contains(this.currentVersion) |
|||
)) || |
|||
(this.currentTitle != null && pathName != null && this.currentTitle.equals(pathName))) { |
|||
fwMsg = String.format("%s: The update was interrupted. The device has the same version: %s.", LOG_LW2M_ERROR, |
|||
this.currentVersion); |
|||
} |
|||
else if (updateResultFw != null && updateResultFw > LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code) { |
|||
fwMsg = String.format("%s: The update was interrupted. The device has the status UpdateResult: error (%d).", LOG_LW2M_ERROR, |
|||
updateResultFw); |
|||
} |
|||
if (fwMsg != null) { |
|||
handler.sendLogsToThingsboard(fwMsg, lwM2MClient.getRegistration().getId()); |
|||
return false; |
|||
} |
|||
else { |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Before operation Execute inspection Update Result : |
|||
* 0 - Initial value |
|||
*/ |
|||
public boolean conditionalFwExecuteStart() { |
|||
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); |
|||
return LwM2mTransportUtil.UpdateResultFw.INITIAL.code == updateResult; |
|||
} |
|||
|
|||
/** |
|||
* After operation Execute success inspection Update Result : |
|||
* 1 - "Firmware updated successfully" |
|||
*/ |
|||
public boolean conditionalFwExecuteAfterSuccess() { |
|||
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); |
|||
return LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code == updateResult; |
|||
} |
|||
|
|||
/** |
|||
* After operation Execute success inspection Update Result : |
|||
* > 1 error: "Firmware updated successfully" |
|||
*/ |
|||
public boolean conditionalFwExecuteAfterError() { |
|||
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); |
|||
return LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code < updateResult; |
|||
} |
|||
|
|||
/** |
|||
* Software start |
|||
* - If Update Result -errors (equal or more than 50) - This means that the previous. the update failed. |
|||
* * - We launch the update regardless of the state of the firmware and its version. |
|||
* - If Update Result is not errors (less than 50) and ver is not empty - This means that before. the update has passed. |
|||
* - If Update Result is not errors and ver is empty - This means that there was no update yet or before. UnInstall update |
|||
* - If Update Result is not errors and ver is not empty - This means that before unInstall update |
|||
* * - Check if the version has changed and launch a new update. |
|||
*/ |
|||
private boolean conditionalSwUpdateStart(DefaultLwM2MTransportMsgHandler handler) { |
|||
Long updateResultSw = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); |
|||
// #1/#2
|
|||
return updateResultSw >= LwM2mTransportUtil.UpdateResultSw.NOT_ENOUGH_STORAGE.code || |
|||
( |
|||
(updateResultSw <= LwM2mTransportUtil.UpdateResultSw.NOT_ENOUGH_STORAGE.code |
|||
) && |
|||
( |
|||
(this.currentVersion != null && !this.currentVersion.equals(this.lwM2MClient.getResourceValue(null, this.pathVerId))) || |
|||
(this.currentTitle != null && !this.currentTitle.equals(this.lwM2MClient.getResourceValue(null, this.pathNameId))) |
|||
) |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Before operation Execute inspection Update Result : |
|||
* 3 - Successfully Downloaded and package integrity verified |
|||
*/ |
|||
public boolean conditionalSwUpdateExecute() { |
|||
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); |
|||
return LwM2mTransportUtil.UpdateResultSw.SUCCESSFULLY_DOWNLOADED_VERIFIED.code == updateResult; |
|||
} |
|||
|
|||
/** |
|||
* After finish operation Execute (success): |
|||
* -- inspection Update Result: |
|||
* ---- FW если Update Result == 1 ("Firmware updated successfully") или SW если Update Result == 2 ("Software successfully installed.") |
|||
* -- fw_state/sw_state = UPDATED |
|||
* <p> |
|||
* After finish operation Execute (error): |
|||
* -- inspection updateResult and send to thingsboard info about error |
|||
* --- send to telemetry ( key - this is name Update Result in model) ( |
|||
* -- fw_state/sw_state = FAILED |
|||
*/ |
|||
public void finishFwSwUpdate(DefaultLwM2MTransportMsgHandler handler, boolean success) { |
|||
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); |
|||
String value = FIRMWARE.equals(this.type) ? LwM2mTransportUtil.UpdateResultFw.fromUpdateResultFwByCode(updateResult.intValue()).type : |
|||
LwM2mTransportUtil.UpdateResultSw.fromUpdateResultSwByCode(updateResult.intValue()).type; |
|||
String key = splitCamelCaseString((String) this.lwM2MClient.getResourceNameByRezId(null, this.pathResultId)); |
|||
if (success) { |
|||
this.stateUpdate = OtaPackageUpdateStatus.UPDATED.name(); |
|||
this.sendLogs(handler, EXECUTE.name(), LOG_LW2M_INFO, null); |
|||
} else { |
|||
this.stateUpdate = OtaPackageUpdateStatus.FAILED.name(); |
|||
this.sendLogs(handler, EXECUTE.name(), LOG_LW2M_ERROR, value); |
|||
} |
|||
handler.helper.sendParametersOnThingsboardTelemetry( |
|||
handler.helper.getKvStringtoThingsboard(key, value), this.lwM2MClient.getSession()); |
|||
} |
|||
|
|||
/** |
|||
* After operation Execute success inspection Update Result : |
|||
* 2 - "Software successfully installed." |
|||
*/ |
|||
public boolean conditionalSwExecuteAfterSuccess() { |
|||
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); |
|||
return LwM2mTransportUtil.UpdateResultSw.SUCCESSFULLY_INSTALLED.code == updateResult; |
|||
} |
|||
|
|||
/** |
|||
* After operation Execute success inspection Update Result : |
|||
* >= 50 - error "NOT_ENOUGH_STORAGE" |
|||
*/ |
|||
public boolean conditionalSwExecuteAfterError() { |
|||
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); |
|||
return LwM2mTransportUtil.UpdateResultSw.NOT_ENOUGH_STORAGE.code <= updateResult; |
|||
} |
|||
|
|||
private void observeStateUpdate(DefaultLwM2MTransportMsgHandler handler, LwM2mTransportRequest request) { |
|||
request.sendAllRequest(lwM2MClient, |
|||
convertPathFromObjectIdToIdVer(this.pathStateId, this.lwM2MClient.getRegistration()), OBSERVE, |
|||
null, null, 0, null); |
|||
request.sendAllRequest(lwM2MClient, |
|||
convertPathFromObjectIdToIdVer(this.pathResultId, this.lwM2MClient.getRegistration()), OBSERVE, |
|||
null, null, 0, null); |
|||
} |
|||
|
|||
public void sendSateOnThingsBoard(DefaultLwM2MTransportMsgHandler handler) { |
|||
if (StringUtils.trimToNull(this.stateUpdate) != null) { |
|||
List<TransportProtos.KeyValueProto> result = new ArrayList<>(); |
|||
TransportProtos.KeyValueProto.Builder kvProto = TransportProtos.KeyValueProto.newBuilder().setKey(getAttributeKey(this.type, STATE)); |
|||
kvProto.setType(TransportProtos.KeyValueType.STRING_V).setStringV(stateUpdate); |
|||
result.add(kvProto.build()); |
|||
handler.helper.sendParametersOnThingsboardTelemetry(result, |
|||
handler.getSessionInfoOrCloseSession(this.lwM2MClient.getRegistration())); |
|||
} |
|||
} |
|||
|
|||
public void sendReadObserveInfo(LwM2mTransportRequest request) { |
|||
this.infoFwSwUpdate = true; |
|||
this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer( |
|||
this.pathStateId, this.lwM2MClient.getRegistration())); |
|||
this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer( |
|||
this.pathResultId, this.lwM2MClient.getRegistration())); |
|||
this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer( |
|||
FW_3_VER_ID, this.lwM2MClient.getRegistration())); |
|||
if (LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY.code == this.updateStrategy || |
|||
LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_19_BINARY.code == this.updateStrategy || |
|||
SOFTWARE.equals(this.type)) { |
|||
this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer( |
|||
this.pathVerId, this.lwM2MClient.getRegistration())); |
|||
this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer( |
|||
this.pathNameId, this.lwM2MClient.getRegistration())); |
|||
} |
|||
this.pendingInfoRequestsStart.forEach(pathIdVer -> { |
|||
request.sendAllRequest(this.lwM2MClient, pathIdVer, OBSERVE, null, 0, this.rpcRequest); |
|||
}); |
|||
|
|||
} |
|||
|
|||
/** |
|||
* Before operation Execute (FwUpdate) inspection Update Result : |
|||
* - after finished operation Write result: success (FwUpdate): fw_state = DOWNLOADED |
|||
* - before start operation Execute (FwUpdate) Update Result = 0 - Initial value |
|||
* - start Execute (FwUpdate) |
|||
* After finished operation Execute (FwUpdate) inspection Update Result : |
|||
* - after start operation Execute (FwUpdate): fw_state = UPDATING |
|||
* - after success finished operation Execute (FwUpdate) Update Result == 1 ("Firmware updated successfully") |
|||
* - finished operation Execute (FwUpdate) |
|||
*/ |
|||
public void updateStateOta(DefaultLwM2MTransportMsgHandler handler, LwM2mTransportRequest request, |
|||
Registration registration, String path, int value) { |
|||
if (OBJ_5_BINARY.code == this.getUpdateStrategy()) { |
|||
if ((convertPathFromObjectIdToIdVer(FW_RESULT_ID, registration).equals(path))) { |
|||
if (DOWNLOADED.name().equals(this.getStateUpdate()) |
|||
&& this.conditionalFwExecuteStart()) { |
|||
this.executeFwSwWare(handler, request); |
|||
} else if (UPDATING.name().equals(this.getStateUpdate()) |
|||
&& this.conditionalFwExecuteAfterSuccess()) { |
|||
this.finishFwSwUpdate(handler, true); |
|||
} else if (UPDATING.name().equals(this.getStateUpdate()) |
|||
&& this.conditionalFwExecuteAfterError()) { |
|||
this.finishFwSwUpdate(handler, false); |
|||
} |
|||
} |
|||
} else if (OBJ_5_TEMP_URL.code == this.getUpdateStrategy()) { |
|||
if (this.currentId != null && (convertPathFromObjectIdToIdVer(FW_STATE_ID, registration).equals(path))) { |
|||
String state = equalsFwSateToFirmwareUpdateStatus(LwM2mTransportUtil.StateFw.fromStateFwByCode(value)).name(); |
|||
if (StringUtils.isNotEmpty(state) && !FAILED.name().equals(this.stateUpdate) && !state.equals(this.stateUpdate)) { |
|||
this.stateUpdate = state; |
|||
this.sendSateOnThingsBoard(handler); |
|||
} |
|||
if (value == LwM2mTransportUtil.StateFw.DOWNLOADED.code) { |
|||
this.executeFwSwWare(handler, request); |
|||
} |
|||
handler.firmwareUpdateState.put(lwM2MClient.getEndpoint(), value); |
|||
} |
|||
if ((convertPathFromObjectIdToIdVer(FW_RESULT_ID, registration).equals(path))) { |
|||
if (this.currentId != null && value == LwM2mTransportUtil.UpdateResultFw.INITIAL.code) { |
|||
this.setStateUpdate(INITIATED.name()); |
|||
} else if (this.currentId != null && value == LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code) { |
|||
this.setStateUpdate(UPDATED.name()); |
|||
} else if (value > LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code) { |
|||
this.setStateUpdate(FAILED.name()); |
|||
} |
|||
this.sendSateOnThingsBoard(handler); |
|||
} |
|||
} else if (OBJ_19_BINARY.code == this.getUpdateStrategy()) { |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.common; |
|||
|
|||
import org.thingsboard.common.util.ThingsBoardExecutors; |
|||
|
|||
import javax.annotation.PreDestroy; |
|||
import java.util.concurrent.ExecutorService; |
|||
|
|||
public abstract class LwM2MExecutorAwareService { |
|||
|
|||
protected ExecutorService executor; |
|||
|
|||
protected abstract int getExecutorSize(); |
|||
|
|||
protected abstract String getExecutorName(); |
|||
|
|||
protected void init() { |
|||
this.executor = ThingsBoardExecutors.newWorkStealingPool(getExecutorSize(), getExecutorName()); |
|||
} |
|||
|
|||
public void destroy() { |
|||
if (executor != null) { |
|||
executor.shutdownNow(); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|||
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; |
|||
|
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_WARN; |
|||
|
|||
@Slf4j |
|||
public abstract class AbstractTbLwM2MRequestCallback<R, T> implements DownlinkRequestCallback<R, T> { |
|||
|
|||
protected final LwM2MTelemetryLogService logService; |
|||
protected final LwM2mClient client; |
|||
|
|||
protected AbstractTbLwM2MRequestCallback(LwM2MTelemetryLogService logService, LwM2mClient client) { |
|||
this.logService = logService; |
|||
this.client = client; |
|||
} |
|||
|
|||
@Override |
|||
public void onValidationError(String params, String msg) { |
|||
log.trace("[{}] Request [{}] validation failed. Reason: {}", client.getEndpoint(), params, msg); |
|||
logService.log(client, String.format("[%s]: Request [%s] validation failed. Reason: %s", LOG_LWM2M_WARN, params, msg)); |
|||
} |
|||
|
|||
@Override |
|||
public void onError(String params, Exception e) { |
|||
log.trace("[{}] Request [{}] processing failed", client.getEndpoint(), params, e); |
|||
logService.log(client, String.format("[%s]: Request [%s] processing failed. Reason: %s", LOG_LWM2M_WARN, params, e)); |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import lombok.Getter; |
|||
|
|||
public abstract class AbstractTbLwM2MTargetedDownlinkRequest<T> implements TbLwM2MDownlinkRequest<T>, HasVersionedId { |
|||
|
|||
@Getter |
|||
private final String versionedId; |
|||
@Getter |
|||
private final long timeout; |
|||
|
|||
public AbstractTbLwM2MTargetedDownlinkRequest(String versionedId, long timeout) { |
|||
this.versionedId = versionedId; |
|||
this.timeout = timeout; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,353 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.eclipse.leshan.core.Link; |
|||
import org.eclipse.leshan.core.attributes.Attribute; |
|||
import org.eclipse.leshan.core.attributes.AttributeSet; |
|||
import org.eclipse.leshan.core.model.ResourceModel; |
|||
import org.eclipse.leshan.core.node.LwM2mPath; |
|||
import org.eclipse.leshan.core.node.LwM2mResource; |
|||
import org.eclipse.leshan.core.node.ObjectLink; |
|||
import org.eclipse.leshan.core.node.codec.CodecException; |
|||
import org.eclipse.leshan.core.observation.Observation; |
|||
import org.eclipse.leshan.core.request.ContentFormat; |
|||
import org.eclipse.leshan.core.request.DeleteRequest; |
|||
import org.eclipse.leshan.core.request.DiscoverRequest; |
|||
import org.eclipse.leshan.core.request.ExecuteRequest; |
|||
import org.eclipse.leshan.core.request.ObserveRequest; |
|||
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.WriteRequest; |
|||
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.ObserveResponse; |
|||
import org.eclipse.leshan.core.response.ReadResponse; |
|||
import org.eclipse.leshan.core.response.WriteAttributesResponse; |
|||
import org.eclipse.leshan.core.response.WriteResponse; |
|||
import org.eclipse.leshan.core.util.Hex; |
|||
import org.eclipse.leshan.server.registration.Registration; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; |
|||
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; |
|||
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|||
import org.thingsboard.server.transport.lwm2m.server.common.LwM2MExecutorAwareService; |
|||
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; |
|||
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.PreDestroy; |
|||
import java.util.Arrays; |
|||
import java.util.Collection; |
|||
import java.util.Date; |
|||
import java.util.LinkedList; |
|||
import java.util.List; |
|||
import java.util.Set; |
|||
import java.util.function.Function; |
|||
import java.util.function.Predicate; |
|||
import java.util.stream.Collectors; |
|||
|
|||
import static org.eclipse.leshan.core.attributes.Attribute.GREATER_THAN; |
|||
import static org.eclipse.leshan.core.attributes.Attribute.LESSER_THAN; |
|||
import static org.eclipse.leshan.core.attributes.Attribute.MAXIMUM_PERIOD; |
|||
import static org.eclipse.leshan.core.attributes.Attribute.MINIMUM_PERIOD; |
|||
import static org.eclipse.leshan.core.attributes.Attribute.STEP; |
|||
|
|||
@Slf4j |
|||
@Service |
|||
@TbLwM2mTransportComponent |
|||
@RequiredArgsConstructor |
|||
public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService implements LwM2mDownlinkMsgHandler { |
|||
|
|||
public LwM2mValueConverterImpl converter; |
|||
|
|||
private final LwM2mTransportContext context; |
|||
private final LwM2MTransportServerConfig config; |
|||
private final LwM2MTelemetryLogService logService; |
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
super.init(); |
|||
this.converter = LwM2mValueConverterImpl.getInstance(); |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void destroy() { |
|||
super.destroy(); |
|||
} |
|||
|
|||
@Override |
|||
protected int getExecutorSize() { |
|||
return config.getDownlinkPoolSize(); |
|||
} |
|||
|
|||
@Override |
|||
protected String getExecutorName() { |
|||
return "LwM2M Downlink"; |
|||
} |
|||
|
|||
@Override |
|||
public void sendReadRequest(LwM2mClient client, TbLwM2MReadRequest request, DownlinkRequestCallback<ReadRequest, ReadResponse> callback) { |
|||
validateVersionedId(client, request); |
|||
ReadRequest downlink = new ReadRequest(getContentFormat(client, request), request.getObjectId()); |
|||
sendRequest(client, downlink, request.getTimeout(), callback); |
|||
} |
|||
|
|||
@Override |
|||
public void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback<ObserveRequest, ObserveResponse> callback) { |
|||
validateVersionedId(client, request); |
|||
LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); |
|||
Set<Observation> observations = context.getServer().getObservationService().getObservations(client.getRegistration()); |
|||
if (observations.stream().noneMatch(observation -> observation.getPath().equals(resultIds))) { |
|||
ObserveRequest downlink; |
|||
ContentFormat contentFormat = getContentFormat(client, request); |
|||
if (resultIds.isResource()) { |
|||
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId()); |
|||
} else if (resultIds.isObjectInstance()) { |
|||
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId()); |
|||
} else { |
|||
downlink = new ObserveRequest(contentFormat, resultIds.getObjectId()); |
|||
} |
|||
log.info("[{}] Send observation: {}.", client.getEndpoint(), request.getVersionedId()); |
|||
sendRequest(client, downlink, request.getTimeout(), callback); |
|||
} else { |
|||
throw new IllegalArgumentException("Observation is already registered!"); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void sendObserveAllRequest(LwM2mClient client, TbLwM2MObserveAllRequest request, DownlinkRequestCallback<TbLwM2MObserveAllRequest, Set<String>> callback) { |
|||
Set<Observation> observations = context.getServer().getObservationService().getObservations(client.getRegistration()); |
|||
Set<String> paths = observations.stream().map(observation -> observation.getPath().toString()).collect(Collectors.toUnmodifiableSet()); |
|||
callback.onSuccess(request, paths); |
|||
} |
|||
|
|||
@Override |
|||
public void sendDiscoverAllRequest(LwM2mClient client, TbLwM2MDiscoverAllRequest request, DownlinkRequestCallback<TbLwM2MDiscoverAllRequest, List<Link>> callback) { |
|||
callback.onSuccess(request, Arrays.asList(client.getRegistration().getSortedObjectLinks())); |
|||
} |
|||
|
|||
@Override |
|||
public void sendExecuteRequest(LwM2mClient client, TbLwM2MExecuteRequest request, DownlinkRequestCallback<ExecuteRequest, ExecuteResponse> callback) { |
|||
ResourceModel resourceModelExecute = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); |
|||
if (resourceModelExecute != null) { |
|||
ExecuteRequest downlink; |
|||
if (request.getParams() != null && !resourceModelExecute.multiple) { |
|||
downlink = new ExecuteRequest(request.getVersionedId(), (String) this.converter.convertValue(request.getParams(), resourceModelExecute.type, ResourceModel.Type.STRING, new LwM2mPath(request.getObjectId()))); |
|||
} else { |
|||
downlink = new ExecuteRequest(request.getVersionedId()); |
|||
} |
|||
sendRequest(client, downlink, request.getTimeout(), callback); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void sendDeleteRequest(LwM2mClient client, TbLwM2MDeleteRequest request, DownlinkRequestCallback<DeleteRequest, DeleteResponse> callback) { |
|||
sendRequest(client, new DeleteRequest(request.getObjectId()), request.getTimeout(), callback); |
|||
} |
|||
|
|||
@Override |
|||
public void sendCancelObserveRequest(LwM2mClient client, TbLwM2MCancelObserveRequest request, DownlinkRequestCallback<TbLwM2MCancelObserveRequest, Integer> callback) { |
|||
int observeCancelCnt = context.getServer().getObservationService().cancelObservations(client.getRegistration(), request.getObjectId()); |
|||
callback.onSuccess(request, observeCancelCnt); |
|||
} |
|||
|
|||
@Override |
|||
public void sendCancelAllRequest(LwM2mClient client, TbLwM2MCancelAllRequest request, DownlinkRequestCallback<TbLwM2MCancelAllRequest, Integer> callback) { |
|||
int observeCancelCnt = context.getServer().getObservationService().cancelObservations(client.getRegistration()); |
|||
callback.onSuccess(request, observeCancelCnt); |
|||
} |
|||
|
|||
@Override |
|||
public void sendDiscoverRequest(LwM2mClient client, TbLwM2MDiscoverRequest request, DownlinkRequestCallback<DiscoverRequest, DiscoverResponse> callback) { |
|||
validateVersionedId(client, request); |
|||
sendRequest(client, new DiscoverRequest(request.getObjectId()), request.getTimeout(), callback); |
|||
} |
|||
|
|||
@Override |
|||
public void sendWriteAttributesRequest(LwM2mClient client, TbLwM2MWriteAttributesRequest request, DownlinkRequestCallback<WriteAttributesRequest, WriteAttributesResponse> callback) { |
|||
validateVersionedId(client, request); |
|||
if (request.getAttributes() == null) { |
|||
throw new IllegalArgumentException("Attributes to write are not specified!"); |
|||
} |
|||
ObjectAttributes params = request.getAttributes(); |
|||
List<Attribute> attributes = new LinkedList<>(); |
|||
// Dimension and Object version are read only attributes.
|
|||
// addAttribute(attributes, DIMENSION, params.getDim(), dim -> dim >= 0 && dim <= 255);
|
|||
// addAttribute(attributes, OBJECT_VERSION, params.getVer(), StringUtils::isNotEmpty, Function.identity());
|
|||
addAttribute(attributes, MAXIMUM_PERIOD, params.getPmax()); |
|||
addAttribute(attributes, MINIMUM_PERIOD, params.getPmin()); |
|||
addAttribute(attributes, GREATER_THAN, params.getGt()); |
|||
addAttribute(attributes, LESSER_THAN, params.getLt()); |
|||
addAttribute(attributes, STEP, params.getSt()); |
|||
AttributeSet attributeSet = new AttributeSet(attributes); |
|||
sendRequest(client, new WriteAttributesRequest(request.getObjectId(), attributeSet), request.getTimeout(), callback); |
|||
} |
|||
|
|||
@Override |
|||
public void sendWriteReplaceRequest(LwM2mClient client, TbLwM2MWriteReplaceRequest request, DownlinkRequestCallback<WriteRequest, WriteResponse> callback) { |
|||
ResourceModel resourceModelWrite = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); |
|||
if (resourceModelWrite != null) { |
|||
ContentFormat contentFormat = convertResourceModelTypeToContentFormat(client, resourceModelWrite.type); |
|||
try { |
|||
LwM2mPath path = new LwM2mPath(request.getObjectId()); |
|||
WriteRequest downlink = this.getWriteRequestSingleResource(resourceModelWrite.type, contentFormat, |
|||
path.getObjectId(), path.getObjectInstanceId(), path.getResourceId(), request.getValue()); |
|||
sendRequest(client, downlink, request.getTimeout(), callback); |
|||
} catch (Exception e) { |
|||
callback.onError(JacksonUtil.toString(request), e); |
|||
} |
|||
} else { |
|||
callback.onValidationError(JacksonUtil.toString(request), "Resource " + request.getVersionedId() + " is not configured in the device profile!"); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void sendWriteUpdateRequest(LwM2mClient client, TbLwM2MWriteUpdateRequest request, DownlinkRequestCallback<WriteRequest, WriteResponse> callback) { |
|||
LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); |
|||
if (resultIds.isResource()) { |
|||
/* |
|||
* send request: path = '/3/0' node == wM2mObjectInstance |
|||
* with params == "\"resources\": {15: resource:{id:15. value:'+01'...}} |
|||
**/ |
|||
Collection<LwM2mResource> resources = client.getNewResourceForInstance(request.getVersionedId(), request.getValue(), this.config.getModelProvider(), this.converter); |
|||
ResourceModel resourceModelWrite = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); |
|||
ContentFormat contentFormat = request.getObjectContentFormat() != null ? request.getObjectContentFormat() : convertResourceModelTypeToContentFormat(client, resourceModelWrite.type); |
|||
WriteRequest downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), |
|||
resultIds.getObjectInstanceId(), resources); |
|||
sendRequest(client, downlink, request.getTimeout(), callback); |
|||
} else if (resultIds.isObjectInstance()) { |
|||
/* |
|||
* params = "{\"id\":0,\"resources\":[{\"id\":14,\"value\":\"+5\"},{\"id\":15,\"value\":\"+9\"}]}" |
|||
* int rscId = resultIds.getObjectInstanceId(); |
|||
* contentFormat – Format of the payload (TLV or JSON). |
|||
*/ |
|||
Collection<LwM2mResource> resources = client.getNewResourcesForInstance(request.getVersionedId(), request.getValue(), this.config.getModelProvider(), this.converter); |
|||
if (resources.size() > 0) { |
|||
ContentFormat contentFormat = request.getObjectContentFormat() != null ? request.getObjectContentFormat() : client.getDefaultContentFormat(); |
|||
WriteRequest downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resources); |
|||
sendRequest(client, downlink, request.getTimeout(), callback); |
|||
} else { |
|||
callback.onValidationError(JacksonUtil.toString(request), "No resources to update!"); |
|||
} |
|||
} else { |
|||
callback.onValidationError(JacksonUtil.toString(request), "Update of the root level object is not supported yet!"); |
|||
} |
|||
} |
|||
|
|||
private <R extends SimpleDownlinkRequest<T>, T extends LwM2mResponse> void sendRequest(LwM2mClient client, R request, long timeoutInMs, DownlinkRequestCallback<R, T> callback) { |
|||
Registration registration = client.getRegistration(); |
|||
try { |
|||
logService.log(client, String.format("[%s][%s] Sending request: %s to %s", registration.getId(), registration.getSocketAddress(), request.getClass().getSimpleName(), request.getPath())); |
|||
context.getServer().send(registration, request, timeoutInMs, response -> { |
|||
executor.submit(() -> { |
|||
try { |
|||
callback.onSuccess(request, response); |
|||
} catch (Exception e) { |
|||
log.error("[{}] failed to process successful response [{}] ", registration.getEndpoint(), response, e); |
|||
} |
|||
}); |
|||
}, e -> { |
|||
executor.submit(() -> { |
|||
callback.onError(JacksonUtil.toString(request), e); |
|||
}); |
|||
}); |
|||
} catch (Exception e) { |
|||
callback.onError(JacksonUtil.toString(request), e); |
|||
} |
|||
} |
|||
|
|||
private WriteRequest getWriteRequestSingleResource(ResourceModel.Type type, ContentFormat contentFormat, int objectId, int instanceId, int resourceId, Object value) { |
|||
switch (type) { |
|||
case STRING: // String
|
|||
return new WriteRequest(contentFormat, objectId, instanceId, resourceId, value.toString()); |
|||
case INTEGER: // Long
|
|||
final long valueInt = Integer.toUnsignedLong(Integer.parseInt(value.toString())); |
|||
return new WriteRequest(contentFormat, objectId, instanceId, resourceId, valueInt); |
|||
case OBJLNK: // ObjectLink
|
|||
return new WriteRequest(contentFormat, objectId, instanceId, resourceId, ObjectLink.fromPath(value.toString())); |
|||
case BOOLEAN: // Boolean
|
|||
return new WriteRequest(contentFormat, objectId, instanceId, resourceId, Boolean.parseBoolean(value.toString())); |
|||
case FLOAT: // Double
|
|||
return new WriteRequest(contentFormat, objectId, instanceId, resourceId, Double.parseDouble(value.toString())); |
|||
case TIME: // Date
|
|||
Date date = new Date(Long.decode(value.toString())); |
|||
return new WriteRequest(contentFormat, objectId, instanceId, resourceId, date); |
|||
case OPAQUE: // byte[] value, base64
|
|||
byte[] valueRequest; |
|||
if (value instanceof byte[]) { |
|||
valueRequest = (byte[]) value; |
|||
} else { |
|||
valueRequest = Hex.decodeHex(value.toString().toCharArray()); |
|||
} |
|||
return new WriteRequest(contentFormat, objectId, instanceId, resourceId, valueRequest); |
|||
default: |
|||
throw new IllegalArgumentException("Not supported type:" + type.name()); |
|||
} |
|||
} |
|||
|
|||
private void validateVersionedId(LwM2mClient client, HasVersionedId request) { |
|||
if (!client.isValidObjectVersion(request.getVersionedId())) { |
|||
throw new IllegalArgumentException("Specified resource id is not configured in the device profile!"); |
|||
} |
|||
if (request.getObjectId() == null) { |
|||
throw new IllegalArgumentException("Specified object id is null!"); |
|||
} |
|||
} |
|||
|
|||
private static <T> void addAttribute(List<Attribute> attributes, String attributeName, T value) { |
|||
addAttribute(attributes, attributeName, value, null, null); |
|||
} |
|||
|
|||
private static <T> void addAttribute(List<Attribute> attributes, String attributeName, T value, Function<T, ?> converter) { |
|||
addAttribute(attributes, attributeName, value, null, converter); |
|||
} |
|||
|
|||
private static <T> void addAttribute(List<Attribute> attributes, String attributeName, T value, Predicate<T> filter, Function<T, ?> converter) { |
|||
if (value != null && ((filter == null) || filter.test(value))) { |
|||
attributes.add(new Attribute(attributeName, converter != null ? converter.apply(value) : value)); |
|||
} |
|||
} |
|||
|
|||
private static ContentFormat convertResourceModelTypeToContentFormat(LwM2mClient client, ResourceModel.Type type) { |
|||
switch (type) { |
|||
case BOOLEAN: |
|||
case STRING: |
|||
case TIME: |
|||
case INTEGER: |
|||
case FLOAT: |
|||
return client.getDefaultContentFormat(); |
|||
case OPAQUE: |
|||
return ContentFormat.OPAQUE; |
|||
case OBJLNK: |
|||
return ContentFormat.LINK; |
|||
default: |
|||
} |
|||
throw new CodecException("Invalid ResourceModel_Type for %s ContentFormat.", type); |
|||
} |
|||
|
|||
private static ContentFormat getContentFormat(LwM2mClient client, HasContentFormat request) { |
|||
return request.getContentFormat() != null ? request.getContentFormat() : client.getDefaultContentFormat(); |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
public interface DownlinkRequestCallback<R, T> { |
|||
|
|||
void onSuccess(R request, T response); |
|||
|
|||
void onValidationError(String params, String msg); |
|||
|
|||
void onError(String params, Exception e); |
|||
|
|||
static <R, T> DownlinkRequestCallback<R, T> doNothing() { |
|||
return new DownlinkRequestCallback<>() { |
|||
|
|||
@Override |
|||
public void onSuccess(R request, T response) { |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public void onValidationError(String params, String msg) { |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public void onError(String params, Exception e) { |
|||
|
|||
} |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import org.eclipse.leshan.core.request.ContentFormat; |
|||
|
|||
public interface HasContentFormat { |
|||
|
|||
ContentFormat getContentFormat(); |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; |
|||
|
|||
public interface HasVersionedId { |
|||
|
|||
String getVersionedId(); |
|||
|
|||
default String getObjectId(){ |
|||
return LwM2mTransportUtil.fromVersionedIdToObjectId(getVersionedId()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import org.eclipse.leshan.core.Link; |
|||
import org.eclipse.leshan.core.request.DeleteRequest; |
|||
import org.eclipse.leshan.core.request.DiscoverRequest; |
|||
import org.eclipse.leshan.core.request.ExecuteRequest; |
|||
import org.eclipse.leshan.core.request.ObserveRequest; |
|||
import org.eclipse.leshan.core.request.ReadRequest; |
|||
import org.eclipse.leshan.core.request.WriteAttributesRequest; |
|||
import org.eclipse.leshan.core.request.WriteRequest; |
|||
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.ObserveResponse; |
|||
import org.eclipse.leshan.core.response.ReadResponse; |
|||
import org.eclipse.leshan.core.response.WriteAttributesResponse; |
|||
import org.eclipse.leshan.core.response.WriteResponse; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|||
|
|||
import java.util.List; |
|||
import java.util.Set; |
|||
|
|||
public interface LwM2mDownlinkMsgHandler { |
|||
|
|||
void sendReadRequest(LwM2mClient client, TbLwM2MReadRequest request, DownlinkRequestCallback<ReadRequest, ReadResponse> callback); |
|||
|
|||
void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback<ObserveRequest, ObserveResponse> callback); |
|||
|
|||
void sendObserveAllRequest(LwM2mClient client, TbLwM2MObserveAllRequest request, DownlinkRequestCallback<TbLwM2MObserveAllRequest, Set<String>> callback); |
|||
|
|||
void sendExecuteRequest(LwM2mClient client, TbLwM2MExecuteRequest request, DownlinkRequestCallback<ExecuteRequest, ExecuteResponse> callback); |
|||
|
|||
void sendDeleteRequest(LwM2mClient client, TbLwM2MDeleteRequest request, DownlinkRequestCallback<DeleteRequest, DeleteResponse> callback); |
|||
|
|||
void sendCancelObserveRequest(LwM2mClient client, TbLwM2MCancelObserveRequest request, DownlinkRequestCallback<TbLwM2MCancelObserveRequest, Integer> callback); |
|||
|
|||
void sendCancelAllRequest(LwM2mClient client, TbLwM2MCancelAllRequest request, DownlinkRequestCallback<TbLwM2MCancelAllRequest, Integer> callback); |
|||
|
|||
void sendDiscoverRequest(LwM2mClient client, TbLwM2MDiscoverRequest request, DownlinkRequestCallback<DiscoverRequest, DiscoverResponse> callback); |
|||
|
|||
void sendDiscoverAllRequest(LwM2mClient client, TbLwM2MDiscoverAllRequest request, DownlinkRequestCallback<TbLwM2MDiscoverAllRequest, List<Link>> callback); |
|||
|
|||
void sendWriteAttributesRequest(LwM2mClient client, TbLwM2MWriteAttributesRequest request, DownlinkRequestCallback<WriteAttributesRequest, WriteAttributesResponse> callback); |
|||
|
|||
void sendWriteReplaceRequest(LwM2mClient client, TbLwM2MWriteReplaceRequest request, DownlinkRequestCallback<WriteRequest, WriteResponse> callback); |
|||
|
|||
void sendWriteUpdateRequest(LwM2mClient client, TbLwM2MWriteUpdateRequest request, DownlinkRequestCallback<WriteRequest, WriteResponse> callback); |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|||
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; |
|||
|
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; |
|||
|
|||
@Slf4j |
|||
public class TbLwM2MCancelAllObserveCallback extends AbstractTbLwM2MRequestCallback<TbLwM2MCancelAllRequest, Integer> { |
|||
|
|||
public TbLwM2MCancelAllObserveCallback(LwM2MTelemetryLogService logService, LwM2mClient client) { |
|||
super(logService, client); |
|||
} |
|||
|
|||
@Override |
|||
public void onSuccess(TbLwM2MCancelAllRequest request, Integer canceledSubscriptionsCount) { |
|||
log.trace("[{}] Cancel of all observations was successful: {}", client.getEndpoint(), canceledSubscriptionsCount); |
|||
logService.log(client, String.format("[%s]: Cancel of all observations was successful. Result: [%s]", LOG_LWM2M_INFO, canceledSubscriptionsCount)); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import lombok.Builder; |
|||
import lombok.Getter; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; |
|||
|
|||
public class TbLwM2MCancelAllRequest implements TbLwM2MDownlinkRequest<Integer> { |
|||
|
|||
@Getter |
|||
private final long timeout; |
|||
|
|||
@Builder |
|||
private TbLwM2MCancelAllRequest(long timeout) { |
|||
this.timeout = timeout; |
|||
} |
|||
|
|||
@Override |
|||
public LwM2mOperationType getType() { |
|||
return LwM2mOperationType.OBSERVE_CANCEL_ALL; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; |
|||
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|||
|
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.OBSERVE_CANCEL; |
|||
|
|||
@Slf4j |
|||
public class TbLwM2MCancelObserveCallback extends AbstractTbLwM2MRequestCallback<TbLwM2MCancelObserveRequest, Integer> { |
|||
|
|||
private final String versionedId; |
|||
|
|||
public TbLwM2MCancelObserveCallback(LwM2MTelemetryLogService logService, LwM2mClient client, String versionedId) { |
|||
super(logService, client); |
|||
this.versionedId = versionedId; |
|||
} |
|||
|
|||
@Override |
|||
public void onSuccess(TbLwM2MCancelObserveRequest request, Integer canceledSubscriptionsCount) { |
|||
log.trace("[{}] Cancel observation of [{}] successful: {}", client.getEndpoint(), versionedId, canceledSubscriptionsCount); |
|||
logService.log(client, String.format("[%s]: Cancel Observe for [%s] successful. Result: [%s]", LOG_LWM2M_INFO, versionedId, canceledSubscriptionsCount)); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import lombok.Builder; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; |
|||
|
|||
public class TbLwM2MCancelObserveRequest extends AbstractTbLwM2MTargetedDownlinkRequest<Integer> { |
|||
|
|||
@Builder |
|||
private TbLwM2MCancelObserveRequest(String versionedId, long timeout) { |
|||
super(versionedId, timeout); |
|||
} |
|||
|
|||
@Override |
|||
public LwM2mOperationType getType() { |
|||
return LwM2mOperationType.OBSERVE_CANCEL; |
|||
} |
|||
|
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import org.eclipse.leshan.core.request.DeleteRequest; |
|||
import org.eclipse.leshan.core.response.DeleteResponse; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|||
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; |
|||
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; |
|||
|
|||
public class TbLwM2MDeleteCallback extends TbLwM2MTargetedCallback<DeleteRequest, DeleteResponse> { |
|||
|
|||
public TbLwM2MDeleteCallback(LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) { |
|||
super(logService, client, targetId); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import lombok.Builder; |
|||
import org.eclipse.leshan.core.response.ReadResponse; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; |
|||
|
|||
public class TbLwM2MDeleteRequest extends AbstractTbLwM2MTargetedDownlinkRequest<ReadResponse> { |
|||
|
|||
@Builder |
|||
private TbLwM2MDeleteRequest(String versionedId, long timeout) { |
|||
super(versionedId, timeout); |
|||
} |
|||
|
|||
@Override |
|||
public LwM2mOperationType getType() { |
|||
return LwM2mOperationType.DELETE; |
|||
} |
|||
|
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import lombok.Builder; |
|||
import lombok.Getter; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; |
|||
|
|||
public class TbLwM2MDiscoverAllRequest implements TbLwM2MDownlinkRequest<String> { |
|||
|
|||
@Getter |
|||
private final long timeout; |
|||
|
|||
@Builder |
|||
private TbLwM2MDiscoverAllRequest(long timeout) { |
|||
this.timeout = timeout; |
|||
} |
|||
|
|||
@Override |
|||
public LwM2mOperationType getType() { |
|||
return LwM2mOperationType.DISCOVER_ALL; |
|||
} |
|||
|
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import org.eclipse.leshan.core.request.DiscoverRequest; |
|||
import org.eclipse.leshan.core.response.DiscoverResponse; |
|||
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; |
|||
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|||
|
|||
public class TbLwM2MDiscoverCallback extends TbLwM2MTargetedCallback<DiscoverRequest, DiscoverResponse> { |
|||
|
|||
public TbLwM2MDiscoverCallback(LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) { |
|||
super(logService, client, targetId); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import lombok.Builder; |
|||
import org.eclipse.leshan.core.response.DiscoverResponse; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; |
|||
|
|||
public class TbLwM2MDiscoverRequest extends AbstractTbLwM2MTargetedDownlinkRequest<DiscoverResponse> { |
|||
|
|||
@Builder |
|||
private TbLwM2MDiscoverRequest(String versionedId, long timeout) { |
|||
super(versionedId, timeout); |
|||
} |
|||
|
|||
@Override |
|||
public LwM2mOperationType getType() { |
|||
return LwM2mOperationType.DISCOVER; |
|||
} |
|||
|
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; |
|||
|
|||
public interface TbLwM2MDownlinkRequest<T> { |
|||
|
|||
LwM2mOperationType getType(); |
|||
|
|||
long getTimeout(); |
|||
|
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import org.eclipse.leshan.core.request.ExecuteRequest; |
|||
import org.eclipse.leshan.core.response.ExecuteResponse; |
|||
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; |
|||
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|||
|
|||
public class TbLwM2MExecuteCallback extends TbLwM2MTargetedCallback<ExecuteRequest, ExecuteResponse> { |
|||
|
|||
public TbLwM2MExecuteCallback(LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) { |
|||
super(logService, client, targetId); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import lombok.Builder; |
|||
import lombok.Getter; |
|||
import org.eclipse.leshan.core.response.ReadResponse; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; |
|||
|
|||
public class TbLwM2MExecuteRequest extends AbstractTbLwM2MTargetedDownlinkRequest<ReadResponse> { |
|||
|
|||
@Getter |
|||
private final Object params; |
|||
|
|||
@Builder |
|||
private TbLwM2MExecuteRequest(String versionedId, long timeout, Object params) { |
|||
super(versionedId, timeout); |
|||
this.params = params; |
|||
} |
|||
|
|||
@Override |
|||
public LwM2mOperationType getType() { |
|||
return LwM2mOperationType.EXECUTE; |
|||
} |
|||
|
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
|
|||
import java.util.concurrent.CountDownLatch; |
|||
|
|||
@RequiredArgsConstructor |
|||
public class TbLwM2MLatchCallback<R, T> implements DownlinkRequestCallback<R, T> { |
|||
|
|||
private final CountDownLatch countDownLatch; |
|||
private final DownlinkRequestCallback<R, T> callback; |
|||
|
|||
@Override |
|||
public void onSuccess(R request, T response) { |
|||
callback.onSuccess(request, response); |
|||
countDownLatch.countDown(); |
|||
} |
|||
|
|||
@Override |
|||
public void onValidationError(String params, String msg) { |
|||
callback.onValidationError(params, msg); |
|||
countDownLatch.countDown(); |
|||
} |
|||
|
|||
@Override |
|||
public void onError(String params, Exception e) { |
|||
callback.onError(params, e); |
|||
countDownLatch.countDown(); |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import lombok.Builder; |
|||
import lombok.Getter; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; |
|||
|
|||
import java.util.Set; |
|||
|
|||
public class TbLwM2MObserveAllRequest implements TbLwM2MDownlinkRequest<Set<String>> { |
|||
|
|||
@Getter |
|||
private final long timeout; |
|||
|
|||
@Builder |
|||
private TbLwM2MObserveAllRequest(long timeout) { |
|||
this.timeout = timeout; |
|||
} |
|||
|
|||
@Override |
|||
public LwM2mOperationType getType() { |
|||
return LwM2mOperationType.OBSERVE_READ_ALL; |
|||
} |
|||
|
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.eclipse.leshan.core.request.ObserveRequest; |
|||
import org.eclipse.leshan.core.response.ObserveResponse; |
|||
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; |
|||
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|||
|
|||
@Slf4j |
|||
public class TbLwM2MObserveCallback extends TbLwM2MUplinkTargetedCallback<ObserveRequest, ObserveResponse> { |
|||
|
|||
public TbLwM2MObserveCallback(LwM2mUplinkMsgHandler handler, LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) { |
|||
super(handler, logService, client, targetId); |
|||
} |
|||
|
|||
@Override |
|||
public void onSuccess(ObserveRequest request, ObserveResponse response) { |
|||
super.onSuccess(request, response); |
|||
handler.onUpdateValueAfterReadResponse(client.getRegistration(), versionedId, response); |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import lombok.Builder; |
|||
import lombok.Getter; |
|||
import org.eclipse.leshan.core.request.ContentFormat; |
|||
import org.eclipse.leshan.core.response.ObserveResponse; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; |
|||
|
|||
public class TbLwM2MObserveRequest extends AbstractTbLwM2MTargetedDownlinkRequest<ObserveResponse> implements HasContentFormat { |
|||
|
|||
@Getter |
|||
private final ContentFormat contentFormat; |
|||
|
|||
@Builder |
|||
private TbLwM2MObserveRequest(String versionedId, long timeout, ContentFormat contentFormat) { |
|||
super(versionedId, timeout); |
|||
this.contentFormat = contentFormat; |
|||
} |
|||
|
|||
@Override |
|||
public LwM2mOperationType getType() { |
|||
return LwM2mOperationType.OBSERVE; |
|||
} |
|||
|
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.eclipse.leshan.core.request.ReadRequest; |
|||
import org.eclipse.leshan.core.response.ReadResponse; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|||
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; |
|||
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; |
|||
|
|||
@Slf4j |
|||
public class TbLwM2MReadCallback extends TbLwM2MUplinkTargetedCallback<ReadRequest, ReadResponse> { |
|||
|
|||
public TbLwM2MReadCallback(LwM2mUplinkMsgHandler handler, LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) { |
|||
super(handler, logService, client, targetId); |
|||
} |
|||
|
|||
@Override |
|||
public void onSuccess(ReadRequest request, ReadResponse response) { |
|||
super.onSuccess(request, response); |
|||
handler.onUpdateValueAfterReadResponse(client.getRegistration(), versionedId, response); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import lombok.Builder; |
|||
import lombok.Getter; |
|||
import org.eclipse.leshan.core.request.ContentFormat; |
|||
import org.eclipse.leshan.core.response.ReadResponse; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; |
|||
|
|||
public class TbLwM2MReadRequest extends AbstractTbLwM2MTargetedDownlinkRequest<ReadResponse> implements HasContentFormat { |
|||
|
|||
@Getter |
|||
private final ContentFormat contentFormat; |
|||
|
|||
@Builder |
|||
private TbLwM2MReadRequest(String versionedId, long timeout, ContentFormat contentFormat) { |
|||
super(versionedId, timeout); |
|||
this.contentFormat = contentFormat; |
|||
} |
|||
|
|||
@Override |
|||
public LwM2mOperationType getType() { |
|||
return LwM2mOperationType.READ; |
|||
} |
|||
|
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|||
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; |
|||
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; |
|||
|
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; |
|||
|
|||
@Slf4j |
|||
public abstract class TbLwM2MTargetedCallback<R, T> extends AbstractTbLwM2MRequestCallback<R, T> { |
|||
|
|||
protected final String versionedId; |
|||
|
|||
public TbLwM2MTargetedCallback(LwM2MTelemetryLogService logService, LwM2mClient client, String versionedId) { |
|||
super(logService, client); |
|||
this.versionedId = versionedId; |
|||
} |
|||
|
|||
@Override |
|||
public void onSuccess(R request, T response) { |
|||
//TODO convert camelCase to "camel case" using .split("(?<!(^|[A-Z]))(?=[A-Z])|(?<!^)(?=[A-Z][a-z])")
|
|||
String requestName = request.getClass().getSimpleName(); |
|||
log.trace("[{}] {} [{}] successful: {}", client.getEndpoint(), requestName, versionedId, response); |
|||
logService.log(client, String.format("[%s]: %s [%s] successful. Result: [%s]", LOG_LWM2M_INFO, requestName, versionedId, response)); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|||
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; |
|||
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; |
|||
|
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; |
|||
|
|||
@Slf4j |
|||
public abstract class TbLwM2MUplinkTargetedCallback<R, T> extends TbLwM2MTargetedCallback<R, T> { |
|||
|
|||
protected LwM2mUplinkMsgHandler handler; |
|||
|
|||
public TbLwM2MUplinkTargetedCallback(LwM2mUplinkMsgHandler handler, LwM2MTelemetryLogService logService, LwM2mClient client, String versionedId) { |
|||
super(logService, client, versionedId); |
|||
this.handler = handler; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import org.eclipse.leshan.core.request.WriteAttributesRequest; |
|||
import org.eclipse.leshan.core.response.WriteAttributesResponse; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|||
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; |
|||
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; |
|||
|
|||
public class TbLwM2MWriteAttributesCallback extends TbLwM2MTargetedCallback<WriteAttributesRequest, WriteAttributesResponse> { |
|||
|
|||
public TbLwM2MWriteAttributesCallback(LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) { |
|||
super(logService, client, targetId); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import lombok.Builder; |
|||
import lombok.Getter; |
|||
import org.eclipse.leshan.core.response.WriteAttributesResponse; |
|||
import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; |
|||
|
|||
public class TbLwM2MWriteAttributesRequest extends AbstractTbLwM2MTargetedDownlinkRequest<WriteAttributesResponse> { |
|||
|
|||
@Getter |
|||
private final ObjectAttributes attributes; |
|||
|
|||
@Builder |
|||
private TbLwM2MWriteAttributesRequest(String versionedId, long timeout, ObjectAttributes attributes) { |
|||
super(versionedId, timeout); |
|||
this.attributes = attributes; |
|||
} |
|||
|
|||
@Override |
|||
public LwM2mOperationType getType() { |
|||
return LwM2mOperationType.WRITE_ATTRIBUTES; |
|||
} |
|||
|
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import lombok.Builder; |
|||
import lombok.Getter; |
|||
import org.eclipse.leshan.core.request.ContentFormat; |
|||
import org.eclipse.leshan.core.response.WriteResponse; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; |
|||
|
|||
public class TbLwM2MWriteReplaceRequest extends AbstractTbLwM2MTargetedDownlinkRequest<WriteResponse> { |
|||
|
|||
@Getter |
|||
private final ContentFormat contentFormat; |
|||
@Getter |
|||
private final Object value; |
|||
|
|||
@Builder |
|||
private TbLwM2MWriteReplaceRequest(String versionedId, long timeout, ContentFormat contentFormat, Object value) { |
|||
super(versionedId, timeout); |
|||
this.contentFormat = contentFormat; |
|||
this.value = value; |
|||
} |
|||
|
|||
@Override |
|||
public LwM2mOperationType getType() { |
|||
return LwM2mOperationType.WRITE_REPLACE; |
|||
} |
|||
|
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import org.eclipse.leshan.core.request.WriteRequest; |
|||
import org.eclipse.leshan.core.response.WriteResponse; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|||
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; |
|||
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; |
|||
|
|||
public class TbLwM2MWriteResponseCallback extends TbLwM2MUplinkTargetedCallback<WriteRequest, WriteResponse> { |
|||
|
|||
public TbLwM2MWriteResponseCallback(LwM2mUplinkMsgHandler handler, LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) { |
|||
super(handler, logService, client, targetId); |
|||
} |
|||
|
|||
@Override |
|||
public void onSuccess(WriteRequest request, WriteResponse response) { |
|||
super.onSuccess(request, response); |
|||
handler.onWriteResponseOk(client, versionedId, request); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.downlink; |
|||
|
|||
import lombok.Builder; |
|||
import lombok.Getter; |
|||
import org.eclipse.leshan.core.request.ContentFormat; |
|||
import org.eclipse.leshan.core.response.WriteResponse; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; |
|||
|
|||
public class TbLwM2MWriteUpdateRequest extends AbstractTbLwM2MTargetedDownlinkRequest<WriteResponse> { |
|||
|
|||
@Getter |
|||
private final Object value; |
|||
@Getter |
|||
private final ContentFormat objectContentFormat; |
|||
|
|||
@Builder |
|||
private TbLwM2MWriteUpdateRequest(String versionedId, long timeout, Object value, ContentFormat objectContentFormat) { |
|||
super(versionedId, timeout); |
|||
this.value = value; |
|||
this.objectContentFormat = objectContentFormat; |
|||
} |
|||
|
|||
@Override |
|||
public LwM2mOperationType getType() { |
|||
return LwM2mOperationType.WRITE_UPDATE; |
|||
} |
|||
|
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,56 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.log; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; |
|||
|
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY; |
|||
|
|||
@Slf4j |
|||
@Service |
|||
@TbLwM2mTransportComponent |
|||
@RequiredArgsConstructor |
|||
public class DefaultLwM2MTelemetryLogService implements LwM2MTelemetryLogService { |
|||
|
|||
private final LwM2mClientContext clientContext; |
|||
private final LwM2mTransportServerHelper helper; |
|||
|
|||
/** |
|||
* @param logMsg - text msg |
|||
* @param registrationId - Id of Registration LwM2M Client |
|||
*/ |
|||
@Override |
|||
public void log(String registrationId, String logMsg) { |
|||
log(clientContext.getClientByRegistrationId(registrationId), logMsg); |
|||
} |
|||
|
|||
@Override |
|||
public void log(LwM2mClient client, String logMsg) { |
|||
if (logMsg != null && client != null && client.getSession() != null) { |
|||
if (logMsg.length() > 1024) { |
|||
logMsg = logMsg.substring(0, 1024); |
|||
} |
|||
this.helper.sendParametersOnThingsboardTelemetry(this.helper.getKvStringtoThingsboard(LOG_LWM2M_TELEMETRY, logMsg), client.getSession()); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.log; |
|||
|
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|||
|
|||
public interface LwM2MTelemetryLogService { |
|||
|
|||
void log(LwM2mClient client, String msg); |
|||
|
|||
void log(String registrationId, String msg); |
|||
|
|||
} |
|||
@ -0,0 +1,360 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.ota; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.eclipse.leshan.core.request.ContentFormat; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.context.annotation.Lazy; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.common.util.DonAsynchron; |
|||
import org.thingsboard.server.cache.ota.OtaPackageDataCache; |
|||
import org.thingsboard.server.common.data.StringUtils; |
|||
import org.thingsboard.server.common.data.ota.OtaPackageKey; |
|||
import org.thingsboard.server.common.data.ota.OtaPackageType; |
|||
import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus; |
|||
import org.thingsboard.server.common.transport.TransportService; |
|||
import org.thingsboard.server.common.transport.TransportServiceCallback; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; |
|||
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2MFirmwareUpdateStrategy; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; |
|||
import org.thingsboard.server.transport.lwm2m.server.UpdateResultFw; |
|||
import org.thingsboard.server.transport.lwm2m.server.UpdateStateFw; |
|||
import org.thingsboard.server.transport.lwm2m.server.attributes.LwM2MAttributesService; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; |
|||
import org.thingsboard.server.transport.lwm2m.server.common.LwM2MExecutorAwareService; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.LwM2mDownlinkMsgHandler; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteCallback; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteRequest; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback; |
|||
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; |
|||
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.PreDestroy; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Optional; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
|
|||
import static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE; |
|||
import static org.thingsboard.server.common.data.ota.OtaPackageUtil.getAttributeKey; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FIRMWARE_UPDATE_COAP_RECOURSE; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY; |
|||
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId; |
|||
|
|||
@Slf4j |
|||
@Service |
|||
@TbLwM2mTransportComponent |
|||
@RequiredArgsConstructor |
|||
public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService implements LwM2MOtaUpdateService { |
|||
|
|||
public static final String FIRMWARE_VERSION = getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION); |
|||
public static final String FIRMWARE_TITLE = getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.TITLE); |
|||
public static final String FIRMWARE_URL = getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.URL); |
|||
public static final String SOFTWARE_VERSION = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.VERSION); |
|||
public static final String SOFTWARE_TITLE = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.TITLE); |
|||
public static final String SOFTWARE_URL = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.URL); |
|||
|
|||
private static final String FW_PACKAGE_5_ID = "/5/0/0"; |
|||
private static final String FW_URL_ID = "/5/0/1"; |
|||
private static final String FW_EXECUTE_ID = "/5/0/2"; |
|||
private static final String FW_NAME_ID = "/5/0/6"; |
|||
private static final String FW_VER_ID = "/5/0/7"; |
|||
private static final String SW_NAME_ID = "/9/0/0"; |
|||
private static final String SW_VER_ID = "/9/0/1"; |
|||
|
|||
private final Map<String, LwM2MClientOtaInfo> fwStates = new ConcurrentHashMap<>(); |
|||
private final Map<String, LwM2MClientOtaInfo> swStates = new ConcurrentHashMap<>(); |
|||
|
|||
private final TransportService transportService; |
|||
private final LwM2mClientContext clientContext; |
|||
private final LwM2MTransportServerConfig config; |
|||
private final LwM2mUplinkMsgHandler uplinkHandler; |
|||
private final LwM2mDownlinkMsgHandler downlinkHandler; |
|||
private final OtaPackageDataCache otaPackageDataCache; |
|||
private final LwM2MTelemetryLogService logService; |
|||
private final LwM2mTransportServerHelper helper; |
|||
|
|||
@Autowired |
|||
@Lazy |
|||
private LwM2MAttributesService attributesService; |
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
super.init(); |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void destroy() { |
|||
super.destroy(); |
|||
} |
|||
|
|||
@Override |
|||
protected int getExecutorSize() { |
|||
return config.getOtaPoolSize(); |
|||
} |
|||
|
|||
@Override |
|||
protected String getExecutorName() { |
|||
return "LwM2M OTA"; |
|||
} |
|||
|
|||
@Override |
|||
public void init(LwM2mClient client) { |
|||
//TODO: add locks by client fwInfo.
|
|||
//TODO: check that the client supports FW and SW by checking the supported objects in the model.
|
|||
List<String> attributesToFetch = new ArrayList<>(); |
|||
LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); |
|||
if (fwInfo.isSupported()) { |
|||
attributesToFetch.add(FIRMWARE_TITLE); |
|||
attributesToFetch.add(FIRMWARE_VERSION); |
|||
attributesToFetch.add(FIRMWARE_URL); |
|||
} |
|||
|
|||
if (!attributesToFetch.isEmpty()) { |
|||
var future = attributesService.getSharedAttributes(client, attributesToFetch); |
|||
DonAsynchron.withCallback(future, attrs -> { |
|||
if (fwInfo.isSupported()) { |
|||
Optional<String> newFirmwareTitle = getAttributeValue(attrs, FIRMWARE_TITLE); |
|||
Optional<String> newFirmwareVersion = getAttributeValue(attrs, FIRMWARE_VERSION); |
|||
Optional<String> newFirmwareUrl = getAttributeValue(attrs, FIRMWARE_URL); |
|||
if (newFirmwareTitle.isPresent() && newFirmwareVersion.isPresent()) { |
|||
onTargetFirmwareUpdate(client, newFirmwareTitle.get(), newFirmwareVersion.get(), newFirmwareUrl); |
|||
} |
|||
} |
|||
}, throwable -> { |
|||
if (fwInfo.isSupported()) { |
|||
fwInfo.setTargetFetchFailure(true); |
|||
} |
|||
}, executor); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void forceFirmwareUpdate(LwM2mClient client) { |
|||
LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); |
|||
fwInfo.setRetryAttempts(0); |
|||
fwInfo.setFailedPackageId(null); |
|||
startFirmwareUpdateIfNeeded(client, fwInfo); |
|||
} |
|||
|
|||
@Override |
|||
public void onTargetFirmwareUpdate(LwM2mClient client, String newFirmwareTitle, String newFirmwareVersion, Optional<String> newFirmwareUrl) { |
|||
LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); |
|||
fwInfo.updateTarget(newFirmwareTitle, newFirmwareVersion, newFirmwareUrl); |
|||
startFirmwareUpdateIfNeeded(client, fwInfo); |
|||
} |
|||
|
|||
@Override |
|||
public void onCurrentFirmwareNameUpdate(LwM2mClient client, String name) { |
|||
log.debug("[{}] Current fw name: {}", client.getEndpoint(), name); |
|||
LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); |
|||
fwInfo.setCurrentName(name); |
|||
} |
|||
|
|||
@Override |
|||
public void onCurrentFirmwareVersion3Update(LwM2mClient client, String version) { |
|||
log.debug("[{}] Current fw version: {}", client.getEndpoint(), version); |
|||
LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); |
|||
fwInfo.setCurrentVersion3(version); |
|||
} |
|||
|
|||
@Override |
|||
public void onCurrentFirmwareVersion5Update(LwM2mClient client, String version) { |
|||
log.debug("[{}] Current fw version: {}", client.getEndpoint(), version); |
|||
LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); |
|||
fwInfo.setCurrentVersion5(version); |
|||
} |
|||
|
|||
@Override |
|||
public void onCurrentFirmwareStateUpdate(LwM2mClient client, Long stateCode) { |
|||
log.debug("[{}] Current fw state: {}", client.getEndpoint(), stateCode); |
|||
LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); |
|||
UpdateStateFw state = UpdateStateFw.fromStateFwByCode(stateCode.intValue()); |
|||
if (UpdateStateFw.DOWNLOADED.equals(state)) { |
|||
executeFwUpdate(client); |
|||
} |
|||
fwInfo.setUpdateState(state); |
|||
Optional<OtaPackageUpdateStatus> status = LwM2mTransportUtil.toOtaPackageUpdateStatus(state); |
|||
status.ifPresent(otaStatus -> sendStateUpdateToTelemetry(client, fwInfo, |
|||
otaStatus, "Firmware Update State: " + state.name())); |
|||
} |
|||
|
|||
@Override |
|||
public void onCurrentFirmwareResultUpdate(LwM2mClient client, Long code) { |
|||
log.debug("[{}] Current fw result: {}", client.getEndpoint(), code); |
|||
LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); |
|||
UpdateResultFw result = UpdateResultFw.fromUpdateResultFwByCode(code.intValue()); |
|||
Optional<OtaPackageUpdateStatus> status = LwM2mTransportUtil.toOtaPackageUpdateStatus(result); |
|||
status.ifPresent(otaStatus -> sendStateUpdateToTelemetry(client, fwInfo, |
|||
otaStatus, "Firmware Update Result: " + result.name())); |
|||
if (result.isAgain() && fwInfo.getRetryAttempts() <= 2) { |
|||
fwInfo.setRetryAttempts(fwInfo.getRetryAttempts() + 1); |
|||
startFirmwareUpdateIfNeeded(client, fwInfo); |
|||
} else { |
|||
fwInfo.setUpdateResult(result); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onCurrentFirmwareDeliveryMethodUpdate(LwM2mClient client, Long value) { |
|||
log.debug("[{}] Current fw delivery method: {}", client.getEndpoint(), value); |
|||
LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); |
|||
fwInfo.setDeliveryMethod(value.intValue()); |
|||
} |
|||
|
|||
@Override |
|||
public void onTargetSoftwareUpdate(LwM2mClient client, String newSoftwareTitle, String newSoftwareVersion) { |
|||
|
|||
} |
|||
|
|||
private void startFirmwareUpdateIfNeeded(LwM2mClient client, LwM2MClientOtaInfo fwInfo) { |
|||
try { |
|||
if (!fwInfo.isSupported()) { |
|||
log.debug("[{}] Fw update is not supported: {}", client.getEndpoint(), fwInfo); |
|||
sendStateUpdateToTelemetry(client, fwInfo, OtaPackageUpdateStatus.FAILED, "Client does not support firmware update or profile misconfiguration!"); |
|||
} else if (fwInfo.isUpdateRequired()) { |
|||
if (StringUtils.isNotEmpty(fwInfo.getTargetUrl())) { |
|||
log.debug("[{}] Starting update to [{}{}] using URL: {}", client.getEndpoint(), fwInfo.getTargetName(), fwInfo.getTargetVersion(), fwInfo.getTargetUrl()); |
|||
startFirmwareUpdateUsingUrl(client, fwInfo.getTargetUrl()); |
|||
} else { |
|||
log.debug("[{}] Starting update to [{}{}] using binary", client.getEndpoint(), fwInfo.getTargetName(), fwInfo.getTargetVersion()); |
|||
startFirmwareUpdateUsingBinary(client, fwInfo); |
|||
} |
|||
} |
|||
} catch (Exception e) { |
|||
log.warn("[{}] failed to update client: {}", client.getEndpoint(), fwInfo, e); |
|||
sendStateUpdateToTelemetry(client, fwInfo, OtaPackageUpdateStatus.FAILED, "Internal server error: " + e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
private void startFirmwareUpdateUsingUrl(LwM2mClient client, String url) { |
|||
String targetIdVer = convertObjectIdToVersionedId(FW_URL_ID, client.getRegistration()); |
|||
TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(targetIdVer).value(url).timeout(config.getTimeout()).build(); |
|||
downlinkHandler.sendWriteReplaceRequest(client, request, new TbLwM2MWriteResponseCallback(uplinkHandler, logService, client, targetIdVer)); |
|||
} |
|||
|
|||
public void startFirmwareUpdateUsingBinary(LwM2mClient client, LwM2MClientOtaInfo fwInfo) { |
|||
String versionedId = convertObjectIdToVersionedId(FW_PACKAGE_5_ID, client.getRegistration()); |
|||
this.transportService.process(client.getSession(), createOtaPackageRequestMsg(client.getSession(), OtaPackageType.FIRMWARE.name()), |
|||
new TransportServiceCallback<>() { |
|||
@Override |
|||
public void onSuccess(TransportProtos.GetOtaPackageResponseMsg response) { |
|||
executor.submit(() -> doUpdateFirmwareUsingBinary(response, fwInfo, versionedId, client)); |
|||
} |
|||
|
|||
@Override |
|||
public void onError(Throwable e) { |
|||
logService.log(client, "Failed to process firmware update: " + e.getMessage()); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private void doUpdateFirmwareUsingBinary(TransportProtos.GetOtaPackageResponseMsg response, LwM2MClientOtaInfo fwInfo, String versionedId, LwM2mClient client) { |
|||
if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())) { |
|||
UUID otaPackageId = new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB()); |
|||
LwM2MFirmwareUpdateStrategy strategy; |
|||
if (fwInfo.getDeliveryMethod() == null || fwInfo.getDeliveryMethod() == 2) { |
|||
strategy = fwInfo.getStrategy(); |
|||
} else { |
|||
strategy = fwInfo.getDeliveryMethod() == 0 ? LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL : LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY; |
|||
} |
|||
switch (strategy) { |
|||
case OBJ_5_BINARY: |
|||
byte[] firmwareChunk = otaPackageDataCache.get(otaPackageId.toString(), 0, 0); |
|||
TbLwM2MWriteReplaceRequest writeRequest = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId) |
|||
.value(firmwareChunk).contentFormat(ContentFormat.OPAQUE) |
|||
.timeout(config.getTimeout()).build(); |
|||
downlinkHandler.sendWriteReplaceRequest(client, writeRequest, new TbLwM2MWriteResponseCallback(uplinkHandler, logService, client, versionedId)); |
|||
break; |
|||
case OBJ_5_TEMP_URL: |
|||
startFirmwareUpdateUsingUrl(client, fwInfo.getBaseUrl() + "/" + FIRMWARE_UPDATE_COAP_RECOURSE + "/" + otaPackageId.toString()); |
|||
break; |
|||
default: |
|||
sendStateUpdateToTelemetry(client, fwInfo, OtaPackageUpdateStatus.FAILED, "Unsupported strategy: " + strategy.name()); |
|||
} |
|||
} else { |
|||
sendStateUpdateToTelemetry(client, fwInfo, OtaPackageUpdateStatus.FAILED, "Failed to fetch OTA package: " + response.getResponseStatus()); |
|||
} |
|||
} |
|||
|
|||
private TransportProtos.GetOtaPackageRequestMsg createOtaPackageRequestMsg(TransportProtos.SessionInfoProto sessionInfo, String nameFwSW) { |
|||
return TransportProtos.GetOtaPackageRequestMsg.newBuilder() |
|||
.setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) |
|||
.setDeviceIdLSB(sessionInfo.getDeviceIdLSB()) |
|||
.setTenantIdMSB(sessionInfo.getTenantIdMSB()) |
|||
.setTenantIdLSB(sessionInfo.getTenantIdLSB()) |
|||
.setType(nameFwSW) |
|||
.build(); |
|||
} |
|||
|
|||
private void executeFwUpdate(LwM2mClient client) { |
|||
TbLwM2MExecuteRequest request = TbLwM2MExecuteRequest.builder().versionedId(FW_EXECUTE_ID).timeout(config.getTimeout()).build(); |
|||
downlinkHandler.sendExecuteRequest(client, request, new TbLwM2MExecuteCallback(logService, client, FW_EXECUTE_ID)); |
|||
} |
|||
|
|||
private Optional<String> getAttributeValue(List<TransportProtos.TsKvProto> attrs, String keyName) { |
|||
for (TransportProtos.TsKvProto attr : attrs) { |
|||
if (keyName.equals(attr.getKv().getKey())) { |
|||
if (attr.getKv().getType().equals(TransportProtos.KeyValueType.STRING_V)) { |
|||
return Optional.of(attr.getKv().getStringV()); |
|||
} else { |
|||
return Optional.empty(); |
|||
} |
|||
} |
|||
} |
|||
return Optional.empty(); |
|||
} |
|||
|
|||
private LwM2MClientOtaInfo getOrInitFwInfo(LwM2mClient client) { |
|||
//TODO: fetch state from the cache or DB.
|
|||
return fwStates.computeIfAbsent(client.getEndpoint(), endpoint -> { |
|||
var profile = clientContext.getProfile(client.getProfileId()); |
|||
return new LwM2MClientOtaInfo(endpoint, OtaPackageType.FIRMWARE, profile.getClientLwM2mSettings().getFwUpdateStrategy(), |
|||
profile.getClientLwM2mSettings().getFwUpdateRecourse()); |
|||
}); |
|||
} |
|||
|
|||
private LwM2MClientOtaInfo getOrInitSwInfo(LwM2mClient client) { |
|||
//TODO: fetch state from the cache or DB.
|
|||
return swStates.computeIfAbsent(client.getEndpoint(), endpoint -> { |
|||
var profile = clientContext.getProfile(client.getProfileId()); |
|||
return new LwM2MClientOtaInfo(endpoint, OtaPackageType.SOFTWARE, profile.getClientLwM2mSettings().getSwUpdateStrategy(), profile.getClientLwM2mSettings().getSwUpdateRecourse()); |
|||
}); |
|||
|
|||
} |
|||
|
|||
private void sendStateUpdateToTelemetry(LwM2mClient client, LwM2MClientOtaInfo fwInfo, OtaPackageUpdateStatus status, String log) { |
|||
List<TransportProtos.KeyValueProto> result = new ArrayList<>(); |
|||
TransportProtos.KeyValueProto.Builder kvProto = TransportProtos.KeyValueProto.newBuilder().setKey(getAttributeKey(fwInfo.getType(), STATE)); |
|||
kvProto.setType(TransportProtos.KeyValueType.STRING_V).setStringV(status.name()); |
|||
result.add(kvProto.build()); |
|||
kvProto = TransportProtos.KeyValueProto.newBuilder().setKey(LOG_LWM2M_TELEMETRY); |
|||
kvProto.setType(TransportProtos.KeyValueType.STRING_V).setStringV(log); |
|||
result.add(kvProto.build()); |
|||
helper.sendParametersOnThingsboardTelemetry(result, client.getSession()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,109 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.ota; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.StringUtils; |
|||
import org.thingsboard.server.common.data.ota.OtaPackageType; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2MFirmwareUpdateStrategy; |
|||
import org.thingsboard.server.transport.lwm2m.server.UpdateStateFw; |
|||
import org.thingsboard.server.transport.lwm2m.server.UpdateResultFw; |
|||
|
|||
import java.util.Optional; |
|||
|
|||
@Data |
|||
public class LwM2MClientOtaInfo { |
|||
|
|||
private final String endpoint; |
|||
private final OtaPackageType type; |
|||
|
|||
private String baseUrl; |
|||
|
|||
private boolean targetFetchFailure; |
|||
private String targetName; |
|||
private String targetVersion; |
|||
private String targetUrl; |
|||
|
|||
private boolean currentFetchFailure; |
|||
private String currentName; |
|||
private String currentVersion3; |
|||
private String currentVersion5; |
|||
private Integer deliveryMethod; |
|||
|
|||
//TODO: use value from device if applicable;
|
|||
private LwM2MFirmwareUpdateStrategy strategy; |
|||
private UpdateStateFw updateState; |
|||
private UpdateResultFw updateResult; |
|||
|
|||
private String failedPackageId; |
|||
private int retryAttempts; |
|||
|
|||
public LwM2MClientOtaInfo(String endpoint, OtaPackageType type, Integer strategyCode, String baseUrl) { |
|||
this.endpoint = endpoint; |
|||
this.type = type; |
|||
this.strategy = LwM2MFirmwareUpdateStrategy.fromStrategyFwByCode(strategyCode); |
|||
this.baseUrl = baseUrl; |
|||
} |
|||
|
|||
public void updateTarget(String targetName, String targetVersion, Optional<String> newFirmwareUrl) { |
|||
this.targetName = targetName; |
|||
this.targetVersion = targetVersion; |
|||
this.targetUrl = newFirmwareUrl.orElse(null); |
|||
} |
|||
|
|||
public boolean isUpdateRequired() { |
|||
if (StringUtils.isEmpty(targetName) || StringUtils.isEmpty(targetVersion) || !isSupported()) { |
|||
return false; |
|||
} else { |
|||
String targetPackageId = getPackageId(targetName, targetVersion); |
|||
String currentPackageIdUsingObject5 = getPackageId(currentName, currentVersion5); |
|||
if (StringUtils.isNotEmpty(failedPackageId) && failedPackageId.equals(targetPackageId)) { |
|||
return false; |
|||
} else { |
|||
if (targetPackageId.equals(currentPackageIdUsingObject5)) { |
|||
return false; |
|||
} else if (StringUtils.isNotEmpty(currentVersion3)) { |
|||
return !currentVersion3.contains(targetPackageId); |
|||
} else { |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public boolean isSupported() { |
|||
return StringUtils.isNotEmpty(currentName) || StringUtils.isNotEmpty(currentVersion5) || StringUtils.isNotEmpty(currentVersion3); |
|||
} |
|||
|
|||
public void setUpdateResult(UpdateResultFw updateResult) { |
|||
this.updateResult = updateResult; |
|||
switch (updateResult) { |
|||
case INITIAL: |
|||
break; |
|||
case UPDATE_SUCCESSFULLY: |
|||
retryAttempts = 0; |
|||
break; |
|||
default: |
|||
failedPackageId = getPackageId(targetName, targetVersion); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
private static String getPackageId(String name, String version) { |
|||
return (StringUtils.isNotEmpty(name) ? name : "") + (StringUtils.isNotEmpty(version) ? version : ""); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.ota; |
|||
|
|||
public enum LwM2MClientOtaState { |
|||
|
|||
IDLE, IN_PROGRESS, SUCCESS, FAILED |
|||
|
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.ota; |
|||
|
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|||
|
|||
import java.util.Optional; |
|||
|
|||
public interface LwM2MOtaUpdateService { |
|||
|
|||
void init(LwM2mClient client); |
|||
|
|||
void forceFirmwareUpdate(LwM2mClient client); |
|||
|
|||
void onTargetFirmwareUpdate(LwM2mClient client, String newFirmwareTitle, String newFirmwareVersion, Optional<String> newFirmwareUrl); |
|||
|
|||
void onTargetSoftwareUpdate(LwM2mClient client, String newSoftwareTitle, String newSoftwareVersion); |
|||
|
|||
void onCurrentFirmwareNameUpdate(LwM2mClient client, String name); |
|||
|
|||
void onCurrentFirmwareVersion3Update(LwM2mClient client, String version); |
|||
|
|||
void onCurrentFirmwareVersion5Update(LwM2mClient client, String version); |
|||
|
|||
void onCurrentFirmwareStateUpdate(LwM2mClient client, Long state); |
|||
|
|||
void onCurrentFirmwareResultUpdate(LwM2mClient client, Long result); |
|||
|
|||
void onCurrentFirmwareDeliveryMethodUpdate(LwM2mClient lwM2MClient, Long value); |
|||
} |
|||
@ -0,0 +1,279 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.rpc; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.eclipse.leshan.core.ResponseCode; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.common.data.StringUtils; |
|||
import org.thingsboard.server.common.transport.TransportService; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; |
|||
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; |
|||
import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.LwM2mDownlinkMsgHandler; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelAllObserveCallback; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelAllRequest; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveCallback; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDeleteCallback; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDeleteRequest; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDiscoverAllRequest; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDiscoverCallback; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDiscoverRequest; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteCallback; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteRequest; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveAllRequest; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveCallback; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveRequest; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadCallback; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadRequest; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteAttributesCallback; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteAttributesRequest; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteUpdateRequest; |
|||
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; |
|||
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; |
|||
|
|||
import java.util.Map; |
|||
import java.util.Set; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Slf4j |
|||
@Service |
|||
@TbLwM2mTransportComponent |
|||
@RequiredArgsConstructor |
|||
public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { |
|||
|
|||
private final TransportService transportService; |
|||
private final LwM2mClientContext clientContext; |
|||
private final LwM2MTransportServerConfig config; |
|||
private final LwM2mUplinkMsgHandler uplinkHandler; |
|||
private final LwM2mDownlinkMsgHandler downlinkHandler; |
|||
private final LwM2MTelemetryLogService logService; |
|||
private final Map<UUID, Long> rpcSubscriptions = new ConcurrentHashMap<>(); |
|||
|
|||
@Override |
|||
public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg rpcRequst, TransportProtos.SessionInfoProto sessionInfo) { |
|||
this.cleanupOldSessions(); |
|||
UUID requestUUID = new UUID(rpcRequst.getRequestIdMSB(), rpcRequst.getRequestIdLSB()); |
|||
log.warn("Received params: {}", rpcRequst.getParams()); |
|||
// We use this map to protect from browser issue that the same command is sent twice.
|
|||
// TODO: This is probably not the best place and should be moved to DeviceActor
|
|||
if (!this.rpcSubscriptions.containsKey(requestUUID)) { |
|||
LwM2mOperationType operationType = LwM2mOperationType.fromType(rpcRequst.getMethodName()); |
|||
if (operationType == null) { |
|||
this.sendErrorRpcResponse(sessionInfo, rpcRequst.getRequestId(), ResponseCode.METHOD_NOT_ALLOWED.getName(), "Unsupported operation type: " + rpcRequst.getMethodName()); |
|||
return; |
|||
} |
|||
LwM2mClient client = clientContext.getClientBySessionInfo(sessionInfo); |
|||
if (client.getRegistration() == null) { |
|||
this.sendErrorRpcResponse(sessionInfo, rpcRequst.getRequestId(), ResponseCode.INTERNAL_SERVER_ERROR.getName(), "Registration is empty"); |
|||
return; |
|||
} |
|||
try { |
|||
if (operationType.isHasObjectId()) { |
|||
String objectId = getIdFromParameters(client, rpcRequst); |
|||
switch (operationType) { |
|||
case READ: |
|||
sendReadRequest(client, rpcRequst, objectId); |
|||
break; |
|||
case OBSERVE: |
|||
sendObserveRequest(client, rpcRequst, objectId); |
|||
break; |
|||
case DISCOVER: |
|||
sendDiscoverRequest(client, rpcRequst, objectId); |
|||
break; |
|||
case EXECUTE: |
|||
sendExecuteRequest(client, rpcRequst, objectId); |
|||
break; |
|||
case WRITE_ATTRIBUTES: |
|||
sendWriteAttributesRequest(client, rpcRequst, objectId); |
|||
break; |
|||
case OBSERVE_CANCEL: |
|||
sendCancelObserveRequest(client, rpcRequst, objectId); |
|||
break; |
|||
case DELETE: |
|||
sendDeleteRequest(client, rpcRequst, objectId); |
|||
break; |
|||
case WRITE_UPDATE: |
|||
sendWriteUpdateRequest(client, rpcRequst, objectId); |
|||
break; |
|||
case WRITE_REPLACE: |
|||
sendWriteReplaceRequest(client, rpcRequst, objectId); |
|||
break; |
|||
default: |
|||
throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); |
|||
} |
|||
} else { |
|||
switch (operationType) { |
|||
case OBSERVE_CANCEL_ALL: |
|||
sendCancelAllObserveRequest(client, rpcRequst); |
|||
break; |
|||
case OBSERVE_READ_ALL: |
|||
sendObserveAllRequest(client, rpcRequst); |
|||
break; |
|||
case DISCOVER_ALL: |
|||
sendDiscoverAllRequest(client, rpcRequst); |
|||
break; |
|||
case FW_UPDATE: |
|||
//TODO: implement and add break statement
|
|||
default: |
|||
throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); |
|||
} |
|||
} |
|||
} catch (IllegalArgumentException e) { |
|||
this.sendErrorRpcResponse(sessionInfo, rpcRequst.getRequestId(), ResponseCode.BAD_REQUEST.getName(), e.getMessage()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void sendReadRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { |
|||
TbLwM2MReadRequest request = TbLwM2MReadRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); |
|||
var mainCallback = new TbLwM2MReadCallback(uplinkHandler, logService, client, versionedId); |
|||
var rpcCallback = new RpcReadResponseCallback<>(transportService, client, requestMsg, versionedId, mainCallback); |
|||
downlinkHandler.sendReadRequest(client, request, rpcCallback); |
|||
} |
|||
|
|||
private void sendObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { |
|||
TbLwM2MObserveRequest request = TbLwM2MObserveRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); |
|||
var mainCallback = new TbLwM2MObserveCallback(uplinkHandler, logService, client, versionedId); |
|||
var rpcCallback = new RpcReadResponseCallback<>(transportService, client, requestMsg, versionedId, mainCallback); |
|||
downlinkHandler.sendObserveRequest(client, request, rpcCallback); |
|||
} |
|||
|
|||
private void sendObserveAllRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) { |
|||
TbLwM2MObserveAllRequest request = TbLwM2MObserveAllRequest.builder().timeout(this.config.getTimeout()).build(); |
|||
downlinkHandler.sendObserveAllRequest(client, request, new RpcLinkSetCallback<>(transportService, client, requestMsg, null)); |
|||
} |
|||
|
|||
private void sendDiscoverAllRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) { |
|||
TbLwM2MDiscoverAllRequest request = TbLwM2MDiscoverAllRequest.builder().timeout(this.config.getTimeout()).build(); |
|||
downlinkHandler.sendDiscoverAllRequest(client, request, new RpcLinkSetCallback<>(transportService, client, requestMsg, null)); |
|||
} |
|||
|
|||
private void sendDiscoverRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { |
|||
TbLwM2MDiscoverRequest request = TbLwM2MDiscoverRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); |
|||
var mainCallback = new TbLwM2MDiscoverCallback(logService, client, versionedId); |
|||
var rpcCallback = new RpcDiscoverCallback(transportService, client, requestMsg, mainCallback); |
|||
downlinkHandler.sendDiscoverRequest(client, request, rpcCallback); |
|||
} |
|||
|
|||
private void sendExecuteRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { |
|||
TbLwM2MExecuteRequest downlink = TbLwM2MExecuteRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); |
|||
var mainCallback = new TbLwM2MExecuteCallback(logService, client, versionedId); |
|||
var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); |
|||
downlinkHandler.sendExecuteRequest(client, downlink, rpcCallback); |
|||
} |
|||
|
|||
private void sendWriteAttributesRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { |
|||
RpcWriteAttributesRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteAttributesRequest.class); |
|||
TbLwM2MWriteAttributesRequest request = TbLwM2MWriteAttributesRequest.builder().versionedId(versionedId) |
|||
.attributes(requestBody.getAttributes()) |
|||
.timeout(this.config.getTimeout()).build(); |
|||
var mainCallback = new TbLwM2MWriteAttributesCallback(logService, client, versionedId); |
|||
var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); |
|||
downlinkHandler.sendWriteAttributesRequest(client, request, rpcCallback); |
|||
} |
|||
|
|||
private void sendWriteUpdateRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { |
|||
RpcWriteUpdateRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteUpdateRequest.class); |
|||
TbLwM2MWriteUpdateRequest.TbLwM2MWriteUpdateRequestBuilder builder = TbLwM2MWriteUpdateRequest.builder().versionedId(versionedId); |
|||
builder.value(requestBody.getValue()).timeout(this.config.getTimeout()); |
|||
var mainCallback = new TbLwM2MWriteResponseCallback(uplinkHandler, logService, client, versionedId); |
|||
var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); |
|||
downlinkHandler.sendWriteUpdateRequest(client, builder.build(), rpcCallback); |
|||
} |
|||
|
|||
private void sendWriteReplaceRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { |
|||
RpcWriteReplaceRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteReplaceRequest.class); |
|||
TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId) |
|||
.value(requestBody.getValue()) |
|||
.timeout(this.config.getTimeout()).build(); |
|||
var mainCallback = new TbLwM2MWriteResponseCallback(uplinkHandler, logService, client, versionedId); |
|||
var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); |
|||
downlinkHandler.sendWriteReplaceRequest(client, request, rpcCallback); |
|||
} |
|||
|
|||
private void sendCancelObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { |
|||
TbLwM2MCancelObserveRequest downlink = TbLwM2MCancelObserveRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); |
|||
var mainCallback = new TbLwM2MCancelObserveCallback(logService, client, versionedId); |
|||
var rpcCallback = new RpcCancelObserveCallback(transportService, client, requestMsg, mainCallback); |
|||
downlinkHandler.sendCancelObserveRequest(client, downlink, rpcCallback); |
|||
} |
|||
|
|||
private void sendDeleteRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { |
|||
TbLwM2MDeleteRequest downlink = TbLwM2MDeleteRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); |
|||
var mainCallback = new TbLwM2MDeleteCallback(logService, client, versionedId); |
|||
var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); |
|||
downlinkHandler.sendDeleteRequest(client, downlink, rpcCallback); |
|||
} |
|||
|
|||
private void sendCancelAllObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) { |
|||
TbLwM2MCancelAllRequest downlink = TbLwM2MCancelAllRequest.builder().timeout(this.config.getTimeout()).build(); |
|||
var mainCallback = new TbLwM2MCancelAllObserveCallback(logService, client); |
|||
var rpcCallback = new RpcCancelAllObserveCallback(transportService, client, requestMsg, mainCallback); |
|||
downlinkHandler.sendCancelAllRequest(client, downlink, rpcCallback); |
|||
} |
|||
|
|||
private String getIdFromParameters(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg rpcRequst) { |
|||
IdOrKeyRequest requestParams = JacksonUtil.fromString(rpcRequst.getParams(), IdOrKeyRequest.class); |
|||
String targetId; |
|||
if (StringUtils.isNotEmpty(requestParams.getKey())) { |
|||
targetId = clientContext.getObjectIdByKeyNameFromProfile(client, requestParams.getKey()); |
|||
} else if (StringUtils.isNotEmpty(requestParams.getId())) { |
|||
targetId = requestParams.getId(); |
|||
} else { |
|||
throw new IllegalArgumentException("Can't find 'key' or 'id' in the requestParams parameters!"); |
|||
} |
|||
return targetId; |
|||
} |
|||
|
|||
private void sendErrorRpcResponse(TransportProtos.SessionInfoProto sessionInfo, int requestId, String result, String error) { |
|||
String payload = JacksonUtil.toString(JacksonUtil.newObjectNode().put("result", result).put("error", error)); |
|||
TransportProtos.ToDeviceRpcResponseMsg msg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(payload).build(); |
|||
transportService.process(sessionInfo, msg, null); |
|||
} |
|||
|
|||
private void cleanupOldSessions() { |
|||
log.warn("4.1) before rpcSubscriptions.size(): [{}]", rpcSubscriptions.size()); |
|||
if (rpcSubscriptions.size() > 0) { |
|||
long currentTime = System.currentTimeMillis(); |
|||
Set<UUID> rpcSubscriptionsToRemove = rpcSubscriptions.entrySet().stream().filter(kv -> currentTime > kv.getValue()).map(Map.Entry::getKey).collect(Collectors.toSet()); |
|||
log.warn("4.2) System.currentTimeMillis(): [{}]", System.currentTimeMillis()); |
|||
log.warn("4.3) rpcSubscriptionsToRemove: [{}]", rpcSubscriptionsToRemove); |
|||
rpcSubscriptionsToRemove.forEach(rpcSubscriptions::remove); |
|||
} |
|||
log.warn("4.4) after rpcSubscriptions.size(): [{}]", rpcSubscriptions.size()); |
|||
} |
|||
|
|||
@Override |
|||
public void onToDeviceRpcResponse(TransportProtos.ToDeviceRpcResponseMsg toDeviceResponse, TransportProtos.SessionInfoProto sessionInfo) { |
|||
log.warn("5) onToDeviceRpcResponse: [{}], sessionUUID: [{}]", toDeviceResponse, new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); |
|||
transportService.process(sessionInfo, toDeviceResponse, null); |
|||
} |
|||
|
|||
public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse) { |
|||
log.info("[{}] toServerRpcResponse", toServerResponse); |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.rpc; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
@JsonIgnoreProperties(ignoreUnknown = true) |
|||
public class IdOrKeyRequest { |
|||
|
|||
private String key; |
|||
private String id; |
|||
|
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.rpc; |
|||
|
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
|
|||
public interface LwM2MRpcRequestHandler { |
|||
|
|||
void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRequest, TransportProtos.SessionInfoProto sessionInfo); |
|||
|
|||
void onToDeviceRpcResponse(TransportProtos.ToDeviceRpcResponseMsg toDeviceRpcResponse, TransportProtos.SessionInfoProto sessionInfo); |
|||
|
|||
void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse); |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.rpc; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonInclude; |
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
@Builder |
|||
@JsonInclude(JsonInclude.Include.NON_NULL) |
|||
public class LwM2MRpcResponseBody { |
|||
|
|||
private String result; |
|||
private String value; |
|||
private String error; |
|||
private String info; |
|||
|
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.rpc; |
|||
|
|||
import org.eclipse.leshan.core.ResponseCode; |
|||
import org.thingsboard.server.common.transport.TransportService; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelAllRequest; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; |
|||
|
|||
public class RpcCancelAllObserveCallback extends RpcDownlinkRequestCallbackProxy<TbLwM2MCancelAllRequest, Integer> { |
|||
|
|||
public RpcCancelAllObserveCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback<TbLwM2MCancelAllRequest, Integer> callback) { |
|||
super(transportService, client, requestMsg, callback); |
|||
} |
|||
|
|||
@Override |
|||
protected void sendRpcReplyOnSuccess(Integer response) { |
|||
reply(LwM2MRpcResponseBody.builder().result(ResponseCode.CONTENT.getName()).value(response.toString()).build()); |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
/** |
|||
* Copyright © 2016-2021 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.transport.lwm2m.server.rpc; |
|||
|
|||
import org.eclipse.leshan.core.ResponseCode; |
|||
import org.eclipse.leshan.core.node.LwM2mObject; |
|||
import org.eclipse.leshan.core.node.LwM2mObjectInstance; |
|||
import org.eclipse.leshan.core.node.LwM2mResource; |
|||
import org.eclipse.leshan.core.response.ReadResponse; |
|||
import org.thingsboard.server.common.data.StringUtils; |
|||
import org.thingsboard.server.common.transport.TransportService; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; |
|||
import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; |
|||
|
|||
import java.util.Optional; |
|||
|
|||
public class RpcCancelObserveCallback extends RpcDownlinkRequestCallbackProxy<TbLwM2MCancelObserveRequest, Integer> { |
|||
|
|||
public RpcCancelObserveCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback<TbLwM2MCancelObserveRequest, Integer> callback) { |
|||
super(transportService, client, requestMsg, callback); |
|||
} |
|||
|
|||
@Override |
|||
protected void sendRpcReplyOnSuccess(Integer response) { |
|||
reply(LwM2MRpcResponseBody.builder().result(ResponseCode.CONTENT.getName()).value(response.toString()).build()); |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue