Browse Source

Merge PSM & eDRX for LwM2M

pull/4958/head
Andrii Shvaika 5 years ago
committed by Andrew Shvayka
parent
commit
a4636347e5
  1. 41
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  2. 3
      application/src/main/resources/logback.xml
  3. 34
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java
  4. 1
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/TbCoapClientState.java
  5. 7
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java
  6. 38
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java
  7. 9
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java
  8. 188
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java
  9. 118
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java
  10. 46
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java
  11. 75
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java
  12. 2
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java
  13. 2
      transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml

41
application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java

@ -41,8 +41,10 @@ import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData;
import org.thingsboard.server.common.data.device.data.CoapDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.data.PowerMode;
import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration;
import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
@ -461,16 +463,6 @@ public class DefaultTransportApiService implements TransportApiService {
}
private DeviceInfoProto getDeviceInfoProto(Device device) throws JsonProcessingException {
PowerMode powerMode = null;
Long edrxCycle = null;
switch (device.getDeviceData().getTransportConfiguration().getType()) {
case LWM2M:
Lwm2mDeviceTransportConfiguration transportConfiguration = (Lwm2mDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration();
powerMode = transportConfiguration.getPowerMode();
edrxCycle = transportConfiguration.getEdrxCycle();
break;
}
DeviceInfoProto.Builder builder = DeviceInfoProto.newBuilder()
.setTenantIdMSB(device.getTenantId().getId().getMostSignificantBits())
.setTenantIdLSB(device.getTenantId().getId().getLeastSignificantBits())
@ -483,9 +475,28 @@ public class DefaultTransportApiService implements TransportApiService {
.setDeviceProfileIdMSB(device.getDeviceProfileId().getId().getMostSignificantBits())
.setDeviceProfileIdLSB(device.getDeviceProfileId().getId().getLeastSignificantBits())
.setAdditionalInfo(mapper.writeValueAsString(device.getAdditionalInfo()));
if (powerMode != null) {
builder.setPowerMode(powerMode.name());
builder.setEdrxCycle(edrxCycle);
PowerSavingConfiguration psmConfiguration = null;
switch (device.getDeviceData().getTransportConfiguration().getType()) {
case LWM2M:
psmConfiguration = (Lwm2mDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration();
break;
case COAP:
psmConfiguration = (CoapDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration();
break;
}
if (psmConfiguration != null) {
PowerMode powerMode = psmConfiguration.getPowerMode();
if (powerMode != null) {
builder.setPowerMode(powerMode.name());
if (powerMode.equals(PowerMode.PSM)) {
builder.setPsmActivityTimer(checkLong(psmConfiguration.getPsmActivityTimer()));
} else if (powerMode.equals(PowerMode.E_DRX)) {
builder.setEdrxCycle(checkLong(psmConfiguration.getEdrxCycle()));
builder.setPagingTransmissionWindow(checkLong(psmConfiguration.getPagingTransmissionWindow()));
}
}
}
return builder.build();
}
@ -584,4 +595,8 @@ public class DefaultTransportApiService implements TransportApiService {
deviceCreationLock.unlock();
}
}
private Long checkLong(Long l) {
return l != null ? l : 0;
}
}

3
application/src/main/resources/logback.xml

@ -41,6 +41,9 @@
<!-- <logger name="org.thingsboard.server.service.queue.TbMsgPackProcessingContext" level="DEBUG" /> -->
<logger name="com.microsoft.azure.servicebus.primitives.CoreMessageReceiver" level="OFF" />
<logger name="org.thingsboard.server.transport" level="TRACE" />
<logger name="org.thingsboard.server.coap.transport" level="TRACE" />
<logger name="org.eclipse" level="TRACE" />
<root level="INFO">
<appender-ref ref="STDOUT"/>

34
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java

@ -166,13 +166,12 @@ public class DefaultCoapClientContext implements CoapClientContext {
var clientProfile = getProfile(client.getProfileId());
if (clientProfile.isPresent()) {
profileSettings = clientProfile.get().getClientSettings();
powerMode = profileSettings.getPowerMode();
if (powerMode == null) {
powerMode = PowerMode.DRX;
if (profileSettings != null) {
powerMode = profileSettings.getPowerMode();
}
}
}
if (PowerMode.DRX.equals(powerMode)) {
if (powerMode == null || PowerMode.DRX.equals(powerMode)) {
client.updateLastUplinkTime();
return;
}
@ -598,13 +597,12 @@ public class DefaultCoapClientContext implements CoapClientContext {
var clientProfile = getProfile(client.getProfileId());
if (clientProfile.isPresent()) {
profileSettings = clientProfile.get().getClientSettings();
powerMode = profileSettings.getPowerMode();
if (powerMode == null) {
powerMode = PowerMode.DRX;
if (profileSettings != null) {
powerMode = profileSettings.getPowerMode();
}
}
}
if (PowerMode.DRX.equals(powerMode)) {
if (powerMode == null || PowerMode.DRX.equals(powerMode)) {
return true;
}
client.lock();
@ -679,10 +677,7 @@ public class DefaultCoapClientContext implements CoapClientContext {
TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(),
new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
if (state.getAttrs() == null) {
transportService.process(state.getSession(), getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null);
transportService.deregisterSession(state.getSession());
state.setSession(null);
//TODO: need to delete the client from context as well.
closeAndCleanup(state);
}
}
}
@ -696,11 +691,18 @@ public class DefaultCoapClientContext implements CoapClientContext {
TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setUnsubscribe(true).build(),
new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
if (state.getRpc() == null) {
transportService.process(state.getSession(), getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null);
transportService.deregisterSession(state.getSession());
state.setSession(null);
//TODO: need to delete the client from context as well.
closeAndCleanup(state);
}
}
}
private void closeAndCleanup(TbCoapClientState state) {
transportService.process(state.getSession(), getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null);
transportService.deregisterSession(state.getSession());
state.setSession(null);
state.setConfiguration(null);
state.setCredentials(null);
state.setAdaptor(null);
//TODO: add optimistic lock check that the client was already deleted and cleanup "clients" map.
}
}

1
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/TbCoapClientState.java

@ -84,6 +84,7 @@ public class TbCoapClientState {
}
public void init(ValidateDeviceCredentialsResponse credentials) {
this.credentials = credentials;
this.profileId = credentials.getDeviceInfo().getDeviceProfileId();
this.powerMode = credentials.getDeviceInfo().getPowerMode();
this.edrxCycle = credentials.getDeviceInfo().getEdrxCycle();

7
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java

@ -128,6 +128,13 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig {
@Value("${transport.lwm2m.log_max_length:}")
private int logMaxLength;
@Getter
@Value("${transport.lwm2m.psm_activity_timer:10000}")
private long psmActivityTimer;
@Getter
@Value("${transport.lwm2m.paging_transmission_window:10000}")
private long pagingTransmissionWindow;
@PostConstruct
public void init() {

38
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java

@ -50,6 +50,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
@ -93,10 +94,24 @@ public class LwM2mClient implements Serializable {
@Getter
private PowerMode powerMode;
@Getter
private Long psmActivityTimer;
@Getter
private Long edrxCycle;
@Getter
private Long pagingTransmissionWindow;
@Getter
@Setter
private Registration registration;
@Getter
@Setter
private boolean asleep;
@Getter
private long lastUplinkTime;
@Getter
@Setter
private transient Future<Void> sleepTask;
private boolean firstEdrxDownlink = true;
public Object clone() throws CloneNotSupportedException {
return super.clone();
@ -118,6 +133,8 @@ public class LwM2mClient implements Serializable {
this.profileId = new UUID(session.getDeviceProfileIdMSB(), session.getDeviceProfileIdLSB());
this.powerMode = credentials.getDeviceInfo().getPowerMode();
this.edrxCycle = credentials.getDeviceInfo().getEdrxCycle();
this.psmActivityTimer = credentials.getDeviceInfo().getPsmActivityTimer();
this.pagingTransmissionWindow = credentials.getDeviceInfo().getPagingTransmissionWindow();
}
public void lock() {
@ -139,6 +156,8 @@ public class LwM2mClient implements Serializable {
Lwm2mDeviceTransportConfiguration transportConfiguration = (Lwm2mDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration();
this.powerMode = transportConfiguration.getPowerMode();
this.edrxCycle = transportConfiguration.getEdrxCycle();
this.psmActivityTimer = transportConfiguration.getPsmActivityTimer();
this.pagingTransmissionWindow = transportConfiguration.getPagingTransmissionWindow();
}
public void onDeviceProfileUpdate(DeviceProfile deviceProfile) {
@ -236,7 +255,12 @@ public class LwM2mClient implements Serializable {
}
public boolean isResourceMultiInstances(String pathIdVer, LwM2mModelProvider modelProvider) {
return getResourceModel(pathIdVer, modelProvider).multiple;
var resourceModel = getResourceModel(pathIdVer, modelProvider);
if (resourceModel != null && resourceModel.multiple != null) {
return resourceModel.multiple;
} else {
return false;
}
}
public ObjectModel getObjectModel(String pathIdVer, LwM2mModelProvider modelProvider) {
@ -344,5 +368,17 @@ public class LwM2mClient implements Serializable {
this.lock = new ReentrantLock();
}
public long updateLastUplinkTime(){
this.lastUplinkTime = System.currentTimeMillis();
this.firstEdrxDownlink = true;
return lastUplinkTime;
}
public boolean checkFirstDownlink() {
boolean result = firstEdrxDownlink;
firstEdrxDownlink = false;
return result;
}
}

9
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java

@ -63,5 +63,14 @@ public interface LwM2mClientContext {
boolean isComposite(LwM2mClient client);
void onUplink(LwM2mClient client);
Long getRequestTimeout(LwM2mClient client);
boolean asleep(LwM2mClient client);
boolean awake(LwM2mClient client);
boolean isDownlinkAllowed(LwM2mClient client);
}

188
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java

@ -53,6 +53,8 @@ import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import static org.eclipse.leshan.core.SecurityMode.NO_SEC;
@ -128,7 +130,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
@Override
public Optional<TransportProtos.SessionInfoProto> register(LwM2mClient client, Registration registration) throws LwM2MClientStateException {
TransportProtos.SessionInfoProto oldSession = null;
TransportProtos.SessionInfoProto oldSession;
client.lock();
try {
if (LwM2MClientState.UNREGISTERED.equals(client.getState())) {
@ -158,13 +160,64 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
client.setRegistration(registration);
this.lwM2mClientsByRegistrationId.put(registration.getId(), client);
client.setState(LwM2MClientState.REGISTERED);
clientStore.put(client);
onUplink(client);
if(!compareAndSetSleepFlag(client, false)){
clientStore.put(client);
}
} finally {
client.unlock();
}
return Optional.ofNullable(oldSession);
}
@Override
public boolean asleep(LwM2mClient client) {
boolean changed = compareAndSetSleepFlag(client, true);
if (changed) {
log.debug("[{}] client is sleeping", client.getEndpoint());
context.getTransportService().log(client.getSession(), "Info : Client is sleeping!");
}
return changed;
}
@Override
public boolean awake(LwM2mClient client) {
onUplink(client);
boolean changed = compareAndSetSleepFlag(client, false);
if (changed) {
log.debug("[{}] client is awake", client.getEndpoint());
context.getTransportService().log(client.getSession(), "Info : Client is awake!");
sendMsgsAfterSleeping(client);
}
return changed;
}
private boolean compareAndSetSleepFlag(LwM2mClient client, boolean sleeping) {
if (sleeping == client.isAsleep()) {
log.trace("[{}] Client is already at sleeping: {}, ignoring event: {}", client.getEndpoint(), client.isAsleep(), sleeping);
return false;
}
client.lock();
try {
if (sleeping == client.isAsleep()) {
log.trace("[{}] Client is already at sleeping: {}, ignoring event: {}", client.getEndpoint(), client.isAsleep(), sleeping);
return false;
} else {
PowerMode powerMode = getPowerMode(client);
if (PowerMode.PSM.equals(powerMode) || PowerMode.E_DRX.equals(powerMode)) {
log.trace("[{}] Switch sleeping from: {} to: {}", client.getEndpoint(), client.isAsleep(), sleeping);
client.setAsleep(sleeping);
update(client);
return true;
} else {
return false;
}
}
} finally {
client.unlock();
}
}
@Override
public void updateRegistration(LwM2mClient client, Registration registration) throws LwM2MClientStateException {
client.lock();
@ -173,8 +226,12 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
throw new LwM2MClientStateException(client.getState(), "Client is in invalid state.");
}
client.setRegistration(registration);
clientStore.put(client);
sendMsgsAfterSleeping(client);
onUplink(client);
if (compareAndSetSleepFlag(client, false)) {
sendMsgsAfterSleeping(client);
} else {
clientStore.put(client);
}
} finally {
client.unlock();
}
@ -268,12 +325,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
@Override
public void sendMsgsAfterSleeping(LwM2mClient lwM2MClient) {
if (LwM2MClientState.REGISTERED.equals(lwM2MClient.getState())) {
PowerMode powerMode = lwM2MClient.getPowerMode();
if (powerMode == null) {
Lwm2mDeviceProfileTransportConfiguration deviceProfile = getProfile(lwM2MClient.getProfileId());
powerMode = deviceProfile.getClientLwM2mSettings().getPowerMode();
}
PowerMode powerMode = getPowerMode(lwM2MClient);
if (PowerMode.PSM.equals(powerMode) || PowerMode.E_DRX.equals(powerMode)) {
defaultLwM2MUplinkMsgHandler.initAttributes(lwM2MClient);
TransportProtos.TransportToDeviceActorMsg persistentRpcRequestMsg = TransportProtos.TransportToDeviceActorMsg
@ -287,6 +339,15 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
}
}
private PowerMode getPowerMode(LwM2mClient lwM2MClient) {
PowerMode powerMode = lwM2MClient.getPowerMode();
if (powerMode == null) {
Lwm2mDeviceProfileTransportConfiguration deviceProfile = getProfile(lwM2MClient.getProfileId());
powerMode = deviceProfile.getClientLwM2mSettings().getPowerMode();
}
return powerMode;
}
@Override
public Collection<LwM2mClient> getLwM2mClients() {
return lwM2mClientsByEndpoint.values();
@ -347,6 +408,113 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
getProfile(client.getProfileId()).getClientLwM2mSettings().isCompositeOperationsSupport();
}
@Override
public boolean isDownlinkAllowed(LwM2mClient client) {
PowerMode powerMode = client.getPowerMode();
OtherConfiguration profileSettings = null;
if (powerMode == null) {
var clientProfile = getProfile(client.getProfileId());
profileSettings = clientProfile.getClientLwM2mSettings();
powerMode = profileSettings.getPowerMode();
if (powerMode == null) {
powerMode = PowerMode.DRX;
}
}
if (PowerMode.DRX.equals(powerMode)) {
return true;
}
client.lock();
long timeSinceLastUplink = System.currentTimeMillis() - client.getLastUplinkTime();
try {
if (PowerMode.PSM.equals(powerMode)) {
Long psmActivityTimer = client.getPsmActivityTimer();
if (psmActivityTimer == null && profileSettings != null) {
psmActivityTimer = profileSettings.getPsmActivityTimer();
}
if (psmActivityTimer == null || psmActivityTimer == 0L) {
psmActivityTimer = config.getPsmActivityTimer();
}
return timeSinceLastUplink <= psmActivityTimer;
} else {
Long pagingTransmissionWindow = client.getPagingTransmissionWindow();
if (pagingTransmissionWindow == null && profileSettings != null) {
pagingTransmissionWindow = profileSettings.getPagingTransmissionWindow();
}
if (pagingTransmissionWindow == null || pagingTransmissionWindow == 0L) {
pagingTransmissionWindow = config.getPagingTransmissionWindow();
}
boolean allowed = timeSinceLastUplink <= pagingTransmissionWindow;
if (!allowed) {
return client.checkFirstDownlink();
} else {
return true;
}
}
} finally {
client.unlock();
}
}
@Override
public void onUplink(LwM2mClient client) {
PowerMode powerMode = client.getPowerMode();
OtherConfiguration profileSettings = null;
if (powerMode == null) {
var clientProfile = getProfile(client.getProfileId());
profileSettings = clientProfile.getClientLwM2mSettings();
powerMode = profileSettings.getPowerMode();
if (powerMode == null) {
powerMode = PowerMode.DRX;
}
}
if (PowerMode.DRX.equals(powerMode)) {
client.updateLastUplinkTime();
return;
}
client.lock();
try {
long uplinkTime = client.updateLastUplinkTime();
long timeout;
if (PowerMode.PSM.equals(powerMode)) {
Long psmActivityTimer = client.getPsmActivityTimer();
if (psmActivityTimer == null && profileSettings != null) {
psmActivityTimer = profileSettings.getPsmActivityTimer();
}
if (psmActivityTimer == null || psmActivityTimer == 0L) {
psmActivityTimer = config.getPsmActivityTimer();
}
timeout = psmActivityTimer;
} else {
Long pagingTransmissionWindow = client.getPagingTransmissionWindow();
if (pagingTransmissionWindow == null && profileSettings != null) {
pagingTransmissionWindow = profileSettings.getPagingTransmissionWindow();
}
if (pagingTransmissionWindow == null || pagingTransmissionWindow == 0L) {
pagingTransmissionWindow = config.getPagingTransmissionWindow();
}
timeout = pagingTransmissionWindow;
}
Future<Void> sleepTask = client.getSleepTask();
if (sleepTask != null) {
sleepTask.cancel(false);
}
Future<Void> task = context.getScheduler().schedule(() -> {
if (uplinkTime == client.getLastUplinkTime()) {
asleep(client);
}
return null;
}, timeout, TimeUnit.MILLISECONDS);
client.setSleepTask(task);
} finally {
client.unlock();
}
}
@Override
public Long getRequestTimeout(LwM2mClient client) {
Long timeout = null;

118
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java

@ -39,6 +39,8 @@ import org.eclipse.leshan.core.request.SimpleDownlinkRequest;
import org.eclipse.leshan.core.request.WriteAttributesRequest;
import org.eclipse.leshan.core.request.WriteCompositeRequest;
import org.eclipse.leshan.core.request.WriteRequest;
import org.eclipse.leshan.core.request.exception.ClientSleepingException;
import org.eclipse.leshan.core.request.exception.TimeoutException;
import org.eclipse.leshan.core.response.DeleteResponse;
import org.eclipse.leshan.core.response.DiscoverResponse;
import org.eclipse.leshan.core.response.ExecuteResponse;
@ -59,6 +61,7 @@ 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.client.LwM2mClientContext;
import org.thingsboard.server.transport.lwm2m.server.common.LwM2MExecutorAwareService;
import org.thingsboard.server.transport.lwm2m.server.downlink.composite.TbLwM2MReadCompositeRequest;
import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService;
@ -95,6 +98,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
private final LwM2mTransportContext context;
private final LwM2MTransportServerConfig config;
private final LwM2MTelemetryLogService logService;
private final LwM2mClientContext clientContext;
@PostConstruct
public void init() {
@ -236,10 +240,10 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
path.getObjectId(), path.getObjectInstanceId(), path.getResourceId(), request.getValue());
sendSimpleRequest(client, downlink, request.getTimeout(), callback);
} catch (Exception e) {
callback.onError(JacksonUtil.toString(request), e);
callback.onError(toString(request), e);
}
} else {
callback.onValidationError(JacksonUtil.toString(request), "Resource " + request.getVersionedId() + " is not configured in the device profile!");
callback.onValidationError(toString(request), "Resource " + request.getVersionedId() + " is not configured in the device profile!");
}
}
@ -251,7 +255,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
//TODO: replace config.getTimeout();
sendWriteCompositeRequest(client, downlink, config.getTimeout(), callback);
} catch (Exception e) {
callback.onError(JacksonUtil.toString(rpcWriteCompositeRequest), e);
callback.onError(toString(rpcWriteCompositeRequest), e);
}
}
@ -281,10 +285,10 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
WriteRequest downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resources);
sendSimpleRequest(client, downlink, request.getTimeout(), callback);
} else {
callback.onValidationError(JacksonUtil.toString(request), "No resources to update!");
callback.onValidationError(toString(request), "No resources to update!");
}
} else {
callback.onValidationError(JacksonUtil.toString(request), "Update of the root level object is not supported yet!");
callback.onValidationError(toString(request), "Update of the root level object is not supported yet!");
}
}
@ -298,6 +302,10 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
}
private <R extends DownlinkRequest<T>, T extends LwM2mResponse> void sendRequest(LwM2mClient client, R request, long timeoutInMs, DownlinkRequestCallback<R, T> callback, Function<R, String> pathToStringFunction) {
if (!clientContext.isDownlinkAllowed(client)) {
log.trace("[{}] ignore downlink request cause client is sleeping.", client.getEndpoint());
return;
}
Registration registration = client.getRegistration();
try {
logService.log(client, String.format("[%s][%s] Sending request: %s to %s", registration.getId(), registration.getSocketAddress(), request.getClass().getSimpleName(), pathToStringFunction.apply(request)));
@ -307,20 +315,21 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
callback.onSuccess(request, response);
} catch (Exception e) {
log.error("[{}] failed to process successful response [{}] ", registration.getEndpoint(), response, e);
} finally {
clientContext.awake(client);
}
});
}, e -> {
executor.submit(() -> {
callback.onError(JacksonUtil.toString(request), e);
});
});
},e -> handleDownlinkError(client, request, callback, e));
} catch (Exception e) {
callback.onError(JacksonUtil.toString(request), e);
handleDownlinkError(client, request, callback, e);
}
}
private <R extends SimpleDownlinkRequest<T>, T extends LwM2mResponse> void sendWriteCompositeRequest(LwM2mClient client, WriteCompositeRequest request, long timeoutInMs,
DownlinkRequestCallback<WriteCompositeRequest, WriteCompositeResponse> callback) {
private <R extends SimpleDownlinkRequest<T>, T extends LwM2mResponse> void sendWriteCompositeRequest(LwM2mClient client, WriteCompositeRequest request, long timeoutInMs, DownlinkRequestCallback<WriteCompositeRequest, WriteCompositeResponse> callback) {
if (!clientContext.isDownlinkAllowed(client)) {
log.trace("[{}] ignore downlink request cause client is sleeping.", client.getEndpoint());
return;
}
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.getPaths()));
@ -334,63 +343,28 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
}
} catch (Exception e) {
log.error("[{}] failed to process successful response [{}] ", registration.getEndpoint(), response, e);
} finally {
clientContext.awake(client);
}
});
}, e -> {
executor.submit(() -> {
callback.onError(JacksonUtil.toString(request), e);
});
});
}, e -> handleDownlinkError(client, request, callback, e));
} catch (Exception e) {
callback.onError(JacksonUtil.toString(request), e);
handleDownlinkError(client, request, callback, e);
}
}
// private <R extends DownlinkRequest<T>, T extends LwM2mResponse> void sendReadRequestComposite(LwM2mClient client, ReadCompositeRequest request, long timeoutInMs,
// DownlinkRequestCallback<ReadCompositeRequest, ReadCompositeResponse> 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.getPaths()));
// context.getServer().send(registration, request, timeoutInMs, response -> {
// executor.submit(() -> {
// try {
// /**
// * [{"bn":"/3/0/","n":"0","vs":"Thingsboard Test Device"},
// * {"n":"1","vs":"Model 500"},
// * {"n":"2","vs":"TH-500-000-0001"},
// * {"n":"3","vs":"TestThingsboard@TestMore1024_2.04"},
// * {"n":"6","v":1},{"n":"7","v":56},
// * {"n":"8","v":42},{"n":"9","v":16},
// * {"n":"10","v":127619},{"n":"13","v":1624520988},
// * {"n":"14","vs":"+03"},{"n":"15","vs":"Europe/Kiev"},
// * {"n":"16","vs":"U"},{"n":"17","vs":"smart meters"},
// * {"n":"18","vs":"1.01"},{"n":"19","vs":"1.02"},
// * {"n":"20","v":3},{"n":"21","v":256000},
// * {"bn":"/5/0/","n":"1","vs":""},
// * {"n":"3","v":0},{"n":"5","v":0},
// * {"n":"6","vs":""},{"n":"7","vs":""},
// * {"n":"8/0","v":0},{"n":"8/1","v":1},
// * {"n":"9","v":2},
// * {"bn":"/1/0/","n":"0","v":123},
// * {"n":"1","v":300},
// * {"n":"6","vb":false},
// * {"n":"22","vs":"U"},
// * {"n":"7","vs":"U"}]
// */
// 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 <R extends DownlinkRequest<T>, T extends LwM2mResponse> void handleDownlinkError(LwM2mClient client, R request, DownlinkRequestCallback<R, T> callback, Exception e) {
log.trace("[{}] Received downlink error: {}.", client.getEndpoint(), e);
executor.submit(() -> {
if (e instanceof TimeoutException || e instanceof ClientSleepingException) {
log.trace("[{}] Received {}, client is probably sleeping", client.getEndpoint(), e.getClass().getSimpleName());
clientContext.asleep(client);
} else {
log.trace("[{}] Received {}", client.getEndpoint(), e.getClass().getSimpleName());
}
callback.onError(toString(request), e);
});
}
private WriteRequest getWriteRequestSingleResource(ResourceModel.Type type, ContentFormat contentFormat, int objectId, int instanceId, int resourceId, Object value) {
switch (type) {
@ -483,10 +457,22 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im
String id = fromVersionedIdToObjectId(versionedId);
if (id != null && new LwM2mPath(id).isResource() && !client.isResourceMultiInstances(versionedId, modelProvider)) {
return client.getDefaultContentFormat();
}
else {
} else {
return ContentFormat.DEFAULT;
}
}
}
private <R> String toString(R request) {
try {
try {
return JacksonUtil.toString(request);
} catch (Exception e) {
return request.toString();
}
} catch (Exception e) {
log.warn("Failed to convert request to string", e);
return request != null ? request.getClass().getSimpleName() : "";
}
}
}

