Commit 3a7a97dd9171296d246c524d44e55960a3540c4c

Authored by Andrii Shvaika
Committed by Andrew Shvayka
1 parent 4364755e

PSM and eDRX implementation draft

@@ -228,57 +228,57 @@ cassandra: @@ -228,57 +228,57 @@ cassandra:
228 228
229 # SQL configuration parameters 229 # SQL configuration parameters
230 sql: 230 sql:
231 - # Specify batch size for persisting attribute updates  
232 - attributes:  
233 - batch_size: "${SQL_ATTRIBUTES_BATCH_SIZE:10000}"  
234 - batch_max_delay: "${SQL_ATTRIBUTES_BATCH_MAX_DELAY_MS:100}"  
235 - stats_print_interval_ms: "${SQL_ATTRIBUTES_BATCH_STATS_PRINT_MS:10000}"  
236 - batch_threads: "${SQL_ATTRIBUTES_BATCH_THREADS:4}"  
237 - ts:  
238 - batch_size: "${SQL_TS_BATCH_SIZE:10000}"  
239 - batch_max_delay: "${SQL_TS_BATCH_MAX_DELAY_MS:100}"  
240 - stats_print_interval_ms: "${SQL_TS_BATCH_STATS_PRINT_MS:10000}"  
241 - batch_threads: "${SQL_TS_BATCH_THREADS:4}"  
242 - ts_latest:  
243 - batch_size: "${SQL_TS_LATEST_BATCH_SIZE:10000}"  
244 - batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}"  
245 - stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}"  
246 - batch_threads: "${SQL_TS_LATEST_BATCH_THREADS:4}"  
247 - update_by_latest_ts: "${SQL_TS_UPDATE_BY_LATEST_TIMESTAMP:true}"  
248 - # Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks  
249 - batch_sort: "${SQL_BATCH_SORT:false}"  
250 - # Specify whether to remove null characters from strValue of attributes and timeseries before insert  
251 - remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}" 231 + # Specify batch size for persisting attribute updates
  232 + attributes:
  233 + batch_size: "${SQL_ATTRIBUTES_BATCH_SIZE:10000}"
  234 + batch_max_delay: "${SQL_ATTRIBUTES_BATCH_MAX_DELAY_MS:100}"
  235 + stats_print_interval_ms: "${SQL_ATTRIBUTES_BATCH_STATS_PRINT_MS:10000}"
  236 + batch_threads: "${SQL_ATTRIBUTES_BATCH_THREADS:4}"
  237 + ts:
  238 + batch_size: "${SQL_TS_BATCH_SIZE:10000}"
  239 + batch_max_delay: "${SQL_TS_BATCH_MAX_DELAY_MS:100}"
  240 + stats_print_interval_ms: "${SQL_TS_BATCH_STATS_PRINT_MS:10000}"
  241 + batch_threads: "${SQL_TS_BATCH_THREADS:4}"
  242 + ts_latest:
  243 + batch_size: "${SQL_TS_LATEST_BATCH_SIZE:10000}"
  244 + batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}"
  245 + stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}"
  246 + batch_threads: "${SQL_TS_LATEST_BATCH_THREADS:4}"
  247 + update_by_latest_ts: "${SQL_TS_UPDATE_BY_LATEST_TIMESTAMP:true}"
  248 + # Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks
  249 + batch_sort: "${SQL_BATCH_SORT:false}"
  250 + # Specify whether to remove null characters from strValue of attributes and timeseries before insert
  251 + remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}"
252 # Specify whether to log database queries and their parameters generated by entity query repository 252 # Specify whether to log database queries and their parameters generated by entity query repository
253 - log_queries: "${SQL_LOG_QUERIES:false}"  
254 - log_queries_threshold: "${SQL_LOG_QUERIES_THRESHOLD:5000}"  
255 - postgres:  
256 - # Specify partitioning size for timestamp key-value storage. Example: DAYS, MONTHS, YEARS, INDEFINITE.  
257 - ts_key_value_partitioning: "${SQL_POSTGRES_TS_KV_PARTITIONING:MONTHS}"  
258 - timescale:  
259 - # Specify Interval size for new data chunks storage.  
260 - chunk_time_interval: "${SQL_TIMESCALE_CHUNK_TIME_INTERVAL:604800000}"  
261 - batch_threads: "${SQL_TIMESCALE_BATCH_THREADS:4}"  
262 - ttl:  
263 - ts:  
264 - enabled: "${SQL_TTL_TS_ENABLED:true}"  
265 - execution_interval_ms: "${SQL_TTL_TS_EXECUTION_INTERVAL:86400000}" # Number of milliseconds. The current value corresponds to one day  
266 - ts_key_value_ttl: "${SQL_TTL_TS_TS_KEY_VALUE_TTL:0}" # Number of seconds  
267 - events:  
268 - enabled: "${SQL_TTL_EVENTS_ENABLED:true}"  
269 - execution_interval_ms: "${SQL_TTL_EVENTS_EXECUTION_INTERVAL:2220000}" # Number of milliseconds (max random initial delay and fixed period). # 37minutes to avoid common interval spikes  
270 - events_ttl: "${SQL_TTL_EVENTS_EVENTS_TTL:0}" # Number of seconds  
271 - debug_events_ttl: "${SQL_TTL_EVENTS_DEBUG_EVENTS_TTL:604800}" # Number of seconds. The current value corresponds to one week  
272 - edge_events:  
273 - enabled: "${SQL_TTL_EDGE_EVENTS_ENABLED:true}"  
274 - execution_interval_ms: "${SQL_TTL_EDGE_EVENTS_EXECUTION_INTERVAL:86400000}" # Number of milliseconds. The current value corresponds to one day  
275 - edge_events_ttl: "${SQL_TTL_EDGE_EVENTS_TTL:2628000}" # Number of seconds. The current value corresponds to one month  
276 - alarms:  
277 - checking_interval: "${SQL_ALARMS_TTL_CHECKING_INTERVAL:7200000}" # Number of milliseconds. The current value corresponds to two hours  
278 - removal_batch_size: "${SQL_ALARMS_TTL_REMOVAL_BATCH_SIZE:3000}" # To delete outdated alarms not all at once but in batches  
279 - rpc:  
280 - enabled: "${SQL_TTL_RPC_ENABLED:true}"  
281 - checking_interval: "${SQL_RPC_TTL_CHECKING_INTERVAL:7200000}" # Number of milliseconds. The current value corresponds to two hours 253 + log_queries: "${SQL_LOG_QUERIES:false}"
  254 + log_queries_threshold: "${SQL_LOG_QUERIES_THRESHOLD:5000}"
  255 + postgres:
  256 + # Specify partitioning size for timestamp key-value storage. Example: DAYS, MONTHS, YEARS, INDEFINITE.
  257 + ts_key_value_partitioning: "${SQL_POSTGRES_TS_KV_PARTITIONING:MONTHS}"
  258 + timescale:
  259 + # Specify Interval size for new data chunks storage.
  260 + chunk_time_interval: "${SQL_TIMESCALE_CHUNK_TIME_INTERVAL:604800000}"
  261 + batch_threads: "${SQL_TIMESCALE_BATCH_THREADS:4}"
  262 + ttl:
  263 + ts:
  264 + enabled: "${SQL_TTL_TS_ENABLED:true}"
  265 + execution_interval_ms: "${SQL_TTL_TS_EXECUTION_INTERVAL:86400000}" # Number of milliseconds. The current value corresponds to one day
  266 + ts_key_value_ttl: "${SQL_TTL_TS_TS_KEY_VALUE_TTL:0}" # Number of seconds
  267 + events:
  268 + enabled: "${SQL_TTL_EVENTS_ENABLED:true}"
  269 + execution_interval_ms: "${SQL_TTL_EVENTS_EXECUTION_INTERVAL:2220000}" # Number of milliseconds (max random initial delay and fixed period). # 37minutes to avoid common interval spikes
  270 + events_ttl: "${SQL_TTL_EVENTS_EVENTS_TTL:0}" # Number of seconds
  271 + debug_events_ttl: "${SQL_TTL_EVENTS_DEBUG_EVENTS_TTL:604800}" # Number of seconds. The current value corresponds to one week
  272 + edge_events:
  273 + enabled: "${SQL_TTL_EDGE_EVENTS_ENABLED:true}"
  274 + execution_interval_ms: "${SQL_TTL_EDGE_EVENTS_EXECUTION_INTERVAL:86400000}" # Number of milliseconds. The current value corresponds to one day
  275 + edge_events_ttl: "${SQL_TTL_EDGE_EVENTS_TTL:2628000}" # Number of seconds. The current value corresponds to one month
  276 + alarms:
  277 + checking_interval: "${SQL_ALARMS_TTL_CHECKING_INTERVAL:7200000}" # Number of milliseconds. The current value corresponds to two hours
  278 + removal_batch_size: "${SQL_ALARMS_TTL_REMOVAL_BATCH_SIZE:3000}" # To delete outdated alarms not all at once but in batches
  279 + rpc:
  280 + enabled: "${SQL_TTL_RPC_ENABLED:true}"
  281 + checking_interval: "${SQL_RPC_TTL_CHECKING_INTERVAL:7200000}" # Number of milliseconds. The current value corresponds to two hours
