Browse Source

Merge branch 'master' of github.com:thingsboard/thingsboard into edge-3.3

pull/4918/head
Volodymyr Babak 5 years ago
parent
commit
ab55619abf
  1. 4
      application/src/main/data/upgrade/3.2.2/schema_update.sql
  2. 5
      application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java
  3. 7
      application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java
  4. 4
      application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java
  5. 16
      application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java
  6. 16
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java
  7. 3
      application/src/main/resources/logback.xml
  8. 2
      application/src/main/resources/thingsboard.yml
  9. 3
      application/src/test/java/org/thingsboard/server/service/resource/BaseTbResourceServiceTest.java
  10. 10
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java
  11. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
  12. 3
      common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java
  13. 7
      common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java
  14. 24
      common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java
  15. 20
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  16. 2
      common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareUpdateStatus.java
  17. 3
      common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java
  18. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java
  19. 205
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java
  20. 11
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java
  21. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java
  22. 86
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java
  23. 359
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java
  24. 38
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java
  25. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java
  26. 23
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java
  27. 27
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFirmwareUpdate.java
  28. 333
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java
  29. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java
  30. 11
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  31. 1
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  32. 5
      dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java
  33. 3
      dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java
  34. 1
      dao/src/main/resources/sql/schema-entities-hsql.sql
  35. 1
      dao/src/main/resources/sql/schema-entities.sql
  36. 1
      ui-ngx/package.json
  37. 5
      ui-ngx/src/app/core/auth/auth.service.ts
  38. 19
      ui-ngx/src/app/core/services/utils.service.ts
  39. 66
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-image-dialog.component.html
  40. 54
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-image-dialog.component.scss
  41. 132
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-image-dialog.component.ts
  42. 11
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html
  43. 38
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts
  44. 3
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.html
  45. 3
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.ts
  46. 3
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  47. 12
      ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html
  48. 1
      ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts
  49. 8
      ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts
  50. 1
      ui-ngx/src/app/shared/models/dashboard.models.ts
  51. 8
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  52. 19
      ui-ngx/yarn.lock

4
application/src/main/data/upgrade/3.2.2/schema_update.sql

@ -130,3 +130,7 @@ DO $$
END;
$$;
ALTER TABLE api_usage_state
ADD COLUMN IF NOT EXISTS alarm_exec VARCHAR(32);
UPDATE api_usage_state SET alarm_exec = 'ENABLED' WHERE alarm_exec IS NULL;

5
application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java

@ -106,6 +106,8 @@ public abstract class BaseApiUsageState {
return apiUsageState.getEmailExecState();
case SMS:
return apiUsageState.getSmsExecState();
case ALARM:
return apiUsageState.getAlarmExecState();
default:
return ApiUsageStateValue.ENABLED;
}
@ -132,6 +134,9 @@ public abstract class BaseApiUsageState {
case SMS:
apiUsageState.setSmsExecState(value);
break;
case ALARM:
apiUsageState.setAlarmExecState(value);
break;
}
return !currentValue.equals(value);
}

7
application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java

@ -24,8 +24,9 @@ import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.FirmwareInfo;
import org.thingsboard.server.common.data.firmware.FirmwareUtil;
import org.thingsboard.server.common.data.firmware.FirmwareType;
import org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus;
import org.thingsboard.server.common.data.firmware.FirmwareUtil;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.FirmwareId;
import org.thingsboard.server.common.data.id.TenantId;
@ -66,11 +67,11 @@ import static org.thingsboard.server.common.data.firmware.FirmwareKey.STATE;
import static org.thingsboard.server.common.data.firmware.FirmwareKey.TITLE;
import static org.thingsboard.server.common.data.firmware.FirmwareKey.TS;
import static org.thingsboard.server.common.data.firmware.FirmwareKey.VERSION;
import static org.thingsboard.server.common.data.firmware.FirmwareType.FIRMWARE;
import static org.thingsboard.server.common.data.firmware.FirmwareType.SOFTWARE;
import static org.thingsboard.server.common.data.firmware.FirmwareUtil.getAttributeKey;
import static org.thingsboard.server.common.data.firmware.FirmwareUtil.getTargetTelemetryKey;
import static org.thingsboard.server.common.data.firmware.FirmwareUtil.getTelemetryKey;
import static org.thingsboard.server.common.data.firmware.FirmwareType.FIRMWARE;
import static org.thingsboard.server.common.data.firmware.FirmwareType.SOFTWARE;
@Slf4j
@Service

4
application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java

@ -309,6 +309,8 @@ public class DefaultMailService implements MailService {
case EMAIL:
case SMS:
return "send";
case ALARM:
return "create";
default:
throw new RuntimeException("Not implemented!");
}
@ -327,6 +329,8 @@ public class DefaultMailService implements MailService {
case EMAIL:
case SMS:
return "sent";
case ALARM:
return "created";
default:
throw new RuntimeException("Not implemented!");
}

16
application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java

@ -44,6 +44,7 @@ import java.util.Base64;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_SEARCH_TEXT;
@ -130,7 +131,7 @@ public class DefaultTbResourceService implements TbResourceService {
List<TbResource> resources = resourceService.findTenantResourcesByResourceTypeAndObjectIds(tenantId, ResourceType.LWM2M_MODEL,
objectIds);
return resources.stream()
.map(this::toLwM2mObject)
.flatMap(s -> Stream.ofNullable(toLwM2mObject(s)))
.sorted(getComparator(sortProperty, sortOrder))
.collect(Collectors.toList());
}
@ -141,7 +142,7 @@ public class DefaultTbResourceService implements TbResourceService {
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
PageData<TbResource> resourcePageData = resourceService.findTenantResourcesByResourceTypeAndPageLink(tenantId, ResourceType.LWM2M_MODEL, pageLink);
return resourcePageData.getData().stream()
.map(this::toLwM2mObject)
.flatMap(s -> Stream.ofNullable(toLwM2mObject(s)))
.sorted(getComparator(sortProperty, sortOrder))
.collect(Collectors.toList());
}
@ -190,9 +191,14 @@ public class DefaultTbResourceService implements TbResourceService {
resources.add(lwM2MResourceObserve);
}
});
instance.setResources(resources.toArray(LwM2mResourceObserve[]::new));
lwM2mObject.setInstances(new LwM2mInstance[]{instance});
return lwM2mObject;
if (resources.size() > 0) {
instance.setResources(resources.toArray(LwM2mResourceObserve[]::new));
lwM2mObject.setInstances(new LwM2mInstance[]{instance});
return lwM2mObject;
}
else {
return null;
}
}
} catch (IOException | InvalidDDFFileException e) {
log.error("Could not parse the XML of objectModel with name [{}]", resource.getSearchText(), e);

16
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java

@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
@ -43,6 +44,8 @@ import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.usagestats.TbApiUsageClient;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.queue.TbClusterService;
import org.thingsboard.server.service.subscription.SubscriptionManagerService;
import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
@ -58,12 +61,18 @@ import java.util.Optional;
public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService implements AlarmSubscriptionService {
private final AlarmService alarmService;
private final TbApiUsageClient apiUsageClient;
private final TbApiUsageStateService apiUsageStateService;
public DefaultAlarmSubscriptionService(TbClusterService clusterService,
PartitionService partitionService,
AlarmService alarmService) {
AlarmService alarmService,
TbApiUsageClient apiUsageClient,
TbApiUsageStateService apiUsageStateService) {
super(clusterService, partitionService);
this.alarmService = alarmService;
this.apiUsageClient = apiUsageClient;
this.apiUsageStateService = apiUsageStateService;
}
@Autowired(required = false)
@ -78,10 +87,13 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
@Override
public Alarm createOrUpdateAlarm(Alarm alarm) {
AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm);
AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm, apiUsageStateService.getApiUsageState(alarm.getTenantId()).isAlarmCreationEnabled());
if (result.isSuccessful()) {
onAlarmUpdated(result);
}
if (result.isCreated()) {
apiUsageClient.report(alarm.getTenantId(), null, ApiUsageRecordKey.CREATED_ALARMS_COUNT);
}
return result.getAlarm();
}

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

@ -26,8 +26,7 @@
</appender>
<logger name="org.thingsboard.server" level="INFO" />
<logger name="org.thingsboard.server.transport.snmp" level="TRACE" />
<!-- <logger name="org.thingsboard.server.service.edge.rpc" level="TRACE" />-->
<logger name="org.thingsboard.server.transport.snmp" level="DEBUG" />
<!-- <logger name="org.thingsboard.server.service.queue" level="TRACE" />-->
<!-- <logger name="org.thingsboard.server.service.transport" level="TRACE" />-->

2
application/src/main/resources/thingsboard.yml

@ -672,6 +672,8 @@ transport:
recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:true}"
response_pool_size: "${LWM2M_RESPONSE_POOL_SIZE:100}"
registered_pool_size: "${LWM2M_REGISTERED_POOL_SIZE:10}"
registration_store_pool_size: "${LWM2M_REGISTRATION_STORE_POOL_SIZE:100}"
clean_period_in_sec: "${LWM2M_CLEAN_PERIOD_IN_SEC:2}"
update_registered_pool_size: "${LWM2M_UPDATE_REGISTERED_POOL_SIZE:10}"
un_registered_pool_size: "${LWM2M_UN_REGISTERED_POOL_SIZE:10}"
log_max_length: "${LWM2M_LOG_MAX_LENGTH:100}"

3
application/src/test/java/org/thingsboard/server/service/resource/BaseTbResourceServiceTest.java

@ -32,7 +32,6 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.AbstractServiceTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.ArrayList;
@ -57,7 +56,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
"<Resources>\n" +
"<Item ID=\"0\">\n" +
"<Name>LWM2M</Name>\n" +
"<Operations></Operations>\n" +
"<Operations>RW</Operations>\n" +
"<MultipleInstances>Single</MultipleInstances>\n" +
"<Mandatory>Mandatory</Mandatory>\n" +
"<Type>String</Type>\n" +

10
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java

@ -26,17 +26,21 @@ import java.util.List;
public class AlarmOperationResult {
private final Alarm alarm;
private final boolean successful;
private final boolean created;
private final List<EntityId> propagatedEntitiesList;
public AlarmOperationResult(Alarm alarm, boolean successful) {
this.alarm = alarm;
this.successful = successful;
this.propagatedEntitiesList = Collections.emptyList();
this(alarm, successful, Collections.emptyList());
}
public AlarmOperationResult(Alarm alarm, boolean successful, List<EntityId> propagatedEntitiesList) {
this(alarm, successful, false, propagatedEntitiesList);
}
public AlarmOperationResult(Alarm alarm, boolean successful, boolean created, List<EntityId> propagatedEntitiesList) {
this.alarm = alarm;
this.successful = successful;
this.created = created;
this.propagatedEntitiesList = propagatedEntitiesList;
}
}

3
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java

@ -29,7 +29,6 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataPageLink;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
import java.util.Collection;
@ -41,6 +40,8 @@ public interface AlarmService {
AlarmOperationResult createOrUpdateAlarm(Alarm alarm);
AlarmOperationResult createOrUpdateAlarm(Alarm alarm, boolean alarmCreationEnabled);
AlarmOperationResult deleteAlarm(TenantId tenantId, AlarmId alarmId);
ListenableFuture<AlarmOperationResult> ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTs);

3
common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java