46
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java

@ -80,53 +80,53 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
private final Map<UUID, Long> rpcSubscriptions = new ConcurrentHashMap<>();
@Override
public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg rpcRequst, TransportProtos.SessionInfoProto sessionInfo) {
public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg rpcRequest, TransportProtos.SessionInfoProto sessionInfo) {
this.cleanupOldSessions();
UUID requestUUID = new UUID(rpcRequst.getRequestIdMSB(), rpcRequst.getRequestIdLSB());
log.debug("Received params: {}", rpcRequst.getParams());
UUID requestUUID = new UUID(rpcRequest.getRequestIdMSB(), rpcRequest.getRequestIdLSB());
log.debug("Received params: {}", rpcRequest.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());
LwM2mOperationType operationType = LwM2mOperationType.fromType(rpcRequest.getMethodName());
if (operationType == null) {
this.sendErrorRpcResponse(sessionInfo, rpcRequst.getRequestId(), ResponseCode.METHOD_NOT_ALLOWED.getName(), "Unsupported operation type: " + rpcRequst.getMethodName());
this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(), ResponseCode.METHOD_NOT_ALLOWED.getName(), "Unsupported operation type: " + rpcRequest.getMethodName());
return;
}
LwM2mClient client = clientContext.getClientBySessionInfo(sessionInfo);
if (client.getRegistration() == null) {
this.sendErrorRpcResponse(sessionInfo, rpcRequst.getRequestId(), ResponseCode.INTERNAL_SERVER_ERROR.getName(), "Registration is empty");
this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(), ResponseCode.INTERNAL_SERVER_ERROR.getName(), "Registration is empty");
return;
}
try {
if (operationType.isHasObjectId()) {
String objectId = getIdFromParameters(client, rpcRequst);
String objectId = getIdFromParameters(client, rpcRequest);
switch (operationType) {
case READ:
sendReadRequest(client, rpcRequst, objectId);
sendReadRequest(client, rpcRequest, objectId);
break;
case OBSERVE:
sendObserveRequest(client, rpcRequst, objectId);
sendObserveRequest(client, rpcRequest, objectId);
break;
case DISCOVER:
sendDiscoverRequest(client, rpcRequst, objectId);
sendDiscoverRequest(client, rpcRequest, objectId);
break;
case EXECUTE:
sendExecuteRequest(client, rpcRequst, objectId);
sendExecuteRequest(client, rpcRequest, objectId);
break;
case WRITE_ATTRIBUTES:
sendWriteAttributesRequest(client, rpcRequst, objectId);
sendWriteAttributesRequest(client, rpcRequest, objectId);
break;
case OBSERVE_CANCEL:
sendCancelObserveRequest(client, rpcRequst, objectId);
sendCancelObserveRequest(client, rpcRequest, objectId);
break;
case DELETE:
sendDeleteRequest(client, rpcRequst, objectId);
sendDeleteRequest(client, rpcRequest, objectId);
break;
case WRITE_UPDATE:
sendWriteUpdateRequest(client, rpcRequst, objectId);
sendWriteUpdateRequest(client, rpcRequest, objectId);
break;
case WRITE_REPLACE:
sendWriteReplaceRequest(client, rpcRequst, objectId);
sendWriteReplaceRequest(client, rpcRequest, objectId);
break;
default:
throw new IllegalArgumentException("Unsupported operation: " + operationType.name());
@ -135,28 +135,28 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
if (clientContext.isComposite(client)) {
switch (operationType) {
case READ_COMPOSITE:
sendReadCompositeRequest(client, rpcRequst);
sendReadCompositeRequest(client, rpcRequest);
break;
case WRITE_COMPOSITE:
sendWriteCompositeRequest(client, rpcRequst);
sendWriteCompositeRequest(client, rpcRequest);
break;
default:
throw new IllegalArgumentException("Unsupported operation: " + operationType.name());
}
} else {
this.sendErrorRpcResponse(sessionInfo, rpcRequst.getRequestId(),
this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(),
ResponseCode.INTERNAL_SERVER_ERROR.getName(), "This device does not support Composite Operation");
}
} else {
switch (operationType) {
case OBSERVE_CANCEL_ALL:
sendCancelAllObserveRequest(client, rpcRequst);
sendCancelAllObserveRequest(client, rpcRequest);
break;
case OBSERVE_READ_ALL:
sendObserveAllRequest(client, rpcRequst);
sendObserveAllRequest(client, rpcRequest);
break;
case DISCOVER_ALL:
sendDiscoverAllRequest(client, rpcRequst);
sendDiscoverAllRequest(client, rpcRequest);
break;
case FW_UPDATE:
//TODO: implement and add break statement
@ -165,7 +165,7 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler {
}
}
} catch (IllegalArgumentException e) {
this.sendErrorRpcResponse(sessionInfo, rpcRequst.getRequestId(), ResponseCode.BAD_REQUEST.getName(), e.getMessage());
this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(), ResponseCode.BAD_REQUEST.getName(), e.getMessage());
}
}
}