282 282
283 # Actor system parameters 283 # Actor system parameters
284 actors: 284 actors:
@@ -573,6 +573,9 @@ transport: @@ -573,6 +573,9 @@ transport:
573 timeout: "${CLIENT_SIDE_RPC_TIMEOUT:60000}" 573 timeout: "${CLIENT_SIDE_RPC_TIMEOUT:60000}"
574 # Enable/disable http/mqtt/coap transport protocols (has higher priority than certain protocol's 'enabled' property) 574 # Enable/disable http/mqtt/coap transport protocols (has higher priority than certain protocol's 'enabled' property)
575 api_enabled: "${TB_TRANSPORT_API_ENABLED:true}" 575 api_enabled: "${TB_TRANSPORT_API_ENABLED:true}"
  576 + log:
  577 + enabled: "${TB_TRANSPORT_LOG_ENABLED:true}"
  578 + max_length: "${TB_TRANSPORT_LOG_MAX_LENGTH:1024}"
576 # Local HTTP transport parameters 579 # Local HTTP transport parameters
577 http: 580 http:
578 enabled: "${HTTP_ENABLED:true}" 581 enabled: "${HTTP_ENABLED:true}"
@@ -618,6 +621,8 @@ transport: @@ -618,6 +621,8 @@ transport:
618 bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" 621 bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}"
619 bind_port: "${COAP_BIND_PORT:5683}" 622 bind_port: "${COAP_BIND_PORT:5683}"
620 timeout: "${COAP_TIMEOUT:10000}" 623 timeout: "${COAP_TIMEOUT:10000}"
  624 + psm_activity_timer: "${COAP_PSM_ACTIVITY_TIMER:10000}"
  625 + paging_transmission_window: "${COAP_PAGING_TRANSMISSION_WINDOW:10000}"