@ -23,7 +23,8 @@ public enum ApiFeature {
RE("ruleEngineApiState", "Rule Engine execution"),
JS("jsExecutionApiState", "JavaScript functions execution"),
EMAIL("emailApiState", "Email messages"),
SMS("smsApiState", "SMS messages");
SMS("smsApiState", "SMS messages"),
ALARM("alarmApiState", "Created alarms");
@Getter
private final String apiStateKey;

7
common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java

@ -25,13 +25,16 @@ public enum ApiUsageRecordKey {
RE_EXEC_COUNT(ApiFeature.RE, "ruleEngineExecutionCount", "ruleEngineExecutionLimit"),
JS_EXEC_COUNT(ApiFeature.JS, "jsExecutionCount", "jsExecutionLimit"),
EMAIL_EXEC_COUNT(ApiFeature.EMAIL, "emailCount", "emailLimit"),
SMS_EXEC_COUNT(ApiFeature.SMS, "smsCount", "smsLimit");
SMS_EXEC_COUNT(ApiFeature.SMS, "smsCount", "smsLimit"),
CREATED_ALARMS_COUNT(ApiFeature.ALARM, "createdAlarmsCount", "createdAlarmsLimit");
private static final ApiUsageRecordKey[] JS_RECORD_KEYS = {JS_EXEC_COUNT};
private static final ApiUsageRecordKey[] RE_RECORD_KEYS = {RE_EXEC_COUNT};
private static final ApiUsageRecordKey[] DB_RECORD_KEYS = {STORAGE_DP_COUNT};
private static final ApiUsageRecordKey[] TRANSPORT_RECORD_KEYS = {TRANSPORT_MSG_COUNT, TRANSPORT_DP_COUNT};
private static final ApiUsageRecordKey[] EMAIL_RECORD_KEYS = {EMAIL_EXEC_COUNT};
private static final ApiUsageRecordKey[] SMS_RECORD_KEYS = {SMS_EXEC_COUNT};
private static final ApiUsageRecordKey[] ALARM_RECORD_KEYS = {CREATED_ALARMS_COUNT};
@Getter
private final ApiFeature apiFeature;
@ -60,6 +63,8 @@ public enum ApiUsageRecordKey {
return EMAIL_RECORD_KEYS;
case SMS:
return SMS_RECORD_KEYS;
case ALARM:
return ALARM_RECORD_KEYS;
default:
return new ApiUsageRecordKey[]{};
}

24
common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java

@ -25,34 +25,21 @@ import org.thingsboard.server.common.data.id.ApiUsageStateId;
@ToString
@EqualsAndHashCode(callSuper = true)
@Getter
@Setter
public class ApiUsageState extends BaseData<ApiUsageStateId> implements HasTenantId {
private static final long serialVersionUID = 8250339805336035966L;
@Getter
@Setter
private TenantId tenantId;
@Getter
@Setter
private EntityId entityId;
@Getter
@Setter
private ApiUsageStateValue transportState;
@Getter
@Setter
private ApiUsageStateValue dbStorageState;
@Getter
@Setter
private ApiUsageStateValue reExecState;
@Getter
@Setter
private ApiUsageStateValue jsExecState;
@Getter
@Setter
private ApiUsageStateValue emailExecState;
@Getter
@Setter
private ApiUsageStateValue smsExecState;
private ApiUsageStateValue alarmExecState;
public ApiUsageState() {
super();
@ -72,6 +59,7 @@ public class ApiUsageState extends BaseData<ApiUsageStateId> implements HasTenan
this.jsExecState = ur.getJsExecState();
this.emailExecState = ur.getEmailExecState();
this.smsExecState = ur.getSmsExecState();
this.alarmExecState = ur.getAlarmExecState();
}
public boolean isTransportEnabled() {
@ -97,4 +85,8 @@ public class ApiUsageState extends BaseData<ApiUsageStateId> implements HasTenan
public boolean isSmsSendEnabled(){
return !ApiUsageStateValue.DISABLED.equals(smsExecState);
}
public boolean isAlarmCreationEnabled() {
return alarmExecState != ApiUsageStateValue.DISABLED;
}
}

20
common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java

@ -92,26 +92,6 @@ public class DataConstants {
public static final String CLIENT_ID = "clientId";
public static final String USERNAME = "username";
public static final String PASSWORD = "password";
//<<<<<<< HEAD
//=======
// //firmware
// //telemetry
// public static final String CURRENT_FIRMWARE_TITLE = "current_fw_title";
// public static final String CURRENT_FIRMWARE_VERSION = "current_fw_version";
// public static final String TARGET_FIRMWARE_TITLE = "target_fw_title";
// public static final String TARGET_FIRMWARE_VERSION = "target_fw_version";
// public static final String TARGET_FIRMWARE_TS = "target_fw_ts";
// public static final String FIRMWARE_STATE = "fw_state";
//
// //attributes
// //telemetry
// public static final String FIRMWARE_TITLE = "fw_title";
// public static final String FIRMWARE_VERSION = "fw_version";
// public static final String FIRMWARE_SIZE = "fw_size";
// public static final String FIRMWARE_CHECKSUM = "fw_checksum";
// public static final String FIRMWARE_CHECKSUM_ALGORITHM = "fw_checksum_algorithm";
//>>>>>>> origin/master
public static final String EDGE_MSG_SOURCE = "edge";
public static final String MSG_SOURCE_KEY = "source";

2
application/src/main/java/org/thingsboard/server/service/firmware/FirmwareUpdateStatus.java → common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareUpdateStatus.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.firmware;
package org.thingsboard.server.common.data.firmware;
public enum FirmwareUpdateStatus {
QUEUED, INITIATED, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING, UPDATED, FAILED

3
common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java

@ -50,6 +50,7 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
private int maxRuleNodeExecutionsPerMessage;
private long maxEmails;
private long maxSms;
private long maxCreatedAlarms;
private int defaultStorageTtlDays;
@ -72,6 +73,8 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
return maxEmails;
case SMS_EXEC_COUNT:
return maxSms;
case CREATED_ALARMS_COUNT:
return maxCreatedAlarms;
}
return 0L;
}

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

@ -64,6 +64,14 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig {
@Value("${transport.lwm2m.registered_pool_size:}")
private int registeredPoolSize;
@Getter
@Value("${transport.lwm2m.registration_store_pool_size:}")
private int registrationStorePoolSize;
@Getter
@Value("${transport.lwm2m.clean_period_in_sec:}")
private int cleanPeriodInSec;
@Getter
@Value("${transport.lwm2m.update_registered_pool_size:}")
private int updateRegisteredPoolSize;

205
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java

@ -44,6 +44,7 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.firmware.FirmwareKey;
import org.thingsboard.server.common.data.firmware.FirmwareType;
import org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus;
import org.thingsboard.server.common.data.firmware.FirmwareUtil;
import org.thingsboard.server.common.data.id.FirmwareId;
import org.thingsboard.server.common.transport.TransportService;
@ -84,19 +85,21 @@ import java.util.stream.Collectors;
import static org.eclipse.californium.core.coap.CoAP.ResponseCode.BAD_REQUEST;
import static org.eclipse.leshan.core.attributes.Attribute.OBJECT_VERSION;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper.getValueFromKvProto;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.CLIENT_NOT_AUTHORIZED;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.DEVICE_ATTRIBUTES_REQUEST;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FR_OBJECT_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FR_PATH_RESOURCE_VER_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.EqualsFwSateToFirmwareUpdateStatus;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_RESULT_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_STATE_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_VALUE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LWM2M_STRATEGY_2;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.DISCOVER;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.DISCOVER_All;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.EXECUTE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_CANCEL;
@ -105,6 +108,7 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.L
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_ATTRIBUTES;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_REPLACE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_UPDATE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertJsonArrayToSet;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromIdVerToObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer;
@ -124,12 +128,12 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
private final TransportService transportService;
private final LwM2mTransportContext context;
private final LwM2MTransportServerConfig config;
private final FirmwareDataCache firmwareDataCache;
private final LwM2mTransportServerHelper helper;
public final LwM2MTransportServerConfig config;
public final FirmwareDataCache firmwareDataCache;
public final LwM2mTransportServerHelper helper;
private final LwM2MJsonAdaptor adaptor;
private final LwM2mClientContext clientContext;
private final LwM2mTransportRequest lwM2mTransportRequest;
public final LwM2mTransportRequest lwM2mTransportRequest;
public DefaultLwM2MTransportMsgHandler(TransportService transportService, LwM2MTransportServerConfig config, LwM2mTransportServerHelper helper,
LwM2mClientContext clientContext,
@ -182,6 +186,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), null);
transportService.process(sessionInfo, TransportProtos.SubscribeToRPCMsg.newBuilder().build(), null);
this.getInfoFirmwareUpdate(lwM2MClient);
this.getInfoSoftwareUpdate(lwM2MClient);
this.initLwM2mFromClientValue(registration, lwM2MClient);
this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client create after Registration", registration.getId());
} else {
@ -327,9 +332,16 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
String pathName = tsKvProto.getKv().getKey();
String pathIdVer = this.getPresentPathIntoProfile(sessionInfo, pathName);
Object valueNew = getValueFromKvProto(tsKvProto.getKv());
//TODO: react on change of the firmware name.
if (FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFrUpdate().getCurrentFwVersion())) {
if ((FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.VERSION).equals(pathName)
&& (!valueNew.equals(lwM2MClient.getFwUpdate().getCurrentVersion())))
|| (FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.TITLE).equals(pathName)
&& (!valueNew.equals(lwM2MClient.getFwUpdate().getCurrentTitle())))) {
this.getInfoFirmwareUpdate(lwM2MClient);
} else if ((FirmwareUtil.getAttributeKey(FirmwareType.SOFTWARE, FirmwareKey.VERSION).equals(pathName)
&& (!valueNew.equals(lwM2MClient.getSwUpdate().getCurrentVersion())))
|| (FirmwareUtil.getAttributeKey(FirmwareType.SOFTWARE, FirmwareKey.TITLE).equals(pathName)
&& (!valueNew.equals(lwM2MClient.getSwUpdate().getCurrentTitle())))) {
this.getInfoSoftwareUpdate(lwM2MClient);
}
if (pathIdVer != null) {
ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer, this.config
@ -354,8 +366,8 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
msg.getSharedUpdatedList().forEach(tsKvProto -> {
String pathName = tsKvProto.getKv().getKey();
Object valueNew = getValueFromKvProto(tsKvProto.getKv());
if (FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFrUpdate().getCurrentFwVersion())) {
lwM2MClient.getFrUpdate().setCurrentFwVersion((String) valueNew);
if (FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFwUpdate().getCurrentVersion())) {
lwM2MClient.getFwUpdate().setCurrentVersion((String) valueNew);
}
});
log.info("[{}] delete [{}] onAttributeUpdate", msg.getSharedDeletedList(), sessionInfo);
@ -460,22 +472,22 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
.getAsJsonObject().toString(), new TypeToken<ConcurrentHashMap<String, Object>>() {
}.getType());
if (WRITE_UPDATE == lwm2mClientRpcRequest.getTypeOper()) {
ConcurrentHashMap<String, Object> paramsResourceId = convertParamsToResourceId (params, sessionInfo);
ConcurrentHashMap<String, Object> paramsResourceId = convertParamsToResourceId(params, sessionInfo);
if (paramsResourceId.size() > 0) {
lwm2mClientRpcRequest.setParams(paramsResourceId);
}
}
else {
} else {
lwm2mClientRpcRequest.setParams(params);
}
}
else if (rpcRequest.has(lwm2mClientRpcRequest.paramsKey) && rpcRequest.get(lwm2mClientRpcRequest.paramsKey).isJsonArray()) {
} else if (rpcRequest.has(lwm2mClientRpcRequest.paramsKey) && rpcRequest.get(lwm2mClientRpcRequest.paramsKey).isJsonArray()) {
new Gson().fromJson(rpcRequest.get(lwm2mClientRpcRequest.paramsKey)
.getAsJsonObject().toString(), new TypeToken<ConcurrentHashMap<String, Object>>() {
}.getType());
}
lwm2mClientRpcRequest.setSessionInfo(sessionInfo);
if (OBSERVE_READ_ALL != lwm2mClientRpcRequest.getTypeOper() && lwm2mClientRpcRequest.getTargetIdVer() == null) {
if (!(OBSERVE_READ_ALL == lwm2mClientRpcRequest.getTypeOper()
|| DISCOVER_All == lwm2mClientRpcRequest.getTypeOper())
&& lwm2mClientRpcRequest.getTargetIdVer() == null) {
lwm2mClientRpcRequest.setErrorMsg(lwm2mClientRpcRequest.targetIdVerKey + " and " +
lwm2mClientRpcRequest.keyNameKey + " is null or bad format");
}
@ -499,12 +511,12 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
return lwm2mClientRpcRequest;
}
private ConcurrentHashMap<String, Object> convertParamsToResourceId (ConcurrentHashMap<String, Object> params,
SessionInfoProto sessionInfo) {
private ConcurrentHashMap<String, Object> convertParamsToResourceId(ConcurrentHashMap<String, Object> params,
SessionInfoProto sessionInfo) {
ConcurrentHashMap<String, Object> paramsIdVer = new ConcurrentHashMap<>();
params.forEach((k, v) -> {
String targetIdVer = this.getPresentPathIntoProfile(sessionInfo, k);
if (targetIdVer != null ) {
if (targetIdVer != null) {
LwM2mPath targetId = new LwM2mPath(convertPathFromIdVerToObjectId(targetIdVer));
if (targetId.isResource()) {
paramsIdVer.put(String.valueOf(targetId.getResourceId()), v);
@ -640,7 +652,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
/**
* @param registration -
* @param lwM2mObject -
* @param pathIdVer -
* @param pathIdVer -
*/
private void updateObjectResourceValue(Registration registration, LwM2mObject lwM2mObject, String pathIdVer) {
LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer));
@ -678,18 +690,31 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
LwM2mClient lwM2MClient = clientContext.getOrRegister(registration);
if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config
.getModelProvider())) {
if (FR_PATH_RESOURCE_VER_ID.equals(convertPathFromIdVerToObjectId(path)) &&
lwM2MClient.getFrUpdate().getCurrentFwVersion() != null
&& !lwM2MClient.getFrUpdate().getCurrentFwVersion().equals(lwM2MClient.getFrUpdate().getClientFwVersion())
&& lwM2MClient.isUpdateFw()) {
/** version != null
* set setClient_fw_version = value
**/
lwM2MClient.setUpdateFw(false);
lwM2MClient.getFrUpdate().setClientFwVersion(lwM2mResource.getValue().toString());
log.warn("updateFirmwareClient3");
this.updateFirmwareClient(lwM2MClient);
/** version != null
* set setClient_fw_info... = value
**/
if (lwM2MClient.getFwUpdate().isInfoFwSwUpdate()) {
lwM2MClient.getFwUpdate().initReadValue(this, path);
}
if (lwM2MClient.getSwUpdate().isInfoFwSwUpdate()) {
lwM2MClient.getSwUpdate().initReadValue(this, path);
}
if (lwM2MClient.getFwUpdate().getStateUpdate() != null
&& !FirmwareUpdateStatus.DOWNLOADING.name().equals(lwM2MClient.getFwUpdate().getStateUpdate())
&& (convertPathFromObjectIdToIdVer(FW_STATE_ID, registration).equals(path)
|| convertPathFromObjectIdToIdVer(FW_RESULT_ID, registration).equals(path))) {
Long stateFw = (Long) lwM2MClient.getResourceValue(null, FW_STATE_ID);
Long updateResultFw = (Long) lwM2MClient.getResourceValue(null, FW_RESULT_ID);
FirmwareUpdateStatus state = EqualsFwSateToFirmwareUpdateStatus(LwM2mTransportUtil.StateFw.fromStateFwByCode(stateFw.intValue()),
LwM2mTransportUtil.UpdateResultFw.fromUpdateResultFwByCode(updateResultFw.intValue()));
if (state != FirmwareUpdateStatus.DOWNLOADING && state != FirmwareUpdateStatus.DOWNLOADED) {
lwM2MClient.getFwUpdate().setStateUpdate(state.name());
lwM2MClient.getFwUpdate().sendLogs(OBSERVE.name());
}
log.warn ("update Resource [{}]", lwM2mResource);
}
if (FirmwareUpdateStatus.DOWNLOADED.name().equals(lwM2MClient.getSwUpdate().getStateUpdate())) {
}
Set<String> paths = new HashSet<>();
paths.add(path);
@ -845,7 +870,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
Object finalvalueKvProto = valueKvProto;
Gson gson = new GsonBuilder().create();
resourceValue.getValues().forEach((k, v) -> {
Object val = this.converter.convertValue(resourceValue.getValue(), currentType, expectedType,
Object val = this.converter.convertValue(v, currentType, expectedType,
new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)));
JsonElement element = gson.toJsonTree(val, val.getClass());
((JsonObject) finalvalueKvProto).add(String.valueOf(k), element);
@ -937,7 +962,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
*/
private void onDeviceProfileUpdate(Set<String> registrationIds, DeviceProfile deviceProfile) {
LwM2mClientProfile lwM2MClientProfileOld = clientContext.getProfiles().get(deviceProfile.getUuidId()).clone();
if (clientContext.toClientProfile(deviceProfile) != null) {
if (clientContext.profileUpdate(deviceProfile) != null) {
// #1
JsonArray attributeOld = lwM2MClientProfileOld.getPostAttributeProfile();
Set<String> attributeSetOld = convertJsonArrayToSet(attributeOld);
@ -947,7 +972,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
JsonObject keyNameOld = lwM2MClientProfileOld.getPostKeyNameProfile();
JsonObject attributeLwm2mOld = lwM2MClientProfileOld.getPostAttributeLwm2mProfile();
LwM2mClientProfile lwM2MClientProfileNew = clientContext.getProfiles().get(deviceProfile.getUuidId());
LwM2mClientProfile lwM2MClientProfileNew = clientContext.getProfiles().get(deviceProfile.getUuidId()).clone();
JsonArray attributeNew = lwM2MClientProfileNew.getPostAttributeProfile();
Set<String> attributeSetNew = convertJsonArrayToSet(attributeNew);
JsonArray telemetryNew = lwM2MClientProfileNew.getPostTelemetryProfile();
@ -996,7 +1021,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
Registration registration = clientContext.getRegistration(registrationId);
this.readObserveFromProfile(registration, sendAttrToThingsboard.getPathPostParametersAdd(), READ);
// send attr/telemetry to tingsboard for new path
this.updateAttrTelemetry(registration, sendAttrToThingsboard.getPathPostParametersAdd());
// this.updateAttrTelemetry(registration, sendAttrToThingsboard.getPathPostParametersAdd());
});
}
// #4.2 del
@ -1275,7 +1300,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
* @param registration - Registration LwM2M Client
* @return - sessionInfo after access connect client
*/
private SessionInfoProto getSessionInfoOrCloseSession(Registration registration) {
public SessionInfoProto getSessionInfoOrCloseSession(Registration registration) {
return getSessionInfoOrCloseSession(clientContext.getOrRegister(registration));
}
@ -1331,62 +1356,72 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
}
public void getInfoFirmwareUpdate(LwM2mClient lwM2MClient) {
SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(lwM2MClient);
if (sessionInfo != null) {
TransportProtos.GetFirmwareRequestMsg getFirmwareRequestMsg = TransportProtos.GetFirmwareRequestMsg.newBuilder()
.setDeviceIdMSB(sessionInfo.getDeviceIdMSB())
.setDeviceIdLSB(sessionInfo.getDeviceIdLSB())
.setTenantIdMSB(sessionInfo.getTenantIdMSB())
.setTenantIdLSB(sessionInfo.getTenantIdLSB())
.setType(FirmwareType.FIRMWARE.name())
.build();
transportService.process(sessionInfo, getFirmwareRequestMsg,
new TransportServiceCallback<>() {
@Override
public void onSuccess(TransportProtos.GetFirmwareResponseMsg response) {
if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())) {
lwM2MClient.getFrUpdate().setCurrentFwVersion(response.getVersion());
lwM2MClient.getFrUpdate().setCurrentFwId(new FirmwareId(new UUID(response.getFirmwareIdMSB(), response.getFirmwareIdLSB())).getId());
lwM2MClient.setUpdateFw(true);
readRequestToClientFirmwareVer(lwM2MClient.getRegistration());
} else {
log.trace("Firmware [{}] [{}]", lwM2MClient.getDeviceName(), response.getResponseStatus().toString());
if (lwM2MClient.getRegistration().getSupportedVersion(FW_ID) != null) {
SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(lwM2MClient);
if (sessionInfo != null) {
DefaultLwM2MTransportMsgHandler serviceImpl = this;
transportService.process(sessionInfo, createFirmwareRequestMsg(sessionInfo, FirmwareType.FIRMWARE.name()),
new TransportServiceCallback<>() {
@Override
public void onSuccess(TransportProtos.GetFirmwareResponseMsg response) {
if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())
&& response.getType().equals(FirmwareType.FIRMWARE.name())) {
lwM2MClient.getFwUpdate().setCurrentVersion(response.getVersion());
lwM2MClient.getFwUpdate().setCurrentTitle(response.getTitle());
lwM2MClient.getFwUpdate().setCurrentId(new FirmwareId(new UUID(response.getFirmwareIdMSB(), response.getFirmwareIdLSB())).getId());
lwM2MClient.getFwUpdate().sendReadInfo(serviceImpl);
} else {
log.trace("Firmware [{}] [{}]", lwM2MClient.getDeviceName(), response.getResponseStatus().toString());
}
}
}
@Override
public void onError(Throwable e) {
log.trace("Failed to process credentials ", e);
}
});
@Override
public void onError(Throwable e) {
log.warn("Failed to process firmwareUpdate ", e);
}
});
}
}
}
/**
* @param registration
*/
public void readRequestToClientFirmwareVer(Registration registration) {
String pathIdVer = convertPathFromObjectIdToIdVer(FR_PATH_RESOURCE_VER_ID, registration);
lwM2mTransportRequest.sendAllRequest(registration, pathIdVer, READ, ContentFormat.TLV.getName(),
null, config.getTimeout(), null);
}
public void getInfoSoftwareUpdate(LwM2mClient lwM2MClient) {
if (lwM2MClient.getRegistration().getSupportedVersion(SW_ID) != null) {
SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(lwM2MClient);
if (sessionInfo != null) {
DefaultLwM2MTransportMsgHandler serviceImpl = this;
transportService.process(sessionInfo, createFirmwareRequestMsg(sessionInfo, FirmwareType.SOFTWARE.name()),
new TransportServiceCallback<>() {
@Override
public void onSuccess(TransportProtos.GetFirmwareResponseMsg response) {
if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())
&& response.getType().equals(FirmwareType.SOFTWARE.name())) {
lwM2MClient.getSwUpdate().setCurrentVersion(response.getVersion());
lwM2MClient.getSwUpdate().setCurrentTitle(response.getTitle());
lwM2MClient.getSwUpdate().setCurrentId(new FirmwareId(new UUID(response.getFirmwareIdMSB(), response.getFirmwareIdLSB())).getId());
lwM2MClient.getSwUpdate().sendReadInfo(serviceImpl);
} else {
log.trace("Software [{}] [{}]", lwM2MClient.getDeviceName(), response.getResponseStatus().toString());
}
}
/**
* @param lwM2MClient -
*/
public void updateFirmwareClient(LwM2mClient lwM2MClient) {
if (!lwM2MClient.getFrUpdate().getCurrentFwVersion().equals(lwM2MClient.getFrUpdate().getClientFwVersion())) {
int chunkSize = 0;
int chunk = 0;
byte[] firmwareChunk = firmwareDataCache.get(lwM2MClient.getFrUpdate().getCurrentFwId().toString(), chunkSize, chunk);
String verSupportedObject = lwM2MClient.getRegistration().getSupportedObject().get(FR_OBJECT_ID);
String targetIdVer = LWM2M_SEPARATOR_PATH + FR_OBJECT_ID + LWM2M_SEPARATOR_KEY + verSupportedObject + LWM2M_SEPARATOR_PATH + 0 + LWM2M_SEPARATOR_PATH + 0;
lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), targetIdVer, WRITE_REPLACE, ContentFormat.OPAQUE.getName(),
firmwareChunk, config.getTimeout(), null);
log.warn("updateFirmwareClient [{}] [{}]", lwM2MClient.getFrUpdate().getCurrentFwVersion(), lwM2MClient.getFrUpdate().getClientFwVersion());
@Override
public void onError(Throwable e) {
log.trace("Failed to process softwareUpdate ", e);
}
});
}
}
}
private TransportProtos.GetFirmwareRequestMsg createFirmwareRequestMsg(SessionInfoProto sessionInfo, String nameFwSW) {
return TransportProtos.GetFirmwareRequestMsg.newBuilder()
.setDeviceIdMSB(sessionInfo.getDeviceIdMSB())
.setDeviceIdLSB(sessionInfo.getDeviceIdLSB())
.setTenantIdMSB(sessionInfo.getTenantIdMSB())
.setTenantIdLSB(sessionInfo.getTenantIdLSB())
.setType(nameFwSW)
.build();
}
/**
* !!! sharedAttr === profileAttr !!!

11
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java

@ -24,11 +24,13 @@ import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.server.californium.LeshanServer;
import org.eclipse.leshan.server.californium.LeshanServerBuilder;
import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore;
import org.eclipse.leshan.server.californium.registration.InMemoryRegistrationStore;
import org.eclipse.leshan.server.model.LwM2mModelProvider;
import org.eclipse.leshan.server.security.DefaultAuthorizer;
import org.eclipse.leshan.server.security.EditableSecurityStore;
import org.eclipse.leshan.server.security.SecurityChecker;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
@ -57,6 +59,8 @@ import java.security.spec.InvalidParameterSpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256;
import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8;
@ -81,6 +85,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
private final CaliforniumRegistrationStore registrationStore;
private final EditableSecurityStore securityStore;
private final LwM2mClientContext lwM2mClientContext;
private ScheduledExecutorService registrationStoreExecutor;
private LeshanServer server;
@ -112,6 +117,9 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
}
private LeshanServer getLhServer() {
// this.registrationStoreExecutor = (ScheduledExecutorService) ThingsBoardExecutors.newWorkStealingPool(this.config.getRegistrationStorePoolSize(), "LwM2M registrationStore");
this.registrationStoreExecutor = Executors.newScheduledThreadPool(this.config.getRegistrationStorePoolSize(), ThingsBoardThreadFactory.forName("LwM2M registrationStore"));
LeshanServerBuilder builder = new LeshanServerBuilder();
builder.setLocalAddress(config.getHost(), config.getPort());
builder.setLocalSecureAddress(config.getSecureHost(), config.getSecurePort());
@ -119,6 +127,9 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
/* Use a magic converter to support bad type send by the UI. */
builder.setEncoder(new DefaultLwM2mNodeEncoder(LwM2mValueConverterImpl.getInstance()));
/* InMemoryRegistrationStore(ScheduledExecutorService schedExecutor, long cleanPeriodInSec) */
InMemoryRegistrationStore registrationStore = new InMemoryRegistrationStore(this.registrationStoreExecutor, this.config.getCleanPeriodInSec());
builder.setRegistrationStore(registrationStore);
/* Create CoAP Config */
builder.setCoapConfig(getCoapConfig(config.getPort(), config.getSecurePort()));

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java

@ -88,7 +88,7 @@ public class LwM2mServerListener {
public void cancelled(Observation observation) {
String msg = String.format("%s: Cancel Observation %s.", LOG_LW2M_INFO, observation.getPath());
service.sendLogsToThingsboard(msg, observation.getRegistrationId());
log.trace(msg);
log.warn(msg);
}
@Override

86
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java

@ -19,6 +19,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.Response;
import org.eclipse.leshan.core.Link;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mNode;
import org.eclipse.leshan.core.node.LwM2mPath;
@ -48,6 +49,7 @@ import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.core.util.NamedThreadFactory;
import org.eclipse.leshan.server.registration.Registration;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
@ -69,14 +71,16 @@ import static org.eclipse.californium.core.coap.CoAP.ResponseCode.CONTENT;
import static org.eclipse.leshan.core.ResponseCode.BAD_REQUEST;
import static org.eclipse.leshan.core.ResponseCode.NOT_FOUND;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.DEFAULT_TIMEOUT;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FR_PATH_RESOURCE_VER_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_PACKAGE_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_VALUE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.DISCOVER_All;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_CANCEL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_READ_ALL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.RESPONSE_CHANNEL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_PACKAGE_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromIdVerToObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.createWriteAttributeRequest;
@ -132,7 +136,13 @@ public class LwM2mTransportRequest {
break;
case OBSERVE:
if (resultIds.isResource()) {
request = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId());
Set<Observation> observations = context.getServer().getObservationService().getObservations(registration);
Set<Observation> paths = observations.stream().filter(observation -> observation.getPath().equals(resultIds)).collect(Collectors.toSet());
if (paths.size() == 0) {
request = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId());
} else {
request = new ReadRequest(contentFormat, target);
}
} else if (resultIds.isObjectInstance()) {
request = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId());
} else if (resultIds.getObjectId() >= 0) {
@ -197,11 +207,10 @@ public class LwM2mTransportRequest {
targetIdVer, params,
this.config.getModelProvider(),
this.converter);
if (resources.size()>0) {
if (resources.size() > 0) {
request = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(),
resultIds.getObjectInstanceId(), resources);
}
else {
} else {
Lwm2mClientRpcRequest rpcRequestClone = (Lwm2mClientRpcRequest) rpcRequest.clone();
if (rpcRequestClone != null) {
String errorMsg = String.format("Path %s params is not valid", targetIdVer);
@ -250,15 +259,21 @@ public class LwM2mTransportRequest {
String errorMsg = String.format("Path %s not found in object version", targetIdVer);
serviceImpl.sentRpcRequest(rpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR);
}
} else if (OBSERVE_READ_ALL.name().equals(typeOper.name())) {
Set<Observation> observations = context.getServer().getObservationService().getObservations(registration);
Set<String> observationPaths = observations.stream().map(observation -> observation.getPath().toString()).collect(Collectors.toUnmodifiableSet());
String msg = String.format("%s: type operation %s observation paths - %s", LOG_LW2M_INFO,
OBSERVE_READ_ALL.type, observationPaths);
} else if (OBSERVE_READ_ALL.name().equals(typeOper.name()) || DISCOVER_All.name().equals(typeOper.name())) {
Set<String> paths;
if (OBSERVE_READ_ALL.name().equals(typeOper.name())) {
Set<Observation> observations = context.getServer().getObservationService().getObservations(registration);
paths = observations.stream().map(observation -> observation.getPath().toString()).collect(Collectors.toUnmodifiableSet());
} else {
Link[] objectLinks = registration.getSortedObjectLinks();
paths = Arrays.stream(objectLinks).map(link -> link.toString()).collect(Collectors.toUnmodifiableSet());
}
String msg = String.format("%s: type operation %s paths - %s", LOG_LW2M_INFO,
OBSERVE_READ_ALL.type, paths);
serviceImpl.sendLogsToThingsboard(msg, registration.getId());
log.trace("[{}] [{}], [{}]", typeOper.name(), registration.getEndpoint(), msg);
log.warn("[{}] [{}], [{}]", typeOper.name(), registration.getEndpoint(), msg);
if (rpcRequest != null) {
String valueMsg = String.format("Observation paths - %s", observationPaths);
String valueMsg = String.format("Paths - %s", paths);
serviceImpl.sentRpcRequest(rpcRequest, CONTENT.name(), valueMsg, LOG_LW2M_VALUE);
}
}
@ -284,6 +299,7 @@ public class LwM2mTransportRequest {
private void sendRequest(Registration registration, LwM2mClient lwM2MClient, DownlinkRequest request,
long timeoutInMs, Lwm2mClientRpcRequest rpcRequest) {
context.getServer().send(registration, request, timeoutInMs, (ResponseCallback<?>) response -> {
if (!lwM2MClient.isInit()) {
lwM2MClient.initReadValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration));
}
@ -301,25 +317,21 @@ public class LwM2mTransportRequest {
if (rpcRequest != null) {
serviceImpl.sentRpcRequest(rpcRequest, response.getCode().getName(), response.getErrorMessage(), LOG_LW2M_ERROR);
}
/* Not Found
set setClient_fw_version = empty
*/
if (FR_PATH_RESOURCE_VER_ID.equals(request.getPath().toString()) && lwM2MClient.isUpdateFw()) {
lwM2MClient.setUpdateFw(false);
lwM2MClient.getFrUpdate().setClientFwVersion("");
/** Not Found
set setClient_fw_info... = empty
**/
if (lwM2MClient.getFwUpdate().isInfoFwSwUpdate()) {
lwM2MClient.getFwUpdate().initReadValue(serviceImpl, request.getPath().toString());
log.warn("updateFirmwareClient1");
serviceImpl.updateFirmwareClient(lwM2MClient);
}
}
}, e -> {
/* version == null
set setClient_fw_version = empty
*/
if (FR_PATH_RESOURCE_VER_ID.equals(request.getPath().toString()) && lwM2MClient.isUpdateFw()) {
lwM2MClient.setUpdateFw(false);
lwM2MClient.getFrUpdate().setClientFwVersion("");
/** version == null
set setClient_fw_info... = empty
**/
if (lwM2MClient.getFwUpdate().isInfoFwSwUpdate()) {
lwM2MClient.getFwUpdate().initReadValue(serviceImpl, request.getPath().toString());
log.warn("updateFirmwareClient2");
serviceImpl.updateFirmwareClient(lwM2MClient);
}
if (!lwM2MClient.isInit()) {
lwM2MClient.initReadValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration));
@ -458,21 +470,35 @@ public class LwM2mTransportRequest {
Math.min(valueLength, config.getLogMaxLength())));
}
value = valueLength > config.getLogMaxLength() ? value + "..." : value;
msg = String.format("%s: Update finished successfully: Lwm2m code - %d Resource path - %s length - %s value - %s",
msg = String.format("%s: Update finished successfully: Lwm2m code - %d Resource path: %s length: %s value: %s",
LOG_LW2M_INFO, response.getCode().getCode(), request.getPath().toString(), valueLength, value);
} else {
value = this.converter.convertValue(singleResource.getValue(),
singleResource.getType(), ResourceModel.Type.STRING, request.getPath());
msg = String.format("%s: Update finished successfully: Lwm2m code - %d Resource path - %s value - %s",
msg = String.format("%s: Update finished successfully. Lwm2m code: %d Resource path: %s value: %s",
LOG_LW2M_INFO, response.getCode().getCode(), request.getPath().toString(), value);
}
if (msg != null) {
serviceImpl.sendLogsToThingsboard(msg, registration.getId());
log.warn("[{}] [{}] [{}] - [{}] [{}] Update finished successfully: [{}]", request.getClass().getName(), registration.getEndpoint(),
((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString(), value);
log.warn(msg);
if (request.getPath().toString().equals(FW_PACKAGE_ID) || request.getPath().toString().equals(SW_PACKAGE_ID)) {
this.executeFwSwUpdate(registration, request);
}
}
} catch (Exception e) {
log.trace("Fail convert value from request to string. ", e);
}
}
private void executeFwSwUpdate(Registration registration, DownlinkRequest request) {
LwM2mClient lwM2mClient = this.lwM2mClientContext.getClientByRegistrationId(registration.getId());
if (request.getPath().toString().equals(FW_PACKAGE_ID)
&& FirmwareUpdateStatus.DOWNLOADING.name().equals(lwM2mClient.getFwUpdate().getStateUpdate())) {
lwM2mClient.getFwUpdate().sendReadInfoForWrite();
}
if (request.getPath().toString().equals(SW_PACKAGE_ID)
&& FirmwareUpdateStatus.DOWNLOADING.name().equals(lwM2mClient.getSwUpdate().getStateUpdate())) {
lwM2mClient.getSwUpdate().sendReadInfoForWrite();
}
}
}

359
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java

@ -43,6 +43,7 @@ import org.eclipse.leshan.server.registration.Registration;
import org.nustaq.serialization.FSTConfiguration;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
@ -70,6 +71,12 @@ import static org.eclipse.leshan.core.model.ResourceModel.Type.OBJLNK;
import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE;
import static org.eclipse.leshan.core.model.ResourceModel.Type.STRING;
import static org.eclipse.leshan.core.model.ResourceModel.Type.TIME;
import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.DOWNLOADED;
import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.DOWNLOADING;
import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.FAILED;
import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.UPDATED;
import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.UPDATING;
import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.VERIFIED;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
@ -107,11 +114,41 @@ public class LwM2mTransportUtil {
public static final int LWM2M_STRATEGY_2 = 2;
public static final String CLIENT_NOT_AUTHORIZED = "Client not authorized";
public static final Integer FR_OBJECT_ID = 5;
public static final Integer FR_RESOURCE_VER_ID = 7;
public static final String FR_PATH_RESOURCE_VER_ID = LWM2M_SEPARATOR_PATH + FR_OBJECT_ID + LWM2M_SEPARATOR_PATH
+ "0" + LWM2M_SEPARATOR_PATH + FR_RESOURCE_VER_ID;
public static final String LWM2M_VERSION_DEFAULT = "1.0";
// FirmWare
public static final String FW_UPDATE = "Firmware update";
public static final Integer FW_ID = 5;
// Package W
public static final String FW_PACKAGE_ID = "/5/0/0";
// State R
public static final String FW_STATE_ID = "/5/0/3";
// Update Result R
public static final String FW_RESULT_ID = "/5/0/5";
// PkgName R
public static final String FW_NAME_ID = "/5/0/6";
// PkgVersion R
public static final String FW_VER_ID = "/5/0/7";
// Update E
public static final String FW_UPDATE_ID = "/5/0/2";
// SoftWare
public static final String SW_UPDATE = "Software update";
public static final Integer SW_ID = 9;
// Package W
public static final String SW_PACKAGE_ID = "/9/0/2";
// Update State R
public static final String SW_UPDATE_STATE_ID = "/9/0/7";
// Update Result R
public static final String SW_RESULT_ID = "/9/0/9";
// PkgName R
public static final String SW_NAME_ID = "/9/0/0";
// PkgVersion R
public static final String SW_VER_ID = "/9/0/1";
// Install E
public static final String SW_INSTALL_ID = "/9/0/4";
// Uninstall E
public static final String SW_UN_INSTALL_ID = "/9/0/6";
public enum LwM2mTypeServer {
BOOTSTRAP(0, "bootstrap"),
@ -144,19 +181,20 @@ public class LwM2mTransportUtil {
*/
READ(0, "Read"),
DISCOVER(1, "Discover"),
OBSERVE_READ_ALL(2, "ObserveReadAll"),
DISCOVER_All(2, "DiscoverAll"),
OBSERVE_READ_ALL(3, "ObserveReadAll"),
/**
* POST
*/
OBSERVE(3, "Observe"),
OBSERVE_CANCEL(4, "ObserveCancel"),
EXECUTE(5, "Execute"),
OBSERVE(4, "Observe"),
OBSERVE_CANCEL(5, "ObserveCancel"),
EXECUTE(6, "Execute"),
/**
* Replaces the Object Instance or the Resource(s) with the new value provided in the “Write” operation. (see
* section 5.3.3 of the LW M2M spec).
* if all resources are to be replaced
*/
WRITE_REPLACE(6, "WriteReplace"),
WRITE_REPLACE(7, "WriteReplace"),
/*
PUT
*/
@ -165,11 +203,13 @@ public class LwM2mTransportUtil {
* 5.3.3 of the LW M2M spec).
* if this is a partial update request
*/
WRITE_UPDATE(7, "WriteUpdate"),
WRITE_ATTRIBUTES(8, "WriteAttributes"),
DELETE(9, "Delete");
WRITE_UPDATE(8, "WriteUpdate"),
WRITE_ATTRIBUTES(9, "WriteAttributes"),
DELETE(10, "Delete"),
// READ_INFO_FW(10, "ReadInfoFirmware");
// only for RPC
READ_INFO_FW(11, "ReadInfoFirmware"),
READ_INFO_SW(12, "ReadInfoSoftware");
public int code;
public String type;
@ -189,10 +229,288 @@ public class LwM2mTransportUtil {
}
}
/**
* /** State R
* 0: Idle (before downloading or after successful updating)
* 1: Downloading (The data sequence is on the way)
* 2: Downloaded
* 3: Updating
*/
public enum StateFw {
IDLE(0, "Idle"),
DOWNLOADING(1, "Downloading"),
DOWNLOADED(2, "Downloaded"),
UPDATING(3, "Updating");
public int code;
public String type;
StateFw(int code, String type) {
this.code = code;
this.type = type;
}
public static StateFw fromStateFwByType(String type) {
for (StateFw to : StateFw.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported FW State type : %s", type));
}
public static StateFw fromStateFwByCode(int code) {
for (StateFw to : StateFw.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported FW State code : %s", code));
}
}
/**
* FW Update Result
* 0: Initial value. Once the updating process is initiated (Download /Update), this Resource MUST be reset to Initial value.
* 1: Firmware updated successfully.
* 2: Not enough flash memory for the new firmware package.
* 3: Out of RAM during downloading process.
* 4: Connection lost during downloading process.
* 5: Integrity check failure for new downloaded package.
* 6: Unsupported package type.
* 7: Invalid URI.
* 8: Firmware update failed.
* 9: Unsupported protocol.
*/
public enum UpdateResultFw {
INITIAL(0, "Initial value", false),
UPDATE_SUCCESSFULLY(1, "Firmware updated successfully", false),
NOT_ENOUGH(2, "Not enough flash memory for the new firmware package", false),
OUT_OFF_MEMORY(3, "Out of RAM during downloading process", false),
CONNECTION_LOST(4, "Connection lost during downloading process", true),
INTEGRITY_CHECK_FAILURE(5, "Integrity check failure for new downloaded package", true),
UNSUPPORTED_TYPE(6, "Unsupported package type", false),
INVALID_URI(7, "Invalid URI", false),
UPDATE_FAILED(8, "Firmware update failed", false),
UNSUPPORTED_PROTOCOL(9, "Unsupported protocol", false);
public int code;
public String type;
public boolean isAgain;
UpdateResultFw(int code, String type, boolean isAgain) {
this.code = code;
this.type = type;
this.isAgain = isAgain;
}
public static UpdateResultFw fromUpdateResultFwByType(String type) {
for (UpdateResultFw to : UpdateResultFw.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported FW Update Result type : %s", type));
}
public static UpdateResultFw fromUpdateResultFwByCode(int code) {
for (UpdateResultFw to : UpdateResultFw.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported FW Update Result code : %s", code));
}
}
/**
* FirmwareUpdateStatus {
* DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING, UPDATED, FAILED
*/
public static FirmwareUpdateStatus EqualsFwSateToFirmwareUpdateStatus(StateFw stateFw, UpdateResultFw updateResultFw) {
switch (updateResultFw) {
case INITIAL:
switch (stateFw) {
case IDLE:
return VERIFIED;
case DOWNLOADING:
return DOWNLOADING;
case DOWNLOADED:
return DOWNLOADED;
case UPDATING:
return UPDATING;
}
case UPDATE_SUCCESSFULLY:
return UPDATED;
case NOT_ENOUGH:
case OUT_OFF_MEMORY:
case CONNECTION_LOST:
case INTEGRITY_CHECK_FAILURE:
case UNSUPPORTED_TYPE:
case INVALID_URI:
case UPDATE_FAILED:
case UNSUPPORTED_PROTOCOL:
return FAILED;
default:
throw new CodecException("Invalid value stateFw %s %s for FirmwareUpdateStatus.", stateFw.name(), updateResultFw.name());
}
}
/**
* Update State R
* 0: INITIAL Before downloading. (see 5.1.2.1)
* 1: DOWNLOAD STARTED The downloading process has started and is on-going. (see 5.1.2.2)
* 2: DOWNLOADED The package has been completely downloaded (see 5.1.2.3)
* 3: DELIVERED In that state, the package has been correctly downloaded and is ready to be installed. (see 5.1.2.4)
* If executing the Install Resource failed, the state remains at DELIVERED.
* If executing the Install Resource was successful, the state changes from DELIVERED to INSTALLED.
* After executing the UnInstall Resource, the state changes to INITIAL.
* 4: INSTALLED
*/
public enum UpdateStateSw {
INITIAL(0, "Initial"),
DOWNLOAD_STARTED(1, "DownloadStarted"),
DOWNLOADED(2, "Downloaded"),
DELIVERED(3, "Delivered"),
INSTALLED(4, "Installed");
public int code;
public String type;
UpdateStateSw(int code, String type) {
this.code = code;
this.type = type;
}
public static UpdateStateSw fromUpdateStateSwByType(String type) {
for (UpdateStateSw to : UpdateStateSw.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported SW State type : %s", type));
}
public static UpdateStateSw fromUpdateStateSwByCode(int code) {
for (UpdateStateSw to : UpdateStateSw.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported SW State type : %s", code));
}
}
/**
* SW Update Result
* Contains the result of downloading or installing/uninstalling the software
* 0: Initial value.
* - Prior to download any new package in the Device, Update Result MUST be reset to this initial value.
* - One side effect of executing the Uninstall resource is to reset Update Result to this initial value "0".
* 1: Downloading.
* - The package downloading process is on-going.
* 2: Software successfully installed.
* 3: Successfully Downloaded and package integrity verified
* (( 4-49, for expansion, of other scenarios))
* ** Failed
* 50: Not enough storage for the new software package.
* 51: Out of memory during downloading process.
* 52: Connection lost during downloading process.
* 53: Package integrity check failure.
* 54: Unsupported package type.
* 56: Invalid URI
* 57: Device defined update error
* 58: Software installation failure
* 59: Uninstallation Failure during forUpdate(arg=0)
* 60-200 : (for expansion, selection to be in blocks depending on new introduction of features)
* This Resource MAY be reported by sending Observe operation.
*/
public enum UpdateResultSw {
INITIAL(0, "Initial value", false),
DOWNLOADING(1, "Downloading", false),
SUCCESSFULLY_INSTALLED(2, "Software successfully installed", false),
SUCCESSFULLY_INSTALLED_VERIFIED(3, "Successfully Downloaded and package integrity verified", false),
NOT_ENOUGH_STORAGE(50, "Not enough storage for the new software package", true),
OUT_OFF_MEMORY(51, "Out of memory during downloading process", true),
CONNECTION_LOST(52, "Connection lost during downloading process", false),
PACKAGE_CHECK_FAILURE(53, "Package integrity check failure.", false),
UNSUPPORTED_PACKAGE_TYPE(54, "Unsupported package type", false),
INVALID_URI(56, "Invalid URI", true),
UPDATE_ERROR(57, "Device defined update error", true),
INSTALL_FAILURE(58, "Software installation failure", true),
UN_INSTALL_FAILURE(59, "Uninstallation Failure during forUpdate(arg=0)", true);
public int code;
public String type;
public boolean isAgain;
UpdateResultSw(int code, String type, boolean isAgain) {
this.code = code;
this.type = type;
this.isAgain = isAgain;
}
public static UpdateResultSw fromUpdateResultSwByType(String type) {
for (UpdateResultSw to : UpdateResultSw.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported SW Update Result type : %s", type));
}
public static UpdateResultSw fromUpdateResultSwByCode(int code) {
for (UpdateResultSw to : UpdateResultSw.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported SW Update Result code : %s", code));
}
}
/**
* FirmwareUpdateStatus {
* DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING, UPDATED, FAILED
*/
public static FirmwareUpdateStatus EqualsSwSateToFirmwareUpdateStatus(UpdateStateSw updateStateSw, UpdateResultSw updateResultSw) {
switch (updateResultSw) {
case INITIAL:
switch (updateStateSw) {
case INITIAL:
case DOWNLOAD_STARTED:
return DOWNLOADING;
case DOWNLOADED:
return DOWNLOADED;
case DELIVERED:
return VERIFIED;
}
case DOWNLOADING:
return DOWNLOADING;
case SUCCESSFULLY_INSTALLED:
return UPDATED;
case SUCCESSFULLY_INSTALLED_VERIFIED:
return VERIFIED;
case NOT_ENOUGH_STORAGE:
case OUT_OFF_MEMORY:
case CONNECTION_LOST:
case PACKAGE_CHECK_FAILURE:
case UNSUPPORTED_PACKAGE_TYPE:
case INVALID_URI:
case UPDATE_ERROR:
case INSTALL_FAILURE:
case UN_INSTALL_FAILURE:
return FAILED;
default:
throw new CodecException("Invalid value stateFw %s %s for FirmwareUpdateStatus.", updateStateSw.name(), updateResultSw.name());
}
}
public static final String EVENT_AWAKE = "AWAKE";
public static final String RESPONSE_CHANNEL = "RESP";
public static boolean equalsResourceValue(Object valueOld, Object valueNew, ResourceModel.Type type, LwM2mPath resourcePath) throws CodecException {
public static boolean equalsResourceValue(Object valueOld, Object valueNew, ResourceModel.Type type, LwM2mPath
resourcePath) throws CodecException {
switch (type) {
case BOOLEAN:
case INTEGER:
@ -256,7 +574,7 @@ public class LwM2mTransportUtil {
* "/3_1.0/0/9": {"pmax": 45}, "/3_1.2": {ver": "3_1.2"}}
*/
public static LwM2mClientProfile toLwM2MClientProfile(DeviceProfile deviceProfile) {
if (deviceProfile != null && ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) {
if (((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) {
Object profile = ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties();
try {
ObjectMapper mapper = new ObjectMapper();
@ -375,7 +693,8 @@ public class LwM2mTransportUtil {
return StringUtils.join(linkedListOut, "");
}
public static <T> TransportServiceCallback<Void> getAckCallback(LwM2mClient lwM2MClient, int requestId, String typeTopic) {
public static <T> TransportServiceCallback<Void> getAckCallback(LwM2mClient lwM2MClient,
int requestId, String typeTopic) {
return new TransportServiceCallback<Void>() {
@Override
public void onSuccess(Void dummy) {
@ -420,7 +739,8 @@ public class LwM2mTransportUtil {
return null;
}
public static String validPathIdVer(String pathIdVer, Registration registration) throws IllegalArgumentException {
public static String validPathIdVer(String pathIdVer, Registration registration) throws
IllegalArgumentException {
if (!pathIdVer.contains(LWM2M_SEPARATOR_PATH)) {
throw new IllegalArgumentException(String.format("Error:"));
} else {
@ -436,6 +756,7 @@ public class LwM2mTransportUtil {
public static String convertPathFromObjectIdToIdVer(String path, Registration registration) {
String ver = registration.getSupportedObject().get(new LwM2mPath(path).getObjectId());
ver = ver != null ? ver : LWM2M_VERSION_DEFAULT;
try {
String[] keyArray = path.split(LWM2M_SEPARATOR_PATH);
if (keyArray.length > 1) {
@ -531,7 +852,7 @@ public class LwM2mTransportUtil {
case "ObjectLink":
return OBJLNK;
default:
return null;
return null;
}
}
}

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

@ -27,6 +27,7 @@ import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.security.SecurityInfo;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.firmware.FirmwareType;
import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
@ -49,8 +50,9 @@ import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.TRANSPORT_DEFAULT_LWM2M_VERSION;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromIdVerToObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getVerFromPathIdVerOrId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.equalsResourceTypeGetSimpleName;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getVerFromPathIdVerOrId;
@Slf4j
public class LwM2mClient implements Cloneable {
@ -74,25 +76,25 @@ public class LwM2mClient implements Cloneable {
private UUID profileId;
@Getter
@Setter
private volatile LwM2mFirmwareUpdate frUpdate;
private volatile LwM2mFwSwUpdate fwUpdate;
@Getter
@Setter
private volatile LwM2mFwSwUpdate swUpdate;
@Getter
@Setter
private Registration registration;
private ValidateDeviceCredentialsResponseMsg credentialsResponse;
@Getter
private final Map<String, ResourceValue> resources;
@Getter
private final Map<String, TsKvProto> delayedRequests;
@Getter
@Setter
private final List<String> pendingReadRequests;
@Getter
private final Queue<LwM2mQueuedRequest> queuedRequests;
@Getter
private boolean init;
@Getter
@Setter
private volatile boolean updateFw;
public Object clone() throws CloneNotSupportedException {
return super.clone();
@ -109,9 +111,9 @@ public class LwM2mClient implements Cloneable {
this.profileId = profileId;
this.sessionId = sessionId;
this.init = false;
this.updateFw = false;
this.queuedRequests = new ConcurrentLinkedQueue<>();
this.frUpdate = new LwM2mFirmwareUpdate();
this.fwUpdate = new LwM2mFwSwUpdate(this, FirmwareType.FIRMWARE);
this.swUpdate = new LwM2mFwSwUpdate(this, FirmwareType.SOFTWARE);
if (this.credentialsResponse != null && this.credentialsResponse.hasDeviceInfo()) {
this.session = createSession(nodeId, sessionId, credentialsResponse);
this.deviceId = new UUID(session.getDeviceIdMSB(), session.getDeviceIdLSB());
@ -164,15 +166,15 @@ public class LwM2mClient implements Cloneable {
.build();
}
public boolean saveResourceValue(String pathRez, LwM2mResource rez, LwM2mModelProvider modelProvider) {
if (this.resources.get(pathRez) != null && this.resources.get(pathRez).getResourceModel() != null) {
this.resources.get(pathRez).setLwM2mResource(rez);
public boolean saveResourceValue(String pathRezIdVer, LwM2mResource rez, LwM2mModelProvider modelProvider) {
if (this.resources.get(pathRezIdVer) != null && this.resources.get(pathRezIdVer).getResourceModel() != null) {
this.resources.get(pathRezIdVer).setLwM2mResource(rez);
return true;
} else {
LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRez));
LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRezIdVer));
ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId());
if (resourceModel != null) {
this.resources.put(pathRez, new ResourceValue(rez, resourceModel));
this.resources.put(pathRezIdVer, new ResourceValue(rez, resourceModel));
return true;
} else {
return false;
@ -180,6 +182,16 @@ public class LwM2mClient implements Cloneable {
}
}
public Object getResourceValue (String pathRezIdVer, String pathRezId) {
String pathRez = pathRezIdVer == null ? convertPathFromObjectIdToIdVer(pathRezId, this.registration) : pathRezIdVer;
if (this.resources.get(pathRez) != null) {
return this.resources.get(pathRez).getLwM2mResource().isMultiInstances() ?
this.resources.get(pathRez).getLwM2mResource().getValues() :
this.resources.get(pathRez).getLwM2mResource().getValue();
}
return null;
}
public ResourceModel getResourceModel(String pathIdVer, LwM2mModelProvider modelProvider) {
LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer));
String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId());

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

@ -54,7 +54,7 @@ public interface LwM2mClientContext {
Map<UUID, LwM2mClientProfile> setProfiles(Map<UUID, LwM2mClientProfile> profiles);
LwM2mClientProfile toClientProfile(DeviceProfile deviceProfile);
LwM2mClientProfile profileUpdate(DeviceProfile deviceProfile);
Set<String> getSupportedIdVerInClient(Registration registration);

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

@ -16,6 +16,7 @@
package org.thingsboard.server.transport.lwm2m.server.client;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.security.EditableSecurityStore;
@ -40,6 +41,7 @@ import java.util.concurrent.ConcurrentHashMap;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer;
@Slf4j
@Service
@TbLwM2mTransportComponent
@RequiredArgsConstructor
@ -112,8 +114,12 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
EndpointSecurityInfo securityInfo = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(endpoint, LwM2mTransportUtil.LwM2mTypeServer.CLIENT);
if (securityInfo.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) {
if (securityInfo.getDeviceProfile() != null) {
toClientProfile(securityInfo.getDeviceProfile());
UUID profileUuid = securityInfo.getDeviceProfile().getUuidId();
UUID profileUuid = profileUpdate(securityInfo.getDeviceProfile())!= null ?
securityInfo.getDeviceProfile().getUuidId() : null;
// TODO: for tests bug.
if (profileUuid== null) {
log.warn("input parameters toClientProfile if the result is null: [{}]", securityInfo.getDeviceProfile());
}
LwM2mClient client;
if (securityInfo.getSecurityInfo() != null) {
client = new LwM2mClient(context.getNodeId(), securityInfo.getSecurityInfo().getEndpoint(),
@ -162,13 +168,16 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
}
@Override
public LwM2mClientProfile toClientProfile(DeviceProfile deviceProfile) {
LwM2mClientProfile lwM2MClientProfile = profiles.get(deviceProfile.getUuidId());
if (lwM2MClientProfile == null) {
lwM2MClientProfile = LwM2mTransportUtil.toLwM2MClientProfile(deviceProfile);
public LwM2mClientProfile profileUpdate(DeviceProfile deviceProfile) {
LwM2mClientProfile lwM2MClientProfile = deviceProfile != null ?
LwM2mTransportUtil.toLwM2MClientProfile(deviceProfile) : null;
if (lwM2MClientProfile != null) {
profiles.put(deviceProfile.getUuidId(), lwM2MClientProfile);
return lwM2MClientProfile;
}
else {
return null;
}
return lwM2MClientProfile;
}
/**

27
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFirmwareUpdate.java

@ -1,27 +0,0 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.server.client;
import lombok.Data;
import java.util.UUID;
@Data
public class LwM2mFirmwareUpdate {
private volatile String clientFwVersion;
private volatile String currentFwVersion;
private volatile UUID currentFwId;
}

333
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java

@ -0,0 +1,333 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.server.client;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.leshan.core.request.ContentFormat;
import org.thingsboard.server.common.data.firmware.FirmwareType;
import org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2MTransportMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import static org.thingsboard.server.common.data.firmware.FirmwareKey.STATE;
import static org.thingsboard.server.common.data.firmware.FirmwareType.FIRMWARE;
import static org.thingsboard.server.common.data.firmware.FirmwareType.SOFTWARE;
import static org.thingsboard.server.common.data.firmware.FirmwareUtil.getAttributeKey;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_NAME_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_PACKAGE_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_RESULT_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_STATE_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_UPDATE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_UPDATE_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_VER_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.EXECUTE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.READ;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_REPLACE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_INSTALL_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_NAME_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_PACKAGE_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_RESULT_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_UN_INSTALL_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_UPDATE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_UPDATE_STATE_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_VER_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer;
@Slf4j
public class LwM2mFwSwUpdate {
// 5/0/6 PkgName
// 9/0/0 PkgName
@Getter
@Setter
private volatile String currentTitle;
// 5/0/7 PkgVersion
// 9/0/1 PkgVersion
@Getter
@Setter
private volatile String currentVersion;
@Getter
@Setter
private volatile UUID currentId;
@Getter
@Setter
private volatile String stateUpdate;
@Getter
private String pathPackageId;
@Getter
private String pathStateId;
@Getter
private String pathResultId;
@Getter
private String pathNameId;
@Getter
private String pathVerId;
@Getter
private String pathInstallId;
@Getter
private String pathUnInstallId;
@Getter
private String wUpdate;
@Getter
@Setter
private volatile boolean infoFwSwUpdate = false;
private final FirmwareType type;
private DefaultLwM2MTransportMsgHandler serviceImpl;
@Getter
LwM2mClient lwM2MClient;
@Getter
@Setter
private final List<String> pendingInfoRequestsStart;
public LwM2mFwSwUpdate(LwM2mClient lwM2MClient, FirmwareType type) {
this.lwM2MClient = lwM2MClient;
this.pendingInfoRequestsStart = new CopyOnWriteArrayList<>();
this.type = type;
this.stateUpdate = null;
this.initPathId();
}
private void initPathId() {
if (this.type.equals(FIRMWARE)) {
this.pathPackageId = FW_PACKAGE_ID;
this.pathStateId = FW_STATE_ID;
this.pathResultId = FW_RESULT_ID;
this.pathNameId = FW_NAME_ID;
this.pathVerId = FW_VER_ID;
this.pathInstallId = FW_UPDATE_ID;
this.wUpdate = FW_UPDATE;
} else if (this.type.equals(SOFTWARE)) {
this.pathPackageId = SW_PACKAGE_ID;
this.pathStateId = SW_UPDATE_STATE_ID;
this.pathResultId = SW_RESULT_ID;
this.pathNameId = SW_NAME_ID;
this.pathVerId = SW_VER_ID;
this.pathInstallId = SW_INSTALL_ID;
this.pathUnInstallId = SW_UN_INSTALL_ID;
this.wUpdate = SW_UPDATE;
}
}
public void initReadValue(DefaultLwM2MTransportMsgHandler serviceImpl, String pathIdVer) {
if (this.serviceImpl == null) this.serviceImpl = serviceImpl;
if (pathIdVer != null) {
this.pendingInfoRequestsStart.remove(pathIdVer);
}
if (this.pendingInfoRequestsStart.size() == 0) {
this.infoFwSwUpdate = false;
if (!FirmwareUpdateStatus.DOWNLOADING.name().equals(this.stateUpdate)) {
boolean conditionalStart = this.type.equals(FIRMWARE) ? this.conditionalFwUpdateStart() :
this.conditionalSwUpdateStart();
if (conditionalStart) {
this.stateUpdate = FirmwareUpdateStatus.DOWNLOADING.name();
this.observeStateUpdate();
this.writeFwSwWare();
this.sendLogs(WRITE_REPLACE.name());
}
} else {
boolean conditionalExecute = this.type.equals(FIRMWARE) ? conditionalFwUpdateExecute() :
conditionalSwUpdateExecute();
if (conditionalExecute) {
this.stateUpdate = FirmwareUpdateStatus.DOWNLOADED.name();
this.observeStateUpdate();
this.executeFwSwWare();
this.sendLogs(EXECUTE.name());
}
}
}
}
private void writeFwSwWare() {
int chunkSize = 0;
int chunk = 0;
byte[] firmwareChunk = this.serviceImpl.firmwareDataCache.get(this.currentId.toString(), chunkSize, chunk);
String targetIdVer = convertPathFromObjectIdToIdVer(this.pathPackageId, this.lwM2MClient.getRegistration());
this.serviceImpl.lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), targetIdVer, WRITE_REPLACE, ContentFormat.OPAQUE.getName(),
firmwareChunk, this.serviceImpl.config.getTimeout(), null);
}
public void sendLogs(String typeOper) {
this.sendSateOnThingsboard();
String msg = String.format("%s: %s, %s, pkgVer: %s: pkgName - %s.",
LOG_LW2M_INFO, this.wUpdate, typeOper, this.currentVersion, this.currentTitle);
serviceImpl.sendLogsToThingsboard(msg, lwM2MClient.getRegistration().getId());
log.warn("{} state: [{}]", msg, this.stateUpdate);
}
public void executeFwSwWare() {
this.serviceImpl.lwM2mTransportRequest.sendAllRequest(this.lwM2MClient.getRegistration(), this.pathInstallId, EXECUTE, ContentFormat.TLV.getName(),
null, 0, null);
}
/**
* FW: start
* Проверяем состояние State (5.3) и Update Result (5.5).
* 1. Если Update Result > 1 (some errors) - Это означает что пред. апдейт не прошел.
* - Запускаем апдейт в независимости от состяния прошивки и ее версии.
* 2. Если Update Result = 1 && State = 0 - Это означает что пред. апдейт прошел.
* 3. Если Update Result = 0 && State = 0 && Ver = "" - Это означает что апдейта еще не было.
* - Проверяем поменялась ли версия и запускаем новый апдейт.
* Новый апдейт:
* 1. Запись новой прошивки в Lwm2mClient
* 2. Мониторим итог зиписи:
* 2.1 State = 2 "Downloaded" и Update Result = 0 "INITIAL" стартуем Update 5.2 (Execute):
* Мониторим состояние Update Result и State и мапим его на наш enum (DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING, UPDATED, FAILED)
* + пишем лог (в телеметрию отдельным полем error) с подробным статусом.
*
* @valerii.sosliuk Вопрос к клиенту - как будем реагировать на Failed update? Когда повторять операцию?
* - На update reg?
* - Или клиент должен послать комканду на рестарт девайса?
* - или переодически?
* отправили прошивку мониторим:
* -- Observe "Update Result" id=5 && "State" id=3
* --- "Update Result" id=5 value must be = 0
* --- "State" id=3 value must be > 0
* --- to telemetry - DOWNLOADING
* "Update Result" id=5 value change > 1 "Firmware updated not successfully" отправили прошивку: telemetry - FAILED
* "Update Result" id=5 value change ==1 "State" id=3 value == 0 "Firmware updated successfully" отправили прошивку: telemetry - UPDATED
*/
private boolean conditionalFwUpdateStart() {
Long stateFw = (Long) this.lwM2MClient.getResourceValue(null, this.pathStateId);
Long updateResultFw = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
String pkgName = (String) this.lwM2MClient.getResourceValue(null, this.pathNameId);
// #1/#2
return updateResultFw > LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code ||
(
(
(stateFw == LwM2mTransportUtil.StateFw.IDLE.code && updateResultFw == LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code) ||
(stateFw == LwM2mTransportUtil.StateFw.IDLE.code && updateResultFw == LwM2mTransportUtil.UpdateResultFw.INITIAL.code
&& StringUtils.trimToEmpty(pkgName).isEmpty())
) &&
(
(this.currentVersion != null && !this.currentVersion.equals(this.lwM2MClient.getResourceValue(null, this.pathVerId))) ||
(this.currentTitle != null && !this.currentTitle.equals(this.lwM2MClient.getResourceValue(null, this.pathNameId)))
)
);
// if (condFwUpdateStart) {
// this.sendSateOnThingsboard(stateFw, updateResultFw, pkgName);
// }
// return condFwUpdateStart;
}
private boolean conditionalFwUpdateExecute() {
Long state = (Long) this.lwM2MClient.getResourceValue(null, this.pathStateId);
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
// #1/#2
return updateResult == LwM2mTransportUtil.UpdateResultFw.INITIAL.code && state == LwM2mTransportUtil.StateFw.DOWNLOADED.code;
}
/**
* FW: start
* Проверяем состояние Update_State (9.7) и Update_Result (9.9).
* 1. Если Update Result > 3 (some errors) - Это означает что пред. апдейт не прошел.
* - Запускаем апдейт в независимости от состяния прошивки и ее версии.
* 2. Если Update Result = 2 && Update State = 4 - Это означает что пред. апдейт прошел
* 3. Если Update Result = 0 && Update State = 0 && Ver = "" - Это означает что апдейта еще не было.
* 4. Если Update Result = 0 && Update State = 0 - Это означает что пред. апдейт UnInstall
* - Проверяем поменялась ли версия и запускаем новый апдейт.
* Новый апдейт:
* 1. Запись новой прошивки в Lwm2mClient
* 2. Мониторим итог зиписи:
* 2.1 Update State = 3 "DELIVERED" стартуем Install 9.4 (Execute):
* Мониторим состояние Update Result и State и мапим его на наш enum (DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING, UPDATED, FAILED)
* + пишем лог (в телеметрию отдельным полем error) с подробным статусом.
*/
private boolean conditionalSwUpdateStart() {
Long updateState = (Long) this.lwM2MClient.getResourceValue(null, this.pathStateId);
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
String pkgName = (String) this.lwM2MClient.getResourceValue(null, this.pathNameId);
// #1/#2
return updateResult > LwM2mTransportUtil.UpdateResultSw.SUCCESSFULLY_INSTALLED_VERIFIED.code ||
(
(
(
(
(updateState == LwM2mTransportUtil.UpdateStateSw.INSTALLED.code && updateResult == LwM2mTransportUtil.UpdateResultSw.SUCCESSFULLY_INSTALLED.code) ||
(updateState == LwM2mTransportUtil.UpdateStateSw.INITIAL.code && updateResult == LwM2mTransportUtil.UpdateResultSw.INITIAL.code &&
StringUtils.trimToEmpty(pkgName).isEmpty())
)
) &&
(updateState == LwM2mTransportUtil.UpdateStateSw.INITIAL.code && updateResult == LwM2mTransportUtil.UpdateResultSw.INITIAL.code)
) &&
(
(this.currentVersion != null && !this.currentVersion.equals(this.lwM2MClient.getResourceValue(null, this.pathVerId))) ||
(this.currentTitle != null && !this.currentTitle.equals(this.lwM2MClient.getResourceValue(null, this.pathNameId)))
)
);
// return condSwUpdateStart;
}
private boolean conditionalSwUpdateExecute() {
Long updateState = (Long) this.lwM2MClient.getResourceValue(null, this.pathStateId);
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
// #1/#2
return (updateResult == LwM2mTransportUtil.UpdateResultSw.INITIAL.code || updateResult == LwM2mTransportUtil.UpdateResultSw.SUCCESSFULLY_INSTALLED_VERIFIED.code) &&
updateState == LwM2mTransportUtil.UpdateStateSw.DELIVERED.code;
}
private void observeStateUpdate() {
this.serviceImpl.lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(),
convertPathFromObjectIdToIdVer(this.pathStateId, this.lwM2MClient.getRegistration()), OBSERVE,
null, null, 0, null);
this.serviceImpl.lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(),
convertPathFromObjectIdToIdVer(this.pathResultId, this.lwM2MClient.getRegistration()), OBSERVE,
null, null, 0, null);
}
public void sendSateOnThingsboard() {
if (StringUtils.trimToNull(this.stateUpdate) != null) {
List<TransportProtos.KeyValueProto> result = new ArrayList<>();
TransportProtos.KeyValueProto.Builder kvProto = TransportProtos.KeyValueProto.newBuilder().setKey(getAttributeKey(this.type, STATE));
kvProto.setType(TransportProtos.KeyValueType.STRING_V).setStringV(stateUpdate);
result.add(kvProto.build());
this.serviceImpl.helper.sendParametersOnThingsboardTelemetry(result,
this.serviceImpl.getSessionInfoOrCloseSession(this.lwM2MClient.getRegistration()));
}
}
public void sendReadInfo(DefaultLwM2MTransportMsgHandler serviceImpl) {
this.serviceImpl = this.serviceImpl == null ? serviceImpl : this.serviceImpl;
this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer(
this.pathVerId, this.lwM2MClient.getRegistration()));
this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer(
this.pathNameId, this.lwM2MClient.getRegistration()));
this.sendReadInfoForWrite();
}
public void sendReadInfoForWrite() {
this.infoFwSwUpdate = true;
this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer(
this.pathStateId, this.lwM2MClient.getRegistration()));
this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer(
this.pathResultId, this.lwM2MClient.getRegistration()));
this.pendingInfoRequestsStart.forEach(pathIdVer -> {
this.serviceImpl.lwM2mTransportRequest.sendAllRequest(this.lwM2MClient.getRegistration(), pathIdVer, READ, ContentFormat.TLV.getName(),
null, 0, null);
});
}
}

4
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java

@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.client;
import com.google.gson.JsonObject;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.server.registration.Registration;
import org.thingsboard.server.gen.transport.TransportProtos;
@ -27,6 +28,7 @@ import java.util.concurrent.ConcurrentHashMap;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.validPathIdVer;
@Slf4j
@Data
public class Lwm2mClientRpcRequest {
public final String targetIdVerKey = "targetIdVer";
@ -113,7 +115,7 @@ public class Lwm2mClientRpcRequest {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
log.error("", e);
}
return null;
}

11
dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java

@ -41,7 +41,6 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.common.data.query.DeviceTypeFilter;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
@ -102,6 +101,11 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
@Override
public AlarmOperationResult createOrUpdateAlarm(Alarm alarm) {
return createOrUpdateAlarm(alarm, true);
}
@Override
public AlarmOperationResult createOrUpdateAlarm(Alarm alarm, boolean alarmCreationEnabled) {
alarmDataValidator.validate(alarm, Alarm::getTenantId);
try {
if (alarm.getStartTs() == 0L) {
@ -114,6 +118,9 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
if (alarm.getId() == null) {
Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType()).get();
if (existing == null || existing.getStatus().isCleared()) {
if (!alarmCreationEnabled) {
throw new IllegalStateException("Alarm creation is disabled");
}
return createAlarm(alarm);
} else {
return updateAlarm(existing, alarm);
@ -159,7 +166,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
log.debug("New Alarm : {}", alarm);
Alarm saved = alarmDao.save(alarm.getTenantId(), alarm);
List<EntityId> propagatedEntitiesList = createAlarmRelations(saved);
return new AlarmOperationResult(saved, true, propagatedEntitiesList);
return new AlarmOperationResult(saved, true, true, propagatedEntitiesList);
}
private List<EntityId> createAlarmRelations(Alarm alarm) throws InterruptedException, ExecutionException {

1
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -465,6 +465,7 @@ public class ModelConstants {
public static final String API_USAGE_STATE_JS_EXEC_COLUMN = "js_exec";
public static final String API_USAGE_STATE_EMAIL_EXEC_COLUMN = "email_exec";
public static final String API_USAGE_STATE_SMS_EXEC_COLUMN = "sms_exec";
public static final String API_USAGE_STATE_ALARM_EXEC_COLUMN = "alarm_exec";
/**
* Resource constants.

5
dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java

@ -69,6 +69,9 @@ public class ApiUsageStateEntity extends BaseSqlEntity<ApiUsageState> implements
@Enumerated(EnumType.STRING)
@Column(name = ModelConstants.API_USAGE_STATE_SMS_EXEC_COLUMN)
private ApiUsageStateValue smsExecState = ApiUsageStateValue.ENABLED;
@Enumerated(EnumType.STRING)
@Column(name = ModelConstants.API_USAGE_STATE_ALARM_EXEC_COLUMN)
private ApiUsageStateValue alarmExecState = ApiUsageStateValue.ENABLED;
public ApiUsageStateEntity() {
}
@ -91,6 +94,7 @@ public class ApiUsageStateEntity extends BaseSqlEntity<ApiUsageState> implements
this.jsExecState = ur.getJsExecState();
this.emailExecState = ur.getEmailExecState();
this.smsExecState = ur.getSmsExecState();
this.alarmExecState = ur.getAlarmExecState();
}
@Override
@ -109,6 +113,7 @@ public class ApiUsageStateEntity extends BaseSqlEntity<ApiUsageState> implements
ur.setJsExecState(jsExecState);
ur.setEmailExecState(emailExecState);
ur.setSmsExecState(smsExecState);
ur.setAlarmExecState(alarmExecState);
return ur;
}

3
dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java

@ -90,6 +90,7 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A
apiUsageState.setDbStorageState(ApiUsageStateValue.ENABLED);
apiUsageState.setSmsExecState(ApiUsageStateValue.ENABLED);
apiUsageState.setEmailExecState(ApiUsageStateValue.ENABLED);
apiUsageState.setAlarmExecState(ApiUsageStateValue.ENABLED);
apiUsageStateValidator.validate(apiUsageState, ApiUsageState::getTenantId);
ApiUsageState saved = apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState);
@ -107,6 +108,8 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A
new StringDataEntry(ApiFeature.EMAIL.getApiStateKey(), ApiUsageStateValue.ENABLED.name())));
apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(),
new StringDataEntry(ApiFeature.SMS.getApiStateKey(), ApiUsageStateValue.ENABLED.name())));
apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(),
new StringDataEntry(ApiFeature.ALARM.getApiStateKey(), ApiUsageStateValue.ENABLED.name())));
tsService.save(tenantId, saved.getId(), apiUsageStates, 0L);
if (entityId.getEntityType() == EntityType.TENANT && !entityId.equals(TenantId.SYS_TENANT_ID)) {

1
dao/src/main/resources/sql/schema-entities-hsql.sql

@ -454,6 +454,7 @@ CREATE TABLE IF NOT EXISTS api_usage_state (
js_exec varchar(32),
email_exec varchar(32),
sms_exec varchar(32),
alarm_exec varchar(32),
CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id)
);

1
dao/src/main/resources/sql/schema-entities.sql

@ -491,6 +491,7 @@ CREATE TABLE IF NOT EXISTS api_usage_state (
js_exec varchar(32),
email_exec varchar(32),
sms_exec varchar(32),
alarm_exec varchar(32),
CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id)
);

1
ui-ngx/package.json

@ -47,6 +47,7 @@
"flot": "git://github.com/thingsboard/flot.git#0.9-work",
"flot.curvedlines": "git://github.com/MichaelZinsmaier/CurvedLines.git#master",
"font-awesome": "^4.7.0",
"html2canvas": "^1.0.0-rc.7",
"jquery": "^3.5.1",
"jquery.terminal": "^2.18.3",
"js-beautify": "^1.13.0",

5
ui-ngx/src/app/core/auth/auth.service.ts

@ -294,10 +294,11 @@ export class AuthService {
})
);
} else if (accessToken) {
this.utils.updateQueryParam('accessToken', null);
const queryParamsToRemove = ['accessToken'];
if (refreshToken) {
this.utils.updateQueryParam('refreshToken', null);
queryParamsToRemove.push('refreshToken');
}
this.utils.removeQueryParams(queryParamsToRemove);
try {
this.updateAndValidateToken(accessToken, 'jwt_token', false);
if (refreshToken) {

19
ui-ngx/src/app/core/services/utils.service.ts

@ -410,9 +410,23 @@ export class UtilsService {
return decodeURIComponent(results[2].replace(/\+/g, ' '));
}
public removeQueryParams(keys: Array<string>) {
let params = this.window.location.search;
for (const key of keys) {
params = this.updateUrlQueryString(params, key, null);
}
const baseUrlPart = [baseUrl(), this.window.location.pathname].join('');
this.window.history.replaceState({}, '', baseUrlPart + params);
}
public updateQueryParam(name: string, value: string | null) {
const baseUrlPart = [baseUrl(), this.window.location.pathname].join('');
const urlQueryString = this.window.location.search;
const params = this.updateUrlQueryString(urlQueryString, name, value);
this.window.history.replaceState({}, '', baseUrlPart + params);
}
private updateUrlQueryString(urlQueryString: string, name: string, value: string | null): string {
let newParam = '';
let params = '';
if (value !== null) {
@ -425,13 +439,16 @@ export class UtilsService {
newParam = '$1' + newParam;
}
params = urlQueryString.replace(keyRegex, newParam);
if (params.startsWith('&')) {
params = '?' + params.substring(1);
}
} else if (newParam) {
params = urlQueryString + '&' + newParam;
}
} else if (newParam) {
params = '?' + newParam;
}
this.window.history.replaceState({}, '', baseUrlPart + params);
return params;
}
public baseUrl(): string {

66
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-image-dialog.component.html

@ -0,0 +1,66 @@
<!--
Copyright © 2016-2021 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<form (ngSubmit)="save()">
<mat-toolbar color="primary">
<h2 translate>dashboard.update-image</h2>
<span fxFlex></span>
<button mat-button mat-icon-button
(click)="cancel()"
type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div mat-dialog-content style="position: relative;">
<fieldset [disabled]="(isLoading$ | async) || (takingScreenshot$ | async)" fxLayout="column" fxLayoutAlign="center center" fxLayoutGap="16px">
<div class="tb-image-preview-container">
<div *ngIf="!safeImageUrl; else elseBlock">{{ 'dashboard.no-image' | translate }}</div>
<ng-template #elseBlock><img class="tb-image-preview" [src]="safeImageUrl" /></ng-template>
</div>
<button mat-raised-button color="accent"
type="button"
[disabled]="(isLoading$ | async) || (takingScreenshot$ | async)"
(click)="takeScreenShot()">
{{ 'dashboard.take-screenshot' | translate }}
</button>
<div [formGroup]="dashboardImageFormGroup">
<tb-image-input [showPreview]="false" label="{{'dashboard.image' | translate}}"
formControlName="dashboardImage">
</tb-image-input>
</div>
</fieldset>
<div *ngIf="takingScreenshot$ | async" class="taking-screenshot-progress tb-absolute-fill" fxLayout="column"
fxLayoutAlign="center center">
<mat-progress-spinner color="accent" mode="indeterminate"></mat-progress-spinner>
</div>
</div>
<div mat-dialog-actions fxLayoutAlign="end center">
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async) || (takingScreenshot$ | async)"
(click)="cancel()" cdkFocusInitial>
{{ 'action.cancel' | translate }}
</button>
<button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || (takingScreenshot$ | async) || !dashboardImageFormGroup.dirty">
{{ 'action.update' | translate }}
</button>
</div>
</form>

54
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-image-dialog.component.scss

@ -0,0 +1,54 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
$previewSize: 300px !default;
:host {
.tb-image-preview {
width: auto;
max-width: $previewSize - 2;
height: auto;
max-height: $previewSize - 2;
}
.tb-image-preview-container {
position: relative;
float: left;
width: $previewSize;
height: $previewSize;
margin-right: 12px;
vertical-align: top;
border: solid 1px;
div {
width: 100%;
font-size: 18px;
text-align: center;
}
div,
.tb-image-preview {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.taking-screenshot-progress {
background-color: rgba(255, 255, 255, 0.65);
}
}

132
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-image-dialog.component.ts

@ -0,0 +1,132 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { FormBuilder, FormGroup } from '@angular/forms';
import { Router } from '@angular/router';
import { DialogComponent } from '@app/shared/components/dialog.component';
import { DashboardId } from '@shared/models/id/dashboard-id';
import { DashboardService } from '@core/http/dashboard.service';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import html2canvas from 'html2canvas';
import { map, share } from 'rxjs/operators';
import { BehaviorSubject, from } from 'rxjs';
export interface DashboardImageDialogData {
dashboardId: DashboardId;
currentImage?: string;
dashboardElement: HTMLElement;
}
export interface DashboardImageDialogResult {
image?: string;
}
@Component({
selector: 'tb-dashboard-image-dialog',
templateUrl: './dashboard-image-dialog.component.html',
styleUrls: ['./dashboard-image-dialog.component.scss']
})
export class DashboardImageDialogComponent extends DialogComponent<DashboardImageDialogComponent, DashboardImageDialogResult> {
takingScreenshotSubject = new BehaviorSubject(false);
takingScreenshot$ = this.takingScreenshotSubject.asObservable().pipe(
share()
);
dashboardId: DashboardId;
safeImageUrl?: SafeUrl;
dashboardElement: HTMLElement;
dashboardImageFormGroup: FormGroup;
constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: DashboardImageDialogData,
public dialogRef: MatDialogRef<DashboardImageDialogComponent, DashboardImageDialogResult>,
private dashboardService: DashboardService,
private sanitizer: DomSanitizer,
private fb: FormBuilder) {
super(store, router, dialogRef);
this.dashboardId = this.data.dashboardId;
this.updateImage(this.data.currentImage);
this.dashboardElement = this.data.dashboardElement;
this.dashboardImageFormGroup = this.fb.group({
dashboardImage: [this.data.currentImage]
});
this.dashboardImageFormGroup.get('dashboardImage').valueChanges.subscribe(
(newImage) => {
this.updateImage(newImage);
}
);
}
takeScreenShot() {
this.takingScreenshotSubject.next(true);
from(html2canvas(this.dashboardElement, {
logging: false,
useCORS: true,
foreignObjectRendering: false,
scale: 512 / this.dashboardElement.clientWidth
})).pipe(
map(canvas => canvas.toDataURL())).subscribe(
(image) => {
this.updateImage(image);
this.dashboardImageFormGroup.patchValue({dashboardImage: image}, {emitEvent: false});
this.dashboardImageFormGroup.markAsDirty();
this.takingScreenshotSubject.next(false);
},
(e) => {
this.takingScreenshotSubject.next(false);
}
);
}
cancel(): void {
this.dialogRef.close(null);
}
save(): void {
this.dashboardService.getDashboard(this.dashboardId.id).subscribe(
(dashboard) => {
const newImage: string = this.dashboardImageFormGroup.get('dashboardImage').value;
dashboard.image = newImage;
this.dashboardService.saveDashboard(dashboard).subscribe(
() => {
this.dialogRef.close({
image: newImage
});
}
);
}
);
}
private updateImage(imageUrl: string) {
if (imageUrl) {
this.safeImageUrl = this.sanitizer.bypassSecurityTrustUrl(imageUrl);
} else {
this.safeImageUrl = null;
}
}
}

11
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html

@ -81,6 +81,12 @@
(click)="isFullscreen = !isFullscreen">
<mat-icon>{{ isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>
</button>
<button [fxShow]="currentDashboardId && !isEdit && isTenantAdmin() && displayUpdateDashboardImage()" mat-icon-button
matTooltip="{{'dashboard.update-image' | translate}}"
matTooltipPosition="below"
(click)="updateDashboardImage($event)">
<mat-icon>wallpaper</mat-icon>
</button>
<button [fxShow]="currentDashboardId && (isEdit || displayExport())" mat-icon-button
matTooltip="{{'dashboard.export' | translate}}"
matTooltipPosition="below"
@ -133,6 +139,7 @@
</tb-dashboard-toolbar>
</section>
<section class="tb-dashboard-container tb-absolute-fill"
#dashboardContainer
[ngClass]="{ 'is-fullscreen': forceFullscreen,
'tb-dashboard-toolbar-opened': toolbarOpened,
'tb-dashboard-toolbar-animated': isToolbarOpenedAnimate,
@ -187,7 +194,7 @@
</tb-dashboard-layout>
</mat-drawer-content>
</mat-drawer-container>
<section fxLayout="row" class="layout-wrap tb-footer-buttons" fxLayoutAlign="start end">
<section data-html2canvas-ignore fxLayout="row" class="layout-wrap tb-footer-buttons" fxLayoutAlign="start end">
<tb-footer-fab-buttons *ngIf="!embedded && !isMobileApp"
[fxShow]="!isAddingWidget && isEdit && !widgetEditMode"
relative
@ -212,7 +219,7 @@
<mat-icon>{{ isEdit ? 'close' : 'edit' }}</mat-icon>
</button>
</section>
<section class="tb-powered-by-footer" [ngStyle]="{'color': dashboard.configuration.settings.titleColor}">
<section data-html2canvas-ignore class="tb-powered-by-footer" [ngStyle]="{'color': dashboard.configuration.settings.titleColor}">
<span>Powered by <a href="https://thingsboard.io" target="_blank">Thingsboard v.{{ thingsboardVersion }}</a></span>
</section>
</mat-drawer-content>

38
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts

@ -16,7 +16,7 @@
import {
ChangeDetectorRef,
Component,
Component, ElementRef,
Inject,
Injector,
Input,
@ -123,6 +123,11 @@ import { DashboardWidgetSelectComponent } from '@home/components/dashboard-page/
import { AliasEntityType, EntityType } from '@shared/models/entity-type.models';
import { MobileService } from '@core/services/mobile.service';
import {
DashboardImageDialogComponent,
DashboardImageDialogData, DashboardImageDialogResult
} from '@home/components/dashboard-page/dashboard-image-dialog.component';
// @dynamic
@Component({
selector: 'tb-dashboard-page',
@ -153,6 +158,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
dashboard: Dashboard;
dashboardConfiguration: DashboardConfiguration;
@ViewChild('dashboardContainer') dashboardContainer: ElementRef<HTMLElement>;
prevDashboard: Dashboard;
iframeMode = this.utils.iframeMode;
@ -468,6 +475,15 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
}
}
public displayUpdateDashboardImage(): boolean {
if (this.dashboard.configuration.settings &&
isDefined(this.dashboard.configuration.settings.showUpdateDashboardImage)) {
return this.dashboard.configuration.settings.showUpdateDashboardImage;
} else {
return true;
}
}
public displayDashboardTimewindow(): boolean {
if (this.dashboard.configuration.settings &&
isDefined(this.dashboard.configuration.settings.showDashboardTimewindow)) {
@ -1248,4 +1264,24 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
onCloseSearchBundle() {
this.searchBundle = '';
}
public updateDashboardImage($event: Event) {
if ($event) {
$event.stopPropagation();
}
this.dialog.open<DashboardImageDialogComponent, DashboardImageDialogData,
DashboardImageDialogResult>(DashboardImageDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
dashboardId: this.dashboard.id,
currentImage: this.dashboard.image,
dashboardElement: this.dashboardContainer.nativeElement
}
}).afterClosed().subscribe((result) => {
if (result) {
this.dashboard.image = result.image;
}
});
}
}

3
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.html

@ -69,6 +69,9 @@
<mat-checkbox fxFlex formControlName="showDashboardExport">
{{ 'dashboard.display-dashboard-export' | translate }}
</mat-checkbox>
<mat-checkbox fxFlex formControlName="showUpdateDashboardImage">
{{ 'dashboard.display-update-dashboard-image' | translate }}
</mat-checkbox>
</div>
<mat-checkbox formControlName="showDashboardLogo">
{{ 'dashboard.display-dashboard-logo' | translate }}

3
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.ts

@ -82,7 +82,8 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
showDashboardLogo: [isUndefined(this.settings.showDashboardLogo) ? false : this.settings.showDashboardLogo, []],
dashboardLogoUrl: [isUndefined(this.settings.dashboardLogoUrl) ? null : this.settings.dashboardLogoUrl, []],
showDashboardTimewindow: [isUndefined(this.settings.showDashboardTimewindow) ? true : this.settings.showDashboardTimewindow, []],
showDashboardExport: [isUndefined(this.settings.showDashboardExport) ? true : this.settings.showDashboardExport, []]
showDashboardExport: [isUndefined(this.settings.showDashboardExport) ? true : this.settings.showDashboardExport, []],
showUpdateDashboardImage: [isUndefined(this.settings.showUpdateDashboardImage) ? true : this.settings.showUpdateDashboardImage, []]
});
this.settingsFormGroup.get('stateControllerId').valueChanges.subscribe(
(stateControllerId: StateControllerId) => {

3
ui-ngx/src/app/modules/home/components/home-components.module.ts

@ -141,6 +141,7 @@ import { EdgeDownlinkTableHeaderComponent } from '@home/components/edge/edge-dow
import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-page/widget-types-panel.component';
import { SecurityConfigLwm2mComponent } from '@home/components/device/security-config-lwm2m.component';
import { SecurityConfigLwm2mServerComponent } from '@home/components/device/security-config-lwm2m-server.component';
import { DashboardImageDialogComponent } from '@home/components/dashboard-page/dashboard-image-dialog.component';
@NgModule({
declarations:
@ -260,6 +261,7 @@ import { SecurityConfigLwm2mServerComponent } from '@home/components/device/secu
DashboardSettingsDialogComponent,
ManageDashboardStatesDialogComponent,
DashboardStateDialogComponent,
DashboardImageDialogComponent,
EmbedDashboardDialogComponent,
DisplayWidgetTypesPanelComponent
],
@ -370,6 +372,7 @@ import { SecurityConfigLwm2mServerComponent } from '@home/components/device/secu
DashboardSettingsDialogComponent,
ManageDashboardStatesDialogComponent,
DashboardStateDialogComponent,
DashboardImageDialogComponent,
EmbedDashboardDialogComponent,
DisplayWidgetTypesPanelComponent
],

12
ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html

@ -196,6 +196,18 @@
{{ 'tenant-profile.max-sms-range' | translate}}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>tenant-profile.max-created-alarms</mat-label>
<input matInput required min="0" step="1"
formControlName="maxCreatedAlarms"
type="number">
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxCreatedAlarms').hasError('required')">
{{ 'tenant-profile.max-created-alarms-required' | translate}}
</mat-error>
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxCreatedAlarms').hasError('min')">
{{ 'tenant-profile.max-created-alarms-range' | translate}}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>tenant-profile.transport-tenant-msg-rate-limit</mat-label>
<input matInput formControlName="transportTenantMsgRateLimit">

1
ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts

@ -73,6 +73,7 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA
maxRuleNodeExecutionsPerMessage: [null, [Validators.required, Validators.min(0)]],
maxEmails: [null, [Validators.required, Validators.min(0)]],
maxSms: [null, [Validators.required, Validators.min(0)]],
maxCreatedAlarms: [null, [Validators.required, Validators.min(0)]],
defaultStorageTtlDays: [null, [Validators.required, Validators.min(0)]]
});
this.defaultTenantProfileConfigurationFormGroup.valueChanges.subscribe(() => {

8
ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts

@ -104,7 +104,8 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
));
};
this.config.onEntityAction = action => this.onEntityViewAction(action);
this.config.detailsReadonly = () => (this.config.componentsData.entityViewScope === 'customer_user' || this.config.componentsData.entityViewScope === 'edge_customer_user');
this.config.detailsReadonly = () => (this.config.componentsData.entityViewScope === 'customer_user' ||
this.config.componentsData.entityViewScope === 'edge_customer_user');
this.config.headerComponent = EntityViewTableHeaderComponent;
@ -151,7 +152,8 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.entityViewScope);
this.config.groupActionDescriptors = this.configureGroupActions(this.config.componentsData.entityViewScope);
this.config.addActionDescriptors = this.configureAddActions(this.config.componentsData.entityViewScope);
this.config.addEnabled = !(this.config.componentsData.entityViewScope === 'customer_user' || this.config.componentsData.entityViewScope !== 'edge_customer_user');
this.config.addEnabled = !(this.config.componentsData.entityViewScope === 'customer_user' ||
this.config.componentsData.entityViewScope === 'edge_customer_user');
this.config.entitiesDeleteEnabled = this.config.componentsData.entityViewScope === 'tenant';
this.config.deleteEnabled = () => this.config.componentsData.entityViewScope === 'tenant';
return this.config;
@ -243,7 +245,7 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
{
name: this.translate.instant('edge.unassign-from-edge'),
icon: 'assignment_return',
isEnabled: (entity) => true,
isEnabled: () => true,
onAction: ($event, entity) => this.unassignFromEdge($event, entity)
}
);

1
ui-ngx/src/app/shared/models/dashboard.models.ts

@ -91,6 +91,7 @@ export interface DashboardSettings {
dashboardLogoUrl?: string;
showDashboardTimewindow?: boolean;
showDashboardExport?: boolean;
showUpdateDashboardImage?: boolean;
toolbarAlwaysOpen?: boolean;
titleColor?: string;
}

8
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -667,6 +667,8 @@
"add-widget": "Add new widget",
"title": "Title",
"image": "Dashboard image",
"update-image": "Update dashboard image",
"take-screenshot": "Take screenshot",
"select-widget-title": "Select widget",
"select-widget-value": "{{title}}: select widget",
"select-widget-subtitle": "List of available widget types",
@ -748,6 +750,7 @@
"display-filters": "Display filters",
"display-dashboard-timewindow": "Display timewindow",
"display-dashboard-export": "Display export",
"display-update-dashboard-image": "Display update dashboard image",
"display-dashboard-logo": "Display logo in dashboard fullscreen mode",
"dashboard-logo-image": "Dashboard logo image",
"import": "Import dashboard",
@ -2528,7 +2531,10 @@
"max-emails-range": "Maximum number of emails sent can't be negative",
"max-sms": "Maximum number of SMS sent (0 - unlimited)",
"max-sms-required": "Maximum number of SMS sent is required.",
"max-sms-range": "Maximum number of SMS sent can't be negative"
"max-sms-range": "Maximum number of SMS sent can't be negative",
"max-created-alarms": "Maximum number of alarms created (0 - unlimited)",
"max-created-alarms-required": "Maximum number of alarms created is required.",
"max-created-alarms-range": "Maximum number of alarms created can't be negative"
},
"timeinterval": {
"seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }",

19
ui-ngx/yarn.lock

@ -2222,6 +2222,11 @@ base64-arraybuffer@0.1.5:
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8"
integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg=
base64-arraybuffer@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz#4b944fac0191aa5907afe2d8c999ccc57ce80f45"
integrity sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==
base64-js@^1.0.2:
version "1.3.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
@ -3206,6 +3211,13 @@ css-declaration-sorter@^4.0.1:
postcss "^7.0.1"
timsort "^0.3.0"
css-line-break@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-1.1.1.tgz#d5e9bdd297840099eb0503c7310fd34927a026ef"
integrity sha512-1feNVaM4Fyzdj4mKPIQNL2n70MmuYzAXZ1aytlROFX1JsOo070OsugwGjj7nl6jnDJWHDM8zRZswkmeYVWZJQA==
dependencies:
base64-arraybuffer "^0.2.0"
css-loader@4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-4.2.2.tgz#b668b3488d566dc22ebcf9425c5f254a05808c89"
@ -4782,6 +4794,13 @@ html-escaper@^2.0.0:
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
html2canvas@^1.0.0-rc.7:
version "1.0.0-rc.7"
resolved "https://registry.yarnpkg.com/html2canvas/-/html2canvas-1.0.0-rc.7.tgz#70c159ce0e63954a91169531894d08ad5627ac98"
integrity sha512-yvPNZGejB2KOyKleZspjK/NruXVQuowu8NnV2HYG7gW7ytzl+umffbtUI62v2dCHQLDdsK6HIDtyJZ0W3neerA==
dependencies:
css-line-break "1.1.1"
http-cache-semantics@^3.8.1:
version "3.8.1"
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2"

Loading…
Cancel
Save