75
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java

@ -213,7 +213,8 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
log.info("[{}] Closing old session: {}", registration.getEndpoint(), new UUID(oldSessionInfo.get().getSessionIdMSB(), oldSessionInfo.get().getSessionIdLSB()));
sessionManager.deregister(oldSessionInfo.get());
}
logService.log(lwM2MClient, LOG_LWM2M_INFO + ": Client registered with registration id: " + registration.getId());
logService.log(lwM2MClient, LOG_LWM2M_INFO + ": Client registered with registration id: " + registration.getId() + " version: "
+ registration.getLwM2mVersion() + " and modes: " + registration.getQueueMode() + ", " + registration.getBindingMode());
sessionManager.register(lwM2MClient.getSession());
this.initClientTelemetry(lwM2MClient);
this.initAttributes(lwM2MClient);
@ -288,9 +289,8 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
@Override
public void onSleepingDev(Registration registration) {
log.info("[{}] [{}] Received endpoint Sleeping version event", registration.getId(), registration.getEndpoint());
logService.log(clientContext.getClientByEndpoint(registration.getEndpoint()), LOG_LWM2M_INFO + ": Client is sleeping!");
//TODO: associate endpointId with device information.
log.debug("[{}] [{}] Received endpoint sleeping event", registration.getId(), registration.getEndpoint());
clientContext.asleep(clientContext.getClientByEndpoint(registration.getEndpoint()));
}
/**
@ -318,6 +318,12 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
}
}
clientContext.update(lwM2MClient);
if (clientContext.awake(lwM2MClient)) {
// clientContext.awake calls clientContext.update
log.debug("[{}] Device is awake", lwM2MClient.getEndpoint());
} else {
clientContext.update(lwM2MClient);
}
}
}
@ -336,6 +342,13 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
}
}
});
clientContext.update(lwM2MClient);
if (clientContext.awake(lwM2MClient)) {
// clientContext.awake calls clientContext.update
log.debug("[{}] Device is awake", lwM2MClient.getEndpoint());
} else {
clientContext.update(lwM2MClient);
}
}
}
@ -390,10 +403,8 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
*/
@Override
public void onAwakeDev(Registration registration) {
log.trace("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint());
LwM2mClient client = this.clientContext.getClientByEndpoint(registration.getEndpoint());
logService.log(client, LOG_LWM2M_INFO + ": Client is awake!");
clientContext.sendMsgsAfterSleeping(client);
log.debug("[{}] [{}] Received endpoint awake event", registration.getId(), registration.getEndpoint());
clientContext.awake(clientContext.getClientByEndpoint(registration.getEndpoint()));
}
/**
@ -420,41 +431,51 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl
}
private void sendReadRequests(LwM2mClient lwM2MClient, Lwm2mDeviceProfileTransportConfiguration profile, Set<String> supportedObjects) {
Set<String> targetIds = new HashSet<>(profile.getObserveAttr().getAttribute());
targetIds.addAll(profile.getObserveAttr().getTelemetry());
targetIds = diffSets(profile.getObserveAttr().getObserve(), targetIds);
targetIds = targetIds.stream().filter(target -> isSupportedTargetId(supportedObjects, target)).collect(Collectors.toSet());
CountDownLatch latch = new CountDownLatch(targetIds.size());
targetIds.forEach(versionedId -> sendReadRequest(lwM2MClient, versionedId,
new TbLwM2MLatchCallback<>(latch, new TbLwM2MReadCallback(this, logService, lwM2MClient, versionedId))));
try {
Set<String> targetIds = new HashSet<>(profile.getObserveAttr().getAttribute());
targetIds.addAll(profile.getObserveAttr().getTelemetry());
targetIds = diffSets(profile.getObserveAttr().getObserve(), targetIds);
targetIds = targetIds.stream().filter(target -> isSupportedTargetId(supportedObjects, target)).collect(Collectors.toSet());
CountDownLatch latch = new CountDownLatch(targetIds.size());
targetIds.forEach(versionedId -> sendReadRequest(lwM2MClient, versionedId,
new TbLwM2MLatchCallback<>(latch, new TbLwM2MReadCallback(this, logService, lwM2MClient, versionedId))));
latch.await();
} catch (InterruptedException e) {
log.error("[{}] Failed to await Read requests!", lwM2MClient.getEndpoint());
} catch (Exception e) {
log.error("[{}] Failed to process read requests!", lwM2MClient.getEndpoint(), e);
logService.log(lwM2MClient, "Failed to process read requests. Possible profile misconfiguration.");
}
}
private void sendObserveRequests(LwM2mClient lwM2MClient, Lwm2mDeviceProfileTransportConfiguration profile, Set<String> supportedObjects) {
Set<String> targetIds = profile.getObserveAttr().getObserve();
targetIds = targetIds.stream().filter(target -> isSupportedTargetId(supportedObjects, target)).collect(Collectors.toSet());
CountDownLatch latch = new CountDownLatch(targetIds.size());
targetIds.forEach(targetId -> sendObserveRequest(lwM2MClient, targetId,
new TbLwM2MLatchCallback<>(latch, new TbLwM2MObserveCallback(this, logService, lwM2MClient, targetId))));
try {
Set<String> targetIds = profile.getObserveAttr().getObserve();
targetIds = targetIds.stream().filter(target -> isSupportedTargetId(supportedObjects, target)).collect(Collectors.toSet());
CountDownLatch latch = new CountDownLatch(targetIds.size());
targetIds.forEach(targetId -> sendObserveRequest(lwM2MClient, targetId,
new TbLwM2MLatchCallback<>(latch, new TbLwM2MObserveCallback(this, logService, lwM2MClient, targetId))));
latch.await();
} catch (InterruptedException e) {
log.error("[{}] Failed to await Observe requests!", lwM2MClient.getEndpoint());
} catch (Exception e) {
log.error("[{}] Failed to process observe requests!", lwM2MClient.getEndpoint(), e);
logService.log(lwM2MClient, "Failed to process observe requests. Possible profile misconfiguration.");
}
}
private void sendWriteAttributeRequests(LwM2mClient lwM2MClient, Lwm2mDeviceProfileTransportConfiguration profile, Set<String> supportedObjects) {
Map<String, ObjectAttributes> attributesMap = profile.getObserveAttr().getAttributeLwm2m();
attributesMap = attributesMap.entrySet().stream().filter(target -> isSupportedTargetId(supportedObjects, target.getKey())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
// TODO: why do we need to put observe into pending read requests?
// lwM2MClient.getPendingReadRequests().addAll(targetIds);
attributesMap.forEach((targetId, params) -> sendWriteAttributesRequest(lwM2MClient, targetId, params));
try {
Map<String, ObjectAttributes> attributesMap = profile.getObserveAttr().getAttributeLwm2m();
attributesMap = attributesMap.entrySet().stream().filter(target -> isSupportedTargetId(supportedObjects, target.getKey())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
attributesMap.forEach((targetId, params) -> sendWriteAttributesRequest(lwM2MClient, targetId, params));
} catch (Exception e) {
log.error("[{}] Failed to process write attribute requests!", lwM2MClient.getEndpoint(), e);
logService.log(lwM2MClient, "Failed to process write attribute requests. Possible profile misconfiguration.");
}
}
private void sendDiscoverRequest(LwM2mClient lwM2MClient, String targetId) {

2
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java

@ -46,7 +46,7 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
pipeline.addLast("decoder", new MqttDecoder(context.getMaxPayloadSize()));
pipeline.addLast("encoder", MqttEncoder.INSTANCE);
MqttTransportHandler handler = new MqttTransportHandler(context,sslHandler);
MqttTransportHandler handler = new MqttTransportHandler(context, sslHandler);
pipeline.addLast(handler);
ch.closeFuture().addListener(handler);

2
transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml

@ -143,6 +143,8 @@ transport:
ota_pool_size: "${LWM2M_OTA_POOL_SIZE:10}"
clean_period_in_sec: "${LWM2M_CLEAN_PERIOD_IN_SEC:2}"
log_max_length: "${LWM2M_LOG_MAX_LENGTH:1024}"
psm_activity_timer: "${LWM2M_PSM_ACTIVITY_TIMER:10000}"
paging_transmission_window: "${LWM2M_PAGING_TRANSMISSION_WINDOW:10000}"
# Use redis for Security and Registration stores
redis.enabled: "${LWM2M_REDIS_ENABLED:false}"

Loading…
Cancel
Save