Commit 3a7a97dd9171296d246c524d44e55960a3540c4c
Committed by
Andrew Shvayka
1 parent
4364755e
PSM and eDRX implementation draft
Showing
17 changed files
with
452 additions
and
63 deletions
@@ -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 | /** |
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
@@ -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); |
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/TbCoapMessageObserver.java
@@ -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) |