621 dtls: 626 dtls:
622 # Enable/disable DTLS 1.2 support 627 # Enable/disable DTLS 1.2 support
623 enabled: "${COAP_DTLS_ENABLED:false}" 628 enabled: "${COAP_DTLS_ENABLED:false}"
@@ -39,6 +39,14 @@ public class CoapServerContext { @@ -39,6 +39,14 @@ public class CoapServerContext {
39 private Long timeout; 39 private Long timeout;
40 40
41 @Getter 41 @Getter
  42 + @Value("${transport.coap.psm_activity_timer:10000}")
  43 + private long psmActivityTimer;
  44 +
  45 + @Getter
  46 + @Value("${transport.coap.paging_transmission_window:10000}")
  47 + private long pagingTransmissionWindow;
  48 +
  49 + @Getter
42 @Autowired(required = false) 50 @Autowired(required = false)
43 private TbCoapDtlsSettings dtlsSettings; 51 private TbCoapDtlsSettings dtlsSettings;
44 52
@@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.device.data.lwm2m; @@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.device.data.lwm2m;
17 17
18 import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 18 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
19 import lombok.Data; 19 import lombok.Data;
  20 +import org.thingsboard.server.common.data.device.data.PowerMode;
20 import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration; 21 import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration;
21 22
22 @Data 23 @Data
@@ -26,6 +27,10 @@ public class OtherConfiguration extends PowerSavingConfiguration { @@ -26,6 +27,10 @@ public class OtherConfiguration extends PowerSavingConfiguration {
26 private Integer fwUpdateStrategy; 27 private Integer fwUpdateStrategy;
27 private Integer swUpdateStrategy; 28 private Integer swUpdateStrategy;
28 private Integer clientOnlyObserveAfterConnect; 29 private Integer clientOnlyObserveAfterConnect;
  30 + private PowerMode powerMode;
  31 + private Long psmActivityTimer;
  32 + private Long edrxCycle;
  33 + private Long pagingTransmissionWindow;
29 private String fwUpdateResource; 34 private String fwUpdateResource;
30 private String swUpdateResource; 35 private String swUpdateResource;
31 private boolean compositeOperationsSupport; 36 private boolean compositeOperationsSupport;
@@ -117,6 +117,8 @@ message DeviceInfoProto { @@ -117,6 +117,8 @@ message DeviceInfoProto {
117 int64 customerIdLSB = 11; 117 int64 customerIdLSB = 11;
118 string powerMode = 12; 118 string powerMode = 12;
119 int64 edrxCycle = 13; 119 int64 edrxCycle = 13;
  120 + int64 psmActivityTimer = 14;
  121 + int64 pagingTransmissionWindow = 15;
120 } 122 }
121 123
122 /** 124 /**
@@ -234,7 +234,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { @@ -234,7 +234,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
234 TbCoapClientState clientState = null; 234 TbCoapClientState clientState = null;
235 try { 235 try {
236 clientState = clients.getOrCreateClient(type, deviceCredentials, deviceProfile); 236 clientState = clients.getOrCreateClient(type, deviceCredentials, deviceProfile);
237 - clientState.updateLastUplinkTime(); 237 + clients.awake(clientState);
238 switch (type) { 238 switch (type) {
239 case POST_ATTRIBUTES_REQUEST: 239 case POST_ATTRIBUTES_REQUEST:
240 handlePostAttributesRequest(clientState, exchange, request); 240 handlePostAttributesRequest(clientState, exchange, request);
@@ -27,6 +27,7 @@ public class TbCoapMessageObserver implements MessageObserver { @@ -27,6 +27,7 @@ public class TbCoapMessageObserver implements MessageObserver {
27 27
28 private final int msgId; 28 private final int msgId;
29 private final Consumer<Integer> onAcknowledge; 29 private final Consumer<Integer> onAcknowledge;
  30 + private final Consumer<Integer> onTimeout;
30 31
31 @Override 32 @Override
32 public void onRetransmission() { 33 public void onRetransmission() {
@@ -50,7 +51,9 @@ public class TbCoapMessageObserver implements MessageObserver { @@ -50,7 +51,9 @@ public class TbCoapMessageObserver implements MessageObserver {
50 51
51 @Override 52 @Override
52 public void onTimeout() { 53 public void onTimeout() {
53 - 54 + if (onTimeout != null) {
  55 + onTimeout.accept(msgId);
  56 + }
54 } 57 }
55 58
56 @Override 59 @Override
@@ -46,4 +46,6 @@ public interface CoapClientContext { @@ -46,4 +46,6 @@ public interface CoapClientContext {
46 void registerObserveRelation(String token, ObserveRelation relation); 46 void registerObserveRelation(String token, ObserveRelation relation);
47 47
48 void deregisterObserveRelation(String token); 48 void deregisterObserveRelation(String token);
  49 +
  50 + boolean awake(TbCoapClientState client);
49 } 51 }
@@ -22,8 +22,13 @@ import org.eclipse.californium.core.coap.Response; @@ -22,8 +22,13 @@ import org.eclipse.californium.core.coap.Response;
22 import org.eclipse.californium.core.observe.ObserveRelation; 22 import org.eclipse.californium.core.observe.ObserveRelation;
23 import org.eclipse.californium.core.server.resources.CoapExchange; 23 import org.eclipse.californium.core.server.resources.CoapExchange;
24 import org.springframework.stereotype.Service; 24 import org.springframework.stereotype.Service;
  25 +import org.thingsboard.server.coapserver.CoapServerContext;
25 import org.thingsboard.server.coapserver.TbCoapServerComponent; 26 import org.thingsboard.server.coapserver.TbCoapServerComponent;
  27 +import org.thingsboard.server.common.data.Device;
26 import org.thingsboard.server.common.data.DeviceProfile; 28 import org.thingsboard.server.common.data.DeviceProfile;
  29 +import org.thingsboard.server.common.data.DeviceTransportType;
  30 +import org.thingsboard.server.common.data.device.data.PowerMode;
  31 +import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration;
27 import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; 32 import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration;
28 import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; 33 import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration;
29 import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration; 34 import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration;
@@ -33,9 +38,11 @@ import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadCon @@ -33,9 +38,11 @@ import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadCon
33 import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; 38 import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration;
34 import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; 39 import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration;
35 import org.thingsboard.server.common.data.id.DeviceId; 40 import org.thingsboard.server.common.data.id.DeviceId;
  41 +import org.thingsboard.server.common.data.id.DeviceProfileId;
36 import org.thingsboard.server.common.msg.session.FeatureType; 42 import org.thingsboard.server.common.msg.session.FeatureType;
37 import org.thingsboard.server.common.msg.session.SessionMsgType; 43 import org.thingsboard.server.common.msg.session.SessionMsgType;
38 import org.thingsboard.server.common.transport.SessionMsgListener; 44 import org.thingsboard.server.common.transport.SessionMsgListener;
  45 +import org.thingsboard.server.common.transport.TransportDeviceProfileCache;
39 import org.thingsboard.server.common.transport.TransportService; 46 import org.thingsboard.server.common.transport.TransportService;
40 import org.thingsboard.server.common.transport.TransportServiceCallback; 47 import org.thingsboard.server.common.transport.TransportServiceCallback;
41 import org.thingsboard.server.common.transport.adaptor.AdaptorException; 48 import org.thingsboard.server.common.transport.adaptor.AdaptorException;
@@ -50,9 +57,11 @@ import org.thingsboard.server.transport.coap.callback.AbstractSyncSessionCallbac @@ -50,9 +57,11 @@ import org.thingsboard.server.transport.coap.callback.AbstractSyncSessionCallbac
50 import org.thingsboard.server.transport.coap.callback.CoapNoOpCallback; 57 import org.thingsboard.server.transport.coap.callback.CoapNoOpCallback;
51 import org.thingsboard.server.transport.coap.callback.CoapOkCallback; 58 import org.thingsboard.server.transport.coap.callback.CoapOkCallback;
52 59
  60 +import java.util.Optional;
53 import java.util.UUID; 61 import java.util.UUID;
54 import java.util.concurrent.ConcurrentHashMap; 62 import java.util.concurrent.ConcurrentHashMap;
55 import java.util.concurrent.ConcurrentMap; 63 import java.util.concurrent.ConcurrentMap;
  64 +import java.util.concurrent.Future;
56 import java.util.concurrent.ThreadLocalRandom; 65 import java.util.concurrent.ThreadLocalRandom;
57 import java.util.concurrent.TimeUnit; 66 import java.util.concurrent.TimeUnit;
58 import java.util.concurrent.atomic.AtomicInteger; 67 import java.util.concurrent.atomic.AtomicInteger;
@@ -66,8 +75,10 @@ import static org.eclipse.californium.core.coap.Message.NONE; @@ -66,8 +75,10 @@ import static org.eclipse.californium.core.coap.Message.NONE;
66 @TbCoapServerComponent 75 @TbCoapServerComponent
67 public class DefaultCoapClientContext implements CoapClientContext { 76 public class DefaultCoapClientContext implements CoapClientContext {
68 77
  78 + private final CoapServerContext config;
69 private final CoapTransportContext transportContext; 79 private final CoapTransportContext transportContext;
70 private final TransportService transportService; 80 private final TransportService transportService;
  81 + private final TransportDeviceProfileCache profileCache;
71 private final ConcurrentMap<DeviceId, TbCoapClientState> clients = new ConcurrentHashMap<>(); 82 private final ConcurrentMap<DeviceId, TbCoapClientState> clients = new ConcurrentHashMap<>();
72 private final ConcurrentMap<String, TbCoapClientState> clientsByToken = new ConcurrentHashMap<>(); 83 private final ConcurrentMap<String, TbCoapClientState> clientsByToken = new ConcurrentHashMap<>();
73 84
@@ -148,6 +159,65 @@ public class DefaultCoapClientContext implements CoapClientContext { @@ -148,6 +159,65 @@ public class DefaultCoapClientContext implements CoapClientContext {
148 } 159 }
149 } 160 }
150 161
  162 + private void onUplink(TbCoapClientState client) {
  163 + PowerMode powerMode = client.getPowerMode();
  164 + PowerSavingConfiguration profileSettings = null;
  165 + if (powerMode == null) {
  166 + var clientProfile = getProfile(client.getProfileId());
  167 + if (clientProfile.isPresent()) {
  168 + profileSettings = clientProfile.get().getClientSettings();
  169 + powerMode = profileSettings.getPowerMode();
  170 + if (powerMode == null) {
  171 + powerMode = PowerMode.DRX;
  172 + }
  173 + }
  174 + }
  175 + if (PowerMode.DRX.equals(powerMode)) {
  176 + client.updateLastUplinkTime();
  177 + return;
  178 + }
  179 + client.lock();
  180 + try {
  181 + long uplinkTime = client.updateLastUplinkTime();
  182 + long timeout;
  183 + if (PowerMode.PSM.equals(powerMode)) {
  184 + Long psmActivityTimer = client.getPsmActivityTimer();
  185 + if (psmActivityTimer == null && profileSettings != null) {
  186 + psmActivityTimer = profileSettings.getPsmActivityTimer();
  187 +
  188 + }
  189 + if (psmActivityTimer == null || psmActivityTimer == 0L) {
  190 + psmActivityTimer = config.getPsmActivityTimer();
  191 + }
  192 +
  193 + timeout = psmActivityTimer;
  194 + } else {
  195 + Long pagingTransmissionWindow = client.getPagingTransmissionWindow();
  196 + if (pagingTransmissionWindow == null && profileSettings != null) {
  197 + pagingTransmissionWindow = profileSettings.getPagingTransmissionWindow();
  198 +
  199 + }
  200 + if (pagingTransmissionWindow == null || pagingTransmissionWindow == 0L) {
  201 + pagingTransmissionWindow = config.getPagingTransmissionWindow();
  202 + }
  203 + timeout = pagingTransmissionWindow;
  204 + }
  205 + Future<Void> sleepTask = client.getSleepTask();
  206 + if (sleepTask != null) {
  207 + sleepTask.cancel(false);
  208 + }
  209 + Future<Void> task = transportContext.getScheduler().schedule(() -> {
  210 + if (uplinkTime == client.getLastUplinkTime()) {
  211 + asleep(client);
  212 + }
  213 + return null;
  214 + }, timeout, TimeUnit.MILLISECONDS);
  215 + client.setSleepTask(task);
  216 + } finally {
  217 + client.unlock();
  218 + }
  219 + }
  220 +
151 private boolean registerFeatureObservation(TbCoapClientState state, String token, CoapExchange exchange, FeatureType featureType) { 221 private boolean registerFeatureObservation(TbCoapClientState state, String token, CoapExchange exchange, FeatureType featureType) {
152 state.lock(); 222 state.lock();
153 try { 223 try {
@@ -182,7 +252,9 @@ public class DefaultCoapClientContext implements CoapClientContext { @@ -182,7 +252,9 @@ public class DefaultCoapClientContext implements CoapClientContext {
182 if (state.getSession() == null) { 252 if (state.getSession() == null) {
183 TransportProtos.SessionInfoProto session = SessionInfoCreator.create(state.getCredentials(), transportContext, UUID.randomUUID()); 253 TransportProtos.SessionInfoProto session = SessionInfoCreator.create(state.getCredentials(), transportContext, UUID.randomUUID());
184 state.setSession(session); 254 state.setSession(session);
185 - transportService.registerAsyncSession(session, new CoapSessionListener(state)); 255 + CoapSessionListener listener = new CoapSessionListener(state);
  256 + state.setListener(listener);
  257 + transportService.registerAsyncSession(session, state.getListener());
186 transportService.process(session, getSessionEventMsg(TransportProtos.SessionEvent.OPEN), null); 258 transportService.process(session, getSessionEventMsg(TransportProtos.SessionEvent.OPEN), null);
187 } 259 }
188 if (FeatureType.ATTRIBUTES.equals(featureType)) { 260 if (FeatureType.ATTRIBUTES.equals(featureType)) {
@@ -261,7 +333,7 @@ public class DefaultCoapClientContext implements CoapClientContext { @@ -261,7 +333,7 @@ public class DefaultCoapClientContext implements CoapClientContext {
261 state.setAdaptor(getCoapTransportAdaptor(state.getConfiguration().isJsonPayload())); 333 state.setAdaptor(getCoapTransportAdaptor(state.getConfiguration().isJsonPayload()));
262 } 334 }
263 if (state.getCredentials() == null) { 335 if (state.getCredentials() == null) {
264 - state.setCredentials(deviceCredentials); 336 + state.init(deviceCredentials);
265 } 337 }
266 } finally { 338 } finally {
267 state.unlock(); 339 state.unlock();
@@ -278,10 +350,6 @@ public class DefaultCoapClientContext implements CoapClientContext { @@ -278,10 +350,6 @@ public class DefaultCoapClientContext implements CoapClientContext {
278 return clients.computeIfAbsent(deviceId, TbCoapClientState::new); 350 return clients.computeIfAbsent(deviceId, TbCoapClientState::new);
279 } 351 }
280 352
281 - private static DeviceId toDeviceId(TransportProtos.SessionInfoProto s) {  
282 - return new DeviceId(new UUID(s.getDeviceIdMSB(), s.getDeviceIdLSB()));  
283 - }  
284 -  
285 private static TransportProtos.SessionEventMsg getSessionEventMsg(TransportProtos.SessionEvent event) { 353 private static TransportProtos.SessionEventMsg getSessionEventMsg(TransportProtos.SessionEvent event) {
286 return TransportProtos.SessionEventMsg.newBuilder() 354 return TransportProtos.SessionEventMsg.newBuilder()
287 .setSessionType(TransportProtos.SessionType.ASYNC) 355 .setSessionType(TransportProtos.SessionType.ASYNC)
@@ -331,7 +399,7 @@ public class DefaultCoapClientContext implements CoapClientContext { @@ -331,7 +399,7 @@ public class DefaultCoapClientContext implements CoapClientContext {
331 } 399 }
332 400
333 @RequiredArgsConstructor 401 @RequiredArgsConstructor
334 - private class CoapSessionListener implements SessionMsgListener { 402 + public class CoapSessionListener implements SessionMsgListener {
335 403
336 private final TbCoapClientState state; 404 private final TbCoapClientState state;
337 405
@@ -355,13 +423,28 @@ public class DefaultCoapClientContext implements CoapClientContext { @@ -355,13 +423,28 @@ public class DefaultCoapClientContext implements CoapClientContext {
355 423
356 @Override 424 @Override
357 public void onAttributeUpdate(UUID sessionId, TransportProtos.AttributeUpdateNotificationMsg msg) { 425 public void onAttributeUpdate(UUID sessionId, TransportProtos.AttributeUpdateNotificationMsg msg) {
  426 + if (!isDownlinkAllowed(state)) {
  427 + log.trace("[{}] ignore downlink request cause client is sleeping.", state.getDeviceId());
  428 + state.lock();
  429 + try {
  430 + state.addQueuedNotification(msg);
  431 + } finally {
  432 + state.unlock();
  433 + }
  434 + return;
  435 + }
358 log.trace("[{}] Received attributes update notification to device", sessionId); 436 log.trace("[{}] Received attributes update notification to device", sessionId);
359 TbCoapObservationState attrs = state.getAttrs(); 437 TbCoapObservationState attrs = state.getAttrs();
360 if (attrs != null) { 438 if (attrs != null) {
361 try { 439 try {
362 boolean conRequest = AbstractSyncSessionCallback.isConRequest(state.getAttrs()); 440 boolean conRequest = AbstractSyncSessionCallback.isConRequest(state.getAttrs());
  441 + int requestId = getNextMsgId();
363 Response response = state.getAdaptor().convertToPublish(conRequest, msg); 442 Response response = state.getAdaptor().convertToPublish(conRequest, msg);
  443 + response.setMID(requestId);
364 attrs.getExchange().respond(response); 444 attrs.getExchange().respond(response);
  445 + if (conRequest) {
  446 + response.addMessageObserver(new TbCoapMessageObserver(requestId, id -> awake(state), id -> asleep(state)));
  447 + }
365 } catch (AdaptorException e) { 448 } catch (AdaptorException e) {
366 log.trace("[{}] Failed to reply due to error", state.getDeviceId(), e); 449 log.trace("[{}] Failed to reply due to error", state.getDeviceId(), e);
367 cancelObserveRelation(attrs); 450 cancelObserveRelation(attrs);
@@ -373,6 +456,17 @@ public class DefaultCoapClientContext implements CoapClientContext { @@ -373,6 +456,17 @@ public class DefaultCoapClientContext implements CoapClientContext {
373 } 456 }
374 457
375 @Override 458 @Override
  459 + public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional<DeviceProfile> deviceProfileOpt) {
  460 + state.onDeviceUpdate(device);
  461 + }
  462 +
  463 + @Override
  464 + public void onDeviceDeleted(DeviceId deviceId) {
  465 + cancelRpcSubscription(state);
  466 + cancelAttributeSubscription(state);
  467 + }
  468 +
  469 + @Override
376 public void onRemoteSessionCloseCommand(UUID sessionId, TransportProtos.SessionCloseNotificationProto sessionCloseNotification) { 470 public void onRemoteSessionCloseCommand(UUID sessionId, TransportProtos.SessionCloseNotificationProto sessionCloseNotification) {
377 log.trace("[{}] Received the remote command to close the session: {}", sessionId, sessionCloseNotification.getMessage()); 471 log.trace("[{}] Received the remote command to close the session: {}", sessionId, sessionCloseNotification.getMessage());
378 cancelRpcSubscription(state); 472 cancelRpcSubscription(state);
@@ -382,6 +476,10 @@ public class DefaultCoapClientContext implements CoapClientContext { @@ -382,6 +476,10 @@ public class DefaultCoapClientContext implements CoapClientContext {
382 @Override 476 @Override
383 public void onToDeviceRpcRequest(UUID sessionId, TransportProtos.ToDeviceRpcRequestMsg msg) { 477 public void onToDeviceRpcRequest(UUID sessionId, TransportProtos.ToDeviceRpcRequestMsg msg) {
384 log.trace("[{}] Received RPC command to device", sessionId); 478 log.trace("[{}] Received RPC command to device", sessionId);
  479 + if (!isDownlinkAllowed(state)) {
  480 + log.trace("[{}] ignore downlink request cause client is sleeping.", state.getDeviceId());
  481 + return;
  482 + }
385 boolean sent = false; 483 boolean sent = false;
386 boolean conRequest = AbstractSyncSessionCallback.isConRequest(state.getRpc()); 484 boolean conRequest = AbstractSyncSessionCallback.isConRequest(state.getRpc());
387 try { 485 try {
@@ -401,7 +499,10 @@ public class DefaultCoapClientContext implements CoapClientContext { @@ -401,7 +499,10 @@ public class DefaultCoapClientContext implements CoapClientContext {
401 if (rpcRequestMsg != null) { 499 if (rpcRequestMsg != null) {
402 transportService.process(state.getSession(), rpcRequestMsg, false, TransportServiceCallback.EMPTY); 500 transportService.process(state.getSession(), rpcRequestMsg, false, TransportServiceCallback.EMPTY);
403 } 501 }
404 - })); 502 + }, null));
  503 + }
  504 + if (conRequest) {
  505 + response.addMessageObserver(new TbCoapMessageObserver(requestId, id -> awake(state), id -> asleep(state)));
405 } 506 }
406 state.getRpc().getExchange().respond(response); 507 state.getRpc().getExchange().respond(response);
407 sent = true; 508 sent = true;
@@ -428,6 +529,143 @@ public class DefaultCoapClientContext implements CoapClientContext { @@ -428,6 +529,143 @@ public class DefaultCoapClientContext implements CoapClientContext {
428 } 529 }
429 } 530 }
430 531
  532 + private boolean asleep(TbCoapClientState client) {
  533 + boolean changed = compareAndSetSleepFlag(client, true);
  534 + if (changed) {
  535 + log.debug("[{}] client is sleeping", client.getDeviceId());
  536 + transportService.log(client.getSession(), "Info: Client is sleeping!");
  537 + }
  538 + return changed;
  539 + }
  540 +
  541 + @Override
  542 + public boolean awake(TbCoapClientState client) {
  543 + onUplink(client);
  544 + boolean changed = compareAndSetSleepFlag(client, false);
  545 + if (changed) {
  546 + log.debug("[{}] client is awake", client.getDeviceId());
  547 + transportService.log(client.getSession(), "Info: Client is awake!");
  548 + sendMsgsAfterSleeping(client);
  549 + }
  550 + return changed;
  551 + }
  552 +
  553 + private void sendMsgsAfterSleeping(TbCoapClientState client) {
  554 + if (client.getRpc() != null) {
  555 + TransportProtos.TransportToDeviceActorMsg persistentRpcRequestMsg = TransportProtos.TransportToDeviceActorMsg
  556 + .newBuilder()
  557 + .setSessionInfo(client.getSession())
  558 + .setSendPendingRPC(TransportProtos.SendPendingRPCMsg.newBuilder().build())
  559 + .build();
  560 + transportService.process(persistentRpcRequestMsg, TransportServiceCallback.EMPTY);
  561 + }
  562 + if (client.getAttrs() != null && client.getMissedAttributeUpdates() != null) {
  563 + client.getListener().onAttributeUpdate(new UUID(client.getSession().getSessionIdMSB(), client.getSession().getSessionIdLSB()), client.getAndClearMissedUpdates());
  564 + }
  565 + }
  566 +
  567 + private boolean compareAndSetSleepFlag(TbCoapClientState client, boolean sleeping) {
  568 + if (sleeping == client.isAsleep()) {
  569 + log.trace("[{}] Client is already at sleeping: {}, ignoring event: {}", client.getDeviceId(), client.isAsleep(), sleeping);
  570 + return false;
  571 + }
  572 + client.lock();
  573 + try {
  574 + if (sleeping == client.isAsleep()) {
  575 + log.trace("[{}] Client is already at sleeping: {}, ignoring event: {}", client.getDeviceId(), client.isAsleep(), sleeping);
  576 + return false;
  577 + } else {
  578 + PowerMode powerMode = getPowerMode(client);
  579 + if (PowerMode.PSM.equals(powerMode) || PowerMode.E_DRX.equals(powerMode)) {
  580 + log.trace("[{}] Switch sleeping from: {} to: {}", client.getDeviceId(), client.isAsleep(), sleeping);
  581 + client.setAsleep(sleeping);
  582 + // TODO: persist changes.
  583 + // update(client);
  584 + return true;
  585 + } else {
  586 + return false;
  587 + }
  588 + }
  589 + } finally {
  590 + client.unlock();
  591 + }
  592 + }
  593 +
  594 + private boolean isDownlinkAllowed(TbCoapClientState client) {
  595 + PowerMode powerMode = client.getPowerMode();
  596 + PowerSavingConfiguration profileSettings = null;
  597 + if (powerMode == null) {
  598 + var clientProfile = getProfile(client.getProfileId());
  599 + if (clientProfile.isPresent()) {
  600 + profileSettings = clientProfile.get().getClientSettings();
  601 + powerMode = profileSettings.getPowerMode();
  602 + if (powerMode == null) {
  603 + powerMode = PowerMode.DRX;
  604 + }
  605 + }
  606 + }
  607 + if (PowerMode.DRX.equals(powerMode)) {
  608 + return true;
  609 + }
  610 + client.lock();
  611 + long timeSinceLastUplink = System.currentTimeMillis() - client.getLastUplinkTime();
  612 + try {
  613 + if (PowerMode.PSM.equals(powerMode)) {
  614 + Long psmActivityTimer = client.getPsmActivityTimer();
  615 + if (psmActivityTimer == null && profileSettings != null) {
  616 + psmActivityTimer = profileSettings.getPsmActivityTimer();
  617 +
  618 + }
  619 + if (psmActivityTimer == null || psmActivityTimer == 0L) {
  620 + psmActivityTimer = config.getPsmActivityTimer();
  621 + }
  622 + return timeSinceLastUplink <= psmActivityTimer;
  623 + } else {
  624 + Long pagingTransmissionWindow = client.getPagingTransmissionWindow();
  625 + if (pagingTransmissionWindow == null && profileSettings != null) {
  626 + pagingTransmissionWindow = profileSettings.getPagingTransmissionWindow();
  627 +
  628 + }
  629 + if (pagingTransmissionWindow == null || pagingTransmissionWindow == 0L) {
  630 + pagingTransmissionWindow = config.getPagingTransmissionWindow();
  631 + }
  632 + boolean allowed = timeSinceLastUplink <= pagingTransmissionWindow;
  633 + if (!allowed) {
  634 + return client.checkFirstDownlink();
  635 + } else {
  636 + return true;
  637 + }
  638 + }
  639 + } finally {
  640 + client.unlock();
  641 + }
  642 + }
  643 +
  644 + private PowerMode getPowerMode(TbCoapClientState client) {
  645 + PowerMode powerMode = client.getPowerMode();
  646 + if (powerMode == null) {
  647 + Optional<CoapDeviceProfileTransportConfiguration> deviceProfile = getProfile(client.getProfileId());
  648 + if (deviceProfile.isPresent()) {
  649 + powerMode = deviceProfile.get().getClientSettings().getPowerMode();
  650 + } else {
  651 + powerMode = PowerMode.PSM;
  652 + }
  653 + }
  654 + return powerMode;
  655 + }
  656 +
  657 + public Optional<CoapDeviceProfileTransportConfiguration> getProfile(DeviceProfileId profileId) {
  658 + DeviceProfile deviceProfile = profileCache.get(profileId);
  659 + if (deviceProfile.getTransportType().equals(DeviceTransportType.COAP)) {
  660 + return Optional.of((CoapDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration());
  661 + } else if (deviceProfile.getTransportType().equals(DeviceTransportType.DEFAULT)) {
  662 + return Optional.empty();
  663 + } else {
  664 + log.warn("[{}] Invalid device profile type: {}", profileId, deviceProfile.getTransportType());
  665 + throw new IllegalArgumentException("Invalid device profile type: " + deviceProfile.getTransportType());
  666 + }
  667 + }
  668 +
431 protected int getNextMsgId() { 669 protected int getNextMsgId() {
432 return ThreadLocalRandom.current().nextInt(NONE, MAX_MID + 1); 670 return ThreadLocalRandom.current().nextInt(NONE, MAX_MID + 1);
433 } 671 }
@@ -18,12 +18,26 @@ package org.thingsboard.server.transport.coap.client; @@ -18,12 +18,26 @@ package org.thingsboard.server.transport.coap.client;
18 import lombok.Data; 18 import lombok.Data;
19 import lombok.Getter; 19 import lombok.Getter;
20 import lombok.Setter; 20 import lombok.Setter;
  21 +import org.eclipse.leshan.server.registration.Registration;
  22 +import org.thingsboard.server.common.data.Device;
  23 +import org.thingsboard.server.common.data.DeviceTransportType;
  24 +import org.thingsboard.server.common.data.device.data.CoapDeviceTransportConfiguration;
  25 +import org.thingsboard.server.common.data.device.data.PowerMode;
21 import org.thingsboard.server.common.data.id.DeviceId; 26 import org.thingsboard.server.common.data.id.DeviceId;
  27 +import org.thingsboard.server.common.data.id.DeviceProfileId;
  28 +import org.thingsboard.server.common.data.id.TenantId;
22 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; 29 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
23 import org.thingsboard.server.gen.transport.TransportProtos; 30 import org.thingsboard.server.gen.transport.TransportProtos;
24 import org.thingsboard.server.transport.coap.TransportConfigurationContainer; 31 import org.thingsboard.server.transport.coap.TransportConfigurationContainer;
25 import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor; 32 import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor;
26 33
  34 +import java.util.ArrayList;
  35 +import java.util.HashMap;
  36 +import java.util.HashSet;
  37 +import java.util.List;
  38 +import java.util.Map;
  39 +import java.util.Set;
  40 +import java.util.UUID;
27 import java.util.concurrent.Future; 41 import java.util.concurrent.Future;
28 import java.util.concurrent.locks.Lock; 42 import java.util.concurrent.locks.Lock;
29 import java.util.concurrent.locks.ReentrantLock; 43 import java.util.concurrent.locks.ReentrantLock;
@@ -37,12 +51,22 @@ public class TbCoapClientState { @@ -37,12 +51,22 @@ public class TbCoapClientState {
37 private volatile TransportConfigurationContainer configuration; 51 private volatile TransportConfigurationContainer configuration;
38 private volatile CoapTransportAdaptor adaptor; 52 private volatile CoapTransportAdaptor adaptor;
39 private volatile ValidateDeviceCredentialsResponse credentials; 53 private volatile ValidateDeviceCredentialsResponse credentials;
40 -  
41 private volatile TransportProtos.SessionInfoProto session; 54 private volatile TransportProtos.SessionInfoProto session;
42 - 55 + private volatile DefaultCoapClientContext.CoapSessionListener listener;
43 private volatile TbCoapObservationState attrs; 56 private volatile TbCoapObservationState attrs;
44 private volatile TbCoapObservationState rpc; 57 private volatile TbCoapObservationState rpc;
  58 + private TransportProtos.AttributeUpdateNotificationMsg missedAttributeUpdates;
45 59
  60 + private DeviceProfileId profileId;
  61 +
  62 + @Getter
  63 + private PowerMode powerMode;
  64 + @Getter
  65 + private Long psmActivityTimer;
  66 + @Getter
  67 + private Long edrxCycle;
  68 + @Getter
  69 + private Long pagingTransmissionWindow;
46 @Getter 70 @Getter
47 @Setter 71 @Setter
48 private boolean asleep; 72 private boolean asleep;
@@ -59,6 +83,14 @@ public class TbCoapClientState { @@ -59,6 +83,14 @@ public class TbCoapClientState {
59 this.lock = new ReentrantLock(); 83 this.lock = new ReentrantLock();
60 } 84 }
61 85
  86 + public void init(ValidateDeviceCredentialsResponse credentials) {
  87 + this.profileId = credentials.getDeviceInfo().getDeviceProfileId();
  88 + this.powerMode = credentials.getDeviceInfo().getPowerMode();
  89 + this.edrxCycle = credentials.getDeviceInfo().getEdrxCycle();
  90 + this.psmActivityTimer = credentials.getDeviceInfo().getPsmActivityTimer();
  91 + this.pagingTransmissionWindow = credentials.getDeviceInfo().getPagingTransmissionWindow();
  92 + }
  93 +
62 public void lock() { 94 public void lock() {
63 lock.lock(); 95 lock.lock();
64 } 96 }
@@ -67,10 +99,54 @@ public class TbCoapClientState { @@ -67,10 +99,54 @@ public class TbCoapClientState {
67 lock.unlock(); 99 lock.unlock();
68 } 100 }
69 101
70 - public long updateLastUplinkTime(){ 102 + public long updateLastUplinkTime() {
71 this.lastUplinkTime = System.currentTimeMillis(); 103 this.lastUplinkTime = System.currentTimeMillis();
72 this.firstEdrxDownlink = true; 104 this.firstEdrxDownlink = true;
73 return lastUplinkTime; 105 return lastUplinkTime;
74 } 106 }
75 107
  108 + public boolean checkFirstDownlink() {
  109 + boolean result = firstEdrxDownlink;
  110 + firstEdrxDownlink = false;
  111 + return result;
  112 + }
  113 +
  114 + public void onDeviceUpdate(Device device) {
  115 + this.profileId = device.getDeviceProfileId();
  116 + var data = device.getDeviceData();
  117 + if (data.getTransportConfiguration() != null && data.getTransportConfiguration().getType().equals(DeviceTransportType.COAP)) {
  118 + CoapDeviceTransportConfiguration configuration = (CoapDeviceTransportConfiguration) data.getTransportConfiguration();
  119 + this.powerMode = configuration.getPowerMode();
  120 + this.edrxCycle = configuration.getEdrxCycle();
  121 + this.psmActivityTimer = configuration.getPsmActivityTimer();
  122 + this.pagingTransmissionWindow = configuration.getPagingTransmissionWindow();
  123 + }
  124 + }
  125 +
  126 + public void addQueuedNotification(TransportProtos.AttributeUpdateNotificationMsg msg) {
  127 + if (missedAttributeUpdates == null) {
  128 + missedAttributeUpdates = msg;
  129 + } else {
  130 + Map<String, TransportProtos.TsKvProto> updatedAttrs = new HashMap<>(missedAttributeUpdates.getSharedUpdatedCount() + msg.getSharedUpdatedCount());
  131 + Set<String> deletedKeys = new HashSet<>(missedAttributeUpdates.getSharedDeletedCount() + msg.getSharedDeletedCount());
  132 + for (TransportProtos.TsKvProto oldUpdatedAttrs : missedAttributeUpdates.getSharedUpdatedList()) {
  133 + updatedAttrs.put(oldUpdatedAttrs.getKv().getKey(), oldUpdatedAttrs);
  134 + }
  135 + deletedKeys.addAll(msg.getSharedDeletedList());
  136 + for (TransportProtos.TsKvProto newUpdatedAttrs : msg.getSharedUpdatedList()) {
  137 + updatedAttrs.put(newUpdatedAttrs.getKv().getKey(), newUpdatedAttrs);
  138 + }
  139 + deletedKeys.addAll(msg.getSharedDeletedList());
  140 + for (String deletedKey : msg.getSharedDeletedList()) {
  141 + updatedAttrs.remove(deletedKey);
  142 + }
  143 + missedAttributeUpdates = TransportProtos.AttributeUpdateNotificationMsg.newBuilder().addAllSharedUpdated(updatedAttrs.values()).addAllSharedDeleted(deletedKeys).build();
  144 + }
  145 + }
  146 +
  147 + public TransportProtos.AttributeUpdateNotificationMsg getAndClearMissedUpdates() {
  148 + var result = this.missedAttributeUpdates;
  149 + this.missedAttributeUpdates = null;
  150 + return result;
  151 + }
76 } 152 }
@@ -126,4 +126,6 @@ public interface TransportService { @@ -126,4 +126,6 @@ public interface TransportService {
126 SessionMetaData reportActivity(SessionInfoProto sessionInfo); 126 SessionMetaData reportActivity(SessionInfoProto sessionInfo);
127 127
128 void deregisterSession(SessionInfoProto sessionInfo); 128 void deregisterSession(SessionInfoProto sessionInfo);
  129 +
  130 + void log(SessionInfoProto sessionInfo, String msg);
129 } 131 }
@@ -36,4 +36,6 @@ public class TransportDeviceInfo implements Serializable { @@ -36,4 +36,6 @@ public class TransportDeviceInfo implements Serializable {
36 private PowerMode powerMode; 36 private PowerMode powerMode;
37 private String additionalInfo; 37 private String additionalInfo;
38 private Long edrxCycle; 38 private Long edrxCycle;
  39 + private Long psmActivityTimer;
  40 + private Long pagingTransmissionWindow;
39 } 41 }
@@ -119,6 +119,12 @@ public class DefaultTransportService implements TransportService { @@ -119,6 +119,12 @@ public class DefaultTransportService implements TransportService {
119 119
120 public static final String OVERWRITE_ACTIVITY_TIME = "overwriteActivityTime"; 120 public static final String OVERWRITE_ACTIVITY_TIME = "overwriteActivityTime";
121 121
  122 + private final AtomicInteger atomicTs = new AtomicInteger(0);
  123 +
  124 + @Value("${transport.log.enabled:true}")
  125 + private boolean logEnabled;
  126 + @Value("${transport.log.max_length:1024}")
  127 + private int logMaxLength;
122 @Value("${transport.sessions.inactivity_timeout}") 128 @Value("${transport.sessions.inactivity_timeout}")
123 private long sessionInactivityTimeout; 129 private long sessionInactivityTimeout;
124 @Value("${transport.sessions.report_timeout}") 130 @Value("${transport.sessions.report_timeout}")
@@ -444,6 +450,8 @@ public class DefaultTransportService implements TransportService { @@ -444,6 +450,8 @@ public class DefaultTransportService implements TransportService {
444 if (StringUtils.isNotEmpty(di.getPowerMode())) { 450 if (StringUtils.isNotEmpty(di.getPowerMode())) {
445 tdi.setPowerMode(PowerMode.valueOf(di.getPowerMode())); 451 tdi.setPowerMode(PowerMode.valueOf(di.getPowerMode()));
446 tdi.setEdrxCycle(di.getEdrxCycle()); 452 tdi.setEdrxCycle(di.getEdrxCycle());
  453 + tdi.setPsmActivityTimer(di.getPsmActivityTimer());
  454 + tdi.setPagingTransmissionWindow(di.getPagingTransmissionWindow());
447 } 455 }
448 return tdi; 456 return tdi;
449 } 457 }
@@ -745,6 +753,26 @@ public class DefaultTransportService implements TransportService { @@ -745,6 +753,26 @@ public class DefaultTransportService implements TransportService {
745 sessions.remove(toSessionId(sessionInfo)); 753 sessions.remove(toSessionId(sessionInfo));
746 } 754 }
747 755
  756 + @Override
  757 + public void log(TransportProtos.SessionInfoProto sessionInfo, String msg) {
  758 + if (!logEnabled || sessionInfo == null || StringUtils.isEmpty(msg)) {
  759 + return;
  760 + }
  761 + if (msg.length() > logMaxLength) {
  762 + msg = msg.substring(0, logMaxLength);
  763 + }
  764 + TransportProtos.PostTelemetryMsg.Builder request = TransportProtos.PostTelemetryMsg.newBuilder();
  765 + TransportProtos.TsKvListProto.Builder builder = TransportProtos.TsKvListProto.newBuilder();
  766 + builder.setTs(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) * 1000L + (atomicTs.getAndIncrement() % 1000));
  767 + builder.addKv(TransportProtos.KeyValueProto.newBuilder()
  768 + .setKey("transportLog")
  769 + .setType(TransportProtos.KeyValueType.STRING_V)
  770 + .setStringV(msg).build());
  771 + request.addTsKvList(builder.build());
  772 + TransportProtos.PostTelemetryMsg postTelemetryMsg = request.build();
  773 + process(sessionInfo, postTelemetryMsg, TransportServiceCallback.EMPTY);
  774 + }
  775 +
748 private boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<?> callback) { 776 private boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<?> callback) {
749 return checkLimits(sessionInfo, msg, callback, 0); 777 return checkLimits(sessionInfo, msg, callback, 0);
750 } 778 }
@@ -89,6 +89,8 @@ transport: @@ -89,6 +89,8 @@ transport:
89 bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" 89 bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}"
90 bind_port: "${COAP_BIND_PORT:5683}" 90 bind_port: "${COAP_BIND_PORT:5683}"
91 timeout: "${COAP_TIMEOUT:10000}" 91 timeout: "${COAP_TIMEOUT:10000}"
  92 + psm_activity_timer: "${COAP_PSM_ACTIVITY_TIMER:10000}"
  93 + paging_transmission_window: "${COAP_PAGING_TRANSMISSION_WINDOW:10000}"
92 dtls: 94 dtls:
93 # Enable/disable DTLS 1.2 support 95 # Enable/disable DTLS 1.2 support
94 enabled: "${COAP_DTLS_ENABLED:false}" 96 enabled: "${COAP_DTLS_ENABLED:false}"
@@ -117,6 +119,9 @@ transport: @@ -117,6 +119,9 @@ transport:
117 type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" 119 type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}"
118 # Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check) 120 # Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check)
119 max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}" 121 max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}"
  122 + log:
  123 + enabled: "${TB_TRANSPORT_LOG_ENABLED:true}"
  124 + max_length: "${TB_TRANSPORT_LOG_MAX_LENGTH:1024}"
120 125
121 queue: 126 queue:
122 type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) 127 type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)
@@ -91,6 +91,9 @@ transport: @@ -91,6 +91,9 @@ transport:
91 type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" 91 type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}"
92 # Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check) 92 # Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check)
93 max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}" 93 max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}"
  94 + log:
  95 + enabled: "${TB_TRANSPORT_LOG_ENABLED:true}"
  96 + max_length: "${TB_TRANSPORT_LOG_MAX_LENGTH:1024}"
94 97
95 queue: 98 queue:
96 type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) 99 type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)
@@ -98,6 +98,9 @@ transport: @@ -98,6 +98,9 @@ transport:
98 # Enable/disable http/mqtt/coap transport protocols (has higher priority than certain protocol's 'enabled' property) 98 # Enable/disable http/mqtt/coap transport protocols (has higher priority than certain protocol's 'enabled' property)
99 api_enabled: "${TB_TRANSPORT_API_ENABLED:true}" 99 api_enabled: "${TB_TRANSPORT_API_ENABLED:true}"
100 # Local LwM2M transport parameters 100 # Local LwM2M transport parameters
  101 + log:
  102 + enabled: "${TB_TRANSPORT_LOG_ENABLED:true}"
  103 + max_length: "${TB_TRANSPORT_LOG_MAX_LENGTH:1024}"
101 lwm2m: 104 lwm2m:
102 # Enable/disable lvm2m transport protocol. 105 # Enable/disable lvm2m transport protocol.
103 enabled: "${LWM2M_ENABLED:true}" 106 enabled: "${LWM2M_ENABLED:true}"
@@ -123,6 +123,10 @@ transport: @@ -123,6 +123,10 @@ transport:
123 type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" 123 type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}"
124 # Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check) 124 # Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check)
125 max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}" 125 max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}"
  126 + log:
  127 + enabled: "${TB_TRANSPORT_LOG_ENABLED:true}"
  128 + max_length: "${TB_TRANSPORT_LOG_MAX_LENGTH:1024}"
  129 +
126 130
127 queue: 131 queue:
128 type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) 132 type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)
@@ -56,6 +56,9 @@ transport: @@ -56,6 +56,9 @@ transport:
56 type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" 56 type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}"
57 # Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check) 57 # Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check)
58 max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}" 58 max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}"
  59 + log:
  60 + enabled: "${TB_TRANSPORT_LOG_ENABLED:true}"
  61 + max_length: "${TB_TRANSPORT_LOG_MAX_LENGTH:1024}"
59 62
60 queue: 63 queue:
61 type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) 64 type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)