Browse Source

Merge branch 'master' into master-to-snmp

# Conflicts:
#	common/data/pom.xml
#	common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java
#	dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java
#	ui-ngx/src/app/shared/models/device.models.ts
pull/4451/head
Viacheslav Klimov 6 years ago
parent
commit
3c1682f57a
  1. 18
      application/src/main/data/json/system/widget_bundles/input_widgets.json
  2. 2
      application/src/main/java/org/thingsboard/server/config/WebConfig.java
  3. 10
      application/src/main/java/org/thingsboard/server/controller/AlarmController.java
  4. 2
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  5. 37
      application/src/main/java/org/thingsboard/server/controller/EventController.java
  6. 3
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  7. 8
      application/src/main/java/org/thingsboard/server/controller/TbResourceController.java
  8. 8
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java
  9. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityDataMsgConstructor.java
  10. 3
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  11. 10
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  12. 2
      application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java
  13. 202
      application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java
  14. 12
      application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java
  15. 9
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java
  16. 6
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  17. 10
      application/src/main/resources/banner.txt
  18. 3
      application/src/main/resources/logback.xml
  19. 3
      application/src/main/resources/thingsboard.yml
  20. 15
      application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java
  21. 33
      application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java
  22. 442
      application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java
  23. 68
      application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java
  24. 43
      application/src/test/java/org/thingsboard/server/service/ServiceSqlTestSuite.java
  25. 56
      application/src/test/java/org/thingsboard/server/service/resource/BaseTbResourceServiceTest.java
  26. 42
      application/src/test/java/org/thingsboard/server/transport/AbstractTransportIntegrationTest.java
  27. 24
      application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java
  28. 2
      application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestProtoIntegrationTest.java
  29. 12
      application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionJsonDeviceTest.java
  30. 12
      application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionProtoDeviceTest.java
  31. 6
      application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java
  32. 4
      application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcJsonIntegrationTest.java
  33. 130
      application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcProtoIntegrationTest.java
  34. 2
      application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesProtoIntegrationTest.java
  35. 26
      application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java
  36. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java
  37. 14
      application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java
  38. 14
      application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java
  39. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java
  40. 96
      application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java
  41. 6
      application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java
  42. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java
  43. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java
  44. 50
      common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java
  45. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java
  46. 4
      common/data/pom.xml
  47. 4
      common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java
  48. 2
      common/data/src/main/java/org/thingsboard/server/common/data/ShortCustomerInfo.java
  49. 27
      common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java
  50. 2
      common/data/src/main/java/org/thingsboard/server/common/data/TbResource.java
  51. 2
      common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java
  52. 4
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java
  53. 1
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileData.java
  54. 3
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java
  55. 55
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java
  56. 45
      common/data/src/main/java/org/thingsboard/server/common/data/event/DebugEvent.java
  57. 23
      common/data/src/main/java/org/thingsboard/server/common/data/event/DebugRuleChainEventFilter.java
  58. 23
      common/data/src/main/java/org/thingsboard/server/common/data/event/DebugRuleNodeEventFilter.java
  59. 36
      common/data/src/main/java/org/thingsboard/server/common/data/event/ErrorEventFilter.java
  60. 40
      common/data/src/main/java/org/thingsboard/server/common/data/event/EventFilter.java
  61. 20
      common/data/src/main/java/org/thingsboard/server/common/data/event/EventType.java
  62. 37
      common/data/src/main/java/org/thingsboard/server/common/data/event/LifeCycleEventFilter.java
  63. 36
      common/data/src/main/java/org/thingsboard/server/common/data/event/StatisticsEventFilter.java
  64. 6
      common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java
  65. 4
      common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java
  66. 3
      common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java
  67. 3
      common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java
  68. 4
      common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java
  69. 4
      common/queue/pom.xml
  70. 140
      common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java
  71. 17
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java
  72. 3
      common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java
  73. 42
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
  74. 5
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java
  75. 5
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java
  76. 15
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/ProtoCoapAdaptor.java
  77. 12
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java
  78. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapServers.java
  79. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mQueuedRequest.java
  80. 6
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java
  81. 11
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java
  82. 134
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContextServer.java
  83. 147
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandler.java
  84. 96
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java
  85. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportService.java
  86. 527
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java
  87. 21
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java
  88. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java
  89. 23
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java
  90. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientProfile.java
  91. 34
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResultsAddKeyValueProto.java
  92. 32
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResultsResourceValue.java
  93. 23
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java
  94. 22
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java
  95. 13
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java
  96. 4
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java
  97. 38
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java
  98. 2
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigServer.java
  99. 4
      dao/pom.xml
  100. 54
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java

18
application/src/main/data/json/system/widget_bundles/input_widgets.json

@ -455,6 +455,24 @@
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Photo camera input\",\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
}
},
{
"alias": "update_json_attribute",
"name": "Update JSON attribute",
"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUoAAADhCAMAAACZWwyuAAAB71BMVEX////w8PDt7e3z8/Pg4ODAwMDc3Nz19fUZhJQ4lKK9vb17e3v9/f2mpqbj4+P4+Pi+vr58fHzT09P7+/uTxcz2+/ucy9GpqakCAgK93OHNzc0eh5b09PSw1dusrKwvkZ+ampqysrIzMzPV1dXCwsKurq4iiZnZ2dnX19fIyMgrj50ABsiKiopmZmbo8/XQ5+ro6OjPz89JnqqCgoLX6u2cnJwmjJv39/ff39+12N7ExMQ2NjaioqKYmJjs7OzD4OTS0tLKysrh8PLq6uq6uroCCMjDw8OOjo6hzdO0tLQuj56UlJS4uvDZ7O7l5eVvsrw0k6EchZWEhIRwcHDU6ezG4eaGv8d1tb+3t7dCmqeIiIj4+/3j8fO62+AKD8tWpbGgoKBqamouLi7u7u47lqTP0PWr0tgXHM1/u8N6uMFqsLpfqrVNoKxFRUXU1faYyM92dnY7Ozvz+Pry8vK5ubm4uLh+fn5sbGzGx/OipOtzduG42t8uM9KNw8pTo690dHT6+vrt9viOkefb29um0NYfHx/p6vu9vvGqrO2YmulXW9uhztRnrrgmJiYQEBDt7fzk5frf4PjL4+eMj+a/3eJpbN9MUNk7P9SQxMuNwslfX19TU1OcnurI4uY9QdYyN9ImK9BVVVVPT097fuNYWFjSJwPyAAALvElEQVR42uzbTWviQBjA8ad5qgNONI05FCSIshUNKERCSFlM2WIxSKCH5FI8RfoFCrvVYwv7xTfG4susxqgpqJkfHoLM6c9MTCYROI7jOC4doiIRoqrEkhQRuIOJEsEVROI1Dw2pIkPlMQ8PyWMez8ItLOD2QnArAlxyIsEYhK/x5CSMJQGX0Cfu8AmXTCEqbkWUPQaBiDtd8hJXMJ6SbNByeWd3iROMRxIPAhEx09MSd1ATDwIJMdPTEndJNIid4D/kkLk4yMbFZTop2fXdonSZktb2XuFKpwRnBzeoGoeklNZS1tjj+BVu2hDKfYhxKXUXTtnXHIqgXd+esk/pNSK+GrQcjZsbuotBVrKUFmzSGs++b1djZqVV1m01D6crqlSpVOldpYKFSmzKWUCN0nI0bq5bWAwiuHquNNnj+JOlSDWA144OJW86KUcp38oAtQZIPZ/mICQEBvWGcLpw7i6acu0iCkXDtQ1hcos4MtdTOt4VYs2h5dk42y4azj3ejhYpVUyEwEZ1F+BpBJKviQVfWUkZuOTZz0e5ba8FJ4xNWZcLPw0De97vezpYTzmUK1iiL18p5T+aEaymxGRU2OhhTMAbgFISATvCMqU0LgEUo4U/KOreKV9LsSlHAeKtgY+09STr6ylvzABfRuWvlC5i4DCz8oiU4Az6Uwug25blzsMyZb8zDpkQkgj04YSxKSf16FwpTOquzZwrnx5lvT3sLlP21lKSoxY4aM4wANCn10DmKemvaFZ2HuBMMCnDT5QSe205x6R8ufICWqowKQ/92WFJ/lsTID+9vhp29FlKpye1/AbUXELqTTgDbMq60S2PDMRHKutsSqzTGrIpD74YYvUohBr+x1D+O0tZMqaO0wASfPjmWdwjbfgFd0wDUZjY+F/Ke6ptSRl/iZ6Nm3Dc4sq7Se/GMRtbQ7hZvjrR+XZGKimrhoZ7pOSbbKntV/Kt39R20fkDiZSf7Yi4i3rBkzLVJ46g4M7mHH+l4Bjpv+hiXfTyTr9lVk+U38Diqzs1n1l8XeibiJaKLP7O74FEiYdMT/RfE0RVJZICe8txSSRKmU9KEIRm8V0QnitNIWvST6m93whhy3/sm/lTEmEYx7/SOLPbdFgjCDGEWm0FBUVEGRRF0WEmlCgxeFSaeKHpNF1ajWW3V4cddh9/aLs078PqCMOCxjrs55dn3mff9/3hM7x7fGfYrSs1ll9leUWFrmrK+ERXahSk0uc9srTKJ7c31+hKjQJUHnnorc2g0lijK9eVGgWovHF1fUaV5zSVyg54RUaVNzWVearcK5FWWWPcqanMU2VjU1NTI6kcNjbpNJX5HvDGq43pA7510/Q+TaVylcyl/F65z1iuqVSi8vgpX23VqaUfO5c1lUpUrq+V2LXUh6PxnPbhuExxhnHrfi3OKFylbseV8ic/tz/WlRoroFK8V9ZMGad0pUZuKstyBSWMpnJ1qfS0tXmk6hhFHmRfR/sXnQJUVswPJy8dy0Vlvd9fL1WDFXmRfR3tv5hn39oT9+JU5TyyNwBQi8rOz3vWVnurc1HZ5XR2rZhK2n8x68buNbwNPqIq43lCVSrLpB/k1aZcVD5wODoAXOQk9ND3hD39DrzhzR39QgiIhs22Xj1YH4EQL5hjQIQ3C5OO9DrbWaCSZ9dpPu2PjdeuHZK7BPD+JdU0g4nrQVWplPizJxeVAaAVgLvF5na7gT6+rnlgAJZ6p6ujWfCD72uesEWpDxP3qvlrGzA+6hwNh2gdU8mus/m0PzYYjYexkO/3qBLxsbtQnUpf8vDCRi4HNcBFADcXsPDoDcF1FnwlYOFaWV9UpQdjiGfrSCW7TvPTVB/FQl4knlIlfr+H6lRWDZ8uU66yjjOLcM2Syj64DCmV41wd65PK1sl6m9WWUSXNz8zd9jtUiaftg6pTuXX4VFkeKk2c84KIXlTZE2IqRzgT65PKrgF/pEem0jpEKuX7ZDPZQDXNC3swGLSnnkNqUbnfW6PwFb1bgEgr1w0JUSXAVJq4AOszVSNcHTBho3VwRUkl7bOQg9WQMdj+nOo/vnwBMDMoEnwxA0AtKm/MbxE5qUBlhGt5Z9AjKtw3WSwylTFLd7gfrM9Uge/wO3ts6XWfhFf+PlJJ84nFj52XH66LxKli1m5/DQl2wM9MX1aFyl//8koFKvHRY3Y5sO5+2Gxtkau0CpMjYH1SdaFDMNf3p9fpo8LF+hBdp/nEopehH3aJ11QRHxuLL1D5y3hbFSqXAvnAV6JIbJuu1lQuC0eStRs1lctC1aU16njsaCFb8VRW5pCxvWnBauW/5ZXZDvKQJ0K6ednYMarGXHIFVFZ99ib36BTklRlVRjmTXCWNDdaccsmgXaIBSlGPSt9BMa/0KcgrM6rUOyFTSWNSmT2XxB2RucQsFKMalRLztxTklbxVEKJ6FktQXhngOK4OIiOTgjXGszHllA5p1Mo1L5FLErPtd6EcFansXDt8TkFeyff7J4RPTCXllXG3+2JKXX/YYhFVsjHllLFewCLEs+WSc2MoOoWoXFtbu6kzg8pM74+9LlJJeSWQUhfg/Kk+G9MBb+GB3g5kySWfioFF0SlE5b4TV4YvKVQ5ZCWVLK9k6tq4kaVVOjk3YkPIwtuECv4gV+C90uc9pkzlWRvljiyvZOpMmVSiq2WciyALH+ZQfApUuV65SsodKRkCPN2p/NGS6rMx5ZQYCk+Es+SSmAk+Q/EpQOX5mgpf8mGZYpUsd5SpfDBgMugR8hikPhtTTolxwdqDzLkkntlHUHzyV9l5I+lNHmhUrpLljjKVzpg55kC8x2a2hsDGlFMCvVwEmXNJPEpABayOOMPggvpZBSrHnRaPCepnBVRuOFFefmIDlg3TX/buXqVhKIzD+FuonEEJJQ4eKogEOrjFQRRSisEiuIl3YPYOvQId7Kg34OSdGgd1SaD5aPmH8zwQsv8g5Jx3OOf08c0GUP+UUezMXDzA8/3lKHNnZS630Oqf8sAK//5QvkKrG+VVsqygXEQvGyibUc5WlZTl44+gbEJ5u5pm1ZT3/hjKBpSLr5NRDeWFf4WyyR78eVRHeecnUG5PebOOaynt0K8ttNpTzpMsy5LkvIry+uNpZqHVnjK6LMvm4yrKqXd84A2X6PzBd0mZux/KERvHfsYZE884o5ch2/LscxOe5C4orfDFxMJrAFP0oQQllHpBCaVeUEKpF5RQ6gUllHpBCaVeUEKpF5RQ6gUllHpBCaVeUEKpF5RQ6gUllHpBCaVeUEKpF5RQ6gUllHpBCaVeUEKpF5RQ6gUllHpBCaVeUEKpF5RQ6gUllHpBCaVeUEKpF5RQ6gUllHpBuWdK2qLwDj8lIiIiIiIKLpdG45ZFqTP6y407heV/aTfK1Oi3qBtlgNdmfLdrxypyw0AAhifJ2AeDZtcohUAEgZGFrdJVjIsgk2DY5urt9xFu9wn2xWMFLklzV3gb45u/GHD7YSML5s3wwUASSqHcbkIplNtLKIVyewll7onaZR7oB/yfJ/UYZfvLmbMXyscpy2CKxplOKB+mtBwRNRcflLJJvhh/Anybir4nBV9sMeJKyonnZaYO/cm4GX1+JC6xr02dPgAlpaFpFMTq7onUsRkx0WEd5RD42ucP3YWJeMJQI4YTJj7H2tz3TzkBlPR0oQEgkUK6AEzz2mPnxhx6LPsW0RA2XHYc8ebKwXPcP2UPgPTynAk9KV2pTHtcRZkxZ8Ma9dkZvqJmO3KLjnPFTimP1EEm/PyP8tMfSp8p7/R9FWWyy9BMrTnp1lwRA53zNx70UrtTSmjiMhIdXykvdADQpDCT2katoiTWiC1Xli0OmbJyZkQ8m4XR7/XYASQ7JNLwSglxfh4qUl+bETUNsIqyM66YAyfPdaz5hpiYuzzDWLHeKyUcYhEH9Zcy/wzZRApebDUOa2873dWZ2iJG54oQENHVuGRrE+Ju30q5g28qoRTK7SWUQrm9hFIot5dQCuX2kvUrWQrcXrKqKgvUkiRJkiRJkiS9328dj4CaN1dagAAAAABJRU5ErkJggg==",
"description": "Simple form to input new JSON value for pre-defined attribute/timeseries key.",
"descriptor": {
"type": "latest",
"sizeX": 7.5,
"sizeY": 3,
"resources": [],
"templateHtml": "<tb-json-input-widget \n [ctx]=\"ctx\">\n</tb-json-input-widget>",
"templateCss": ".attribute-update-form {\n overflow: hidden;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.attribute-update-form__grid {\n display: flex;\n}\n.grid__element:first-child {\n flex: 1;\n}\n.grid__element:last-child {\n margin-top: 19px;\n margin-left: 7px;\n}\n.grid__element {\n display: flex;\n}\n\n.attribute-update-form .mat-button.mat-icon-button {\n width: 32px;\n min-width: 32px;\n height: 32px;\n min-height: 32px;\n padding: 0 !important;\n margin: 0 !important;\n line-height: 20px;\n}\n\n.attribute-update-form .mat-icon-button mat-icon {\n width: 20px;\n min-width: 20px;\n height: 20px;\n min-height: 20px;\n font-size: 20px;\n}\n\n.tb-toast {\n font-size: 14px!important;\n}",
"controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.jsonInputWidget.onDataUpdated();\n}\n\nself.onResize = function() {\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n}",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"AdvancedSettings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"widgetMode\": {\n \"title\": \"Widget mode\",\n \"type\": \"string\",\n \"default\": \"ATTRIBUTE\"\n },\n \"attributeScope\": {\n \"title\": \"Attribute scope\",\n \"type\": \"string\",\n \"default\": \"SERVER_SCOPE\"\n },\n \"showLabel\":{\n \"title\": \"Show label\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"labelValue\": {\n \"title\": \"Label\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"attributeRequired\": {\n \"title\": \"Value required\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showResultMessage\": {\n \"title\": \"Show result message\",\n \"type\": \"boolean\",\n \"default\": true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n {\n \"key\": \"widgetMode\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"ATTRIBUTE\",\n \"label\": \"Update attribute\"\n },\n {\n \"value\": \"TIME_SERIES\",\n \"label\": \"Update timeseries\"\n }\n ]\n },\n {\n \"key\": \"attributeScope\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"condition\": \"model.widgetMode === 'ATTRIBUTE'\",\n \"items\": [\n {\n \"value\": \"SERVER_SCOPE\",\n \"label\": \"Server attribute\"\n },\n {\n \"value\": \"SHARED_SCOPE\",\n \"label\": \"Shared attribute\"\n }\n ]\n },\n \"showLabel\",\n {\n \"key\": \"labelValue\",\n \"condition\": \"model.showLabel\"\n },\n \"attributeRequired\",\n \"showResultMessage\"\n ]\n}",
"dataKeySettingsSchema": "{}",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"attributeScope\":\"SERVER_SCOPE\",\"showLabel\":true,\"attributeRequired\":true,\"showResultMessage\":true},\"title\":\"Update JSON attribute\",\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"showLegend\":false}"
}
}
]
}

2
application/src/main/java/org/thingsboard/server/config/WebConfig.java

@ -21,7 +21,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class WebConfig {
@RequestMapping(value = "/{path:^(?!api$)(?!assets$)(?!static$)(?!webjars$)[^\\.]*}/**")
@RequestMapping(value = {"/assets", "/assets/", "/{path:^(?!api$)(?!assets$)(?!static$)(?!webjars$)[^\\.]*}/**"})
public String redirect() {
return "forward:/index.html";
}

10
application/src/main/java/org/thingsboard/server/controller/AlarmController.java

@ -46,8 +46,6 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.UUID;
@RestController
@TbCoreComponent
@RequestMapping("/api")
@ -177,7 +175,6 @@ public class AlarmController extends BaseController {
@RequestParam(required = false) String sortOrder,
@RequestParam(required = false) Long startTime,
@RequestParam(required = false) Long endTime,
@RequestParam(required = false) String offset,
@RequestParam(required = false) Boolean fetchOriginator
) throws ThingsboardException {
checkParameter("EntityId", strEntityId);
@ -191,12 +188,9 @@ public class AlarmController extends BaseController {
}
checkEntityId(entityId, Operation.READ);
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
UUID idOffsetUuid = null;
if (StringUtils.isNotEmpty(offset)) {
idOffsetUuid = toUUID(offset);
}
try {
return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator, idOffsetUuid)).get());
return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get());
} catch (Exception e) {
throw handleException(e);
}

2
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -110,7 +110,6 @@ import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
import org.thingsboard.server.dao.oauth2.OAuth2Service;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.TbResourceService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantProfileService;
@ -130,6 +129,7 @@ import org.thingsboard.server.service.edge.rpc.init.SyncEdgeService;
import org.thingsboard.server.service.lwm2m.LwM2MModelsRepository;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.TbClusterService;
import org.thingsboard.server.service.resource.TbResourceService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.AccessControlService;
import org.thingsboard.server.service.security.permission.Operation;

37
application/src/main/java/org/thingsboard/server/controller/EventController.java

@ -18,12 +18,14 @@ package org.thingsboard.server.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.event.EventFilter;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
@ -31,6 +33,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.Operation;
@ -101,4 +104,38 @@ public class EventController extends BaseController {
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/events/{entityType}/{entityId}", method = RequestMethod.POST)
@ResponseBody
public PageData<Event> getEvents(
@PathVariable("entityType") String strEntityType,
@PathVariable("entityId") String strEntityId,
@RequestParam("tenantId") String strTenantId,
@RequestParam int pageSize,
@RequestParam int page,
@RequestBody EventFilter eventFilter,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder,
@RequestParam(required = false) Long startTime,
@RequestParam(required = false) Long endTime) throws ThingsboardException {
checkParameter("EntityId", strEntityId);
checkParameter("EntityType", strEntityType);
try {
TenantId tenantId = new TenantId(toUUID(strTenantId));
EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId);
checkEntityId(entityId, Operation.READ);
if(sortProperty != null && sortProperty.equals("createdTime") && eventFilter.hasFilterForJsonBody()) {
sortProperty = ModelConstants.CREATED_TIME_PROPERTY;
}
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
return checkNotNull(eventService.findEventsByFilter(tenantId, entityId, eventFilter, pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
}

3
application/src/main/java/org/thingsboard/server/controller/RuleChainController.java

@ -244,7 +244,8 @@ public class RuleChainController extends BaseController {
}
RuleChain ruleChain = checkRuleChain(ruleChainMetaData.getRuleChainId(), Operation.WRITE);
RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData));
checkNotNull(ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData) ? true : null);
RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.loadRuleChainMetaData(tenantId, ruleChainMetaData.getRuleChainId()));
if (RuleChainType.CORE.equals(ruleChain.getType())) {
tbClusterService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.UPDATED);

8
application/src/main/java/org/thingsboard/server/controller/TbResourceController.java

@ -36,8 +36,8 @@ import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.resource.TbResourceService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.resource.TbResourceService;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
@ -52,12 +52,6 @@ public class TbResourceController extends BaseController {
public static final String RESOURCE_ID = "resourceId";
private final TbResourceService resourceService;
public TbResourceController(TbResourceService resourceService) {
this.resourceService = resourceService;
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/resource/{resourceId}/download", method = RequestMethod.GET)
@ResponseBody

8
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java

@ -40,8 +40,6 @@ public class DeviceProfileMsgConstructor {
.setName(deviceProfile.getName())
.setDefault(deviceProfile.isDefault())
.setType(deviceProfile.getType().name())
.setTransportType(deviceProfile.getTransportType().name())
.setProvisionType(deviceProfile.getProvisionType().name())
.setProfileDataBytes(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile.getProfileData())));
// TODO: voba - should this be always null at the moment??
// if (deviceProfile.getDefaultRuleChainId() != null) {
@ -54,6 +52,12 @@ public class DeviceProfileMsgConstructor {
if (deviceProfile.getDescription() != null) {
builder.setDescription(deviceProfile.getDescription());
}
if (deviceProfile.getTransportType() != null) {
builder.setTransportType(deviceProfile.getTransportType().name());
}
if (deviceProfile.getProvisionType() != null) {
builder.setProvisionType(deviceProfile.getProvisionType().name());
}
if (deviceProfile.getProvisionDeviceKey() != null) {
builder.setProvisionDeviceKey(deviceProfile.getProvisionDeviceKey());
}

4
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityDataMsgConstructor.java

@ -15,13 +15,13 @@
*/
package org.thingsboard.server.service.edge.rpc.constructor;
import com.google.gson.reflect.TypeToken;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
@ -82,7 +82,7 @@ public class EntityDataMsgConstructor {
AttributeDeleteMsg.Builder attributeDeleteMsg = AttributeDeleteMsg.newBuilder();
attributeDeleteMsg.setScope(entityData.getAsJsonObject().getAsJsonPrimitive("scope").getAsString());
JsonArray jsonArray = entityData.getAsJsonObject().getAsJsonArray("keys");
List<String> keys = new Gson().fromJson(jsonArray.toString(), List.class);
List<String> keys = new Gson().fromJson(jsonArray.toString(), new TypeToken<>(){}.getType());
attributeDeleteMsg.addAllAttributeNames(keys);
attributeDeleteMsg.build();
builder.setAttributeDeleteMsg(attributeDeleteMsg);

3
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java

@ -58,11 +58,8 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.DynamicValueSourceType;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.EntityKeyValueType;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.security.Authority;

10
application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java

@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
import org.thingsboard.server.dao.resource.TbResourceService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
@ -97,7 +97,7 @@ public class InstallScripts {
private OAuth2ConfigTemplateService oAuth2TemplateService;
@Autowired
private TbResourceService resourceService;
private ResourceService resourceService;
private Path getTenantRuleChainsDir() {
return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR);
@ -259,18 +259,12 @@ public class InstallScripts {
try {
createDefaultRuleChains(tenantId);
createDefaultRuleChain(tenantId, "Thermostat");
loadEdgeDemoRuleChains(tenantId);
} catch (Exception e) {
log.error("Unable to load dashboard from json", e);
throw new RuntimeException("Unable to load dashboard from json", e);
}
}
private void loadEdgeDemoRuleChains(TenantId tenantId) throws Exception {
Path edgeDemoRuleChainsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, EDGE_MANAGEMENT, RULE_CHAINS_DIR);
loadRuleChainsFromPath(tenantId, edgeDemoRuleChainsDir);
}
public void createOAuth2Templates() throws Exception {
Path oauth2ConfigTemplatesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, OAUTH2_CONFIG_TEMPLATES_DIR);
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(oauth2ConfigTemplatesDir, path -> path.toString().endsWith(JSON_EXT))) {

2
application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java

@ -159,7 +159,7 @@ public class CassandraDbHelper {
} else if (type.getProtocolCode() == ProtocolConstants.DataType.TIMESTAMP) {
str = ""+row.getInstant(index).toEpochMilli();
} else if (type.getProtocolCode() == ProtocolConstants.DataType.BOOLEAN) {
str = new Boolean(row.getBoolean(index)).toString();
str = Boolean.valueOf(row.getBoolean(index)).toString();
} else {
str = row.getString(index);
}

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

@ -0,0 +1,202 @@
/**
* 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.service.resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.leshan.core.model.DDFFileParser;
import org.eclipse.leshan.core.model.DefaultDDFFileValidator;
import org.eclipse.leshan.core.model.InvalidDDFFileException;
import org.eclipse.leshan.core.model.ObjectModel;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.lwm2m.LwM2mInstance;
import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
import org.thingsboard.server.common.data.lwm2m.LwM2mResourceObserve;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.resource.ResourceService;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
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;
import static org.thingsboard.server.dao.device.DeviceServiceImpl.INCORRECT_TENANT_ID;
import static org.thingsboard.server.dao.service.Validator.validateId;
@Slf4j
@Service
public class DefaultTbResourceService implements TbResourceService {
private final ResourceService resourceService;
private final DDFFileParser ddfFileParser;
public DefaultTbResourceService(ResourceService resourceService) {
this.resourceService = resourceService;
this.ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator());
}
@Override
public TbResource saveResource(TbResource resource) throws ThingsboardException {
log.trace("Executing saveResource [{}]", resource);
if (StringUtils.isEmpty(resource.getData())) {
throw new DataValidationException("Resource data should be specified!");
}
if (ResourceType.LWM2M_MODEL.equals(resource.getResourceType())) {
try {
List<ObjectModel> objectModels =
ddfFileParser.parseEx(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText());
if (!objectModels.isEmpty()) {
ObjectModel objectModel = objectModels.get(0);
String resourceKey = objectModel.id + LWM2M_SEPARATOR_KEY + objectModel.getVersion();
String name = objectModel.name;
resource.setResourceKey(resourceKey);
if (resource.getId() == null) {
resource.setTitle(name + " id=" + objectModel.id + " v" + objectModel.getVersion());
}
resource.setSearchText(resourceKey + LWM2M_SEPARATOR_SEARCH_TEXT + name);
} else {
throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getSearchText()));
}
} catch (InvalidDDFFileException | IOException e) {
throw new ThingsboardException(e, ThingsboardErrorCode.GENERAL);
}
if (resource.getResourceType().equals(ResourceType.LWM2M_MODEL) && toLwM2mObject(resource) == null) {
throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getSearchText()));
}
} else {
resource.setResourceKey(resource.getFileName());
}
return resourceService.saveResource(resource);
}
@Override
public TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId) {
return resourceService.getResource(tenantId, resourceType, resourceId);
}
@Override
public TbResource findResourceById(TenantId tenantId, TbResourceId resourceId) {
return resourceService.findResourceById(tenantId, resourceId);
}
@Override
public TbResourceInfo findResourceInfoById(TenantId tenantId, TbResourceId resourceId) {
return resourceService.findResourceInfoById(tenantId, resourceId);
}
@Override
public PageData<TbResourceInfo> findAllTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink) {
return resourceService.findAllTenantResourcesByTenantId(tenantId, pageLink);
}
@Override
public PageData<TbResourceInfo> findTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink) {
return resourceService.findTenantResourcesByTenantId(tenantId, pageLink);
}
@Override
public List<LwM2mObject> findLwM2mObject(TenantId tenantId, String sortOrder, String sortProperty, String[] objectIds) {
log.trace("Executing findByTenantId [{}]", tenantId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
List<TbResource> resources = resourceService.findTenantResourcesByResourceTypeAndObjectIds(tenantId, ResourceType.LWM2M_MODEL,
objectIds);
return resources.stream()
.map(this::toLwM2mObject)
.sorted(getComparator(sortProperty, sortOrder))
.collect(Collectors.toList());
}
@Override
public List<LwM2mObject> findLwM2mObjectPage(TenantId tenantId, String sortProperty, String sortOrder, PageLink pageLink) {
log.trace("Executing findByTenantId [{}]", tenantId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
PageData<TbResource> resourcePageData = resourceService.findTenantResourcesByResourceTypeAndPageLink(tenantId, ResourceType.LWM2M_MODEL, pageLink);
return resourcePageData.getData().stream()
.map(this::toLwM2mObject)
.sorted(getComparator(sortProperty, sortOrder))
.collect(Collectors.toList());
}
@Override
public void deleteResource(TenantId tenantId, TbResourceId resourceId) {
resourceService.deleteResource(tenantId, resourceId);
}
@Override
public void deleteResourcesByTenantId(TenantId tenantId) {
resourceService.deleteResourcesByTenantId(tenantId);
}
private Comparator<? super LwM2mObject> getComparator(String sortProperty, String sortOrder) {
Comparator<LwM2mObject> comparator;
if ("name".equals(sortProperty)) {
comparator = Comparator.comparing(LwM2mObject::getName);
} else {
comparator = Comparator.comparingLong(LwM2mObject::getId);
}
return "DESC".equals(sortOrder) ? comparator.reversed() : comparator;
}
private LwM2mObject toLwM2mObject(TbResource resource) {
try {
DDFFileParser ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator());
List<ObjectModel> objectModels =
ddfFileParser.parseEx(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText());
if (objectModels.size() == 0) {
return null;
} else {
ObjectModel obj = objectModels.get(0);
LwM2mObject lwM2mObject = new LwM2mObject();
lwM2mObject.setId(obj.id);
lwM2mObject.setKeyId(resource.getResourceKey());
lwM2mObject.setName(obj.name);
lwM2mObject.setMultiple(obj.multiple);
lwM2mObject.setMandatory(obj.mandatory);
LwM2mInstance instance = new LwM2mInstance();
instance.setId(0);
List<LwM2mResourceObserve> resources = new ArrayList<>();
obj.resources.forEach((k, v) -> {
if (!v.operations.isExecutable()) {
LwM2mResourceObserve lwM2MResourceObserve = new LwM2mResourceObserve(k, v.name, false, false, false);
resources.add(lwM2MResourceObserve);
}
});
instance.setResources(resources.toArray(LwM2mResourceObserve[]::new));
lwM2mObject.setInstances(new LwM2mInstance[]{instance});
return lwM2mObject;
}
} catch (IOException | InvalidDDFFileException e) {
log.error("Could not parse the XML of objectModel with name [{}]", resource.getSearchText(), e);
return null;
}
}
}

12
common/dao-api/src/main/java/org/thingsboard/server/dao/resource/TbResourceService.java → application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java

@ -13,24 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.resource;
package org.thingsboard.server.service.resource;
import org.eclipse.leshan.core.model.InvalidDDFFileException;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import java.io.IOException;
import java.util.List;
public interface TbResourceService {
TbResource saveResource(TbResource resource) throws InvalidDDFFileException, IOException;
TbResource saveResource(TbResource resource) throws ThingsboardException;
TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId);
@ -55,4 +54,5 @@ public interface TbResourceService {
void deleteResource(TenantId tenantId, TbResourceId resourceId);
void deleteResourcesByTenantId(TenantId tenantId);
}

9
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java

@ -17,8 +17,10 @@ package org.thingsboard.server.service.security.auth.oauth2;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@ -85,6 +87,10 @@ public abstract class AbstractOAuth2ClientMapper {
@Autowired
protected TbClusterService tbClusterService;
@Value("${edges.enabled}")
@Getter
private boolean edgesEnabled;
private final Lock userCreationLock = new ReentrantLock();
protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2ClientRegistrationInfo clientRegistration) {
@ -171,6 +177,9 @@ public abstract class AbstractOAuth2ClientMapper {
tenant.setTitle(tenantName);
tenant = tenantService.saveTenant(tenant);
installScripts.createDefaultRuleChains(tenant.getId());
if (edgesEnabled) {
installScripts.createDefaultEdgeRuleChains(tenant.getId());
}
tenantProfileCache.evict(tenant.getId());
tbClusterService.onTenantChange(tenant, null);
tbClusterService.onEntityStateChange(tenant.getId(), tenant.getId(),

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

@ -57,7 +57,7 @@ import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
import org.thingsboard.server.dao.device.provision.ProvisionRequest;
import org.thingsboard.server.dao.device.provision.ProvisionResponse;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.TbResourceService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
@ -117,7 +117,7 @@ public class DefaultTransportApiService implements TransportApiService {
private final TbClusterService tbClusterService;
private final DataDecodingEncodingService dataDecodingEncodingService;
private final DeviceProvisionService deviceProvisionService;
private final TbResourceService resourceService;
private final ResourceService resourceService;
private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>();
@ -126,7 +126,7 @@ public class DefaultTransportApiService implements TransportApiService {
RelationService relationService, DeviceCredentialsService deviceCredentialsService,
DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService,
TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService,
DeviceProvisionService deviceProvisionService, TbResourceService resourceService) {
DeviceProvisionService deviceProvisionService, ResourceService resourceService) {
this.deviceProfileCache = deviceProfileCache;
this.tenantProfileCache = tenantProfileCache;
this.apiUsageStateService = apiUsageStateService;

10
application/src/main/resources/banner.txt

@ -1,8 +1,8 @@
______ __ _ __ __
/_ __/ / /_ (_) ____ ____ _ _____ / /_ ____ ____ _ _____ ____/ /
/ / / __ \ / / / __ \ / __ `/ / ___/ / __ \ / __ \ / __ `/ / ___/ / __ /
/ / / / / / / / / / / / / /_/ / (__ ) / /_/ // /_/ // /_/ / / / / /_/ /
/_/ /_/ /_/ /_/ /_/ /_/ \__, / /____/ /_.___/ \____/ \__,_/ /_/ \__,_/
______ __ _ ____ __
/_ __/ / /_ (_) ____ ____ _ _____ / __ ) ____ ____ _ _____ ____/ /
/ / / __ \ / / / __ \ / __ `/ / ___/ / __ | / __ \ / __ `/ / ___/ / __ /
/ / / / / / / / / / / / / /_/ / (__ ) / /_/ / / /_/ // /_/ / / / / /_/ /
/_/ /_/ /_/ /_/ /_/ /_/ \__, / /____/ /_____/ \____/ \__,_/ /_/ \__,_/
/____/
===================================================

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

@ -38,6 +38,9 @@
<!-- <logger name="org.eclipse.californium.scandium.DTLSConnector" level="TRACE" />-->
<!-- <logger name="org.eclipse.californium.scandium.dtls.Handshaker" level="DEBUG" />-->
<!-- Top Rule Nodes by max execution time -->
<!-- <logger name="org.thingsboard.server.service.queue.TbMsgPackProcessingContext" level="DEBUG" /> -->
<logger name="com.microsoft.azure.servicebus.primitives.CoreMessageReceiver" level="OFF" />
<root level="INFO">

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

@ -243,6 +243,7 @@ sql:
batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}"
stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}"
batch_threads: "${SQL_TS_LATEST_BATCH_THREADS:4}"
update_by_latest_ts: "${SQL_TS_UPDATE_BY_LATEST_TIMESTAMP:true}"
# Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks
batch_sort: "${SQL_BATCH_SORT:false}"
# Specify whether to remove null characters from strValue of attributes and timeseries before insert
@ -516,7 +517,7 @@ js:
# Built-in JVM JavaScript environment properties
local:
# Use Sandboxed (secured) JVM JavaScript environment
use_js_sandbox: "${USE_LOCAL_JS_SANDBOX:false}"
use_js_sandbox: "${USE_LOCAL_JS_SANDBOX:true}"
# Specify thread pool size for JavaScript sandbox resource monitor
monitor_thread_pool_size: "${LOCAL_JS_SANDBOX_MONITOR_THREAD_POOL_SIZE:4}"
# Maximum CPU time in milliseconds allowed for script execution

15
application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java

@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.edge.imitator.EdgeImitator;
import org.thingsboard.server.gen.edge.AssetUpdateMsg;
import org.thingsboard.server.gen.edge.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.DeviceUpdateMsg;
import org.thingsboard.server.gen.edge.RuleChainUpdateMsg;
import org.thingsboard.server.gen.edge.UserCredentialsUpdateMsg;
@ -672,28 +673,32 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
edgeImitator.ignoreType(UserCredentialsUpdateMsg.class);
edgeImitator.expectMessageAmount(7);
edgeImitator.connect();
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
Assert.assertEquals(7, edgeImitator.getDownlinkMsgs().size());
Assert.assertTrue(edgeImitator.findMessageByType(RuleChainUpdateMsg.class).isPresent());
Assert.assertTrue(edgeImitator.findMessageByType(DeviceProfileUpdateMsg.class).isPresent());
Assert.assertTrue(edgeImitator.findMessageByType(DeviceUpdateMsg.class).isPresent());
Assert.assertTrue(edgeImitator.findMessageByType(AssetUpdateMsg.class).isPresent());
Assert.assertTrue(edgeImitator.findMessageByType(UserUpdateMsg.class).isPresent());
edgeImitator.getDownlinkMsgs().clear();
edgeImitator.expectMessageAmount(4);
edgeImitator.expectMessageAmount(7);
doPost("/api/edge/sync/" + edge.getId());
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
Assert.assertEquals(4, edgeImitator.getDownlinkMsgs().size());
Assert.assertEquals(7, edgeImitator.getDownlinkMsgs().size());
Assert.assertTrue(edgeImitator.findMessageByType(RuleChainUpdateMsg.class).isPresent());
Assert.assertTrue(edgeImitator.findMessageByType(DeviceProfileUpdateMsg.class).isPresent());
Assert.assertTrue(edgeImitator.findMessageByType(DeviceUpdateMsg.class).isPresent());
Assert.assertTrue(edgeImitator.findMessageByType(AssetUpdateMsg.class).isPresent());
Assert.assertTrue(edgeImitator.findMessageByType(UserUpdateMsg.class).isPresent());
edgeImitator.allowIgnoredTypes();
edgeImitator.disconnect();
try {
edgeImitator.disconnect();
} catch (Exception ignored) {}
doDelete("/api/device/" + savedDevice.getId().getId().toString())
.andExpect(status().isOk());

33
application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java

@ -42,7 +42,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
public class BaseEdgeEventControllerTest extends AbstractControllerTest {
private Tenant savedTenant;
private TenantId tenantId;
private User tenantAdmin;
@Before
@ -52,7 +51,6 @@ public class BaseEdgeEventControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
tenantId = savedTenant.getId();
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -63,6 +61,10 @@ public class BaseEdgeEventControllerTest extends AbstractControllerTest {
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
// sleep 1 seconds to avoid CREDENTIALS updated message for the user
// user credentials is going to be stored and updated event pushed to edge notification service
// while service will be processing this event edge could be already added and additional message will be pushed
Thread.sleep(1000);
}
@After
@ -75,7 +77,6 @@ public class BaseEdgeEventControllerTest extends AbstractControllerTest {
@Test
public void testGetEdgeEvents() throws Exception {
Thread.sleep(500);
Edge edge = constructEdge("TestEdge", "default");
edge = doPost("/api/edge", edge, Edge.class);
@ -83,29 +84,31 @@ public class BaseEdgeEventControllerTest extends AbstractControllerTest {
Device savedDevice = doPost("/api/device", device, Device.class);
doPost("/api/edge/" + edge.getId().toString() + "/device/" + savedDevice.getId().toString(), Device.class);
Thread.sleep(500);
Asset asset = constructAsset("TestAsset", "default");
Asset savedAsset = doPost("/api/asset", asset, Asset.class);
doPost("/api/edge/" + edge.getId().toString() + "/asset/" + savedAsset.getId().toString(), Asset.class);
Thread.sleep(500);
EntityRelation relation = new EntityRelation(savedAsset.getId(), savedDevice.getId(), EntityRelation.CONTAINS_TYPE);
doPost("/api/relation", relation);
Thread.sleep(500);
List<EdgeEvent> edgeEvents = doGetTypedWithTimePageLink("/api/edge/" + edge.getId().toString() + "/events?",
new TypeReference<PageData<EdgeEvent>>() {
}, new TimePageLink(4)).getData();
Assert.assertFalse(edgeEvents.isEmpty());
// wait while edge event for the relation entity persisted to DB
Thread.sleep(100);
List<EdgeEvent> edgeEvents;
int attempt = 1;
do {
edgeEvents = doGetTypedWithTimePageLink("/api/edge/" + edge.getId().toString() + "/events?",
new TypeReference<PageData<EdgeEvent>>() {}, new TimePageLink(4)).getData();
attempt++;
Thread.sleep(100);
} while (edgeEvents.size() != 4 && attempt < 5);
Assert.assertEquals(4, edgeEvents.size());
Assert.assertEquals(EdgeEventType.RULE_CHAIN, edgeEvents.get(0).getType());
Assert.assertEquals(EdgeEventType.DEVICE, edgeEvents.get(1).getType());
Assert.assertEquals(EdgeEventType.ASSET, edgeEvents.get(2).getType());
Assert.assertEquals(EdgeEventType.RELATION, edgeEvents.get(3).getType());
Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.RULE_CHAIN.equals(ee.getType())));
Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.DEVICE.equals(ee.getType())));
Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.ASSET.equals(ee.getType())));
Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.RELATION.equals(ee.getType())));
}
private Device constructDevice(String name, String type) {

442
application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java

@ -32,10 +32,12 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
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.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant;
@ -45,6 +47,15 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.device.profile.AlarmCondition;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey;
import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
import org.thingsboard.server.common.data.device.profile.AlarmRule;
import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -57,6 +68,9 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.query.EntityKeyValueType;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChain;
@ -71,7 +85,6 @@ import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.edge.imitator.EdgeImitator;
import org.thingsboard.server.gen.edge.AlarmUpdateMsg;
import org.thingsboard.server.gen.edge.AssetUpdateMsg;
@ -81,6 +94,7 @@ import org.thingsboard.server.gen.edge.CustomerUpdateMsg;
import org.thingsboard.server.gen.edge.DashboardUpdateMsg;
import org.thingsboard.server.gen.edge.DeviceCredentialsRequestMsg;
import org.thingsboard.server.gen.edge.DeviceCredentialsUpdateMsg;
import org.thingsboard.server.gen.edge.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.DeviceRpcCallMsg;
import org.thingsboard.server.gen.edge.DeviceUpdateMsg;
import org.thingsboard.server.gen.edge.EdgeConfiguration;
@ -106,6 +120,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@ -114,6 +129,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@Slf4j
abstract public class BaseEdgeTest extends AbstractControllerTest {
private static final String CUSTOM_DEVICE_PROFILE_NAME = "Thermostat";
private Tenant savedTenant;
private TenantId tenantId;
private User tenantAdmin;
@ -145,17 +162,25 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
// sleep 1 seconds to avoid CREDENTIALS updated message for the user
// user credentials is going to be stored and updated event pushed to edge notification service
// while service will be processing this event edge could be already added and additional message will be pushed
Thread.sleep(1000);
installation();
edgeImitator = new EdgeImitator("localhost", 7070, edge.getRoutingKey(), edge.getSecret());
// should be less, but events from SyncEdgeService stack with events from controller. will be fixed in next releases
edgeImitator.expectMessageAmount(7);
// TODO: voba - should be less, but events from SyncEdgeService stack with events from controller. will be fixed in next releases
// so ideally sync process should check current edge queue and add only missing entities to the edge queue
edgeImitator.expectMessageAmount(10);
edgeImitator.connect();
}
@After
public void afterTest() throws Exception {
edgeImitator.disconnect();
try {
edgeImitator.disconnect();
} catch (Exception ignored) {}
loginSysAdmin();
@ -163,23 +188,63 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
.andExpect(status().isOk());
}
@Test
public void test() throws Exception {
testReceivedInitialData();
int expectedDownlinkSize = 10;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testDevices();
expectedDownlinkSize = expectedDownlinkSize + 4;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testAssets();
expectedDownlinkSize = expectedDownlinkSize + 4;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testRuleChains();
expectedDownlinkSize = expectedDownlinkSize + 3;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testDashboards();
expectedDownlinkSize = expectedDownlinkSize + 3;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testRelations();
expectedDownlinkSize = expectedDownlinkSize + 2;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testAlarms();
expectedDownlinkSize = expectedDownlinkSize + 3;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testEntityView();
expectedDownlinkSize = expectedDownlinkSize + 2;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testCustomer();
expectedDownlinkSize = expectedDownlinkSize + 2;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testWidgetsBundleAndWidgetType();
expectedDownlinkSize = expectedDownlinkSize + 4;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testTimeseries();
expectedDownlinkSize = expectedDownlinkSize + 1;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testAttributes();
expectedDownlinkSize = expectedDownlinkSize + 3;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testSendMessagesToCloud();
expectedDownlinkSize = expectedDownlinkSize + 9;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
testRpcCall();
expectedDownlinkSize = expectedDownlinkSize + 1;
Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size());
}
private Device findDeviceByName(String deviceName) throws Exception {
@ -204,10 +269,10 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
return asset;
}
private Device saveDevice(String deviceName) throws Exception {
private Device saveDevice(String deviceName, String type) throws Exception {
Device device = new Device();
device.setName(deviceName);
device.setType("test");
device.setType(type);
return doPost("/api/device", device, Device.class);
}
@ -233,7 +298,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectMessageAmount(1);
edgeEventService.saveAsync(edgeEvent);
clusterService.onEdgeEventUpdate(tenantId, edge.getId());
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceRpcCallMsg);
@ -243,7 +308,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void testReceivedInitialData() throws Exception {
log.info("Checking received data");
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
EdgeConfiguration configuration = edgeImitator.getConfiguration();
Assert.assertNotNull(configuration);
@ -253,9 +318,9 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
UserId userId = edgeImitator.getUserId();
Assert.assertNotNull(userId);
Optional<DeviceUpdateMsg> optionalMsg1 = edgeImitator.findMessageByType(DeviceUpdateMsg.class);
Assert.assertTrue(optionalMsg1.isPresent());
DeviceUpdateMsg deviceUpdateMsg = optionalMsg1.get();
Optional<DeviceUpdateMsg> deviceUpdateMsgOpt = edgeImitator.findMessageByType(DeviceUpdateMsg.class);
Assert.assertTrue(deviceUpdateMsgOpt.isPresent());
DeviceUpdateMsg deviceUpdateMsg = deviceUpdateMsgOpt.get();
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceUpdateMsg.getMsgType());
UUID deviceUUID = new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB());
Device device = doGet("/api/device/" + deviceUUID.toString(), Device.class);
@ -264,9 +329,25 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
new TypeReference<PageData<Device>>() {}, new PageLink(100)).getData();
Assert.assertTrue(edgeDevices.contains(device));
Optional<AssetUpdateMsg> optionalMsg2 = edgeImitator.findMessageByType(AssetUpdateMsg.class);
Assert.assertTrue(optionalMsg2.isPresent());
AssetUpdateMsg assetUpdateMsg = optionalMsg2.get();
List<DeviceProfileUpdateMsg> deviceProfileUpdateMsgList = edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class);
Assert.assertEquals(3, deviceProfileUpdateMsgList.size());
Optional<DeviceProfileUpdateMsg> deviceProfileUpdateMsgOpt =
deviceProfileUpdateMsgList.stream().filter(dfum -> CUSTOM_DEVICE_PROFILE_NAME.equals(dfum.getName())).findAny();
Assert.assertTrue(deviceProfileUpdateMsgOpt.isPresent());
DeviceProfileUpdateMsg deviceProfileUpdateMsg = deviceProfileUpdateMsgOpt.get();
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType());
UUID deviceProfileUUID = new UUID(deviceProfileUpdateMsg.getIdMSB(), deviceProfileUpdateMsg.getIdLSB());
DeviceProfile deviceProfile = doGet("/api/deviceProfile/" + deviceProfileUUID.toString(), DeviceProfile.class);
Assert.assertNotNull(deviceProfile);
Assert.assertNotNull(deviceProfile.getProfileData());
Assert.assertNotNull(deviceProfile.getProfileData().getAlarms());
Assert.assertNotNull(deviceProfile.getProfileData().getAlarms().get(0).getClearRule());
testAutoGeneratedCodeByProtobuf(deviceProfileUpdateMsg);
Optional<AssetUpdateMsg> assetUpdateMsgOpt = edgeImitator.findMessageByType(AssetUpdateMsg.class);
Assert.assertTrue(assetUpdateMsgOpt.isPresent());
AssetUpdateMsg assetUpdateMsg = assetUpdateMsgOpt.get();
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, assetUpdateMsg.getMsgType());
UUID assetUUID = new UUID(assetUpdateMsg.getIdMSB(), assetUpdateMsg.getIdLSB());
Asset asset = doGet("/api/asset/" + assetUUID.toString(), Asset.class);
@ -277,9 +358,9 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
testAutoGeneratedCodeByProtobuf(assetUpdateMsg);
Optional<RuleChainUpdateMsg> optionalMsg3 = edgeImitator.findMessageByType(RuleChainUpdateMsg.class);
Assert.assertTrue(optionalMsg3.isPresent());
RuleChainUpdateMsg ruleChainUpdateMsg = optionalMsg3.get();
Optional<RuleChainUpdateMsg> ruleChainUpdateMsgOpt = edgeImitator.findMessageByType(RuleChainUpdateMsg.class);
Assert.assertTrue(ruleChainUpdateMsgOpt.isPresent());
RuleChainUpdateMsg ruleChainUpdateMsg = ruleChainUpdateMsgOpt.get();
Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, ruleChainUpdateMsg.getMsgType());
UUID ruleChainUUID = new UUID(ruleChainUpdateMsg.getIdMSB(), ruleChainUpdateMsg.getIdLSB());
RuleChain ruleChain = doGet("/api/ruleChain/" + ruleChainUUID.toString(), RuleChain.class);
@ -296,13 +377,12 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void testDevices() throws Exception {
log.info("Testing devices");
Device savedDevice = saveDevice("Edge Device 2");
// 1
edgeImitator.expectMessageAmount(1);
Device savedDevice = saveDevice("Edge Device 2", "Default");
doPost("/api/edge/" + edge.getId().getId().toString()
+ "/device/" + savedDevice.getId().getId().toString(), Device.class);
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
DeviceUpdateMsg deviceUpdateMsg = (DeviceUpdateMsg) latestMessage;
@ -312,11 +392,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(deviceUpdateMsg.getName(), savedDevice.getName());
Assert.assertEquals(deviceUpdateMsg.getType(), savedDevice.getType());
// 2
edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getId().getId().toString()
+ "/device/" + savedDevice.getId().getId().toString(), Device.class);
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
deviceUpdateMsg = (DeviceUpdateMsg) latestMessage;
@ -324,11 +404,34 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(deviceUpdateMsg.getIdMSB(), savedDevice.getUuidId().getMostSignificantBits());
Assert.assertEquals(deviceUpdateMsg.getIdLSB(), savedDevice.getUuidId().getLeastSignificantBits());
// 3
edgeImitator.expectMessageAmount(1);
doDelete("/api/device/" + savedDevice.getId().getId().toString())
.andExpect(status().isOk());
edgeImitator.waitForMessages();
// we should not get any message because device is not assigned to edge any more
Assert.assertFalse(edgeImitator.waitForMessages(1));
// 4
edgeImitator.expectMessageAmount(1);
savedDevice = saveDevice("Edge Device 3", "Default");
doPost("/api/edge/" + edge.getId().getId().toString()
+ "/device/" + savedDevice.getId().getId().toString(), Device.class);
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
deviceUpdateMsg = (DeviceUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceUpdateMsg.getMsgType());
Assert.assertEquals(deviceUpdateMsg.getIdMSB(), savedDevice.getUuidId().getMostSignificantBits());
Assert.assertEquals(deviceUpdateMsg.getIdLSB(), savedDevice.getUuidId().getLeastSignificantBits());
Assert.assertEquals(deviceUpdateMsg.getName(), savedDevice.getName());
Assert.assertEquals(deviceUpdateMsg.getType(), savedDevice.getType());
// 5
edgeImitator.expectMessageAmount(1);
doDelete("/api/device/" + savedDevice.getId().getId().toString())
.andExpect(status().isOk());
// in this case we should get messages because device was assigned to edge
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
deviceUpdateMsg = (DeviceUpdateMsg) latestMessage;
@ -342,13 +445,13 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void testAssets() throws Exception {
log.info("Testing assets");
Asset savedAsset = saveAsset("Edge Asset 2");
// 1
edgeImitator.expectMessageAmount(1);
Asset savedAsset = saveAsset("Edge Asset 2");
doPost("/api/edge/" + edge.getId().getId().toString()
+ "/asset/" + savedAsset.getId().getId().toString(), Asset.class);
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AssetUpdateMsg);
AssetUpdateMsg assetUpdateMsg = (AssetUpdateMsg) latestMessage;
@ -358,11 +461,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(assetUpdateMsg.getName(), savedAsset.getName());
Assert.assertEquals(assetUpdateMsg.getType(), savedAsset.getType());
// 2
edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getId().getId().toString()
+ "/asset/" + savedAsset.getId().getId().toString(), Asset.class);
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AssetUpdateMsg);
assetUpdateMsg = (AssetUpdateMsg) latestMessage;
@ -370,11 +473,32 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(assetUpdateMsg.getIdMSB(), savedAsset.getUuidId().getMostSignificantBits());
Assert.assertEquals(assetUpdateMsg.getIdLSB(), savedAsset.getUuidId().getLeastSignificantBits());
// 3
edgeImitator.expectMessageAmount(1);
doDelete("/api/asset/" + savedAsset.getId().getId().toString())
.andExpect(status().isOk());
edgeImitator.waitForMessages();
Assert.assertFalse(edgeImitator.waitForMessages(1));
// 4
edgeImitator.expectMessageAmount(1);
savedAsset = saveAsset("Edge Asset 3");
doPost("/api/edge/" + edge.getId().getId().toString()
+ "/asset/" + savedAsset.getId().getId().toString(), Asset.class);
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AssetUpdateMsg);
assetUpdateMsg = (AssetUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, assetUpdateMsg.getMsgType());
Assert.assertEquals(assetUpdateMsg.getIdMSB(), savedAsset.getUuidId().getMostSignificantBits());
Assert.assertEquals(assetUpdateMsg.getIdLSB(), savedAsset.getUuidId().getLeastSignificantBits());
Assert.assertEquals(assetUpdateMsg.getName(), savedAsset.getName());
Assert.assertEquals(assetUpdateMsg.getType(), savedAsset.getType());
// 5
edgeImitator.expectMessageAmount(1);
doDelete("/api/asset/" + savedAsset.getId().getId().toString())
.andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AssetUpdateMsg);
assetUpdateMsg = (AssetUpdateMsg) latestMessage;
@ -387,21 +511,21 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void testRuleChains() throws Exception {
log.info("Testing RuleChains");
// 1
edgeImitator.expectMessageAmount(1);
RuleChain ruleChain = new RuleChain();
ruleChain.setName("Edge Test Rule Chain");
ruleChain.setType(RuleChainType.EDGE);
RuleChain savedRuleChain = doPost("/api/ruleChain", ruleChain, RuleChain.class);
createRuleChainMetadata(savedRuleChain);
// Wait before rule chain metadata saved to database before rule chain is assigned to edge
// sleep 1 seconds to avoid ENTITY_UPDATED_RPC_MESSAGE for the rule chain
// rule chain metadata is going to be stored and updated event pushed to edge notification service
// while service will be processing this event assignment rule chain to edge will be completed if bad timing
Thread.sleep(1000);
edgeImitator.expectMessageAmount(1);
doPost("/api/edge/" + edge.getId().getId().toString()
+ "/ruleChain/" + savedRuleChain.getId().getId().toString(), RuleChain.class);
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof RuleChainUpdateMsg);
RuleChainUpdateMsg ruleChainUpdateMsg = (RuleChainUpdateMsg) latestMessage;
@ -410,13 +534,14 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(ruleChainUpdateMsg.getIdLSB(), savedRuleChain.getUuidId().getLeastSignificantBits());
Assert.assertEquals(ruleChainUpdateMsg.getName(), savedRuleChain.getName());
// 2
testRuleChainMetadataRequestMsg(savedRuleChain.getId());
// 3
edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getId().getId().toString()
+ "/ruleChain/" + savedRuleChain.getId().getId().toString(), RuleChain.class);
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof RuleChainUpdateMsg);
ruleChainUpdateMsg = (RuleChainUpdateMsg) latestMessage;
@ -424,17 +549,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(ruleChainUpdateMsg.getIdMSB(), savedRuleChain.getUuidId().getMostSignificantBits());
Assert.assertEquals(ruleChainUpdateMsg.getIdLSB(), savedRuleChain.getUuidId().getLeastSignificantBits());
// 4
edgeImitator.expectMessageAmount(1);
doDelete("/api/ruleChain/" + savedRuleChain.getId().getId().toString())
.andExpect(status().isOk());
edgeImitator.waitForMessages();
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof RuleChainUpdateMsg);
ruleChainUpdateMsg = (RuleChainUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, ruleChainUpdateMsg.getMsgType());
Assert.assertEquals(ruleChainUpdateMsg.getIdMSB(), savedRuleChain.getUuidId().getMostSignificantBits());
Assert.assertEquals(ruleChainUpdateMsg.getIdLSB(), savedRuleChain.getUuidId().getLeastSignificantBits());
Assert.assertFalse(edgeImitator.waitForMessages(1));
log.info("RuleChains tested successfully");
}
@ -452,8 +571,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectResponsesAmount(1);
edgeImitator.expectMessageAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
edgeImitator.waitForResponses();
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForResponses());
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof RuleChainMetadataUpdateMsg);
@ -502,15 +621,15 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void testDashboards() throws Exception {
log.info("Testing Dashboards");
// 1
edgeImitator.expectMessageAmount(1);
Dashboard dashboard = new Dashboard();
dashboard.setTitle("Edge Test Dashboard");
Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
edgeImitator.expectMessageAmount(1);
doPost("/api/edge/" + edge.getId().getId().toString()
+ "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg);
DashboardUpdateMsg dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage;
@ -518,25 +637,24 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(dashboardUpdateMsg.getIdMSB(), savedDashboard.getUuidId().getMostSignificantBits());
Assert.assertEquals(dashboardUpdateMsg.getIdLSB(), savedDashboard.getUuidId().getLeastSignificantBits());
Assert.assertEquals(dashboardUpdateMsg.getTitle(), savedDashboard.getName());
testAutoGeneratedCodeByProtobuf(dashboardUpdateMsg);
// 2
edgeImitator.expectMessageAmount(1);
savedDashboard.setTitle("Updated Edge Test Dashboard");
doPost("/api/dashboard", savedDashboard, Dashboard.class);
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg);
dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, dashboardUpdateMsg.getMsgType());
Assert.assertEquals(dashboardUpdateMsg.getTitle(), savedDashboard.getName());
// 3
edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getId().getId().toString()
+ "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg);
dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage;
@ -544,17 +662,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(dashboardUpdateMsg.getIdMSB(), savedDashboard.getUuidId().getMostSignificantBits());
Assert.assertEquals(dashboardUpdateMsg.getIdLSB(), savedDashboard.getUuidId().getLeastSignificantBits());
// 4
edgeImitator.expectMessageAmount(1);
doDelete("/api/dashboard/" + savedDashboard.getId().getId().toString())
.andExpect(status().isOk());
edgeImitator.waitForMessages();
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg);
dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, dashboardUpdateMsg.getMsgType());
Assert.assertEquals(dashboardUpdateMsg.getIdMSB(), savedDashboard.getUuidId().getMostSignificantBits());
Assert.assertEquals(dashboardUpdateMsg.getIdLSB(), savedDashboard.getUuidId().getLeastSignificantBits());
Assert.assertFalse(edgeImitator.waitForMessages(1));
log.info("Dashboards tested successfully");
}
@ -562,19 +674,17 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void testRelations() throws Exception {
log.info("Testing Relations");
// 1
edgeImitator.expectMessageAmount(1);
Device device = findDeviceByName("Edge Device 1");
Asset asset = findAssetByName("Edge Asset 1");
EntityRelation relation = new EntityRelation();
relation.setType("test");
relation.setFrom(device.getId());
relation.setTo(asset.getId());
relation.setTypeGroup(RelationTypeGroup.COMMON);
edgeImitator.expectMessageAmount(1);
doPost("/api/relation", relation);
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof RelationUpdateMsg);
RelationUpdateMsg relationUpdateMsg = (RelationUpdateMsg) latestMessage;
@ -588,6 +698,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(relationUpdateMsg.getToEntityType(), relation.getTo().getEntityType().name());
Assert.assertEquals(relationUpdateMsg.getTypeGroup(), relation.getTypeGroup().name());
// 2
edgeImitator.expectMessageAmount(1);
doDelete("/api/relation?" +
"fromId=" + relation.getFrom().getId().toString() +
@ -597,8 +708,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
"&toId=" + relation.getTo().getId().toString() +
"&toType=" + relation.getTo().getEntityType().name())
.andExpect(status().isOk());
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof RelationUpdateMsg);
relationUpdateMsg = (RelationUpdateMsg) latestMessage;
@ -617,18 +727,17 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void testAlarms() throws Exception {
log.info("Testing Alarms");
Device device = findDeviceByName("Edge Device 1");
// 1
edgeImitator.expectMessageAmount(1);
Device device = findDeviceByName("Edge Device 1");
Alarm alarm = new Alarm();
alarm.setOriginator(device.getId());
alarm.setStatus(AlarmStatus.ACTIVE_UNACK);
alarm.setType("alarm");
alarm.setSeverity(AlarmSeverity.CRITICAL);
edgeImitator.expectMessageAmount(1);
Alarm savedAlarm = doPost("/api/alarm", alarm, Alarm.class);
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg);
AlarmUpdateMsg alarmUpdateMsg = (AlarmUpdateMsg) latestMessage;
@ -639,10 +748,10 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(alarmUpdateMsg.getStatus(), savedAlarm.getStatus().name());
Assert.assertEquals(alarmUpdateMsg.getSeverity(), savedAlarm.getSeverity().name());
// 2
edgeImitator.expectMessageAmount(1);
doPost("/api/alarm/" + savedAlarm.getId().getId().toString() + "/ack");
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg);
alarmUpdateMsg = (AlarmUpdateMsg) latestMessage;
@ -652,10 +761,10 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(alarmUpdateMsg.getOriginatorName(), device.getName());
Assert.assertEquals(alarmUpdateMsg.getStatus(), AlarmStatus.ACTIVE_ACK.name());
// 3
edgeImitator.expectMessageAmount(1);
doPost("/api/alarm/" + savedAlarm.getId().getId().toString() + "/clear");
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg);
alarmUpdateMsg = (AlarmUpdateMsg) latestMessage;
@ -665,26 +774,29 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(alarmUpdateMsg.getOriginatorName(), device.getName());
Assert.assertEquals(alarmUpdateMsg.getStatus(), AlarmStatus.CLEARED_ACK.name());
// 4
edgeImitator.expectMessageAmount(1);
doDelete("/api/alarm/" + savedAlarm.getId().getId().toString())
.andExpect(status().isOk());
Assert.assertFalse(edgeImitator.waitForMessages(1));
log.info("Alarms tested successfully");
}
private void testEntityView() throws Exception {
log.info("Testing EntityView");
Device device = findDeviceByName("Edge Device 1");
// 1
edgeImitator.expectMessageAmount(1);
Device device = findDeviceByName("Edge Device 1");
EntityView entityView = new EntityView();
entityView.setName("Edge EntityView 1");
entityView.setType("test");
entityView.setEntityId(device.getId());
EntityView savedEntityView = doPost("/api/entityView", entityView, EntityView.class);
edgeImitator.expectMessageAmount(1);
doPost("/api/edge/" + edge.getId().getId().toString()
+ "/entityView/" + savedEntityView.getId().getId().toString(), EntityView.class);
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityViewUpdateMsg);
EntityViewUpdateMsg entityViewUpdateMsg = (EntityViewUpdateMsg) latestMessage;
@ -697,11 +809,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(entityViewUpdateMsg.getEntityIdLSB(), device.getUuidId().getLeastSignificantBits());
Assert.assertEquals(entityViewUpdateMsg.getEntityType().name(), device.getId().getEntityType().name());
// 2
edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getId().getId().toString()
+ "/entityView/" + savedEntityView.getId().getId().toString(), EntityView.class);
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityViewUpdateMsg);
entityViewUpdateMsg = (EntityViewUpdateMsg) latestMessage;
@ -709,17 +821,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(entityViewUpdateMsg.getIdMSB(), savedEntityView.getUuidId().getMostSignificantBits());
Assert.assertEquals(entityViewUpdateMsg.getIdLSB(), savedEntityView.getUuidId().getLeastSignificantBits());
// 3
edgeImitator.expectMessageAmount(1);
doDelete("/api/entityView/" + savedEntityView.getId().getId().toString())
.andExpect(status().isOk());
edgeImitator.waitForMessages();
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityViewUpdateMsg);
entityViewUpdateMsg = (EntityViewUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, entityViewUpdateMsg.getMsgType());
Assert.assertEquals(entityViewUpdateMsg.getIdMSB(), savedEntityView.getUuidId().getMostSignificantBits());
Assert.assertEquals(entityViewUpdateMsg.getIdLSB(), savedEntityView.getUuidId().getLeastSignificantBits());
Assert.assertFalse(edgeImitator.waitForMessages(1));
log.info("EntityView tested successfully");
}
@ -727,15 +833,14 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void testCustomer() throws Exception {
log.info("Testing Customer");
// 1
edgeImitator.expectMessageAmount(1);
Customer customer = new Customer();
customer.setTitle("Edge Customer 1");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
edgeImitator.expectMessageAmount(1);
doPost("/api/customer/" + savedCustomer.getId().getId().toString()
+ "/edge/" + edge.getId().getId().toString(), Edge.class);
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof CustomerUpdateMsg);
CustomerUpdateMsg customerUpdateMsg = (CustomerUpdateMsg) latestMessage;
@ -743,13 +848,12 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(customerUpdateMsg.getIdMSB(), savedCustomer.getUuidId().getMostSignificantBits());
Assert.assertEquals(customerUpdateMsg.getIdLSB(), savedCustomer.getUuidId().getLeastSignificantBits());
Assert.assertEquals(customerUpdateMsg.getTitle(), savedCustomer.getTitle());
testAutoGeneratedCodeByProtobuf(customerUpdateMsg);
// 2
edgeImitator.expectMessageAmount(1);
doDelete("/api/customer/edge/" + edge.getId().getId().toString(), Edge.class);
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof CustomerUpdateMsg);
customerUpdateMsg = (CustomerUpdateMsg) latestMessage;
@ -757,17 +861,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(customerUpdateMsg.getIdMSB(), savedCustomer.getUuidId().getMostSignificantBits());
Assert.assertEquals(customerUpdateMsg.getIdLSB(), savedCustomer.getUuidId().getLeastSignificantBits());
// 3
edgeImitator.expectMessageAmount(1);
doDelete("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isOk());
edgeImitator.waitForMessages();
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof CustomerUpdateMsg);
customerUpdateMsg = (CustomerUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, customerUpdateMsg.getMsgType());
Assert.assertEquals(customerUpdateMsg.getIdMSB(), savedCustomer.getUuidId().getMostSignificantBits());
Assert.assertEquals(customerUpdateMsg.getIdLSB(), savedCustomer.getUuidId().getLeastSignificantBits());
Assert.assertFalse(edgeImitator.waitForMessages(1));
log.info("Customer tested successfully");
}
@ -775,13 +873,12 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void testWidgetsBundleAndWidgetType() throws Exception {
log.info("Testing WidgetsBundle and WidgetType");
// 1
edgeImitator.expectMessageAmount(1);
WidgetsBundle widgetsBundle = new WidgetsBundle();
widgetsBundle.setTitle("Test Widget Bundle");
edgeImitator.expectMessageAmount(1);
WidgetsBundle savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class);
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof WidgetsBundleUpdateMsg);
WidgetsBundleUpdateMsg widgetsBundleUpdateMsg = (WidgetsBundleUpdateMsg) latestMessage;
@ -790,20 +887,18 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(widgetsBundleUpdateMsg.getIdLSB(), savedWidgetsBundle.getUuidId().getLeastSignificantBits());
Assert.assertEquals(widgetsBundleUpdateMsg.getAlias(), savedWidgetsBundle.getAlias());
Assert.assertEquals(widgetsBundleUpdateMsg.getTitle(), savedWidgetsBundle.getTitle());
testAutoGeneratedCodeByProtobuf(widgetsBundleUpdateMsg);
// 2
edgeImitator.expectMessageAmount(1);
WidgetType widgetType = new WidgetType();
widgetType.setName("Test Widget Type");
widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
ObjectNode descriptor = mapper.createObjectNode();
descriptor.put("key", "value");
widgetType.setDescriptor(descriptor);
edgeImitator.expectMessageAmount(1);
WidgetType savedWidgetType = doPost("/api/widgetType", widgetType, WidgetType.class);
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof WidgetTypeUpdateMsg);
WidgetTypeUpdateMsg widgetTypeUpdateMsg = (WidgetTypeUpdateMsg) latestMessage;
@ -814,11 +909,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(widgetTypeUpdateMsg.getName(), savedWidgetType.getName());
Assert.assertEquals(JacksonUtil.toJsonNode(widgetTypeUpdateMsg.getDescriptorJson()), savedWidgetType.getDescriptor());
// 3
edgeImitator.expectMessageAmount(1);
doDelete("/api/widgetType/" + savedWidgetType.getId().getId().toString())
.andExpect(status().isOk());
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof WidgetTypeUpdateMsg);
widgetTypeUpdateMsg = (WidgetTypeUpdateMsg) latestMessage;
@ -826,11 +921,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(widgetTypeUpdateMsg.getIdMSB(), savedWidgetType.getUuidId().getMostSignificantBits());
Assert.assertEquals(widgetTypeUpdateMsg.getIdLSB(), savedWidgetType.getUuidId().getLeastSignificantBits());
// 4
edgeImitator.expectMessageAmount(1);
doDelete("/api/widgetsBundle/" + savedWidgetsBundle.getId().getId().toString())
.andExpect(status().isOk());
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof WidgetsBundleUpdateMsg);
widgetsBundleUpdateMsg = (WidgetsBundleUpdateMsg) latestMessage;
@ -843,15 +938,15 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void testTimeseries() throws Exception {
log.info("Testing timeseries");
Device device = findDeviceByName("Edge Device 1");
edgeImitator.expectMessageAmount(1);
Device device = findDeviceByName("Edge Device 1");
String timeseriesData = "{\"data\":{\"temperature\":25},\"ts\":" + System.currentTimeMillis() + "}";
JsonNode timeseriesEntityData = mapper.readTree(timeseriesData);
EdgeEvent edgeEvent1 = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData);
edgeImitator.expectMessageAmount(1);
edgeEventService.saveAsync(edgeEvent1);
EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData);
edgeEventService.saveAsync(edgeEvent);
clusterService.onEdgeEventUpdate(tenantId, edge.getId());
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityDataProto);
@ -890,7 +985,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectMessageAmount(1);
edgeEventService.saveAsync(edgeEvent);
clusterService.onEdgeEventUpdate(tenantId, edge.getId());
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityDataProto);
@ -916,7 +1011,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectMessageAmount(1);
edgeEventService.saveAsync(edgeEvent);
clusterService.onEdgeEventUpdate(tenantId, edge.getId());
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityDataProto);
@ -941,7 +1036,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectMessageAmount(1);
edgeEventService.saveAsync(edgeEvent1);
clusterService.onEdgeEventUpdate(tenantId, edge.getId());
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityDataProto);
@ -996,8 +1091,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
edgeImitator.waitForResponses();
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForResponses());
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
@ -1013,7 +1108,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void sendDeviceWithNameThatAlreadyExistsOnCloud() throws Exception {
String deviceOnCloudName = RandomStringUtils.randomAlphanumeric(15);
Device deviceOnCloud = saveDevice(deviceOnCloudName);
Device deviceOnCloud = saveDevice(deviceOnCloudName, "Default");
UUID uuid = Uuids.timeBased();
@ -1033,8 +1128,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
edgeImitator.waitForResponses();
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForResponses());
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
@ -1063,7 +1158,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectMessageAmount(1);
doPost("/api/relation", relation);
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForMessages());
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
RelationRequestMsg.Builder relationRequestMsgBuilder = RelationRequestMsg.newBuilder();
@ -1078,8 +1173,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectResponsesAmount(1);
edgeImitator.expectMessageAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
edgeImitator.waitForResponses();
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForResponses());
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof RelationUpdateMsg);
@ -1115,7 +1210,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
edgeImitator.waitForResponses();
Assert.assertTrue(edgeImitator.waitForResponses());
List<AlarmInfo> alarms = doGetTypedWithPageLink("/api/alarm/{entityType}/{entityId}?",
@ -1158,7 +1253,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
edgeImitator.waitForResponses();
Assert.assertTrue(edgeImitator.waitForResponses());
EntityRelation relation = doGet("/api/relation?" +
"&fromId=" + device2.getId().getId().toString() +
@ -1212,7 +1307,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder2);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder2.build());
edgeImitator.waitForResponses();
Assert.assertTrue(edgeImitator.waitForResponses());
// Wait before device attributes saved to database before requesting them from controller
Thread.sleep(1000);
@ -1243,8 +1338,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectResponsesAmount(1);
edgeImitator.expectMessageAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
edgeImitator.waitForResponses();
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForResponses());
Assert.assertTrue(edgeImitator.waitForMessages());;
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof RuleChainMetadataUpdateMsg);
@ -1270,8 +1365,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectResponsesAmount(1);
edgeImitator.expectMessageAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
edgeImitator.waitForResponses();
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForResponses());
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof UserCredentialsUpdateMsg);
@ -1299,8 +1394,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectResponsesAmount(1);
edgeImitator.expectMessageAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
edgeImitator.waitForResponses();
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForResponses());
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceCredentialsUpdateMsg);
@ -1327,7 +1422,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
edgeImitator.waitForResponses();
Assert.assertTrue(edgeImitator.waitForResponses());
}
private void sendDeviceRpcResponse() throws Exception {
@ -1352,7 +1447,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
edgeImitator.waitForResponses();
Assert.assertTrue(edgeImitator.waitForResponses());
}
private void sendAttributesRequest() throws Exception {
@ -1383,8 +1478,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectResponsesAmount(1);
edgeImitator.expectMessageAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
edgeImitator.waitForResponses();
edgeImitator.waitForMessages();
Assert.assertTrue(edgeImitator.waitForResponses());
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityDataProto);
@ -1416,7 +1511,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(upLinkMsgBuilder.build());
edgeImitator.waitForResponses();
Assert.assertTrue(edgeImitator.waitForResponses());
device = doGet("/api/device/" + device.getId().getId().toString(), Device.class);
Assert.assertNotNull(device);
List<Device> edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getId().getId().toString() + "/devices?",
@ -1428,7 +1523,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void installation() throws Exception {
edge = doPost("/api/edge", constructEdge("Test Edge", "test"), Edge.class);
Device savedDevice = saveDevice("Edge Device 1");
DeviceProfile deviceProfile = this.createDeviceProfile(CUSTOM_DEVICE_PROFILE_NAME, null);
extendDeviceProfileData(deviceProfile);
doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
Device savedDevice = saveDevice("Edge Device 1", CUSTOM_DEVICE_PROFILE_NAME);
doPost("/api/edge/" + edge.getId().getId().toString()
+ "/device/" + savedDevice.getId().getId().toString(), Device.class);
@ -1437,6 +1536,35 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
+ "/asset/" + savedAsset.getId().getId().toString(), Asset.class);
}
private void extendDeviceProfileData(DeviceProfile deviceProfile) {
DeviceProfileData profileData = deviceProfile.getProfileData();
List<DeviceProfileAlarm> alarms = new ArrayList<>();
DeviceProfileAlarm deviceProfileAlarm = new DeviceProfileAlarm();
deviceProfileAlarm.setAlarmType("High Temperature");
AlarmRule alarmRule = new AlarmRule();
alarmRule.setAlarmDetails("Alarm Details");
AlarmCondition alarmCondition = new AlarmCondition();
alarmCondition.setSpec(new SimpleAlarmConditionSpec());
List<AlarmConditionFilter> condition = new ArrayList<>();
AlarmConditionFilter alarmConditionFilter = new AlarmConditionFilter();
alarmConditionFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, "temperature"));
NumericFilterPredicate predicate = new NumericFilterPredicate();
predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
predicate.setValue(new FilterPredicateValue<>(55.0));
alarmConditionFilter.setPredicate(predicate);
alarmConditionFilter.setValueType(EntityKeyValueType.NUMERIC);
condition.add(alarmConditionFilter);
alarmCondition.setCondition(condition);
alarmRule.setCondition(alarmCondition);
deviceProfileAlarm.setClearRule(alarmRule);
TreeMap<AlarmSeverity, AlarmRule> createRules = new TreeMap<>();
createRules.put(AlarmSeverity.CRITICAL, alarmRule);
deviceProfileAlarm.setCreateRules(createRules);
alarms.add(deviceProfileAlarm);
profileData.setAlarms(alarms);
profileData.setProvisionConfiguration(new AllowCreateNewDevicesDeviceProfileProvisionConfiguration("123"));
}
private EdgeEvent constructEdgeEvent(TenantId tenantId, EdgeId edgeId, EdgeEventActionType edgeEventAction, UUID entityId, EdgeEventType edgeEventType, JsonNode entityBody) {
EdgeEvent edgeEvent = new EdgeEvent();
edgeEvent.setEdgeId(edgeId);

68
application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java

@ -32,6 +32,7 @@ import org.thingsboard.server.gen.edge.CustomerUpdateMsg;
import org.thingsboard.server.gen.edge.DashboardUpdateMsg;
import org.thingsboard.server.gen.edge.DeviceCredentialsRequestMsg;
import org.thingsboard.server.gen.edge.DeviceCredentialsUpdateMsg;
import org.thingsboard.server.gen.edge.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.DeviceRpcCallMsg;
import org.thingsboard.server.gen.edge.DeviceUpdateMsg;
import org.thingsboard.server.gen.edge.DownlinkMsg;
@ -58,6 +59,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
@Slf4j
public class EdgeImitator {
@ -154,88 +156,93 @@ public class EdgeImitator {
private ListenableFuture<List<Void>> processDownlinkMsg(DownlinkMsg downlinkMsg) {
List<ListenableFuture<Void>> result = new ArrayList<>();
if (downlinkMsg.getDeviceUpdateMsgList() != null && !downlinkMsg.getDeviceUpdateMsgList().isEmpty()) {
if (downlinkMsg.getDeviceUpdateMsgCount() > 0) {
for (DeviceUpdateMsg deviceUpdateMsg: downlinkMsg.getDeviceUpdateMsgList()) {
result.add(saveDownlinkMsg(deviceUpdateMsg));
}
}
if (downlinkMsg.getDeviceCredentialsUpdateMsgList() != null && !downlinkMsg.getDeviceCredentialsUpdateMsgList().isEmpty()) {
if (downlinkMsg.getDeviceProfileUpdateMsgCount() > 0) {
for (DeviceProfileUpdateMsg deviceProfileUpdateMsg : downlinkMsg.getDeviceProfileUpdateMsgList()) {
result.add(saveDownlinkMsg(deviceProfileUpdateMsg));
}
}
if (downlinkMsg.getDeviceCredentialsUpdateMsgCount() > 0) {
for (DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg: downlinkMsg.getDeviceCredentialsUpdateMsgList()) {
result.add(saveDownlinkMsg(deviceCredentialsUpdateMsg));
}
}
if (downlinkMsg.getAssetUpdateMsgList() != null && !downlinkMsg.getAssetUpdateMsgList().isEmpty()) {
if (downlinkMsg.getAssetUpdateMsgCount() > 0) {
for (AssetUpdateMsg assetUpdateMsg: downlinkMsg.getAssetUpdateMsgList()) {
result.add(saveDownlinkMsg(assetUpdateMsg));
}
}
if (downlinkMsg.getRuleChainUpdateMsgList() != null && !downlinkMsg.getRuleChainUpdateMsgList().isEmpty()) {
if (downlinkMsg.getRuleChainUpdateMsgCount() > 0) {
for (RuleChainUpdateMsg ruleChainUpdateMsg: downlinkMsg.getRuleChainUpdateMsgList()) {
result.add(saveDownlinkMsg(ruleChainUpdateMsg));
}
}
if (downlinkMsg.getRuleChainMetadataUpdateMsgList() != null && !downlinkMsg.getRuleChainMetadataUpdateMsgList().isEmpty()) {
if (downlinkMsg.getRuleChainMetadataUpdateMsgCount() > 0) {
for (RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg: downlinkMsg.getRuleChainMetadataUpdateMsgList()) {
result.add(saveDownlinkMsg(ruleChainMetadataUpdateMsg));
}
}
if (downlinkMsg.getDashboardUpdateMsgList() != null && !downlinkMsg.getDashboardUpdateMsgList().isEmpty()) {
if (downlinkMsg.getDashboardUpdateMsgCount() > 0) {
for (DashboardUpdateMsg dashboardUpdateMsg: downlinkMsg.getDashboardUpdateMsgList()) {
result.add(saveDownlinkMsg(dashboardUpdateMsg));
}
}
if (downlinkMsg.getRelationUpdateMsgList() != null && !downlinkMsg.getRelationUpdateMsgList().isEmpty()) {
if (downlinkMsg.getRelationUpdateMsgCount() > 0) {
for (RelationUpdateMsg relationUpdateMsg: downlinkMsg.getRelationUpdateMsgList()) {
result.add(saveDownlinkMsg(relationUpdateMsg));
}
}
if (downlinkMsg.getAlarmUpdateMsgList() != null && !downlinkMsg.getAlarmUpdateMsgList().isEmpty()) {
if (downlinkMsg.getAlarmUpdateMsgCount() > 0) {
for (AlarmUpdateMsg alarmUpdateMsg: downlinkMsg.getAlarmUpdateMsgList()) {
result.add(saveDownlinkMsg(alarmUpdateMsg));
}
}
if (downlinkMsg.getEntityDataList() != null && !downlinkMsg.getEntityDataList().isEmpty()) {
if (downlinkMsg.getEntityDataCount() > 0) {
for (EntityDataProto entityData: downlinkMsg.getEntityDataList()) {
result.add(saveDownlinkMsg(entityData));
}
}
if (downlinkMsg.getEntityViewUpdateMsgList() != null && !downlinkMsg.getEntityViewUpdateMsgList().isEmpty()) {
if (downlinkMsg.getEntityViewUpdateMsgCount() > 0) {
for (EntityViewUpdateMsg entityViewUpdateMsg: downlinkMsg.getEntityViewUpdateMsgList()) {
result.add(saveDownlinkMsg(entityViewUpdateMsg));
}
}
if (downlinkMsg.getCustomerUpdateMsgList() != null && !downlinkMsg.getCustomerUpdateMsgList().isEmpty()) {
if (downlinkMsg.getCustomerUpdateMsgCount() > 0) {
for (CustomerUpdateMsg customerUpdateMsg: downlinkMsg.getCustomerUpdateMsgList()) {
result.add(saveDownlinkMsg(customerUpdateMsg));
}
}
if (downlinkMsg.getWidgetsBundleUpdateMsgList() != null && !downlinkMsg.getWidgetsBundleUpdateMsgList().isEmpty()) {
if (downlinkMsg.getWidgetsBundleUpdateMsgCount() > 0) {
for (WidgetsBundleUpdateMsg widgetsBundleUpdateMsg: downlinkMsg.getWidgetsBundleUpdateMsgList()) {
result.add(saveDownlinkMsg(widgetsBundleUpdateMsg));
}
}
if (downlinkMsg.getWidgetTypeUpdateMsgList() != null && !downlinkMsg.getWidgetTypeUpdateMsgList().isEmpty()) {
if (downlinkMsg.getWidgetTypeUpdateMsgCount() > 0) {
for (WidgetTypeUpdateMsg widgetTypeUpdateMsg: downlinkMsg.getWidgetTypeUpdateMsgList()) {
result.add(saveDownlinkMsg(widgetTypeUpdateMsg));
}
}
if (downlinkMsg.getUserUpdateMsgList() != null && !downlinkMsg.getUserUpdateMsgList().isEmpty()) {
if (downlinkMsg.getUserUpdateMsgCount() > 0) {
for (UserUpdateMsg userUpdateMsg: downlinkMsg.getUserUpdateMsgList()) {
onUserUpdate(userUpdateMsg);
result.add(saveDownlinkMsg(userUpdateMsg));
}
}
if (downlinkMsg.getUserCredentialsUpdateMsgList() != null && !downlinkMsg.getUserCredentialsUpdateMsgList().isEmpty()) {
if (downlinkMsg.getUserCredentialsUpdateMsgCount() > 0) {
for (UserCredentialsUpdateMsg userCredentialsUpdateMsg: downlinkMsg.getUserCredentialsUpdateMsgList()) {
result.add(saveDownlinkMsg(userCredentialsUpdateMsg));
}
}
if (downlinkMsg.getDeviceRpcCallMsgList() != null && !downlinkMsg.getDeviceRpcCallMsgList().isEmpty()) {
if (downlinkMsg.getDeviceRpcCallMsgCount() > 0) {
for (DeviceRpcCallMsg deviceRpcCallMsg: downlinkMsg.getDeviceRpcCallMsgList()) {
result.add(saveDownlinkMsg(deviceRpcCallMsg));
}
}
if (downlinkMsg.getDeviceCredentialsRequestMsgList() != null && !downlinkMsg.getDeviceCredentialsRequestMsgList().isEmpty()) {
if (downlinkMsg.getDeviceCredentialsRequestMsgCount() > 0) {
for (DeviceCredentialsRequestMsg deviceCredentialsRequestMsg: downlinkMsg.getDeviceCredentialsRequestMsgList()) {
result.add(saveDownlinkMsg(deviceCredentialsRequestMsg));
}
@ -256,21 +263,28 @@ public class EdgeImitator {
return Futures.immediateFuture(null);
}
public void waitForMessages() throws InterruptedException {
messagesLatch.await(5, TimeUnit.SECONDS);
public boolean waitForMessages() throws InterruptedException {
return waitForMessages(5);
}
public boolean waitForMessages(int timeout) throws InterruptedException {
return messagesLatch.await(timeout, TimeUnit.SECONDS);
}
public void expectMessageAmount(int messageAmount) {
messagesLatch = new CountDownLatch(messageAmount);
}
public void waitForResponses() throws InterruptedException { responsesLatch.await(5, TimeUnit.SECONDS); }
public boolean waitForResponses() throws InterruptedException {
return responsesLatch.await(5, TimeUnit.SECONDS);
}
public void expectResponsesAmount(int messageAmount) {
responsesLatch = new CountDownLatch(messageAmount);
}
public <T> Optional<T> findMessageByType(Class<T> tClass) {
@SuppressWarnings("unchecked")
public <T extends AbstractMessage> Optional<T> findMessageByType(Class<T> tClass) {
Optional<T> result;
try {
lock.lock();
@ -281,6 +295,18 @@ public class EdgeImitator {
return result;
}
@SuppressWarnings("unchecked")
public <T extends AbstractMessage> List<T> findAllMessagesByType(Class<T> tClass) {
List<T> result;
try {
lock.lock();
result = (List<T>) downlinkMsgs.stream().filter(downlinkMsg -> downlinkMsg.getClass().isAssignableFrom(tClass)).collect(Collectors.toList());
} finally {
lock.unlock();
}
return result;
}
public AbstractMessage getLatestMessage() {
return downlinkMsgs.get(downlinkMsgs.size() - 1);
}

43
application/src/test/java/org/thingsboard/server/service/ServiceSqlTestSuite.java

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.extensions.cpsuite.ClasspathSuite;
import org.junit.runner.RunWith;
import org.thingsboard.server.dao.CustomSqlUnit;
import org.thingsboard.server.queue.memory.InMemoryStorage;
import java.util.Arrays;
@RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({
"org.thingsboard.server.service.resource.*Test",
})
public class ServiceSqlTestSuite {
@ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema-types-hsql.sql", "sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql"),
"sql/hsql/drop-all-tables.sql",
"sql-test.properties");
@BeforeClass
public static void cleanupInMemStorage(){
InMemoryStorage.getInstance().cleanup();
}
}

56
dao/src/test/java/org/thingsboard/server/dao/service/BaseTbResourceServiceTest.java → application/src/test/java/org/thingsboard/server/service/resource/BaseTbResourceServiceTest.java

@ -13,28 +13,37 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.service;
package org.thingsboard.server.service.resource;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
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;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
public abstract class BaseTbResourceServiceTest extends AbstractServiceTest {
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest
public class BaseTbResourceServiceTest extends AbstractControllerTest {
private static final String LWM2M_TEST_MODEL = "<LWM2M xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"http://www.openmobilealliance.org/tech/profiles/LWM2M-v1_1.xsd\">\n" +
"<Object ObjectType=\"MODefinition\">\n" +
@ -67,18 +76,38 @@ public abstract class BaseTbResourceServiceTest extends AbstractServiceTest {
private TenantId tenantId;
@Autowired
private TbResourceService resourceService;
private Tenant savedTenant;
private User tenantAdmin;
@Before
public void before() {
public void beforeTest() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = tenantService.saveTenant(tenant);
Assert.assertNotNull(savedTenant);
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
tenantId = savedTenant.getId();
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
}
@After
public void after() {
tenantService.deleteTenant(tenantId);
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
@ -239,9 +268,10 @@ public abstract class BaseTbResourceServiceTest extends AbstractServiceTest {
@Test
public void testFindTenantResourcesByTenantId() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("Test tenant");
tenant = tenantService.saveTenant(tenant);
tenant = doPost("/api/tenant", tenant, Tenant.class);
TenantId tenantId = tenant.getId();
@ -279,14 +309,17 @@ public abstract class BaseTbResourceServiceTest extends AbstractServiceTest {
Assert.assertFalse(pageData.hasNext());
Assert.assertTrue(pageData.getData().isEmpty());
tenantService.deleteTenant(tenantId);
doDelete("/api/tenant/" + tenantId.getId().toString())
.andExpect(status().isOk());
}
@Test
public void testFindAllTenantResourcesByTenantId() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("Test tenant");
tenant = tenantService.saveTenant(tenant);
tenant = doPost("/api/tenant", tenant, Tenant.class);
TenantId tenantId = tenant.getId();
@ -344,7 +377,8 @@ public abstract class BaseTbResourceServiceTest extends AbstractServiceTest {
Assert.assertFalse(pageData.hasNext());
Assert.assertTrue(pageData.getData().isEmpty());
tenantService.deleteTenant(tenantId);
doDelete("/api/tenant/" + tenantId.getId().toString())
.andExpect(status().isOk());
}
}

42
application/src/test/java/org/thingsboard/server/transport/AbstractTransportIntegrationTest.java

@ -15,35 +15,11 @@
*/
package org.thingsboard.server.transport;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.junit.Assert;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration;
import org.thingsboard.server.common.data.device.profile.CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.DeviceProfileProvisionConfiguration;
import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration;
import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.gen.transport.TransportProtos;
@ -51,8 +27,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
@ -105,6 +79,22 @@ public abstract class AbstractTransportIntegrationTest extends AbstractControlle
" }\n" +
"}";
protected static final String DEVICE_RPC_RESPONSE_PROTO_SCHEMA = "syntax =\"proto3\";\n" +
"package rpc;\n" +
"\n" +
"message RpcResponseMsg {\n" +
" string payload = 1;\n" +
"}";
protected static final String DEVICE_RPC_REQUEST_PROTO_SCHEMA = "syntax =\"proto3\";\n" +
"package rpc;\n" +
"\n" +
"message RpcRequestMsg {\n" +
" string method = 1;\n" +
" int32 requestId = 2;\n" +
" string params = 3;\n" +
"}";
protected Tenant savedTenant;
protected User tenantAdmin;

24
application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java

@ -53,7 +53,7 @@ import static org.junit.Assert.assertNotNull;
public abstract class AbstractCoapIntegrationTest extends AbstractTransportIntegrationTest {
protected void processBeforeTest(String deviceName, CoapDeviceType coapDeviceType, TransportPayloadType payloadType) throws Exception {
this.processBeforeTest(deviceName, coapDeviceType, payloadType, null, null, DeviceProfileProvisionType.DISABLED, null, null);
this.processBeforeTest(deviceName, coapDeviceType, payloadType, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED);
}
protected void processBeforeTest(String deviceName,
@ -61,8 +61,11 @@ public abstract class AbstractCoapIntegrationTest extends AbstractTransportInteg
TransportPayloadType payloadType,
String telemetryProtoSchema,
String attributesProtoSchema,
DeviceProfileProvisionType provisionType,
String provisionKey, String provisionSecret
String rpcResponseProtoSchema,
String rpcRequestProtoSchema,
String provisionKey,
String provisionSecret,
DeviceProfileProvisionType provisionType
) throws Exception {
loginSysAdmin();
@ -85,7 +88,7 @@ public abstract class AbstractCoapIntegrationTest extends AbstractTransportInteg
device.setType("default");
if (coapDeviceType != null) {
DeviceProfile coapDeviceProfile = createCoapDeviceProfile(payloadType, coapDeviceType, attributesProtoSchema, provisionType, provisionKey, provisionSecret, telemetryProtoSchema);
DeviceProfile coapDeviceProfile = createCoapDeviceProfile(payloadType, coapDeviceType, provisionSecret, provisionType, provisionKey, attributesProtoSchema, telemetryProtoSchema, rpcResponseProtoSchema, rpcRequestProtoSchema);
deviceProfile = doPost("/api/deviceProfile", coapDeviceProfile, DeviceProfile.class);
device.setType(deviceProfile.getName());
device.setDeviceProfileId(deviceProfile.getId());
@ -103,8 +106,9 @@ public abstract class AbstractCoapIntegrationTest extends AbstractTransportInteg
}
protected DeviceProfile createCoapDeviceProfile(TransportPayloadType transportPayloadType, CoapDeviceType coapDeviceType,
String attributesProtoSchema, DeviceProfileProvisionType provisionType,
String provisionKey, String provisionSecret, String telemetryProtoSchema) {
String provisionSecret, DeviceProfileProvisionType provisionType,
String provisionKey, String attributesProtoSchema,
String telemetryProtoSchema, String rpcResponseProtoSchema, String rpcRequestProtoSchema) {
DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setName(transportPayloadType.name());
deviceProfile.setType(DeviceProfileType.DEFAULT);
@ -127,8 +131,16 @@ public abstract class AbstractCoapIntegrationTest extends AbstractTransportInteg
if (StringUtils.isEmpty(attributesProtoSchema)) {
attributesProtoSchema = DEVICE_ATTRIBUTES_PROTO_SCHEMA;
}
if (StringUtils.isEmpty(rpcResponseProtoSchema)) {
rpcResponseProtoSchema = DEVICE_RPC_RESPONSE_PROTO_SCHEMA;
}
if (StringUtils.isEmpty(rpcRequestProtoSchema)) {
rpcRequestProtoSchema = DEVICE_RPC_REQUEST_PROTO_SCHEMA;
}
protoTransportPayloadConfiguration.setDeviceTelemetryProtoSchema(telemetryProtoSchema);
protoTransportPayloadConfiguration.setDeviceAttributesProtoSchema(attributesProtoSchema);
protoTransportPayloadConfiguration.setDeviceRpcResponseProtoSchema(rpcResponseProtoSchema);
protoTransportPayloadConfiguration.setDeviceRpcRequestProtoSchema(rpcRequestProtoSchema);
transportPayloadTypeConfiguration = protoTransportPayloadConfiguration;
} else {
transportPayloadTypeConfiguration = new JsonTransportPayloadConfiguration();

2
application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestProtoIntegrationTest.java

@ -79,7 +79,7 @@ public abstract class AbstractCoapAttributesRequestProtoIntegrationTest extends
@Test
public void testRequestAttributesValuesFromTheServer() throws Exception {
super.processBeforeTest("Test Request attribute values from the server proto", CoapDeviceType.DEFAULT,
TransportPayloadType.PROTOBUF, null, ATTRIBUTES_SCHEMA_STR, DeviceProfileProvisionType.DISABLED, null, null);
TransportPayloadType.PROTOBUF, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED);
processTestRequestAttributesValuesFromTheServer();
}

12
application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionJsonDeviceTest.java

@ -88,7 +88,7 @@ public abstract class AbstractCoapProvisionJsonDeviceTest extends AbstractCoapIn
private void processTestProvisioningDisabledDevice() throws Exception {
super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.DISABLED, null, null);
super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED);
byte[] result = createCoapClientAndPublish().getPayload();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString());
@ -97,7 +97,7 @@ public abstract class AbstractCoapProvisionJsonDeviceTest extends AbstractCoapIn
private void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception {
super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret");
super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES);
byte[] result = createCoapClientAndPublish().getPayload();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
@ -113,7 +113,7 @@ public abstract class AbstractCoapProvisionJsonDeviceTest extends AbstractCoapIn
private void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception {
super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret");
super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES);
String requestCredentials = ",\"credentialsType\": \"ACCESS_TOKEN\",\"token\": \"test_token\"";
byte[] result = createCoapClientAndPublish(requestCredentials).getPayload();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
@ -132,7 +132,7 @@ public abstract class AbstractCoapProvisionJsonDeviceTest extends AbstractCoapIn
private void processTestProvisioningCreateNewDeviceWithCert() throws Exception {
super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret");
super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES);
String requestCredentials = ",\"credentialsType\": \"X509_CERTIFICATE\",\"hash\": \"testHash\"";
byte[] result = createCoapClientAndPublish(requestCredentials).getPayload();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
@ -156,7 +156,7 @@ public abstract class AbstractCoapProvisionJsonDeviceTest extends AbstractCoapIn
}
private void processTestProvisioningCheckPreProvisionedDevice() throws Exception {
super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret");
super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES);
byte[] result = createCoapClientAndPublish().getPayload();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
@ -167,7 +167,7 @@ public abstract class AbstractCoapProvisionJsonDeviceTest extends AbstractCoapIn
}
private void processTestProvisioningWithBadKeyDevice() throws Exception {
super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret");
super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, null, null, "testProvisionKeyOrig", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES);
byte[] result = createCoapClientAndPublish().getPayload();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString());

12
application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionProtoDeviceTest.java

@ -92,14 +92,14 @@ public abstract class AbstractCoapProvisionProtoDeviceTest extends AbstractCoapI
private void processTestProvisioningDisabledDevice() throws Exception {
super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.DISABLED, null, null);
super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED);
ProvisionDeviceResponseMsg result = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish().getPayload());
Assert.assertNotNull(result);
Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getStatus().toString());
}
private void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception {
super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret");
super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES);
ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish().getPayload());
Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device");
@ -113,7 +113,7 @@ public abstract class AbstractCoapProvisionProtoDeviceTest extends AbstractCoapI
}
private void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception {
super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret");
super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES);
CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken("test_token").build()).build();
ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish(createTestsProvisionMessage(CredentialsType.ACCESS_TOKEN, requestCredentials)).getPayload());
@ -131,7 +131,7 @@ public abstract class AbstractCoapProvisionProtoDeviceTest extends AbstractCoapI
}
private void processTestProvisioningCreateNewDeviceWithCert() throws Exception {
super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret");
super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES);
CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder().setHash("testHash").build()).build();
ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish(createTestsProvisionMessage(CredentialsType.X509_CERTIFICATE, requestCredentials)).getPayload());
@ -155,7 +155,7 @@ public abstract class AbstractCoapProvisionProtoDeviceTest extends AbstractCoapI
}
private void processTestProvisioningCheckPreProvisionedDevice() throws Exception {
super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret");
super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES);
ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish().getPayload());
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId());
@ -165,7 +165,7 @@ public abstract class AbstractCoapProvisionProtoDeviceTest extends AbstractCoapI
}
private void processTestProvisioningWithBadKeyDevice() throws Exception {
super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret");
super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, null, null, "testProvisionKeyOrig", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES);
ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish().getPayload());
Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getStatus().toString());
}

6
application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java

@ -124,7 +124,7 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC
return COAP_BASE_URL + token + "/" + FeatureType.RPC.name().toLowerCase() + "/" + requestId;
}
private class TestCoapCallback implements CoapHandler {
protected class TestCoapCallback implements CoapHandler {
private final CoapClient client;
private final CountDownLatch latch;
@ -136,7 +136,7 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC
private Integer observe;
private TestCoapCallback(CoapClient client, CountDownLatch latch, boolean isOneWayRpc) {
TestCoapCallback(CoapClient client, CountDownLatch latch, boolean isOneWayRpc) {
this.client = client;
this.latch = latch;
this.isOneWayRpc = isOneWayRpc;
@ -144,7 +144,7 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC
@Override
public void onLoad(CoapResponse response) {
log.warn("coap response: {}, {}", response, response.getCode());
log.warn("coap response: {}, {}", response.getResponseText(), response.getCode());
assertNotNull(response.getPayload());
assertEquals(response.getCode(), CoAP.ResponseCode.CONTENT);
observe = response.getOptions().getObserve();

4
application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcJsonIntegrationTest.java

@ -36,12 +36,12 @@ public abstract class AbstractCoapServerSideRpcJsonIntegrationTest extends Abstr
}
@Test
public void testServerMqttOneWayRpc() throws Exception {
public void testServerCoapOneWayRpc() throws Exception {
processOneWayRpcTest();
}
@Test
public void testServerMqttTwoWayRpc() throws Exception {
public void testServerCoapTwoWayRpc() throws Exception {
processTwoWayRpcTest();
}

130
application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcProtoIntegrationTest.java

@ -15,26 +15,62 @@
*/
package org.thingsboard.server.transport.coap.rpc;
import com.github.os72.protobuf.dynamic.DynamicSchema;
import com.google.protobuf.Descriptors;
import com.google.protobuf.DynamicMessage;
import com.google.protobuf.InvalidProtocolBufferException;
import com.squareup.wire.schema.internal.parser.ProtoFileElement;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapHandler;
import org.eclipse.californium.core.CoapObserveRelation;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.MediaTypeRegistry;
import org.eclipse.californium.core.coap.Request;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.CoapDeviceType;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration;
import org.thingsboard.server.common.msg.session.FeatureType;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
public abstract class AbstractCoapServerSideRpcProtoIntegrationTest extends AbstractCoapServerSideRpcIntegrationTest {
private static final String RPC_REQUEST_PROTO_SCHEMA = "syntax =\"proto3\";\n" +
"package rpc;\n" +
"\n" +
"message RpcRequestMsg {\n" +
" string method = 1;\n" +
" int32 requestId = 2;\n" +
" Params params = 3;\n" +
"\n" +
" message Params {\n" +
" string pin = 1;\n" +
" int32 value = 2;\n" +
" }\n" +
"}";
@Before
public void beforeTest() throws Exception {
processBeforeTest("RPC test device", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF);
processBeforeTest("RPC test device", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED);
}
@After
@ -43,33 +79,91 @@ public abstract class AbstractCoapServerSideRpcProtoIntegrationTest extends Abst
}
@Test
public void testServerMqttOneWayRpc() throws Exception {
public void testServerCoapOneWayRpc() throws Exception {
processOneWayRpcTest();
}
@Test
public void testServerMqttTwoWayRpc() throws Exception {
public void testServerCoapTwoWayRpc() throws Exception {
processTwoWayRpcTest();
}
protected void processTwoWayRpcTest() throws Exception {
CoapClient client = getCoapClient(FeatureType.RPC);
client.useCONs();
CountDownLatch latch = new CountDownLatch(1);
TestCoapCallback testCoapCallback = new TestCoapCallback(client, latch, false);
Request request = Request.newGet().setObserve();
request.setType(CoAP.Type.CON);
CoapObserveRelation observeRelation = client.observe(request, testCoapCallback);
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}";
String deviceId = savedDevice.getId().getId().toString();
String expected = "{\"payload\":\"{\\\"value1\\\":\\\"A\\\",\\\"value2\\\":\\\"B\\\"}\"}";
String result = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
latch.await(3, TimeUnit.SECONDS);
assertEquals(expected, result);
assertEquals(0, testCoapCallback.getObserve().intValue());
observeRelation.proactiveCancel();
assertTrue(observeRelation.isCanceled());
}
@Override
protected void processOnLoadResponse(CoapResponse response, CoapClient client, Integer observe, CountDownLatch latch) {
client.setURI(getRpcResponseFeatureTokenUrl(accessToken, observe));
TransportProtos.ToDeviceRpcResponseMsg toDeviceRpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder()
.setPayload(DEVICE_RESPONSE)
.setRequestId(observe)
.build();
client.post(new CoapHandler() {
@Override
public void onLoad(CoapResponse response) {
log.warn("Command Response Ack: {}, {}", response.getCode(), response.getResponseText());
latch.countDown();
}
ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = getProtoTransportPayloadConfiguration();
ProtoFileElement rpcRequestProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(RPC_REQUEST_PROTO_SCHEMA);
DynamicSchema rpcRequestProtoSchema = protoTransportPayloadConfiguration.getDynamicSchema(rpcRequestProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_REQUEST_PROTO_SCHEMA);
@Override
public void onError() {
log.warn("Command Response Ack Error, No connect");
byte[] requestPayload = response.getPayload();
DynamicMessage.Builder rpcRequestMsg = rpcRequestProtoSchema.newMessageBuilder("RpcRequestMsg");
Descriptors.Descriptor rpcRequestMsgDescriptor = rpcRequestMsg.getDescriptorForType();
assertNotNull(rpcRequestMsgDescriptor);
try {
DynamicMessage dynamicMessage = DynamicMessage.parseFrom(rpcRequestMsgDescriptor, requestPayload);
List<Descriptors.FieldDescriptor> fields = rpcRequestMsgDescriptor.getFields();
for (Descriptors.FieldDescriptor fieldDescriptor: fields) {
assertTrue(dynamicMessage.hasField(fieldDescriptor));
}
}, toDeviceRpcResponseMsg.toByteArray(), MediaTypeRegistry.APPLICATION_JSON);
ProtoFileElement rpcResponseProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_RPC_RESPONSE_PROTO_SCHEMA);
DynamicSchema rpcResponseProtoSchema = protoTransportPayloadConfiguration.getDynamicSchema(rpcResponseProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_RESPONSE_PROTO_SCHEMA);
DynamicMessage.Builder rpcResponseBuilder = rpcResponseProtoSchema.newMessageBuilder("RpcResponseMsg");
Descriptors.Descriptor rpcResponseMsgDescriptor = rpcResponseBuilder.getDescriptorForType();
assertNotNull(rpcResponseMsgDescriptor);
DynamicMessage rpcResponseMsg = rpcResponseBuilder
.setField(rpcResponseMsgDescriptor.findFieldByName("payload"), DEVICE_RESPONSE)
.build();
client.post(new CoapHandler() {
@Override
public void onLoad(CoapResponse response) {
log.warn("Command Response Ack: {}, {}", response.getCode(), response.getResponseText());
latch.countDown();
}
@Override
public void onError() {
log.warn("Command Response Ack Error, No connect");
}
}, rpcResponseMsg.toByteArray(), MediaTypeRegistry.APPLICATION_JSON);
} catch (InvalidProtocolBufferException e) {
log.warn("Command Response Ack Error, Invalid response received: ", e);
}
}
private ProtoTransportPayloadConfiguration getProtoTransportPayloadConfiguration() {
DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration);
CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration;
CoapDeviceTypeConfiguration coapDeviceTypeConfiguration = coapDeviceProfileTransportConfiguration.getCoapDeviceTypeConfiguration();
assertTrue(coapDeviceTypeConfiguration instanceof DefaultCoapDeviceTypeConfiguration);
DefaultCoapDeviceTypeConfiguration defaultCoapDeviceTypeConfiguration = (DefaultCoapDeviceTypeConfiguration) coapDeviceTypeConfiguration;
TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = defaultCoapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration();
assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration);
return (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration;
}
}

2
application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesProtoIntegrationTest.java

@ -114,7 +114,7 @@ public abstract class AbstractCoapTimeseriesProtoIntegrationTest extends Abstrac
" }\n" +
" }\n" +
"}";
super.processBeforeTest("Test Post Telemetry device proto payload", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, schemaStr, null, DeviceProfileProvisionType.DISABLED, null, null);
super.processBeforeTest("Test Post Telemetry device proto payload", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, schemaStr, null, null, null, null, null, DeviceProfileProvisionType.DISABLED);
DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration);
CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration;

26
application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java

@ -62,7 +62,7 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg
protected DeviceProfile deviceProfile;
protected void processBeforeTest (String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception {
this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, null, null, DeviceProfileProvisionType.DISABLED, null, null);
this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED);
}
protected void processBeforeTest(String deviceName,
@ -72,9 +72,12 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg
String attributesTopic,
String telemetryProtoSchema,
String attributesProtoSchema,
DeviceProfileProvisionType provisionType,
String provisionKey, String provisionSecret
) throws Exception {
String rpcResponseProtoSchema,
String rpcRequestProtoSchema,
String provisionKey,
String provisionSecret,
DeviceProfileProvisionType provisionType
) throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
@ -103,7 +106,7 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg
gateway.setAdditionalInfo(additionalInfo);
if (payloadType != null) {
DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic, telemetryProtoSchema, attributesProtoSchema, provisionType, provisionKey, provisionSecret);
DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic, telemetryProtoSchema, attributesProtoSchema, rpcResponseProtoSchema, rpcRequestProtoSchema, provisionKey, provisionSecret, provisionType);
deviceProfile = doPost("/api/deviceProfile", mqttDeviceProfile, DeviceProfile.class);
device.setType(deviceProfile.getName());
device.setDeviceProfileId(deviceProfile.getId());
@ -157,8 +160,9 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg
protected DeviceProfile createMqttDeviceProfile(TransportPayloadType transportPayloadType,
String telemetryTopic, String attributesTopic,
String telemetryProtoSchema, String attributesProtoSchema,
DeviceProfileProvisionType provisionType,
String provisionKey, String provisionSecret) {
String rpcResponseProtoSchema, String rpcRequestProtoSchema,
String provisionKey, String provisionSecret,
DeviceProfileProvisionType provisionType) {
DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setName(transportPayloadType.name());
deviceProfile.setType(DeviceProfileType.DEFAULT);
@ -186,8 +190,16 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg
if (StringUtils.isEmpty(attributesProtoSchema)) {
attributesProtoSchema = DEVICE_ATTRIBUTES_PROTO_SCHEMA;
}
if (StringUtils.isEmpty(rpcResponseProtoSchema)) {
rpcResponseProtoSchema = DEVICE_RPC_RESPONSE_PROTO_SCHEMA;
}
if (StringUtils.isEmpty(rpcRequestProtoSchema)) {
rpcRequestProtoSchema = DEVICE_RPC_REQUEST_PROTO_SCHEMA;
}
protoTransportPayloadConfiguration.setDeviceTelemetryProtoSchema(telemetryProtoSchema);
protoTransportPayloadConfiguration.setDeviceAttributesProtoSchema(attributesProtoSchema);
protoTransportPayloadConfiguration.setDeviceRpcResponseProtoSchema(rpcResponseProtoSchema);
protoTransportPayloadConfiguration.setDeviceRpcRequestProtoSchema(rpcRequestProtoSchema);
transportPayloadTypeConfiguration = protoTransportPayloadConfiguration;
}
mqttDeviceProfileTransportConfiguration.setTransportPayloadTypeConfiguration(transportPayloadTypeConfiguration);

2
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java

@ -83,7 +83,7 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends
@Test
public void testRequestAttributesValuesFromTheServer() throws Exception {
super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto",
TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, DeviceProfileProvisionType.DISABLED, null, null);
TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED);
processTestRequestAttributesValuesFromTheServer();
}

14
application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java

@ -94,7 +94,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn
protected void processTestProvisioningDisabledDevice() throws Exception {
super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.DISABLED, null, null);
super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED);
byte[] result = createMqttClientAndPublish().getPayloadBytes();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString());
@ -103,7 +103,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn
protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception {
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret");
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES);
byte[] result = createMqttClientAndPublish().getPayloadBytes();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
@ -119,7 +119,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn
protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception {
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret");
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES);
String requestCredentials = ",\"credentialsType\": \"ACCESS_TOKEN\",\"token\": \"test_token\"";
byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
@ -138,7 +138,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn
protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception {
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret");
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES);
String requestCredentials = ",\"credentialsType\": \"X509_CERTIFICATE\",\"hash\": \"testHash\"";
byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
@ -163,7 +163,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn
protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception {
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret");
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES);
String requestCredentials = ",\"credentialsType\": \"MQTT_BASIC\",\"clientId\": \"test_clientId\",\"username\": \"test_username\",\"password\": \"test_password\"";
byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
@ -188,7 +188,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn
}
protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception {
super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret");
super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES);
byte[] result = createMqttClientAndPublish().getPayloadBytes();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
@ -199,7 +199,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn
}
protected void processTestProvisioningWithBadKeyDevice() throws Exception {
super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret");
super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKeyOrig", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES);
byte[] result = createMqttClientAndPublish().getPayloadBytes();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString());

14
application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java

@ -101,14 +101,14 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI
protected void processTestProvisioningDisabledDevice() throws Exception {
super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.DISABLED, null, null);
super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED);
ProvisionDeviceResponseMsg result = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes());
Assert.assertNotNull(result);
Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getStatus().toString());
}
protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception {
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret");
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES);
ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes());
Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device");
@ -122,7 +122,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI
}
protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception {
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null,null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret");
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null,null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES);
CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken("test_token").build()).build();
ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.ACCESS_TOKEN, requestCredentials)).getPayloadBytes());
@ -140,7 +140,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI
}
protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception {
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret");
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES);
CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder().setHash("testHash").build()).build();
ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.X509_CERTIFICATE, requestCredentials)).getPayloadBytes());
@ -164,7 +164,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI
}
protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception {
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret");
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES);
CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateBasicMqttCredRequestMsg(
ValidateBasicMqttCredRequestMsg.newBuilder()
.setClientId("test_clientId")
@ -195,7 +195,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI
}
protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception {
super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret");
super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES);
ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes());
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId());
@ -205,7 +205,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI
}
protected void processTestProvisioningWithBadKeyDevice() throws Exception {
super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret");
super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKeyOrig", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES);
ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes());
Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getStatus().toString());
}

2
application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java

@ -180,7 +180,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
return message;
}
private class TestMqttCallback implements MqttCallback {
protected class TestMqttCallback implements MqttCallback {
private final MqttAsyncClient client;
private final CountDownLatch latch;

96
application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java

@ -15,25 +15,60 @@
*/
package org.thingsboard.server.transport.mqtt.rpc;
import com.github.os72.protobuf.dynamic.DynamicSchema;
import com.google.protobuf.Descriptors;
import com.google.protobuf.DynamicMessage;
import com.google.protobuf.InvalidProtocolBufferException;
import com.squareup.wire.schema.internal.parser.ProtoFileElement;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.CoapHandler;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.coap.MediaTypeRegistry;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.jetbrains.annotations.NotNull;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration;
import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
public abstract class AbstractMqttServerSideRpcProtoIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest {
private static final String RPC_REQUEST_PROTO_SCHEMA = "syntax =\"proto3\";\n" +
"package rpc;\n" +
"\n" +
"message RpcRequestMsg {\n" +
" string method = 1;\n" +
" int32 requestId = 2;\n" +
" Params params = 3;\n" +
"\n" +
" message Params {\n" +
" string pin = 1;\n" +
" int32 value = 2;\n" +
" }\n" +
"}";
@Before
public void beforeTest() throws Exception {
processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null);
processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED);
}
@After
@ -83,14 +118,55 @@ public abstract class AbstractMqttServerSideRpcProtoIntegrationTest extends Abst
return builder.build();
}
protected void processTwoWayRpcTest() throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken);
client.subscribe(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC, 1);
CountDownLatch latch = new CountDownLatch(1);
TestMqttCallback callback = new TestMqttCallback(client, latch);
client.setCallback(callback);
Thread.sleep(1000);
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}";
String deviceId = savedDevice.getId().getId().toString();
String result = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
String expected = "{\"payload\":\"{\\\"value1\\\":\\\"A\\\",\\\"value2\\\":\\\"B\\\"}\"}";
latch.await(3, TimeUnit.SECONDS);
Assert.assertEquals(expected, result);
}
protected MqttMessage processMessageArrived(String requestTopic, MqttMessage mqttMessage) throws MqttException, InvalidProtocolBufferException {
MqttMessage message = new MqttMessage();
if (requestTopic.startsWith(MqttTopics.BASE_DEVICE_API_TOPIC)) {
TransportProtos.ToDeviceRpcResponseMsg toDeviceRpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder()
.setPayload(DEVICE_RESPONSE)
.setRequestId(0)
ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = getProtoTransportPayloadConfiguration();
ProtoFileElement rpcRequestProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(RPC_REQUEST_PROTO_SCHEMA);
DynamicSchema rpcRequestProtoSchema = protoTransportPayloadConfiguration.getDynamicSchema(rpcRequestProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_REQUEST_PROTO_SCHEMA);
byte[] requestPayload = mqttMessage.getPayload();
DynamicMessage.Builder rpcRequestMsg = rpcRequestProtoSchema.newMessageBuilder("RpcRequestMsg");
Descriptors.Descriptor rpcRequestMsgDescriptor = rpcRequestMsg.getDescriptorForType();
assertNotNull(rpcRequestMsgDescriptor);
try {
DynamicMessage dynamicMessage = DynamicMessage.parseFrom(rpcRequestMsgDescriptor, requestPayload);
List<Descriptors.FieldDescriptor> fields = rpcRequestMsgDescriptor.getFields();
for (Descriptors.FieldDescriptor fieldDescriptor: fields) {
assertTrue(dynamicMessage.hasField(fieldDescriptor));
}
ProtoFileElement transportProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_RPC_RESPONSE_PROTO_SCHEMA);
DynamicSchema rpcResponseProtoSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_RESPONSE_PROTO_SCHEMA);
DynamicMessage.Builder rpcResponseBuilder = rpcResponseProtoSchema.newMessageBuilder("RpcResponseMsg");
Descriptors.Descriptor rpcResponseMsgDescriptor = rpcResponseBuilder.getDescriptorForType();
assertNotNull(rpcResponseMsgDescriptor);
DynamicMessage rpcResponseMsg = rpcResponseBuilder
.setField(rpcResponseMsgDescriptor.findFieldByName("payload"), DEVICE_RESPONSE)
.build();
message.setPayload(toDeviceRpcResponseMsg.toByteArray());
message.setPayload(rpcResponseMsg.toByteArray());
} catch (InvalidProtocolBufferException e) {
log.warn("Command Response Ack Error, Invalid response received: ", e);
}
} else {
TransportApiProtos.GatewayDeviceRpcRequestMsg msg = TransportApiProtos.GatewayDeviceRpcRequestMsg.parseFrom(mqttMessage.getPayload());
String deviceName = msg.getDeviceName();
@ -105,6 +181,14 @@ public abstract class AbstractMqttServerSideRpcProtoIntegrationTest extends Abst
return message;
}
private ProtoTransportPayloadConfiguration getProtoTransportPayloadConfiguration() {
DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration);
MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration;
TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration();
assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration);
return (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration;
}
}

6
application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java

@ -119,7 +119,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
" }\n" +
" }\n" +
"}";
super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, DeviceProfileProvisionType.DISABLED, null, null);
super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, null, null, null, null, DeviceProfileProvisionType.DISABLED);
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration);
@ -172,7 +172,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
@Test
public void testPushMqttTelemetryGateway() throws Exception {
super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.DISABLED, null, null);
super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED);
TransportApiProtos.GatewayTelemetryMsg.Builder gatewayTelemetryMsgProtoBuilder = TransportApiProtos.GatewayTelemetryMsg.newBuilder();
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
String deviceName1 = "Device A";
@ -186,7 +186,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
@Test
public void testGatewayConnect() throws Exception {
super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, null, null, DeviceProfileProvisionType.DISABLED, null, null);
super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED);
String deviceName = "Device A";
TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName);
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);

2
common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java

@ -102,4 +102,6 @@ public interface DeviceService {
PageData<Device> findDevicesByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, PageLink pageLink);
PageData<Device> findDevicesByTenantIdAndEdgeIdAndType(TenantId tenantId, EdgeId edgeId, String type, PageLink pageLink);
long countByTenantId(TenantId tenantId);
}

3
common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java

@ -17,6 +17,7 @@ package org.thingsboard.server.dao.event;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.event.EventFilter;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
@ -41,6 +42,8 @@ public interface EventService {
List<Event> findLatestEvents(TenantId tenantId, EntityId entityId, String eventType, int limit);
PageData<Event> findEventsByFilter(TenantId tenantId, EntityId entityId, EventFilter eventFilter, TimePageLink pageLink);
void removeEvents(TenantId tenantId, EntityId entityId);
}

50
common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java

@ -0,0 +1,50 @@
/**
* 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.dao.resource;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import java.util.List;
public interface ResourceService {
TbResource saveResource(TbResource resource);
TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId);
TbResource findResourceById(TenantId tenantId, TbResourceId resourceId);
TbResourceInfo findResourceInfoById(TenantId tenantId, TbResourceId resourceId);
PageData<TbResourceInfo> findAllTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink);
PageData<TbResourceInfo> findTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink);
List<TbResource> findTenantResourcesByResourceTypeAndObjectIds(TenantId tenantId, ResourceType lwm2mModel, String[] objectIds);
PageData<TbResource> findTenantResourcesByResourceTypeAndPageLink(TenantId tenantId, ResourceType lwm2mModel, PageLink pageLink);
void deleteResource(TenantId tenantId, TbResourceId resourceId);
void deleteResourcesByTenantId(TenantId tenantId);
}

2
common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java

@ -43,7 +43,7 @@ public interface RuleChainService {
boolean setRootRuleChain(TenantId tenantId, RuleChainId ruleChainId);
RuleChainMetaData saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData);
boolean saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData);
RuleChainMetaData loadRuleChainMetaData(TenantId tenantId, RuleChainId ruleChainId);

4
common/data/pom.xml

@ -91,10 +91,6 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.leshan</groupId>
<artifactId>leshan-core</artifactId>
</dependency>
</dependencies>
<build>

4
common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java

@ -19,14 +19,18 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.NoXss;
import javax.validation.Valid;
import java.util.HashSet;
import java.util.Set;
public class DashboardInfo extends SearchTextBased<DashboardId> implements HasName, HasTenantId {
private TenantId tenantId;
@NoXss
private String title;
@Valid
private Set<ShortCustomerInfo> assignedCustomers;
public DashboardInfo() {

2
common/data/src/main/java/org/thingsboard/server/common/data/ShortCustomerInfo.java

@ -19,6 +19,7 @@ import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.validation.NoXss;
/**
* Created by igor on 2/27/18.
@ -31,6 +32,7 @@ public class ShortCustomerInfo {
private CustomerId customerId;
@Getter @Setter
@NoXss
private String title;
@Getter @Setter

27
common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data;
public class StringUtils {
public static boolean isEmpty(String source) {
return source == null || source.isEmpty();
}
public static boolean isNotEmpty(String source) {
return source != null && !source.isEmpty();
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/TbResource.java

@ -19,6 +19,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.validation.NoXss;
@Slf4j
@Data
@ -27,6 +28,7 @@ public class TbResource extends TbResourceInfo {
private static final long serialVersionUID = 7379609705527272306L;
@NoXss
private String fileName;
private String data;

2
common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java

@ -20,6 +20,7 @@ import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.NoXss;
@Slf4j
@Data
@ -27,6 +28,7 @@ import org.thingsboard.server.common.data.id.TenantId;
public class TbResourceInfo extends SearchTextBased<TbResourceId> implements HasTenantId {
private TenantId tenantId;
@NoXss
private String title;
private ResourceType resourceType;
private String resourceKey;

4
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java

@ -19,11 +19,8 @@ import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TimePageLink;
import java.util.UUID;
/**
* Created by ashvayka on 11.05.17.
*/
@ -37,6 +34,5 @@ public class AlarmQuery {
private AlarmSearchStatus searchStatus;
private AlarmStatus status;
private Boolean fetchOriginator;
private UUID idOffset;
}

1
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileData.java

@ -25,6 +25,7 @@ import java.util.List;
public class DeviceProfileData implements Serializable {
private DeviceProfileConfiguration configuration;
@Valid
private DeviceProfileTransportConfiguration transportConfiguration;
private DeviceProfileProvisionConfiguration provisionConfiguration;
@Valid

3
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java

@ -17,11 +17,14 @@ package org.thingsboard.server.common.data.device.profile;
import lombok.Data;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.validation.NoXss;
@Data
public class MqttDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration {
@NoXss
private String deviceTelemetryTopic = MqttTopics.DEVICE_TELEMETRY_TOPIC;
@NoXss
private String deviceAttributesTopic = MqttTopics.DEVICE_ATTRIBUTES_TOPIC;
private TransportPayloadTypeConfiguration transportPayloadTypeConfiguration;

55
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java

@ -15,7 +15,6 @@
*/
package org.thingsboard.server.common.data.device.profile;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.github.os72.protobuf.dynamic.DynamicSchema;
import com.github.os72.protobuf.dynamic.EnumDefinition;
import com.github.os72.protobuf.dynamic.MessageDefinition;
@ -46,9 +45,13 @@ public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeC
public static final Location LOCATION = new Location("", "", -1, -1);
public static final String ATTRIBUTES_PROTO_SCHEMA = "attributes proto schema";
public static final String TELEMETRY_PROTO_SCHEMA = "telemetry proto schema";
public static final String RPC_RESPONSE_PROTO_SCHEMA = "rpc response proto schema";
public static final String RPC_REQUEST_PROTO_SCHEMA = "rpc request proto schema";
private String deviceTelemetryProtoSchema;
private String deviceAttributesProtoSchema;
private String deviceRpcRequestProtoSchema;
private String deviceRpcResponseProtoSchema;
@Override
public TransportPayloadType getTransportPayloadType() {
@ -63,13 +66,45 @@ public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeC
return getDescriptor(deviceAttributesProtoSchema, ATTRIBUTES_PROTO_SCHEMA);
}
public Descriptors.Descriptor getRpcResponseDynamicMessageDescriptor(String deviceRpcResponseProtoSchema) {
return getDescriptor(deviceRpcResponseProtoSchema, RPC_RESPONSE_PROTO_SCHEMA);
}
public DynamicMessage.Builder getRpcRequestDynamicMessageBuilder(String deviceRpcRequestProtoSchema) {
return getDynamicMessageBuilder(deviceRpcRequestProtoSchema, RPC_REQUEST_PROTO_SCHEMA);
}
public String getDeviceRpcResponseProtoSchema() {
if (!isEmptyStr(deviceRpcResponseProtoSchema)) {
return deviceRpcResponseProtoSchema;
} else {
return "syntax =\"proto3\";\n" +
"package rpc;\n" +
"\n" +
"message RpcResponseMsg {\n" +
" string payload = 1;\n" +
"}";
}
}
public String getDeviceRpcRequestProtoSchema() {
if (!isEmptyStr(deviceRpcRequestProtoSchema)) {
return deviceRpcRequestProtoSchema;
} else {
return "syntax =\"proto3\";\n" +
"package rpc;\n" +
"\n" +
"message RpcRequestMsg {\n" +
" string method = 1;\n" +
" int32 requestId = 2;\n" +
" string params = 3;\n" +
"}";
}
}
private Descriptors.Descriptor getDescriptor(String protoSchema, String schemaName) {
try {
ProtoFileElement protoFileElement = getTransportProtoSchema(protoSchema);
DynamicSchema dynamicSchema = getDynamicSchema(protoFileElement, schemaName);
String lastMsgName = getMessageTypes(protoFileElement.getTypes()).stream()
.map(MessageElement::getName).reduce((previous, last) -> last).get();
DynamicMessage.Builder builder = dynamicSchema.newMessageBuilder(lastMsgName);
DynamicMessage.Builder builder = getDynamicMessageBuilder(protoSchema, schemaName);
return builder.getDescriptorForType();
} catch (Exception e) {
log.warn("Failed to get Message Descriptor due to {}", e.getMessage());
@ -77,6 +112,14 @@ public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeC
}
}
public DynamicMessage.Builder getDynamicMessageBuilder(String protoSchema, String schemaName) {
ProtoFileElement protoFileElement = getTransportProtoSchema(protoSchema);
DynamicSchema dynamicSchema = getDynamicSchema(protoFileElement, schemaName);
String lastMsgName = getMessageTypes(protoFileElement.getTypes()).stream()
.map(MessageElement::getName).reduce((previous, last) -> last).get();
return dynamicSchema.newMessageBuilder(lastMsgName);
}
public DynamicSchema getDynamicSchema(ProtoFileElement protoFileElement, String schemaName) {
DynamicSchema.Builder schemaBuilder = DynamicSchema.newBuilder();
schemaBuilder.setName(schemaName);

45
common/data/src/main/java/org/thingsboard/server/common/data/event/DebugEvent.java

@ -0,0 +1,45 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.event;
import lombok.Data;
import org.thingsboard.server.common.data.StringUtils;
@Data
public abstract class DebugEvent implements EventFilter {
private String msgDirectionType;
private String server;
private String dataSearch;
private String metadataSearch;
private String entityName;
private String relationType;
private String entityId;
private String msgType;
private boolean isError;
private String error;
public void setIsError(boolean isError) {
this.isError = isError;
}
@Override
public boolean hasFilterForJsonBody() {
return !StringUtils.isEmpty(msgDirectionType) || !StringUtils.isEmpty(server) || !StringUtils.isEmpty(dataSearch) || !StringUtils.isEmpty(metadataSearch)
|| !StringUtils.isEmpty(entityName) || !StringUtils.isEmpty(relationType) || !StringUtils.isEmpty(entityId) || !StringUtils.isEmpty(msgType) || !StringUtils.isEmpty(error) || isError;
}
}

23
common/data/src/main/java/org/thingsboard/server/common/data/event/DebugRuleChainEventFilter.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.event;
public class DebugRuleChainEventFilter extends DebugEvent {
@Override
public EventType getEventType() {
return EventType.DEBUG_RULE_CHAIN;
}
}

23
common/data/src/main/java/org/thingsboard/server/common/data/event/DebugRuleNodeEventFilter.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.event;
public class DebugRuleNodeEventFilter extends DebugEvent {
@Override
public EventType getEventType() {
return EventType.DEBUG_RULE_NODE;
}
}

36
common/data/src/main/java/org/thingsboard/server/common/data/event/ErrorEventFilter.java

@ -0,0 +1,36 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.event;
import lombok.Data;
import org.thingsboard.server.common.data.StringUtils;
@Data
public class ErrorEventFilter implements EventFilter {
private String server;
private String method;
private String error;
@Override
public EventType getEventType() {
return EventType.ERROR;
}
@Override
public boolean hasFilterForJsonBody() {
return !StringUtils.isEmpty(server) || !StringUtils.isEmpty(method) || !StringUtils.isEmpty(error);
}
}

40
common/data/src/main/java/org/thingsboard/server/common/data/event/EventFilter.java

@ -0,0 +1,40 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.event;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "eventType")
@JsonSubTypes({
@JsonSubTypes.Type(value = DebugRuleNodeEventFilter.class, name = "DEBUG_RULE_NODE"),
@JsonSubTypes.Type(value = DebugRuleChainEventFilter.class, name = "DEBUG_RULE_CHAIN"),
@JsonSubTypes.Type(value = ErrorEventFilter.class, name = "ERROR"),
@JsonSubTypes.Type(value = LifeCycleEventFilter.class, name = "LC_EVENT"),
@JsonSubTypes.Type(value = StatisticsEventFilter.class, name = "STATS")
})
public interface EventFilter {
@JsonIgnore
EventType getEventType();
boolean hasFilterForJsonBody();
}

20
common/data/src/main/java/org/thingsboard/server/common/data/event/EventType.java

@ -0,0 +1,20 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.event;
public enum EventType {
ERROR, LC_EVENT, STATS, DEBUG_RULE_NODE, DEBUG_RULE_CHAIN
}

37
common/data/src/main/java/org/thingsboard/server/common/data/event/LifeCycleEventFilter.java

@ -0,0 +1,37 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.event;
import lombok.Data;
import org.thingsboard.server.common.data.StringUtils;
@Data
public class LifeCycleEventFilter implements EventFilter {
private String server;
private String event;
private String status;
private String error;
@Override
public EventType getEventType() {
return EventType.LC_EVENT;
}
@Override
public boolean hasFilterForJsonBody() {
return !StringUtils.isEmpty(server) || !StringUtils.isEmpty(event) || !StringUtils.isEmpty(status) || !StringUtils.isEmpty(error);
}
}

36
common/data/src/main/java/org/thingsboard/server/common/data/event/StatisticsEventFilter.java

@ -0,0 +1,36 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.event;
import lombok.Data;
import org.thingsboard.server.common.data.StringUtils;
@Data
public class StatisticsEventFilter implements EventFilter {
private String server;
private Integer messagesProcessed;
private Integer errorsOccurred;
@Override
public EventType getEventType() {
return EventType.STATS;
}
@Override
public boolean hasFilterForJsonBody() {
return !StringUtils.isEmpty(server) || (messagesProcessed != null && messagesProcessed > 0) || (errorsOccurred != null && errorsOccurred > 0);
}
}

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

@ -15,10 +15,16 @@
*/
package org.thingsboard.server.common.data.tenant.profile;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.TenantProfileType;
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class DefaultTenantProfileConfiguration implements TenantProfileConfiguration {

4
common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java

@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.validation.NoXss;
@Data
public class BaseWidgetType extends BaseData<WidgetTypeId> implements HasTenantId {
@ -27,8 +28,11 @@ public class BaseWidgetType extends BaseData<WidgetTypeId> implements HasTenantI
private static final long serialVersionUID = 8388684344603660756L;
private TenantId tenantId;
@NoXss
private String bundleAlias;
@NoXss
private String alias;
@NoXss
private String name;
public BaseWidgetType() {

3
common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java

@ -15,14 +15,15 @@
*/
package org.thingsboard.server.common.data.widget;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.validation.NoXss;
@Data
public class WidgetTypeDetails extends WidgetType {
private String image;
@NoXss
private String description;
public WidgetTypeDetails() {

3
common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java

@ -17,12 +17,15 @@ package org.thingsboard.server.common.data.widget;
import lombok.Data;
import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.validation.NoXss;
@Data
public class WidgetTypeInfo extends BaseWidgetType {
private String image;
@NoXss
private String description;
@NoXss
private String widgetType;
public WidgetTypeInfo() {

4
common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java

@ -19,6 +19,7 @@ import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.SearchTextBased;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetsBundleId;
import org.thingsboard.server.common.data.validation.NoXss;
import java.util.Arrays;
@ -27,9 +28,12 @@ public class WidgetsBundle extends SearchTextBased<WidgetsBundleId> implements H
private static final long serialVersionUID = -7627368878362410489L;
private TenantId tenantId;
@NoXss
private String alias;
@NoXss
private String title;
private String image;
@NoXss
private String description;
public WidgetsBundle() {

4
common/queue/pom.xml

@ -112,6 +112,10 @@
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>

140
common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java

@ -21,22 +21,28 @@ import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueMsg;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import static java.util.Collections.emptyList;
@Slf4j
public abstract class AbstractTbQueueConsumerTemplate<R, T extends TbQueueMsg> implements TbQueueConsumer<T> {
private volatile boolean subscribed;
protected volatile boolean stopped = false;
protected volatile Set<TopicPartitionInfo> partitions;
protected final Lock consumerLock = new ReentrantLock();
protected final ReentrantLock consumerLock = new ReentrantLock(); //NonfairSync
final Queue<Set<TopicPartitionInfo>> subscribeQueue = new ConcurrentLinkedQueue<>();
@Getter
private final String topic;
@ -47,84 +53,101 @@ public abstract class AbstractTbQueueConsumerTemplate<R, T extends TbQueueMsg> i
@Override
public void subscribe() {
consumerLock.lock();
try {
partitions = Collections.singleton(new TopicPartitionInfo(topic, null, null, true));
subscribed = false;
} finally {
consumerLock.unlock();
log.info("enqueue topic subscribe {} ", topic);
if (stopped) {
log.error("trying subscribe, but consumer stopped for topic {}", topic);
return;
}
subscribeQueue.add(Collections.singleton(new TopicPartitionInfo(topic, null, null, true)));
}
@Override
public void subscribe(Set<TopicPartitionInfo> partitions) {
log.info("enqueue topics subscribe {} ", partitions);
if (stopped) {
log.error("trying subscribe, but consumer stopped for topic {}", topic);
return;
}
subscribeQueue.add(partitions);
}
@Override
public List<T> poll(long durationInMillis) {
List<R> records;
long startNanos = System.nanoTime();
if (stopped) {
return errorAndReturnEmpty();
}
if (!subscribed && partitions == null && subscribeQueue.isEmpty()) {
return sleepAndReturnEmpty(startNanos, durationInMillis);
}
if (consumerLock.isLocked()) {
log.error("poll. consumerLock is locked. will wait with no timeout. it looks like a race conditions or deadlock", new RuntimeException("stacktrace"));
}
consumerLock.lock();
try {
this.partitions = partitions;
subscribed = false;
while (!subscribeQueue.isEmpty()) {
subscribed = false;
partitions = subscribeQueue.poll();
}
if (!subscribed) {
List<String> topicNames = partitions.stream().map(TopicPartitionInfo::getFullTopicName).collect(Collectors.toList());
doSubscribe(topicNames);
subscribed = true;
}
records = partitions.isEmpty() ? emptyList() : doPoll(durationInMillis);
} finally {
consumerLock.unlock();
}
if (records.isEmpty()) { return sleepAndReturnEmpty(startNanos, durationInMillis); }
return decodeRecords(records);
}
@Override
public List<T> poll(long durationInMillis) {
if (!subscribed && partitions == null) {
try {
Thread.sleep(durationInMillis);
} catch (InterruptedException e) {
log.debug("Failed to await subscription", e);
}
} else {
long pollStartTs = System.currentTimeMillis();
consumerLock.lock();
@Nonnull
List<T> decodeRecords(@Nonnull List<R> records) {
List<T> result = new ArrayList<>(records.size());
records.forEach(record -> {
try {
if (!subscribed) {
List<String> topicNames = partitions.stream().map(TopicPartitionInfo::getFullTopicName).collect(Collectors.toList());
doSubscribe(topicNames);
subscribed = true;
if (record != null) {
result.add(decode(record));
}
} catch (IOException e) {
log.error("Failed decode record: [{}]", record);
throw new RuntimeException("Failed to decode record: ", e);
}
});
return result;
}
List<R> records;
if (partitions.isEmpty()) {
records = Collections.emptyList();
} else {
records = doPoll(durationInMillis);
}
if (!records.isEmpty()) {
List<T> result = new ArrayList<>(records.size());
records.forEach(record -> {
try {
if (record != null) {
result.add(decode(record));
}
} catch (IOException e) {
log.error("Failed decode record: [{}]", record);
throw new RuntimeException("Failed to decode record: ", e);
}
});
return result;
} else {
long pollDuration = System.currentTimeMillis() - pollStartTs;
if (pollDuration < durationInMillis) {
try {
Thread.sleep(durationInMillis - pollDuration);
} catch (InterruptedException e) {
if (!stopped) {
log.error("Failed to wait.", e);
}
}
}
List<T> errorAndReturnEmpty() {
log.error("poll invoked but consumer stopped for topic" + topic, new RuntimeException("stacktrace"));
return emptyList();
}
List<T> sleepAndReturnEmpty(final long startNanos, final long durationInMillis) {
long durationNanos = TimeUnit.MILLISECONDS.toNanos(durationInMillis);
long spentNanos = System.nanoTime() - startNanos;
if (spentNanos < durationNanos) {
try {
Thread.sleep(Math.max(TimeUnit.NANOSECONDS.toMillis(durationNanos - spentNanos), 1));
} catch (InterruptedException e) {
if (!stopped) {
log.error("Failed to wait", e);
}
} finally {
consumerLock.unlock();
}
}
return Collections.emptyList();
return emptyList();
}
@Override
public void commit() {
if (consumerLock.isLocked()) {
log.error("commit. consumerLock is locked. will wait with no timeout. it looks like a race conditions or deadlock", new RuntimeException("stacktrace"));
}
consumerLock.lock();
try {
doCommit();
@ -135,6 +158,7 @@ public abstract class AbstractTbQueueConsumerTemplate<R, T extends TbQueueMsg> i
@Override
public void unsubscribe() {
log.info("unsubscribe topic and stop consumer {}", getTopic());
stopped = true;
consumerLock.lock();
try {

17
common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java

@ -128,9 +128,10 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
log.debug("Ignoring application ready event, ZK client is not started, ZK client state [{}]", client.getState());
return;
}
log.info("Going to publish current server...");
publishCurrentServer();
TransportProtos.ServiceInfo currentService = serviceInfoProvider.getServiceInfo();
partitionService.recalculatePartitions(currentService, getOtherServers());
log.info("Going to recalculate partitions...");
recalculatePartitions();
}
public synchronized void publishCurrentServer() {
@ -285,11 +286,19 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
case CHILD_ADDED:
case CHILD_UPDATED:
case CHILD_REMOVED:
TransportProtos.ServiceInfo currentService = serviceInfoProvider.getServiceInfo();
partitionService.recalculatePartitions(currentService, getOtherServers());
recalculatePartitions();
break;
default:
break;
}
}
/**
* A single entry point to recalculate partitions
* Synchronized to ensure that other servers info is up to date
* */
synchronized void recalculatePartitions() {
partitionService.recalculatePartitions(serviceInfoProvider.getServiceInfo(), getOtherServers());
}
}

3
common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java

@ -72,8 +72,10 @@ public class TbKafkaConsumerTemplate<T extends TbQueueMsg> extends AbstractTbQue
protected void doSubscribe(List<String> topicNames) {
if (!topicNames.isEmpty()) {
topicNames.forEach(admin::createTopicIfNotExists);
log.info("subscribe topics {}", topicNames);
consumer.subscribe(topicNames);
} else {
log.info("unsubscribe due to empty topic list");
consumer.unsubscribe();
}
}
@ -102,6 +104,7 @@ public class TbKafkaConsumerTemplate<T extends TbQueueMsg> extends AbstractTbQue
@Override
protected void doUnsubscribe() {
log.info("unsubscribe topic and close consumer for topic {}", getTopic());
if (consumer != null) {
consumer.unsubscribe();
consumer.close();

42
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java

@ -17,6 +17,7 @@ package org.thingsboard.server.transport.coap;
import com.google.gson.JsonParseException;
import com.google.protobuf.Descriptors;
import com.google.protobuf.DynamicMessage;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.coap.CoAP;
@ -261,7 +262,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
TransportProtos.SessionInfoProto currentAttrSession = tokenToSessionIdMap.get(getTokenFromRequest(request));
if (currentAttrSession == null) {
attributeSubscriptions.add(sessionId);
registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, getTokenFromRequest(request));
registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor,
transportConfigurationContainer.getRpcRequestDynamicMessageBuilder(), getTokenFromRequest(request));
transportService.process(sessionInfo,
TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(), new CoapNoOpCallback(exchange));
}
@ -281,7 +283,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
TransportProtos.SessionInfoProto currentRpcSession = tokenToSessionIdMap.get(getTokenFromRequest(request));
if (currentRpcSession == null) {
rpcSubscriptions.add(sessionId);
registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, getTokenFromRequest(request));
registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor,
transportConfigurationContainer.getRpcRequestDynamicMessageBuilder(), getTokenFromRequest(request));
transportService.process(sessionInfo,
TransportProtos.SubscribeToRPCMsg.getDefaultInstance(),
new CoapNoOpCallback(exchange));
@ -303,17 +306,19 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
break;
case TO_DEVICE_RPC_RESPONSE:
transportService.process(sessionInfo,
coapTransportAdaptor.convertToDeviceRpcResponse(sessionId, request),
coapTransportAdaptor.convertToDeviceRpcResponse(sessionId, request, transportConfigurationContainer.getRpcResponseMsgDescriptor()),
new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR));
break;
case TO_SERVER_RPC_REQUEST:
transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor), timeout);
transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor,
transportConfigurationContainer.getRpcRequestDynamicMessageBuilder()), timeout);
transportService.process(sessionInfo,
coapTransportAdaptor.convertToServerRpcRequest(sessionId, request),
new CoapNoOpCallback(exchange));
break;
case GET_ATTRIBUTES_REQUEST:
transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor), timeout);
transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor,
transportConfigurationContainer.getRpcRequestDynamicMessageBuilder()), timeout);
transportService.process(sessionInfo,
coapTransportAdaptor.convertToGetAttributes(sessionId, request),
new CoapNoOpCallback(exchange));
@ -330,14 +335,14 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
return tokenToSessionIdMap.remove(token);
}
private void registerAsyncCoapSession(CoapExchange exchange, TransportProtos.SessionInfoProto sessionInfo, CoapTransportAdaptor coapTransportAdaptor, String token) {
private void registerAsyncCoapSession(CoapExchange exchange, TransportProtos.SessionInfoProto sessionInfo, CoapTransportAdaptor coapTransportAdaptor, DynamicMessage.Builder rpcRequestDynamicMessageBuilder, String token) {
tokenToSessionIdMap.putIfAbsent(token, sessionInfo);
transportService.registerAsyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor));
transportService.registerAsyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor, rpcRequestDynamicMessageBuilder));
transportService.process(sessionInfo, getSessionEventMsg(TransportProtos.SessionEvent.OPEN), null);
}
private CoapSessionListener getCoapSessionListener(CoapExchange exchange, CoapTransportAdaptor coapTransportAdaptor) {
return new CoapSessionListener(exchange, coapTransportAdaptor);
private CoapSessionListener getCoapSessionListener(CoapExchange exchange, CoapTransportAdaptor coapTransportAdaptor, DynamicMessage.Builder rpcRequestDynamicMessageBuilder) {
return new CoapSessionListener(exchange, coapTransportAdaptor, rpcRequestDynamicMessageBuilder);
}
private String getTokenFromRequest(Request request) {
@ -423,10 +428,12 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
private final CoapExchange exchange;
private final CoapTransportAdaptor coapTransportAdaptor;
private final DynamicMessage.Builder rpcRequestDynamicMessageBuilder;
CoapSessionListener(CoapExchange exchange, CoapTransportAdaptor coapTransportAdaptor) {
CoapSessionListener(CoapExchange exchange, CoapTransportAdaptor coapTransportAdaptor, DynamicMessage.Builder rpcRequestDynamicMessageBuilder) {
this.exchange = exchange;
this.coapTransportAdaptor = coapTransportAdaptor;
this.rpcRequestDynamicMessageBuilder = rpcRequestDynamicMessageBuilder;
}
@Override
@ -457,7 +464,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
@Override
public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg msg) {
try {
exchange.respond(coapTransportAdaptor.convertToPublish(isConRequest(), msg));
exchange.respond(coapTransportAdaptor.convertToPublish(isConRequest(), msg, rpcRequestDynamicMessageBuilder));
} catch (AdaptorException e) {
log.trace("Failed to reply due to error", e);
exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR);
@ -545,9 +552,14 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
(ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration;
String deviceTelemetryProtoSchema = protoTransportPayloadConfiguration.getDeviceTelemetryProtoSchema();
String deviceAttributesProtoSchema = protoTransportPayloadConfiguration.getDeviceAttributesProtoSchema();
String deviceRpcRequestProtoSchema = protoTransportPayloadConfiguration.getDeviceRpcRequestProtoSchema();
String deviceRpcResponseProtoSchema = protoTransportPayloadConfiguration.getDeviceRpcResponseProtoSchema();
return new TransportConfigurationContainer(false,
protoTransportPayloadConfiguration.getTelemetryDynamicMessageDescriptor(deviceTelemetryProtoSchema),
protoTransportPayloadConfiguration.getAttributesDynamicMessageDescriptor(deviceAttributesProtoSchema));
protoTransportPayloadConfiguration.getAttributesDynamicMessageDescriptor(deviceAttributesProtoSchema),
protoTransportPayloadConfiguration.getRpcResponseDynamicMessageDescriptor(deviceRpcResponseProtoSchema),
protoTransportPayloadConfiguration.getRpcRequestDynamicMessageBuilder(deviceRpcRequestProtoSchema)
);
}
} else {
throw new AdaptorException("Invalid CoapDeviceTypeConfiguration type: " + coapDeviceTypeConfiguration.getClass().getSimpleName() + "!");
@ -567,11 +579,15 @@ public class CoapTransportResource extends AbstractCoapTransportResource {
private boolean jsonPayload;
private Descriptors.Descriptor telemetryMsgDescriptor;
private Descriptors.Descriptor attributesMsgDescriptor;
private Descriptors.Descriptor rpcResponseMsgDescriptor;
private DynamicMessage.Builder rpcRequestDynamicMessageBuilder;
public TransportConfigurationContainer(boolean jsonPayload, Descriptors.Descriptor telemetryMsgDescriptor, Descriptors.Descriptor attributesMsgDescriptor) {
public TransportConfigurationContainer(boolean jsonPayload, Descriptors.Descriptor telemetryMsgDescriptor, Descriptors.Descriptor attributesMsgDescriptor, Descriptors.Descriptor rpcResponseMsgDescriptor, DynamicMessage.Builder rpcRequestDynamicMessageBuilder) {
this.jsonPayload = jsonPayload;
this.telemetryMsgDescriptor = telemetryMsgDescriptor;
this.attributesMsgDescriptor = attributesMsgDescriptor;
this.rpcResponseMsgDescriptor = rpcResponseMsgDescriptor;
this.rpcRequestDynamicMessageBuilder = rpcRequestDynamicMessageBuilder;
}
public TransportConfigurationContainer(boolean jsonPayload) {

5
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java

@ -16,6 +16,7 @@
package org.thingsboard.server.transport.coap.adaptors;
import com.google.protobuf.Descriptors;
import com.google.protobuf.DynamicMessage;
import org.eclipse.californium.core.coap.Request;
import org.eclipse.californium.core.coap.Response;
import org.thingsboard.server.common.transport.adaptor.AdaptorException;
@ -32,7 +33,7 @@ public interface CoapTransportAdaptor {
TransportProtos.GetAttributeRequestMsg convertToGetAttributes(UUID sessionId, Request inbound) throws AdaptorException;
TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(UUID sessionId, Request inbound) throws AdaptorException;
TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(UUID sessionId, Request inbound, Descriptors.Descriptor rpcResponseMsgDescriptor) throws AdaptorException;
TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(UUID sessionId, Request inbound) throws AdaptorException;
@ -42,7 +43,7 @@ public interface CoapTransportAdaptor {
Response convertToPublish(boolean isConfirmable, TransportProtos.AttributeUpdateNotificationMsg notificationMsg) throws AdaptorException;
Response convertToPublish(boolean isConfirmable, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) throws AdaptorException;
Response convertToPublish(boolean isConfirmable, TransportProtos.ToDeviceRpcRequestMsg rpcRequest, DynamicMessage.Builder rpcRequestDynamicMessageBuilder) throws AdaptorException;
Response convertToPublish(TransportProtos.ToServerRpcResponseMsg msg) throws AdaptorException;

5
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java

@ -20,6 +20,7 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import com.google.protobuf.Descriptors;
import com.google.protobuf.DynamicMessage;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.Request;
@ -64,7 +65,7 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor {
}
@Override
public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(UUID sessionId, Request inbound) throws AdaptorException {
public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(UUID sessionId, Request inbound, Descriptors.Descriptor rpcResponseMsgDescriptor) throws AdaptorException {
Optional<Integer> requestId = CoapTransportResource.getRequestId(inbound);
String payload = validatePayload(sessionId, inbound, false);
JsonObject response = new JsonParser().parse(payload).getAsJsonObject();
@ -95,7 +96,7 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor {
}
@Override
public Response convertToPublish(boolean isConfirmable, TransportProtos.ToDeviceRpcRequestMsg msg) throws AdaptorException {
public Response convertToPublish(boolean isConfirmable, TransportProtos.ToDeviceRpcRequestMsg msg, DynamicMessage.Builder rpcRequestDynamicMessageBuilder) throws AdaptorException {
return getObserveNotification(isConfirmable, JsonConverter.toJson(msg, true));
}

15
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/ProtoCoapAdaptor.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.transport.coap.adaptors;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.protobuf.Descriptors;
import com.google.protobuf.DynamicMessage;
@ -63,16 +64,16 @@ public class ProtoCoapAdaptor implements CoapTransportAdaptor {
}
@Override
public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(UUID sessionId, Request inbound) throws AdaptorException {
public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(UUID sessionId, Request inbound, Descriptors.Descriptor rpcResponseMsgDescriptor) throws AdaptorException {
Optional<Integer> requestId = CoapTransportResource.getRequestId(inbound);
if (requestId.isEmpty()) {
throw new AdaptorException("Request id is missing!");
} else {
try {
String payload = TransportProtos.ToDeviceRpcResponseMsg.parseFrom(inbound.getPayload()).getPayload();
return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId.get())
.setPayload(payload).build();
} catch (InvalidProtocolBufferException e) {
JsonElement response = new JsonParser().parse(dynamicMsgToJson(inbound.getPayload(), rpcResponseMsgDescriptor));
return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId.orElseThrow(() -> new AdaptorException("Request id is missing!")))
.setPayload(response.toString()).build();
} catch (Exception e) {
throw new AdaptorException(e);
}
}
@ -112,8 +113,8 @@ public class ProtoCoapAdaptor implements CoapTransportAdaptor {
}
@Override
public Response convertToPublish(boolean isConfirmable, TransportProtos.ToDeviceRpcRequestMsg msg) throws AdaptorException {
return getObserveNotification(isConfirmable, msg.toByteArray());
public Response convertToPublish(boolean isConfirmable, TransportProtos.ToDeviceRpcRequestMsg rpcRequest, DynamicMessage.Builder rpcRequestDynamicMessageBuilder) throws AdaptorException {
return getObserveNotification(isConfirmable, ProtoConverter.convertToRpcRequest(rpcRequest, rpcRequestDynamicMessageBuilder));
}
@Override

12
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java

@ -35,12 +35,11 @@ import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInf
import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore;
import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContextServer;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler;
import org.thingsboard.server.transport.lwm2m.utils.TypeServer;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
@ -70,8 +69,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
@Override
public List<SecurityInfo> getAllByEndpoint(String endPoint) {
String endPointKey = endPoint;
ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(endPointKey, TypeServer.BOOTSTRAP);
ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(endPoint, TypeServer.BOOTSTRAP);
if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) {
/** add value to store from BootstrapJson */
this.setBootstrapConfigScurityInfo(store);
@ -87,7 +85,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
} catch (InvalidConfigurationException e) {
log.error("", e);
}
return store.getSecurityInfo() == null ? null : Arrays.asList(store.getSecurityInfo());
return store.getSecurityInfo() == null ? null : Collections.singletonList(store.getSecurityInfo());
}
}
return null;
@ -166,13 +164,13 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap);
lwM2MBootstrapConfig.lwm2mServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer);
String logMsg = String.format("%s: getParametersBootstrap: %s Access connect client with bootstrap server.", LOG_LW2M_INFO, store.getEndPoint());
context.sendParametersOnThingsboard(context.getTelemetryMsgObject(logMsg), LwM2mTransportHandler.DEVICE_TELEMETRY_TOPIC, sessionInfo);
context.sendParametersOnThingsboardTelemetry(context.getKvLogyToThingsboard(logMsg), sessionInfo);
return lwM2MBootstrapConfig;
} else {
log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndPoint());
log.error("{} getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndPoint());
String logMsg = String.format("%s: getParametersBootstrap: %s Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndPoint());
context.sendParametersOnThingsboard(context.getTelemetryMsgObject(logMsg), LwM2mTransportHandler.DEVICE_TELEMETRY_TOPIC, sessionInfo);
context.sendParametersOnThingsboardTelemetry(context.getKvLogyToThingsboard(logMsg), sessionInfo);
return null;
}
}

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapServers.java

@ -23,5 +23,5 @@ public class LwM2MBootstrapServers {
private Integer lifetime = 300;
private Integer defaultMinPeriod = 1;
private boolean notifIfDisabled = true;
private String binding = "U";
private String binding = "UQ";
}

5
ui-ngx/src/app/shared/components/time/quick-time-interval.component.scss → common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mQueuedRequest.java

@ -13,7 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.server;
:host {
min-width: 364px;
public interface LwM2mQueuedRequest {
void send();
}

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

@ -26,7 +26,7 @@ import org.eclipse.leshan.server.registration.RegistrationUpdate;
import java.util.Collection;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToIdVerFromObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromObjectIdToIdVer;
@Slf4j
public class LwM2mServerListener {
@ -92,7 +92,7 @@ public class LwM2mServerListener {
public void onResponse(Observation observation, Registration registration, ObserveResponse response) {
if (registration != null) {
try {
service.onObservationResponse(registration, convertToIdVerFromObjectId(observation.getPath().toString(), registration), response);
service.onObservationResponse(registration, convertPathFromObjectIdToIdVer(observation.getPath().toString(), registration), response);
} catch (Exception e) {
log.error("[{}] onResponse", e.toString());
@ -107,7 +107,7 @@ public class LwM2mServerListener {
@Override
public void newObservation(Observation observation, Registration registration) {
// log.info("Received newObservation from [{}] endpoint [{}] ", observation.getPath(), registration.getEndpoint());
log.info("Received newObservation from [{}] endpoint [{}] ", observation.getPath(), registration.getEndpoint());
}
};

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

@ -18,6 +18,7 @@ package org.thingsboard.server.transport.lwm2m.server;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.ResourceType;
@ -74,12 +75,12 @@ public class LwM2mSessionMsgListener implements GenericFutureListener<Future<? s
@Override
public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg toDeviceRequest) {
log.info("[{}] toDeviceRpcRequest", toDeviceRequest);
this.service.onToDeviceRpcRequest(toDeviceRequest);
}
@Override
public void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse) {
log.info("[{}] toServerRpcResponse", toServerResponse);
this.service.onToServerRpcResponse(toServerResponse);
}
@Override
@ -87,13 +88,15 @@ public class LwM2mSessionMsgListener implements GenericFutureListener<Future<? s
log.info("[{}] operationComplete", future);
}
public void onResourceUpdate(Optional<TransportProtos.ResourceUpdateMsg> resourceUpdateMsgOpt) {
@Override
public void onResourceUpdate(@NotNull Optional<TransportProtos.ResourceUpdateMsg> resourceUpdateMsgOpt) {
if (ResourceType.LWM2M_MODEL.name().equals(resourceUpdateMsgOpt.get().getResourceType())) {
this.service.onResourceUpdate(resourceUpdateMsgOpt);
}
}
public void onResourceDelete(Optional<TransportProtos.ResourceDeleteMsg> resourceDeleteMsgOpt) {
@Override
public void onResourceDelete(@NotNull Optional<TransportProtos.ResourceDeleteMsg> resourceDeleteMsgOpt) {
if (ResourceType.LWM2M_MODEL.name().equals(resourceDeleteMsgOpt.get().getResourceType())) {
this.service.onResourceDelete(resourceDeleteMsgOpt);
}

134
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContextServer.java

@ -30,31 +30,33 @@ package org.thingsboard.server.transport.lwm2m.server;
* limitations under the License.
*/
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.DDFFileParser;
import org.eclipse.leshan.core.model.DefaultDDFFileValidator;
import org.eclipse.leshan.core.model.InvalidDDFFileException;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.codec.CodecException;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.TransportContext;
import org.thingsboard.server.common.transport.TransportResourceCache;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigServer;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.server.adaptors.LwM2MJsonAdaptor;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static org.thingsboard.server.gen.transport.TransportProtos.KeyValueType.BOOLEAN_V;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_TELEMETRY;
@Slf4j
@ -91,7 +93,7 @@ public class LwM2mTransportContextServer extends TransportContext {
/**
* send to Thingsboard Attribute || Telemetry
*
* @param msg - JsonObject: [{name: value}]
* @param msg - JsonObject: [{name: value}]
* @return - dummy
*/
private <T> TransportServiceCallback<Void> getPubAckCallbackSendAttrTelemetry(final T msg) {
@ -108,33 +110,29 @@ public class LwM2mTransportContextServer extends TransportContext {
};
}
public void sendParametersOnThingsboard(JsonElement msg, String topicName, SessionInfoProto sessionInfo) {
try {
if (topicName.equals(LwM2mTransportHandler.DEVICE_ATTRIBUTES_TOPIC)) {
PostAttributeMsg postAttributeMsg = adaptor.convertToPostAttributes(msg);
TransportServiceCallback call = this.getPubAckCallbackSendAttrTelemetry(postAttributeMsg);
transportService.process(sessionInfo, postAttributeMsg, this.getPubAckCallbackSendAttrTelemetry(call));
} else if (topicName.equals(LwM2mTransportHandler.DEVICE_TELEMETRY_TOPIC)) {
PostTelemetryMsg postTelemetryMsg = adaptor.convertToPostTelemetry(msg);
TransportServiceCallback call = this.getPubAckCallbackSendAttrTelemetry(postTelemetryMsg);
transportService.process(sessionInfo, postTelemetryMsg, this.getPubAckCallbackSendAttrTelemetry(call));
}
} catch (AdaptorException e) {
log.error("[{}] Failed to process publish msg [{}]", topicName, e);
log.info("[{}] Closing current session due to invalid publish", topicName);
}
public void sendParametersOnThingsboardAttribute(List<TransportProtos.KeyValueProto> result, SessionInfoProto sessionInfo) {
PostAttributeMsg.Builder request = PostAttributeMsg.newBuilder();
request.addAllKv(result);
PostAttributeMsg postAttributeMsg = request.build();
TransportServiceCallback call = this.getPubAckCallbackSendAttrTelemetry(postAttributeMsg);
transportService.process(sessionInfo, postAttributeMsg, this.getPubAckCallbackSendAttrTelemetry(call));
}
public JsonObject getTelemetryMsgObject(String logMsg) {
JsonObject telemetries = new JsonObject();
telemetries.addProperty(LOG_LW2M_TELEMETRY, logMsg);
return telemetries;
public void sendParametersOnThingsboardTelemetry(List<TransportProtos.KeyValueProto> result, SessionInfoProto sessionInfo) {
PostTelemetryMsg.Builder request = PostTelemetryMsg.newBuilder();
TransportProtos.TsKvListProto.Builder builder = TransportProtos.TsKvListProto.newBuilder();
builder.setTs(System.currentTimeMillis());
builder.addAllKv(result);
request.addTsKvList(builder.build());
PostTelemetryMsg postTelemetryMsg = request.build();
TransportServiceCallback call = this.getPubAckCallbackSendAttrTelemetry(postTelemetryMsg);
transportService.process(sessionInfo, postTelemetryMsg, this.getPubAckCallbackSendAttrTelemetry(call));
}
/**
* @return - sessionInfo after access connect client
*/
public SessionInfoProto getValidateSessionInfo(ValidateDeviceCredentialsResponseMsg msg, long mostSignificantBits, long leastSignificantBits) {
public SessionInfoProto getValidateSessionInfo(TransportProtos.ValidateDeviceCredentialsResponseMsg msg, long mostSignificantBits, long leastSignificantBits) {
return SessionInfoProto.newBuilder()
.setNodeId(this.getNodeId())
.setSessionIdMSB(mostSignificantBits)
@ -159,4 +157,90 @@ public class LwM2mTransportContextServer extends TransportContext {
return null;
}
}
/**
*
* @param logMsg - info about Logs
* @return- KeyValueProto for telemetry (Logs)
*/
public List <TransportProtos.KeyValueProto> getKvLogyToThingsboard(String logMsg) {
List <TransportProtos.KeyValueProto> result = new ArrayList<>();
result.add(TransportProtos.KeyValueProto.newBuilder()
.setKey(LOG_LW2M_TELEMETRY)
.setType(TransportProtos.KeyValueType.STRING_V)
.setStringV(logMsg).build());
return result;
}
/**
* @return - KeyValueProto for attribute/telemetry (change value)
* @throws CodecException -
*/
public TransportProtos.KeyValueProto getKvAttrTelemetryToThingsboard(ResourceModel.Type resourceType, String resourceName, Object value, boolean isMultiInstances) {
TransportProtos.KeyValueProto.Builder kvProto = TransportProtos.KeyValueProto.newBuilder().setKey(resourceName);
if (isMultiInstances) {
kvProto.setType(TransportProtos.KeyValueType.JSON_V)
.setJsonV((String) value);
}
else {
switch (resourceType) {
case BOOLEAN:
kvProto.setType(BOOLEAN_V).setBoolV((Boolean) value).build();
break;
case STRING:
case TIME:
case OPAQUE:
case OBJLNK:
kvProto.setType(TransportProtos.KeyValueType.STRING_V).setStringV((String) value);
break;
case INTEGER:
kvProto.setType(TransportProtos.KeyValueType.LONG_V).setLongV((Long) value);
break;
case FLOAT:
kvProto.setType(TransportProtos.KeyValueType.DOUBLE_V).setDoubleV((Double) value);
}
}
return kvProto.build();
}
/**
*
* @param currentType -
* @param resourcePath -
* @return
*/
public ResourceModel.Type getResourceModelTypeEqualsKvProtoValueType(ResourceModel.Type currentType, String resourcePath) {
switch (currentType) {
case BOOLEAN:
return ResourceModel.Type.BOOLEAN;
case STRING:
case TIME:
case OPAQUE:
case OBJLNK:
return ResourceModel.Type.STRING;
case INTEGER:
return ResourceModel.Type.INTEGER;
case FLOAT:
return ResourceModel.Type.FLOAT;
default:
}
throw new CodecException("Invalid ResourceModel_Type for resource %s, got %s", resourcePath, currentType);
}
public Object getValueFromKvProto (TransportProtos.KeyValueProto kv) {
switch (kv.getType()) {
case BOOLEAN_V:
return kv.getBoolV();
case LONG_V:
return kv.getLongV();
case DOUBLE_V:
return kv.getDoubleV();
case STRING_V:
return kv.getStringV();
case JSON_V:
return kv.getJsonV();
}
return null;
}
}

147
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandler.java

@ -22,15 +22,14 @@ import com.google.gson.JsonSyntaxException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.leshan.core.attributes.Attribute;
import org.eclipse.leshan.core.attributes.AttributeSet;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mMultipleResource;
import org.eclipse.leshan.core.node.LwM2mNode;
import org.eclipse.leshan.core.node.LwM2mObject;
import org.eclipse.leshan.core.node.LwM2mObjectInstance;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mSingleResource;
import org.eclipse.leshan.core.node.codec.CodecException;
import org.eclipse.leshan.core.request.DownlinkRequest;
import org.eclipse.leshan.core.request.WriteAttributesRequest;
import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.server.californium.LeshanServerBuilder;
import org.eclipse.leshan.server.registration.Registration;
@ -44,11 +43,19 @@ import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import static org.eclipse.leshan.core.attributes.Attribute.DIMENSION;
import static org.eclipse.leshan.core.attributes.Attribute.MAXIMUM_PERIOD;
import static org.eclipse.leshan.core.attributes.Attribute.MINIMUM_PERIOD;
import static org.eclipse.leshan.core.attributes.Attribute.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;
@ -56,8 +63,21 @@ import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPA
public class LwM2mTransportHandler {
public static final String BASE_DEVICE_API_TOPIC = "v1/devices/me";
public static final String CLIENT_LWM2M_SETTINGS = "clientLwM2mSettings";
public static final String BOOTSTRAP = "bootstrap";
public static final String SERVERS = "servers";
public static final String LWM2M_SERVER = "lwm2mServer";
public static final String BOOTSTRAP_SERVER = "bootstrapServer";
public static final String OBSERVE_ATTRIBUTE_TELEMETRY = "observeAttr";
public static final String ATTRIBUTE = "attribute";
public static final String TELEMETRY = "telemetry";
public static final String KEY_NAME = "keyName";
public static final String OBSERVE = "observe";
public static final String ATTRIBUTE_LWM2M = "attributeLwm2m";
public static final String RESOURCE_VALUE = "resValue";
public static final String RESOURCE_TYPE = "resType";
private static final String REQUEST = "/request";
private static final String RESPONSE = "/response";
private static final String ATTRIBUTES = "/" + ATTRIBUTE;
@ -70,14 +90,6 @@ public class LwM2mTransportHandler {
public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + TELEMETRIES;
public static final long DEFAULT_TIMEOUT = 2 * 60 * 1000L; // 2min in ms
public static final String OBSERVE_ATTRIBUTE_TELEMETRY = "observeAttr";
public static final String CLIENT_LWM2M_SETTINGS = "clientLwM2mSettings";
public static final String KEY_NAME = "keyName";
public static final String OBSERVE = "observe";
public static final String BOOTSTRAP = "bootstrap";
public static final String SERVERS = "servers";
public static final String LWM2M_SERVER = "lwm2mServer";
public static final String BOOTSTRAP_SERVER = "bootstrapServer";
public static final String LOG_LW2M_TELEMETRY = "logLwm2m";
public static final String LOG_LW2M_INFO = "info";
@ -144,19 +156,19 @@ public class LwM2mTransportHandler {
throw new CodecException("Invalid value type for resource %s, type %s", resourcePath, type);
}
}
public static LwM2mNode getLvM2mNodeToObject(LwM2mNode content) {
if (content instanceof LwM2mObject) {
return (LwM2mObject) content;
} else if (content instanceof LwM2mObjectInstance) {
return (LwM2mObjectInstance) content;
} else if (content instanceof LwM2mSingleResource) {
return (LwM2mSingleResource) content;
} else if (content instanceof LwM2mMultipleResource) {
return (LwM2mMultipleResource) content;
}
return null;
}
//
// public static LwM2mNode getLvM2mNodeToObject(LwM2mNode content) {
// if (content instanceof LwM2mObject) {
// return (LwM2mObject) content;
// } else if (content instanceof LwM2mObjectInstance) {
// return (LwM2mObjectInstance) content;
// } else if (content instanceof LwM2mSingleResource) {
// return (LwM2mSingleResource) content;
// } else if (content instanceof LwM2mMultipleResource) {
// return (LwM2mMultipleResource) content;
// }
// return null;
// }
public static LwM2mClientProfile getNewProfileParameters(JsonObject profilesConfigData, TenantId tenantId) {
LwM2mClientProfile lwM2MClientProfile = new LwM2mClientProfile();
@ -166,6 +178,7 @@ public class LwM2mTransportHandler {
lwM2MClientProfile.setPostAttributeProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE).getAsJsonArray());
lwM2MClientProfile.setPostTelemetryProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(TELEMETRY).getAsJsonArray());
lwM2MClientProfile.setPostObserveProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE).getAsJsonArray());
lwM2MClientProfile.setPostAttributeLwm2mProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE_LWM2M).getAsJsonObject());
return lwM2MClientProfile;
}
@ -184,6 +197,9 @@ public class LwM2mTransportHandler {
* "attribute":["/2/0/1","/3/0/9"],
* "telemetry":["/1/0/1","/2/0/1","/6/0/1"],
* "observe":["/2/0","/2/0/0","/4/0/2"]}
* "attributeLwm2m": {"/3_1.0": {"ver": "currentTimeTest11"},
* "/3_1.0/0": {"gt": 17},
* "/3_1.0/0/9": {"pmax": 45}, "/3_1.2": {ver": "3_1.2"}}
*/
public static LwM2mClientProfile getLwM2MClientProfileFromThingsboard(DeviceProfile deviceProfile) {
if (deviceProfile != null && ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) {
@ -192,7 +208,7 @@ public class LwM2mTransportHandler {
ObjectMapper mapper = new ObjectMapper();
String profileStr = mapper.writeValueAsString(profile);
JsonObject profileJson = (profileStr != null) ? validateJson(profileStr) : null;
return (getValidateCredentialsBodyFromThingsboard(profileJson)) ? LwM2mTransportHandler.getNewProfileParameters(profileJson, deviceProfile.getTenantId()) : null;
return getValidateCredentialsBodyFromThingsboard(profileJson) ? LwM2mTransportHandler.getNewProfileParameters(profileJson, deviceProfile.getTenantId()) : null;
} catch (IOException e) {
log.error("", e);
}
@ -240,7 +256,10 @@ public class LwM2mTransportHandler {
objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(TELEMETRY).isJsonArray() &&
objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().has(OBSERVE) &&
!objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE).isJsonNull() &&
objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE).isJsonArray());
objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE).isJsonArray() &&
objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().has(ATTRIBUTE_LWM2M) &&
!objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE_LWM2M).isJsonNull() &&
objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE_LWM2M).isJsonObject());
}
private static boolean getValidateBootstrapProfileFromThingsboard(JsonObject objectMsg) {
@ -316,28 +335,30 @@ public class LwM2mTransportHandler {
};
}
public static String convertToObjectIdFromIdVer(String key) {
public static String convertPathFromIdVerToObjectId(String pathIdVer) {
try {
String[] keyArray = key.split(LWM2M_SEPARATOR_PATH);
String[] keyArray = pathIdVer.split(LWM2M_SEPARATOR_PATH);
if (keyArray.length > 1 && keyArray[1].split(LWM2M_SEPARATOR_KEY).length == 2) {
keyArray[1] = keyArray[1].split(LWM2M_SEPARATOR_KEY)[0];
return StringUtils.join(keyArray, LWM2M_SEPARATOR_PATH);
} else {
return key;
}
else {
return pathIdVer;
}
} catch (Exception e) {
return null;
}
}
public static String convertToIdVerFromObjectId(String path, Registration registration) {
public static String convertPathFromObjectIdToIdVer(String path, Registration registration) {
String ver = registration.getSupportedObject().get(new LwM2mPath(path).getObjectId());
try {
String[] keyArray = path.split(LWM2M_SEPARATOR_PATH);
if (keyArray.length > 1) {
keyArray[1] = keyArray[1] + LWM2M_SEPARATOR_KEY + ver;
return StringUtils.join(keyArray, LWM2M_SEPARATOR_PATH);
} else {
}
else {
return path;
}
} catch (Exception e) {
@ -345,14 +366,6 @@ public class LwM2mTransportHandler {
}
}
public static Integer validateObjectIdFromKey(String key) {
try {
return Integer.parseInt(key.split(LWM2M_SEPARATOR_PATH)[1].split(LWM2M_SEPARATOR_KEY)[0]);
} catch (Exception e) {
return null;
}
}
public static String validateObjectVerFromKey(String key) {
try {
return (key.split(LWM2M_SEPARATOR_PATH)[1].split(LWM2M_SEPARATOR_KEY)[1]);
@ -360,4 +373,54 @@ public class LwM2mTransportHandler {
return ObjectModel.DEFAULT_VERSION;
}
}
/**
* As example:
* a)Write-Attributes/3/0/9?pmin=1 means the Battery Level value will be notified
* to the Server with a minimum interval of 1sec;
* this value is set at theResource level.
* b)Write-Attributes/3/0/9?pmin means the Battery Level will be notified
* to the Server with a minimum value (pmin) given by the default one
* (resource 2 of Object Server ID=1),
* or with another value if this Attribute has been set at another level
* (Object or Object Instance: see section5.1.1).
* c)Write-Attributes/3/0?pmin=10 means that all Resources of Instance 0 of the Object ‘Device (ID:3)’
* will be notified to the Server with a minimum interval of 10 sec;
* this value is set at the Object Instance level.
* d)Write-Attributes /3/0/9?gt=45&st=10 means the Battery Level will be notified to the Server
* when:
* a.old value is 20 and new value is 35 due to step condition
* b.old value is 45 and new value is 50 due to gt condition
* c.old value is 50 and new value is 40 due to both gt and step conditions
* d.old value is 35 and new value is 20 due to step conditione)
* Write-Attributes /3/0/9?lt=20&gt=85&st=10 means the Battery Level will be notified to the Server
* when:
* a.old value is 17 and new value is 24 due to lt condition
* b.old value is 75 and new value is 90 due to both gt and step conditions
* String uriQueries = "pmin=10&pmax=60";
* AttributeSet attributes = AttributeSet.parse(uriQueries);
* WriteAttributesRequest request = new WriteAttributesRequest(target, attributes);
* Attribute gt = new Attribute(GREATER_THAN, Double.valueOf("45"));
* Attribute st = new Attribute(LESSER_THAN, Double.valueOf("10"));
* Attribute pmax = new Attribute(MAXIMUM_PERIOD, "60");
* Attribute [] attrs = {gt, st};
*/
public static DownlinkRequest createWriteAttributeRequest(String target, Object params) {
AttributeSet attrSet = new AttributeSet(createWriteAttributes(params));
return attrSet.getAttributes().size() > 0 ? new WriteAttributesRequest(target, attrSet) : null;
}
private static Attribute[] createWriteAttributes(Object params) {
List attributeLists = new ArrayList<Attribute>();
ObjectMapper oMapper = new ObjectMapper();
Map<String, Object> map = oMapper.convertValue(params, ConcurrentHashMap.class);
map.forEach((k, v) -> {
if (!v.toString().isEmpty() || (v.toString().isEmpty() && OBJECT_VERSION.equals(k))) {
attributeLists.add(new Attribute(k,
(DIMENSION.equals(k) || MINIMUM_PERIOD.equals(k) || MAXIMUM_PERIOD.equals(k)) ?
((Double) v).longValue() : v));
}
});
return (Attribute[]) attributeLists.toArray(Attribute[]::new);
}
}

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

@ -16,9 +16,8 @@
package org.thingsboard.server.transport.lwm2m.server;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.Response;
import org.eclipse.leshan.core.attributes.Attribute;
import org.eclipse.leshan.core.attributes.AttributeSet;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mNode;
import org.eclipse.leshan.core.node.LwM2mPath;
@ -32,8 +31,8 @@ import org.eclipse.leshan.core.request.DownlinkRequest;
import org.eclipse.leshan.core.request.ExecuteRequest;
import org.eclipse.leshan.core.request.ObserveRequest;
import org.eclipse.leshan.core.request.ReadRequest;
import org.eclipse.leshan.core.request.WriteAttributesRequest;
import org.eclipse.leshan.core.request.WriteRequest;
import org.eclipse.leshan.core.request.exception.ClientSleepingException;
import org.eclipse.leshan.core.response.CancelObservationResponse;
import org.eclipse.leshan.core.response.DeleteResponse;
import org.eclipse.leshan.core.response.DiscoverResponse;
@ -58,8 +57,6 @@ import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static org.eclipse.californium.core.coap.CoAP.ResponseCode.isSuccess;
import static org.eclipse.leshan.core.attributes.Attribute.MINIMUM_PERIOD;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEFAULT_TIMEOUT;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_DISCOVER;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_OBSERVE;
@ -72,8 +69,9 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandle
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.PUT_TYPE_OPER_WRITE_ATTRIBUTES;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.PUT_TYPE_OPER_WRITE_UPDATE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.RESPONSE_CHANNEL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToIdVerFromObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToObjectIdFromIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromIdVerToObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromObjectIdToIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.createWriteAttributeRequest;
@Slf4j
@Service
@ -116,13 +114,13 @@ public class LwM2mTransportRequest {
*/
public void sendAllRequest(Registration registration, String targetIdVer, String typeOper,
String contentFormatParam, Observation observation, Object params, long timeoutInMs) {
String target = convertToObjectIdFromIdVer(targetIdVer);
String target = convertPathFromIdVerToObjectId(targetIdVer);
LwM2mPath resultIds = new LwM2mPath(target);
if (registration != null && resultIds.getObjectId() >= 0) {
DownlinkRequest request = null;
ContentFormat contentFormat = contentFormatParam != null ? ContentFormat.fromName(contentFormatParam.toUpperCase()) : null;
LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null);
ResourceModel resource = lwM2MClient.getResourceModel(targetIdVer);
ResourceModel resource = null;
timeoutInMs = timeoutInMs > 0 ? timeoutInMs : DEFAULT_TIMEOUT;
switch (typeOper) {
case GET_TYPE_OPER_READ:
@ -144,6 +142,7 @@ public class LwM2mTransportRequest {
request = new CancelObservationRequest(observation);
break;
case POST_TYPE_OPER_EXECUTE:
resource = lwM2MClient.getResourceModel(targetIdVer);
if (params != null && resource != null && !resource.multiple) {
request = new ExecuteRequest(target, (String) this.converter.convertValue(params, resource.type, ResourceModel.Type.STRING, resultIds));
} else {
@ -152,6 +151,7 @@ public class LwM2mTransportRequest {
break;
case POST_TYPE_OPER_WRITE_REPLACE:
// Request to write a <b>String Single-Instance Resource</b> using the TLV content format.
resource = lwM2MClient.getResourceModel(targetIdVer);
if (resource != null && contentFormat != null) {
// if (contentFormat.equals(ContentFormat.TLV) && !resource.multiple) {
if (contentFormat.equals(ContentFormat.TLV)) {
@ -173,55 +173,21 @@ public class LwM2mTransportRequest {
}
break;
case PUT_TYPE_OPER_WRITE_ATTRIBUTES:
/**
* As example:
* a)Write-Attributes/3/0/9?pmin=1 means the Battery Level value will be notified
* to the Server with a minimum interval of 1sec;
* this value is set at theResource level.
* b)Write-Attributes/3/0/9?pmin means the Battery Level will be notified
* to the Server with a minimum value (pmin) given by the default one
* (resource 2 of Object Server ID=1),
* or with another value if this Attribute has been set at another level
* (Object or Object Instance: see section5.1.1).
* c)Write-Attributes/3/0?pmin=10 means that all Resources of Instance 0 of the Object ‘Device (ID:3)’
* will be notified to the Server with a minimum interval of 10 sec;
* this value is set at the Object Instance level.
* d)Write-Attributes /3/0/9?gt=45&st=10 means the Battery Level will be notified to the Server
* when:
* a.old value is 20 and new value is 35 due to step condition
* b.old value is 45 and new value is 50 due to gt condition
* c.old value is 50 and new value is 40 due to both gt and step conditions
* d.old value is 35 and new value is 20 due to step conditione)
* Write-Attributes /3/0/9?lt=20&gt=85&st=10 means the Battery Level will be notified to the Server
* when:
* a.old value is 17 and new value is 24 due to lt condition
* b.old value is 75 and new value is 90 due to both gt and step conditions
* String uriQueries = "pmin=10&pmax=60";
* AttributeSet attributes = AttributeSet.parse(uriQueries);
* WriteAttributesRequest request = new WriteAttributesRequest(target, attributes);
* Attribute gt = new Attribute(GREATER_THAN, Double.valueOf("45"));
* Attribute st = new Attribute(LESSER_THAN, Double.valueOf("10"));
* Attribute pmax = new Attribute(MAXIMUM_PERIOD, "60");
* Attribute [] attrs = {gt, st};
*/
Attribute pmin = new Attribute(MINIMUM_PERIOD, Integer.toUnsignedLong(Integer.parseInt("1")));
Attribute[] attrs = {pmin};
AttributeSet attrSet = new AttributeSet(attrs);
if (resultIds.isResource()) {
request = new WriteAttributesRequest(resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), attrSet);
} else if (resultIds.isObjectInstance()) {
request = new WriteAttributesRequest(resultIds.getObjectId(), resultIds.getObjectInstanceId(), attrSet);
} else if (resultIds.getObjectId() >= 0) {
request = new WriteAttributesRequest(resultIds.getObjectId(), attrSet);
}
request = createWriteAttributeRequest(target, params);
break;
default:
}
if (request != null) {
this.sendRequest(registration, lwM2MClient, request, timeoutInMs);
}
else {
try {
this.sendRequest(registration, lwM2MClient, request, timeoutInMs);
} catch (ClientSleepingException e) {
DownlinkRequest finalRequest = request;
long finalTimeoutInMs = timeoutInMs;
lwM2MClient.getQueuedRequests().add(() -> sendRequest(registration, lwM2MClient, finalRequest, finalTimeoutInMs));
} catch (Exception e) {
log.error("[{}] [{}] [{}] Failed to send downlink.", registration.getEndpoint(), targetIdVer, typeOper, e);
}
} else {
log.error("[{}], [{}] - [{}] error SendRequest", registration.getEndpoint(), typeOper, targetIdVer);
}
}
@ -238,17 +204,21 @@ public class LwM2mTransportRequest {
private void sendRequest(Registration registration, LwM2mClient lwM2MClient, DownlinkRequest request, long timeoutInMs) {
leshanServer.send(registration, request, timeoutInMs, (ResponseCallback<?>) response -> {
if (!lwM2MClient.isInit()) {
lwM2MClient.initValue(this.serviceImpl, convertToIdVerFromObjectId(request.getPath().toString(), registration));
lwM2MClient.initValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration));
}
if (isSuccess(((Response) response.getCoapResponse()).getCode())) {
if (CoAP.ResponseCode.isSuccess(((Response) response.getCoapResponse()).getCode())) {
this.handleResponse(registration, request.getPath().toString(), response, request);
if (request instanceof WriteRequest && ((WriteRequest) request).isReplaceRequest()) {
LwM2mNode node = ((WriteRequest) request).getNode();
Object value = this.converter.convertValue(((LwM2mSingleResource) node).getValue(),
((LwM2mSingleResource) node).getType(), ResourceModel.Type.STRING, request.getPath());
String msg = String.format("%s: sendRequest Replace: CoapCde - %s Lwm2m code - %d name - %s Resource path - %s value - %s SendRequest to Client",
LOG_LW2M_INFO, ((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString(),
((LwM2mSingleResource) ((WriteRequest) request).getNode()).getValue().toString());
LOG_LW2M_INFO, ((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(),
response.getCode().getName(), request.getPath().toString(), value);
serviceImpl.sendLogsToThingsboard(msg, registration);
log.info("[{}] [{}] - [{}] [{}] Update SendRequest[{}]", registration.getEndpoint(), ((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString(),
((LwM2mSingleResource) ((WriteRequest) request).getNode()).getValue());
log.debug("[{}] [{}] - [{}] [{}] Update SendRequest[{}]", registration.getEndpoint(),
((Response) response.getCoapResponse()).getCode(), response.getCode(),
request.getPath().toString(), value);
}
} else {
String msg = String.format("%s: sendRequest: CoapCode - %s Lwm2m code - %d name - %s Resource path - %s SendRequest to Client", LOG_LW2M_ERROR,
@ -258,7 +228,7 @@ public class LwM2mTransportRequest {
}
}, e -> {
if (!lwM2MClient.isInit()) {
lwM2MClient.initValue(this.serviceImpl, convertToIdVerFromObjectId(request.getPath().toString(), registration));
lwM2MClient.initValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration));
}
String msg = String.format("%s: sendRequest: Resource path - %s msg error - %s SendRequest to Client",
LOG_LW2M_ERROR, request.getPath().toString(), e.toString());
@ -283,7 +253,7 @@ public class LwM2mTransportRequest {
case FLOAT: // Double
return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Double.parseDouble(value.toString())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Double.parseDouble(value.toString()));
case TIME: // Date
Date date = new Date(Long.decode(value.toString()));
Date date = new Date(Long.decode(value.toString()));
return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, date) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, date);
case OPAQUE: // byte[] value, base64
return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Hex.decodeHex(value.toString().toCharArray())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Hex.decodeHex(value.toString().toCharArray()));
@ -317,7 +287,7 @@ public class LwM2mTransportRequest {
* @param response -
*/
private void sendResponse(Registration registration, String path, LwM2mResponse response, DownlinkRequest request) {
String pathIdVer = convertToIdVerFromObjectId(path, registration);
String pathIdVer = convertPathFromObjectIdToIdVer(path, registration);
if (response instanceof ReadResponse) {
serviceImpl.onObservationResponse(registration, pathIdVer, (ReadResponse) response);
} else if (response instanceof CancelObservationResponse) {

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

@ -52,6 +52,10 @@ public interface LwM2mTransportService extends TbTransportService {
void onResourceDelete(Optional<TransportProtos.ResourceDeleteMsg> resourceDeleteMsgOpt);
void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRequest);
void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse);
void doTrigger(Registration registration, String path);
void doDisconnect(TransportProtos.SessionInfoProto sessionInfo);

527
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java

@ -18,6 +18,7 @@ package org.thingsboard.server.transport.lwm2m.server;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.collect.Sets;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@ -43,7 +44,6 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.common.transport.service.DefaultTransportService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
@ -53,12 +53,12 @@ import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile;
import org.thingsboard.server.transport.lwm2m.server.client.ResultsAddKeyValueProto;
import org.thingsboard.server.transport.lwm2m.server.client.ResultsAnalyzerParameters;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
@ -75,24 +75,22 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.transport.util.JsonUtils.getJsonObject;
import static org.eclipse.leshan.core.attributes.Attribute.OBJECT_VERSION;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.CLIENT_NOT_AUTHORIZED;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEVICE_ATTRIBUTES_REQUEST;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEVICE_ATTRIBUTES_TOPIC;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEVICE_TELEMETRY_TOPIC;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_DISCOVER;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_OBSERVE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_READ;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_TELEMETRY;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LWM2M_STRATEGY_2;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.POST_TYPE_OPER_EXECUTE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.POST_TYPE_OPER_WRITE_REPLACE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.PUT_TYPE_OPER_WRITE_ATTRIBUTES;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.SERVICE_CHANNEL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToIdVerFromObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToObjectIdFromIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromIdVerToObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.getAckCallback;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.validateObjectIdFromKey;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.validateObjectVerFromKey;
@Slf4j
@ -147,9 +145,9 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
* Next -> Create new LwM2MClient for current session -> setModelClient...
*
* @param registration - Registration LwM2M Client
* @param previousObsersations - may be null
* @param previousObservations - may be null
*/
public void onRegistered(Registration registration, Collection<Observation> previousObsersations) {
public void onRegistered(Registration registration, Collection<Observation> previousObservations) {
executorRegistered.submit(() -> {
try {
log.warn("[{}] [{{}] Client: create after Registration", registration.getEndpoint(), registration.getId());
@ -161,6 +159,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, sessionInfo));
transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.OPEN), null);
transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), null);
transportService.process(sessionInfo, TransportProtos.SubscribeToRPCMsg.newBuilder().build(), null);
this.initLwM2mFromClientValue(registration, lwM2MClient);
this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client create after Registration", registration);
} else {
@ -189,6 +188,13 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
LwM2mClient lwM2MClient = this.lwM2mClientContext.getLwM2MClient(sessionInfo);
if (lwM2MClient.getDeviceId() == null && lwM2MClient.getProfileId() == null) {
initLwM2mClient(lwM2MClient, sessionInfo);
} else {
if (registration.getBindingMode().useQueueMode()) {
LwM2mQueuedRequest request;
while ((request = lwM2MClient.getQueuedRequests().poll()) != null) {
request.send();
}
}
}
log.info("Client: [{}] updatedReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType());
@ -240,8 +246,11 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
}
}
@Override
public void onSleepingDev(Registration registration) {
log.info("[{}] [{}] Received endpoint Sleeping version event", registration.getId(), registration.getEndpoint());
this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client is sleeping!", registration);
//TODO: associate endpointId with device information.
}
@ -297,33 +306,32 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
@Override
public void onAttributeUpdate(AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo) {
if (msg.getSharedUpdatedCount() > 0) {
JsonElement el = JsonConverter.toJson(msg);
el.getAsJsonObject().entrySet().forEach(de -> {
String pathIdVer = this.getPathAttributeUpdate(sessionInfo, de.getKey());
String value = de.getValue().getAsString();
msg.getSharedUpdatedList().forEach(tsKvProto -> {
String pathName = tsKvProto.getKv().getKey();
String pathIdVer = this.validatePathIntoProfile(sessionInfo, pathName);
Object valueNew = this.lwM2mTransportContextServer.getValueFromKvProto(tsKvProto.getKv());
LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClient(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()));
LwM2mClientProfile clientProfile = lwM2mClientContext.getProfile(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB()));
if (pathIdVer != null && !pathIdVer.isEmpty() && (this.validatePathInAttrProfile(clientProfile, pathIdVer) || this.validatePathInTelemetryProfile(clientProfile, pathIdVer))) {
if (pathIdVer != null) {
ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer);
if (resourceModel != null && resourceModel.operations.isWritable()) {
lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), pathIdVer, POST_TYPE_OPER_WRITE_REPLACE,
ContentFormat.TLV.getName(), null, value, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout());
this.updateResourcesValueToClient(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), valueNew, pathIdVer);
} else {
log.error("Resource path - [{}] value - [{}] is not Writable and cannot be updated", pathIdVer, value);
log.error("Resource path - [{}] value - [{}] is not Writable and cannot be updated", pathIdVer, valueNew);
String logMsg = String.format("%s: attributeUpdate: Resource path - %s value - %s is not Writable and cannot be updated",
LOG_LW2M_ERROR, pathIdVer, value);
LOG_LW2M_ERROR, pathIdVer, valueNew);
this.sendLogsToThingsboard(logMsg, lwM2MClient.getRegistration());
}
} else {
log.error("Attribute name - [{}] value - [{}] is not present as attribute in profile and cannot be updated", de.getKey(), value);
log.error("Resource name name - [{}] value - [{}] is not present as attribute/telemetry in profile and cannot be updated", pathName, valueNew);
String logMsg = String.format("%s: attributeUpdate: attribute name - %s value - %s is not present as attribute in profile and cannot be updated",
LOG_LW2M_ERROR, de.getKey(), value);
LOG_LW2M_ERROR, pathName, valueNew);
this.sendLogsToThingsboard(logMsg, lwM2MClient.getRegistration());
}
});
} else if (msg.getSharedDeletedCount() > 0) {
log.info("[{}] delete [{}] onAttributeUpdate", msg.getSharedDeletedList(), sessionInfo);
}
}
/**
@ -356,17 +364,15 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
}
/**
*
* @param resourceUpdateMsgOpt -
*/
@Override
public void onResourceUpdate (Optional<TransportProtos.ResourceUpdateMsg> resourceUpdateMsgOpt) {
public void onResourceUpdate(Optional<TransportProtos.ResourceUpdateMsg> resourceUpdateMsgOpt) {
String idVer = resourceUpdateMsgOpt.get().getResourceKey();
lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.updateResourceModel(idVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getModelProvider()));
}
/**
*
* @param resourceDeleteMsgOpt -
*/
@Override
@ -375,6 +381,14 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.deleteResources(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getModelProvider()));
}
public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRequest) {
log.info("[{}] toDeviceRpcRequest", toDeviceRequest);
}
public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse) {
log.info("[{}] toServerRpcResponse", toServerResponse);
}
/**
* Trigger Server path = "/1/0/8"
* <p>
@ -418,6 +432,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
*/
protected void onAwakeDev(Registration registration) {
log.info("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint());
this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client is awake!", registration);
//TODO: associate endpointId with device information.
}
@ -443,32 +458,13 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
}
/**
* @param msg - text msg
* @param logMsg - text msg
* @param registration - Id of Registration LwM2M Client
*/
public void sendLogsToThingsboard(String msg, Registration registration) {
if (msg != null) {
JsonObject telemetries = new JsonObject();
telemetries.addProperty(LOG_LW2M_TELEMETRY, msg);
this.updateParametersOnThingsboard(telemetries, DEVICE_TELEMETRY_TOPIC, registration);
}
}
/**
* // !!! Ok
* Prepare send to Thigsboard callback - Attribute or Telemetry
*
* @param msg - JsonArray: [{name: value}]
* @param topicName - Api Attribute or Telemetry
* @param registration - Id of Registration LwM2M Client
*/
public void updateParametersOnThingsboard(JsonElement msg, String topicName, Registration registration) {
public void sendLogsToThingsboard(String logMsg, Registration registration) {
SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration);
if (sessionInfo != null) {
lwM2mTransportContextServer.sendParametersOnThingsboard(msg, topicName, sessionInfo);
} else {
log.error("Client: [{}] updateParametersOnThingsboard [{}] sessionInfo ", registration, null);
if (logMsg != null && sessionInfo != null) {
this.lwM2mTransportContextServer.sendParametersOnThingsboardTelemetry(this.lwM2mTransportContextServer.getKvLogyToThingsboard(logMsg), sessionInfo);
}
}
@ -486,15 +482,20 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
*/
private void initLwM2mFromClientValue(Registration registration, LwM2mClient lwM2MClient) {
LwM2mClientProfile lwM2MClientProfile = lwM2mClientContext.getProfile(registration);
Set<String> clientObjects = this.getAllOjectsInClient(registration);
if (clientObjects != null && LWM2M_STRATEGY_2 == LwM2mTransportHandler.getClientOnlyObserveAfterConnect(lwM2MClientProfile)) {
// #2
lwM2MClient.getPendingRequests().addAll(clientObjects);
clientObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(registration, path, GET_TYPE_OPER_READ, ContentFormat.TLV.getName(),
null, null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout()));
Set<String> clientObjects = lwM2mClientContext.getSupportedIdVerInClient(registration);
if (clientObjects != null && clientObjects.size() > 0) {
if (LWM2M_STRATEGY_2 == LwM2mTransportHandler.getClientOnlyObserveAfterConnect(lwM2MClientProfile)) {
// #2
lwM2MClient.getPendingRequests().addAll(clientObjects);
clientObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(registration, path, GET_TYPE_OPER_READ, ContentFormat.TLV.getName(),
null, null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout()));
}
// #1
this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, GET_TYPE_OPER_READ, clientObjects);
this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, GET_TYPE_OPER_OBSERVE, clientObjects);
this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, PUT_TYPE_OPER_WRITE_ATTRIBUTES, clientObjects);
this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, GET_TYPE_OPER_DISCOVER, clientObjects);
}
// #1
this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, GET_TYPE_OPER_OBSERVE);
}
/**
@ -553,17 +554,20 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
* @param registration - Registration LwM2M Client
*/
private void updateAttrTelemetry(Registration registration, Set<String> paths) {
JsonObject attributes = new JsonObject();
JsonObject telemetries = new JsonObject();
try {
this.getParametersFromProfile(attributes, telemetries, registration, paths);
ResultsAddKeyValueProto results = getParametersFromProfile(registration, paths);
SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration);
if (results != null && sessionInfo != null) {
if (results.getResultAttributes().size() > 0) {
this.lwM2mTransportContextServer.sendParametersOnThingsboardAttribute(results.getResultAttributes(), sessionInfo);
}
if (results.getResultTelemetries().size() > 0) {
this.lwM2mTransportContextServer.sendParametersOnThingsboardTelemetry(results.getResultTelemetries(), sessionInfo);
}
}
} catch (Exception e) {
log.error("UpdateAttrTelemetry", e);
}
if (attributes.getAsJsonObject().entrySet().size() > 0)
this.updateParametersOnThingsboard(attributes, DEVICE_ATTRIBUTES_TOPIC, registration);
if (telemetries.getAsJsonObject().entrySet().size() > 0)
this.updateParametersOnThingsboard(telemetries, DEVICE_TELEMETRY_TOPIC, registration);
}
/**
@ -601,41 +605,61 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
/**
* Start observe/read: Attr/Telemetry
* #1 - Analyze:
* #1.1 path in resource profile == client resource
* #1 - Analyze: path in resource profile == client resource
*
* @param registration -
*/
private void initReadAttrTelemetryObserveToClient(Registration registration, LwM2mClient lwM2MClient, String typeOper) {
private void initReadAttrTelemetryObserveToClient(Registration registration, LwM2mClient lwM2MClient,
String typeOper, Set<String> clientObjects) {
LwM2mClientProfile lwM2MClientProfile = lwM2mClientContext.getProfile(registration);
Set<String> clientInstances = this.getAllInstancesInClient(registration);
Set<String> result;
Set<String> result = null;
ConcurrentHashMap<String, Object> params = null;
if (GET_TYPE_OPER_READ.equals(typeOper)) {
result = JacksonUtil.fromString(lwM2MClientProfile.getPostAttributeProfile().toString(), new TypeReference<>() {
});
result.addAll(JacksonUtil.fromString(lwM2MClientProfile.getPostTelemetryProfile().toString(), new TypeReference<>() {
}));
} else {
result = JacksonUtil.fromString(lwM2MClientProfile.getPostObserveProfile().toString(), new TypeReference<>() {
});
result = JacksonUtil.fromString(lwM2MClientProfile.getPostAttributeProfile().toString(),
new TypeReference<>() {
});
result.addAll(JacksonUtil.fromString(lwM2MClientProfile.getPostTelemetryProfile().toString(),
new TypeReference<>() {
}));
} else if (GET_TYPE_OPER_OBSERVE.equals(typeOper)) {
result = JacksonUtil.fromString(lwM2MClientProfile.getPostObserveProfile().toString(),
new TypeReference<>() {
});
} else if (GET_TYPE_OPER_DISCOVER.equals(typeOper)) {
result = this.getPathForWriteAttributes(lwM2MClientProfile.getPostAttributeLwm2mProfile()).keySet();
;
} else if (PUT_TYPE_OPER_WRITE_ATTRIBUTES.equals(typeOper)) {
params = this.getPathForWriteAttributes(lwM2MClientProfile.getPostAttributeLwm2mProfile());
result = params.keySet();
}
Set<String> pathSend = ConcurrentHashMap.newKeySet();
result.forEach(target -> {
// #1.1
String[] resPath = target.split("/");
String instance = "/" + resPath[1] + "/" + resPath[2];
if (clientInstances != null && clientInstances.size() > 0 && clientInstances.contains(instance)) {
pathSend.add(target);
if (!result.isEmpty()) {
// #1
Set<String> pathSend = result.stream().filter(target -> {
return target.split(LWM2M_SEPARATOR_PATH).length < 3 ?
clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1]) :
clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1] + "/" + target.split(LWM2M_SEPARATOR_PATH)[2]);
}
)
.collect(Collectors.toUnmodifiableSet());
if (!pathSend.isEmpty()) {
lwM2MClient.getPendingRequests().addAll(pathSend);
ConcurrentHashMap<String, Object> finalParams = params;
pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, ContentFormat.TLV.getName(),
null, finalParams != null ? finalParams.get(target) : null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout()));
if (GET_TYPE_OPER_OBSERVE.equals(typeOper)) {
lwM2MClient.initValue(this, null);
}
}
});
lwM2MClient.getPendingRequests().addAll(pathSend);
pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, ContentFormat.TLV.getName(),
null, null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout()));
if (GET_TYPE_OPER_OBSERVE.equals(typeOper)) {
lwM2MClient.initValue(this, null);
}
}
private ConcurrentHashMap<String, Object> getPathForWriteAttributes(JsonObject objectJson) {
ConcurrentHashMap<String, Object> pathAttributes = new Gson().fromJson(objectJson.toString(),
new TypeToken<ConcurrentHashMap<String, Object>>() {
}.getType());
return pathAttributes;
}
/**
* Update parameters device in LwM2MClient
* If new deviceProfile != old deviceProfile => update deviceProfile
@ -656,96 +680,109 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
}
/**
* @param registration -
* @return - all object in client
*/
private Set<String> getAllOjectsInClient(Registration registration) {
Set<String> clientObjects = ConcurrentHashMap.newKeySet();
Arrays.stream(registration.getObjectLinks()).forEach(url -> {
LwM2mPath pathIds = new LwM2mPath(url.getUrl());
if (pathIds.isObjectInstance()) {
clientObjects.add("/" + pathIds.getObjectId());
}
});
return (clientObjects.size() > 0) ? clientObjects : null;
}
/**
* @param registration -
* @return all instances in client
*/
private Set<String> getAllInstancesInClient(Registration registration) {
Set<String> clientInstances = ConcurrentHashMap.newKeySet();
Arrays.stream(registration.getObjectLinks()).forEach(url -> {
LwM2mPath pathIds = new LwM2mPath(url.getUrl());
if (pathIds.isObjectInstance()) {
clientInstances.add(convertToIdVerFromObjectId(url.getUrl(), registration));
}
});
return (clientInstances.size() > 0) ? clientInstances : null;
}
/**
* @param attributes - new JsonObject
* @param telemetry - new JsonObject
* // * @param attributes - new JsonObject
* // * @param telemetry - new JsonObject
*
* @param registration - Registration LwM2M Client
* @param path -
*/
private void getParametersFromProfile(JsonObject attributes, JsonObject telemetry, Registration registration, Set<String> path) {
private ResultsAddKeyValueProto getParametersFromProfile(Registration registration, Set<String> path) {
if (path != null && path.size() > 0) {
ResultsAddKeyValueProto results = new ResultsAddKeyValueProto();
LwM2mClientProfile lwM2MClientProfile = lwM2mClientContext.getProfile(registration);
lwM2MClientProfile.getPostAttributeProfile().forEach(idVer -> {
if (path.contains(idVer.getAsString())) {
this.addParameters(idVer.getAsString(), attributes, registration);
List<TransportProtos.KeyValueProto> resultAttributes = new ArrayList<>();
lwM2MClientProfile.getPostAttributeProfile().forEach(pathIdVer -> {
if (path.contains(pathIdVer.getAsString())) {
TransportProtos.KeyValueProto kvAttr = this.getKvToThingsboard(pathIdVer.getAsString(), registration);
if (kvAttr != null) {
resultAttributes.add(kvAttr);
}
}
});
lwM2MClientProfile.getPostTelemetryProfile().forEach(idVer -> {
if (path.contains(idVer.getAsString())) {
this.addParameters(idVer.getAsString(), telemetry, registration);
List<TransportProtos.KeyValueProto> resultTelemetries = new ArrayList<>();
lwM2MClientProfile.getPostTelemetryProfile().forEach(pathIdVer -> {
if (path.contains(pathIdVer.getAsString())) {
TransportProtos.KeyValueProto kvAttr = this.getKvToThingsboard(pathIdVer.getAsString(), registration);
if (kvAttr != null) {
resultTelemetries.add(kvAttr);
}
}
});
if (resultAttributes.size() > 0) {
results.setResultAttributes(resultAttributes);
}
if (resultTelemetries.size() > 0) {
results.setResultTelemetries(resultTelemetries);
}
return results;
}
return null;
}
/**
* @param parameters - JsonObject attributes/telemetry
* @param registration - Registration LwM2M Client
*/
private void addParameters(String path, JsonObject parameters, Registration registration) {
LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null);
// public TransportProtos.KeyValueProto getKvToThingsboard(String pathIdVer, Registration registration) {
// ResultsResourceValue resultsResourceValue = getResultsResourceValue(pathIdVer, registration);
// return resultsResourceValue != null ? this.lwM2mTransportContextServer.getKvAttrTelemetryToThingsboard(resultsResourceValue) : null;
// }
private TransportProtos.KeyValueProto getKvToThingsboard(String pathIdVer, Registration registration) {
LwM2mClient lwM2MClient = this.lwM2mClientContext.getLwM2mClientWithReg(null, registration.getId());
JsonObject names = lwM2mClientContext.getProfiles().get(lwM2MClient.getProfileId()).getPostKeyNameProfile();
String resName = names.get(path).getAsString();
if (resName != null && !resName.isEmpty()) {
try {
String resValue = this.getResourceValueToString(lwM2MClient, path);
if (resValue != null) {
parameters.addProperty(resName, resValue);
if (names != null && names.has(pathIdVer)) {
String resourceName = names.get(pathIdVer).getAsString();
if (resourceName != null && !resourceName.isEmpty()) {
try {
LwM2mResource resourceValue = lwM2MClient != null ? getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer) : null;
if (resourceValue != null) {
ResourceModel.Type currentType = resourceValue.getType();
ResourceModel.Type expectedType = this.lwM2mTransportContextServer.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer);
Object valueKvProto = null;
if (resourceValue.isMultiInstances()) {
valueKvProto = new JsonObject();
Object finalvalueKvProto = valueKvProto;
Gson gson = new GsonBuilder().create();
resourceValue.getValues().forEach((k, v) -> {
Object val = this.converter.convertValue(resourceValue.getValue(), currentType, expectedType,
new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)));
JsonElement element = gson.toJsonTree(val, val.getClass());
((JsonObject) finalvalueKvProto).add(String.valueOf(k), element);
});
valueKvProto = gson.toJson(valueKvProto);
} else {
valueKvProto = this.converter.convertValue(resourceValue.getValue(), currentType, expectedType,
new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)));
}
return valueKvProto != null ? this.lwM2mTransportContextServer.getKvAttrTelemetryToThingsboard(currentType, resourceName, valueKvProto, resourceValue.isMultiInstances()) : null;
}
} catch (Exception e) {
log.error("Failed to add parameters.", e);
}
} catch (Exception e) {
log.error("Failed to add parameters.", e);
}
} else {
log.error("Failed to add parameters. path: [{}], names: [{}]", pathIdVer, names);
}
return null;
}
/**
* @param path - path resource
* @return - value of Resource or null
* @param pathIdVer - path resource
* @return - value of Resource into format KvProto or null
*/
private String getResourceValueToString(LwM2mClient lwM2MClient, String path) {
LwM2mPath pathIds = new LwM2mPath(convertToObjectIdFromIdVer(path));
LwM2mResource resourceValue = this.returnResourceValueFromLwM2MClient(lwM2MClient, path);
return resourceValue == null ? null :
this.converter.convertValue(resourceValue.isMultiInstances() ? resourceValue.getValues() : resourceValue.getValue(), resourceValue.getType(), ResourceModel.Type.STRING, pathIds).toString();
private Object getResourceValueFormatKv(LwM2mClient lwM2MClient, String pathIdVer) {
LwM2mResource resourceValue = this.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer);
ResourceModel.Type currentType = resourceValue.getType();
ResourceModel.Type expectedType = this.lwM2mTransportContextServer.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer);
return this.converter.convertValue(resourceValue.getValue(), currentType, expectedType,
new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)));
}
/**
* @param lwM2MClient -
* @param path -
* @param path -
* @return - return value of Resource by idPath
*/
private LwM2mResource returnResourceValueFromLwM2MClient(LwM2mClient lwM2MClient, String path) {
private LwM2mResource getResourceValueFromLwM2MClient(LwM2mClient lwM2MClient, String path) {
LwM2mResource resourceValue = null;
if (new LwM2mPath(convertToObjectIdFromIdVer(path)).isResource()) {
if (new LwM2mPath(convertPathFromIdVerToObjectId(path)).isResource()) {
resourceValue = lwM2MClient.getResources().get(path).getLwM2mResource();
}
return resourceValue;
@ -769,6 +806,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
* #3.1 Attribute isChange (add&del)
* #3.2 Telemetry isChange (add&del)
* #3.3 KeyName isChange (add)
* #3.4 attributeLwm2m isChange (update WrightAttribute: add/update/del)
* #4 update
* #4.1 add If #3 isChange, then analyze and update Value in Transport form Client and send Value to thingsboard
* #4.2 del
@ -779,6 +817,9 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
* -- path Attr/Telemetry includes newObserve and does not include oldObserve: send Request observe to Client
* #5.3 Observe.del
* -- different between newObserve and oldObserve: send Request cancel observe to client
* #6
* #6.1 - update WriteAttribute
* #6.2 - del WriteAttribute
*
* @param registrationIds -
* @param deviceProfile -
@ -793,6 +834,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
Set<String> telemetrySetOld = this.convertJsonArrayToSet(telemetryOld);
JsonArray observeOld = lwM2MClientProfileOld.getPostObserveProfile();
JsonObject keyNameOld = lwM2MClientProfileOld.getPostKeyNameProfile();
JsonObject attributeLwm2mOld = lwM2MClientProfileOld.getPostAttributeLwm2mProfile();
LwM2mClientProfile lwM2MClientProfileNew = lwM2mClientContext.getProfiles().get(deviceProfile.getUuidId());
JsonArray attributeNew = lwM2MClientProfileNew.getPostAttributeProfile();
@ -801,32 +843,41 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
Set<String> telemetrySetNew = this.convertJsonArrayToSet(telemetryNew);
JsonArray observeNew = lwM2MClientProfileNew.getPostObserveProfile();
JsonObject keyNameNew = lwM2MClientProfileNew.getPostKeyNameProfile();
JsonObject attributeLwm2mNew = lwM2MClientProfileNew.getPostAttributeLwm2mProfile();
// #3
ResultsAnalyzerParameters sendAttrToThingsboard = new ResultsAnalyzerParameters();
// #3.1
if (!attributeOld.equals(attributeNew)) {
ResultsAnalyzerParameters postAttributeAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(attributeOld, new TypeToken<Set<String>>() {
}.getType()), attributeSetNew);
ResultsAnalyzerParameters postAttributeAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(attributeOld,
new TypeToken<Set<String>>() {
}.getType()), attributeSetNew);
sendAttrToThingsboard.getPathPostParametersAdd().addAll(postAttributeAnalyzer.getPathPostParametersAdd());
sendAttrToThingsboard.getPathPostParametersDel().addAll(postAttributeAnalyzer.getPathPostParametersDel());
}
// #3.2
if (!telemetryOld.equals(telemetryNew)) {
ResultsAnalyzerParameters postTelemetryAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(telemetryOld, new TypeToken<Set<String>>() {
}.getType()), telemetrySetNew);
ResultsAnalyzerParameters postTelemetryAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(telemetryOld,
new TypeToken<Set<String>>() {
}.getType()), telemetrySetNew);
sendAttrToThingsboard.getPathPostParametersAdd().addAll(postTelemetryAnalyzer.getPathPostParametersAdd());
sendAttrToThingsboard.getPathPostParametersDel().addAll(postTelemetryAnalyzer.getPathPostParametersDel());
}
// #3.3
if (!keyNameOld.equals(keyNameNew)) {
ResultsAnalyzerParameters keyNameChange = this.getAnalyzerKeyName(new Gson().fromJson(keyNameOld.toString(), new TypeToken<ConcurrentHashMap<String, String>>() {
ResultsAnalyzerParameters keyNameChange = this.getAnalyzerKeyName(new Gson().fromJson(keyNameOld.toString(),
new TypeToken<ConcurrentHashMap<String, String>>() {
}.getType()),
new Gson().fromJson(keyNameNew.toString(), new TypeToken<ConcurrentHashMap<String, String>>() {
}.getType()));
sendAttrToThingsboard.getPathPostParametersAdd().addAll(keyNameChange.getPathPostParametersAdd());
}
// #3.4, #6
if (!attributeLwm2mOld.equals(attributeLwm2mNew)) {
this.getAnalyzerAttributeLwm2m(registrationIds, attributeLwm2mOld, attributeLwm2mNew);
}
// #4.1 add
if (sendAttrToThingsboard.getPathPostParametersAdd().size() > 0) {
// update value in Resources
@ -860,10 +911,14 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
// send Request observe to Client
registrationIds.forEach(registrationId -> {
Registration registration = lwM2mClientContext.getRegistration(registrationId);
this.readResourceValueObserve(registration, postObserveAnalyzer.getPathPostParametersAdd(), GET_TYPE_OPER_OBSERVE);
if (postObserveAnalyzer.getPathPostParametersAdd().size() > 0) {
this.readResourceValueObserve(registration, postObserveAnalyzer.getPathPostParametersAdd(), GET_TYPE_OPER_OBSERVE);
}
// 5.3 del
// send Request cancel observe to Client
this.cancelObserveIsValue(registration, postObserveAnalyzer.getPathPostParametersDel());
if (postObserveAnalyzer.getPathPostParametersDel().size() > 0) {
this.cancelObserveIsValue(registration, postObserveAnalyzer.getPathPostParametersDel());
}
});
}
}
@ -910,7 +965,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
*/
private void readResourceValueObserve(Registration registration, Set<String> targets, String typeOper) {
targets.forEach(target -> {
LwM2mPath pathIds = new LwM2mPath(convertToObjectIdFromIdVer(target));
LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(target));
if (pathIds.isResource()) {
if (GET_TYPE_OPER_READ.equals(typeOper)) {
lwM2mTransportRequest.sendAllRequest(registration, target, typeOper,
@ -923,7 +978,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
});
}
private ResultsAnalyzerParameters getAnalyzerKeyName(ConcurrentMap<String, String> keyNameOld, ConcurrentMap<String, String> keyNameNew) {
private ResultsAnalyzerParameters getAnalyzerKeyName(ConcurrentHashMap<String, String> keyNameOld, ConcurrentHashMap<String, String> keyNameNew) {
ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters();
Set<String> paths = keyNameNew.entrySet()
.stream()
@ -933,22 +988,91 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
return analyzerParameters;
}
/**
* #3.4, #6
* #6
* #6.1 - send update WriteAttribute
* #6.2 - send empty WriteAttribute
*
* @param attributeLwm2mOld -
* @param attributeLwm2mNew -
* @return
*/
private void getAnalyzerAttributeLwm2m(Set<String> registrationIds, JsonObject attributeLwm2mOld, JsonObject attributeLwm2mNew) {
ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters();
ConcurrentHashMap<String, Object> lwm2mAttributesOld = new Gson().fromJson(attributeLwm2mOld.toString(),
new TypeToken<ConcurrentHashMap<String, Object>>() {
}.getType());
ConcurrentHashMap<String, Object> lwm2mAttributesNew = new Gson().fromJson(attributeLwm2mNew.toString(),
new TypeToken<ConcurrentHashMap<String, Object>>() {
}.getType());
Set<String> pathOld = lwm2mAttributesOld.keySet();
Set<String> pathNew = lwm2mAttributesNew.keySet();
analyzerParameters.setPathPostParametersAdd(pathNew
.stream().filter(p -> !pathOld.contains(p)).collect(Collectors.toSet()));
analyzerParameters.setPathPostParametersDel(pathOld
.stream().filter(p -> !pathNew.contains(p)).collect(Collectors.toSet()));
Set<String> pathCommon = pathNew
.stream().filter(p -> pathOld.contains(p)).collect(Collectors.toSet());
Set<String> pathCommonChange = pathCommon
.stream().filter(p -> !lwm2mAttributesOld.get(p).equals(lwm2mAttributesNew.get(p))).collect(Collectors.toSet());
analyzerParameters.getPathPostParametersAdd().addAll(pathCommonChange);
// #6
// #6.2
if (analyzerParameters.getPathPostParametersAdd().size() > 0) {
registrationIds.forEach(registrationId -> {
Registration registration = this.lwM2mClientContext.getRegistration(registrationId);
Set<String> clientObjects = lwM2mClientContext.getSupportedIdVerInClient(registration);
Set<String> pathSend = analyzerParameters.getPathPostParametersAdd().stream().filter(target -> clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1]))
.collect(Collectors.toUnmodifiableSet());
if (!pathSend.isEmpty()) {
ConcurrentHashMap<String, Object> finalParams = lwm2mAttributesNew;
pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(registration, target, PUT_TYPE_OPER_WRITE_ATTRIBUTES, ContentFormat.TLV.getName(),
null, finalParams.get(target), this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout()));
}
});
}
// #6.2
if (analyzerParameters.getPathPostParametersDel().size() > 0) {
registrationIds.forEach(registrationId -> {
Registration registration = this.lwM2mClientContext.getRegistration(registrationId);
Set<String> clientObjects = lwM2mClientContext.getSupportedIdVerInClient(registration);
Set<String> pathSend = analyzerParameters.getPathPostParametersDel().stream().filter(target -> clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1]))
.collect(Collectors.toUnmodifiableSet());
if (!pathSend.isEmpty()) {
pathSend.forEach(target -> {
Map<String, Object> params = (Map<String, Object>) lwm2mAttributesOld.get(target);
params.clear();
params.put(OBJECT_VERSION, "");
lwM2mTransportRequest.sendAllRequest(registration, target, PUT_TYPE_OPER_WRITE_ATTRIBUTES, ContentFormat.TLV.getName(),
null, params, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout());
});
}
});
}
}
private void cancelObserveIsValue(Registration registration, Set<String> paramAnallyzer) {
LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null);
paramAnallyzer.forEach(p -> {
if (this.returnResourceValueFromLwM2MClient(lwM2MClient, p) != null) {
this.setCancelObservationRecourse(registration, convertToObjectIdFromIdVer(p));
if (this.getResourceValueFromLwM2MClient(lwM2MClient, p) != null) {
this.setCancelObservationRecourse(registration, convertPathFromIdVerToObjectId(p));
}
}
);
}
private void putDelayedUpdateResourcesClient(LwM2mClient lwM2MClient, Object valueOld, Object valueNew, String path) {
private void updateResourcesValueToClient(LwM2mClient lwM2MClient, Object valueOld, Object valueNew, String path) {
if (valueNew != null && (valueOld == null || !valueNew.toString().equals(valueOld.toString()))) {
lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), path, POST_TYPE_OPER_WRITE_REPLACE,
ContentFormat.TLV.getName(), null, valueNew, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout());
} else {
log.error("05 delayError");
log.error("Failed update resource [{}] [{}]", path, valueNew);
String logMsg = String.format("%s: Failed update resource path - %s value - %s. Value is not changed or bad",
LOG_LW2M_ERROR, path, valueNew);
this.sendLogsToThingsboard(logMsg, lwM2MClient.getRegistration());
log.info("Failed update resource [{}] [{}]", path, valueNew);
}
}
@ -968,9 +1092,9 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
* @param name -
* @return path if path isPresent in postProfile
*/
private String getPathAttributeUpdate(TransportProtos.SessionInfoProto sessionInfo, String name) {
String profilePath = this.getPathAttributeUpdateProfile(sessionInfo, name);
return !profilePath.isEmpty() ? profilePath : null;
private String validatePathIntoProfile(TransportProtos.SessionInfoProto sessionInfo, String name) {
String pathIdVer = this.getPresentPathIntoProfile(sessionInfo, name);
return !pathIdVer.isEmpty() ? pathIdVer : null;
}
/**
@ -980,7 +1104,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
* @param name -
* @return -
*/
private String getPathAttributeUpdateProfile(TransportProtos.SessionInfoProto sessionInfo, String name) {
private String getPresentPathIntoProfile(TransportProtos.SessionInfoProto sessionInfo, String name) {
LwM2mClientProfile profile = lwM2mClientContext.getProfile(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB()));
LwM2mClient lwM2mClient = lwM2mClientContext.getLwM2MClient(sessionInfo);
return profile.getPostKeyNameProfile().getAsJsonObject().entrySet().stream()
@ -991,40 +1115,49 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
/**
* Update resource value on client: if there is a difference in values between the current resource values and the shared attribute values
* #1 Get path resource by result attributesResponse
* #1.1 If two names have equal path => last time attribute
* #2.1 if there is a difference in values between the current resource values and the shared attribute values
* => send to client Request Update of value (new value from shared attribute)
* and LwM2MClient.delayedRequests.add(path)
* #2.1 if there is not a difference in values between the current resource values and the shared attribute values
*
* @param attributesResponse -
* @param sessionInfo -
*/
public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg attributesResponse, TransportProtos.SessionInfoProto sessionInfo) {
try {
LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2MClient(sessionInfo);
attributesResponse.getSharedAttributeListList().forEach(attr -> {
String path = this.getPathAttributeUpdate(sessionInfo, attr.getKv().getKey());
if (path != null) {
// #1.1
if (lwM2MClient.getDelayedRequests().containsKey(path) && attr.getTs() > lwM2MClient.getDelayedRequests().get(path).getTs()) {
lwM2MClient.getDelayedRequests().put(path, attr);
} else {
lwM2MClient.getDelayedRequests().put(path, attr);
}
}
});
// #2.1
lwM2MClient.getDelayedRequests().forEach((k, v) -> {
ArrayList<TransportProtos.KeyValueProto> listV = new ArrayList<>();
listV.add(v.getKv());
this.putDelayedUpdateResourcesClient(lwM2MClient, this.getResourceValueToString(lwM2MClient, k), getJsonObject(listV).get(v.getKv().getKey()), k);
});
List<TransportProtos.TsKvProto> tsKvProtos = attributesResponse.getSharedAttributeListList();
this.updateAttriuteFromThingsboard(tsKvProtos, sessionInfo);
} catch (Exception e) {
log.error(String.valueOf(e));
}
}
/**
* #1.1 If two names have equal path => last time attribute
* #2.1 if there is a difference in values between the current resource values and the shared attribute values
* => send to client Request Update of value (new value from shared attribute)
* and LwM2MClient.delayedRequests.add(path)
* #2.1 if there is not a difference in values between the current resource values and the shared attribute values
*
* @param tsKvProtos
* @param sessionInfo
*/
public void updateAttriuteFromThingsboard(List<TransportProtos.TsKvProto> tsKvProtos, TransportProtos.SessionInfoProto sessionInfo) {
LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2MClient(sessionInfo);
tsKvProtos.forEach(tsKvProto -> {
String pathIdVer = this.validatePathIntoProfile(sessionInfo, tsKvProto.getKv().getKey());
if (pathIdVer != null) {
// #1.1
if (lwM2MClient.getDelayedRequests().containsKey(pathIdVer) && tsKvProto.getTs() > lwM2MClient.getDelayedRequests().get(pathIdVer).getTs()) {
lwM2MClient.getDelayedRequests().put(pathIdVer, tsKvProto);
} else if (!lwM2MClient.getDelayedRequests().containsKey(pathIdVer)) {
lwM2MClient.getDelayedRequests().put(pathIdVer, tsKvProto);
}
}
});
// #2.1
lwM2MClient.getDelayedRequests().forEach((pathIdVer, tsKvProto) -> {
this.updateResourcesValueToClient(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer),
this.lwM2mTransportContextServer.getValueFromKvProto(tsKvProto.getKv()), pathIdVer);
});
}
/**
* @param lwM2MClient -
* @return SessionInfoProto -
@ -1141,11 +1274,11 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService {
return new ArrayList<>(namesIsWritable);
}
private boolean validateResourceInModel(LwM2mClient lwM2mClient, String pathKey, boolean isWritable) {
ResourceModel resourceModel = lwM2mClient.getResourceModel(pathKey);
Integer objectId = validateObjectIdFromKey(pathKey);
String objectVer = validateObjectVerFromKey(pathKey);
return resourceModel != null && (isWritable ?
private boolean validateResourceInModel(LwM2mClient lwM2mClient, String pathIdVer, boolean isWritableNotOptional) {
ResourceModel resourceModel = lwM2mClient.getResourceModel(pathIdVer);
Integer objectId = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)).getObjectId();
String objectVer = validateObjectVerFromKey(pathIdVer);
return resourceModel != null && (isWritableNotOptional ?
objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId)) && resourceModel.operations.isWritable() :
objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId)));
}

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

@ -25,18 +25,21 @@ import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.security.SecurityInfo;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServiceImpl;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
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.LwM2mTransportHandler.convertToObjectIdFromIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromIdVerToObjectId;
@Slf4j
@Data
@ -54,6 +57,7 @@ public class LwM2mClient implements Cloneable {
private final Map<String, ResourceValue> resources;
private final Map<String, TransportProtos.TsKvProto> delayedRequests;
private final List<String> pendingRequests;
private final Queue<LwM2mQueuedRequest> queuedRequests;
private boolean init;
public Object clone() throws CloneNotSupportedException {
@ -71,6 +75,7 @@ public class LwM2mClient implements Cloneable {
this.profileId = profileId;
this.sessionId = sessionId;
this.init = false;
this.queuedRequests = new ConcurrentLinkedQueue<>();
}
public boolean saveResourceValue(String pathRez, LwM2mResource rez, LwM2mModelProvider modelProvider) {
@ -78,7 +83,7 @@ public class LwM2mClient implements Cloneable {
this.resources.get(pathRez).setLwM2mResource(rez);
return true;
} else {
LwM2mPath pathIds = new LwM2mPath(convertToObjectIdFromIdVer(pathRez));
LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRez));
ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId());
if (resourceModel != null) {
this.resources.put(pathRez, new ResourceValue(rez, resourceModel));
@ -103,9 +108,9 @@ public class LwM2mClient implements Cloneable {
* @param modelProvider -
*/
public void deleteResources(String pathIdVer, LwM2mModelProvider modelProvider) {
Set key = getKeysEqualsIdVer(pathIdVer);
Set<String> key = getKeysEqualsIdVer(pathIdVer);
key.forEach(pathRez -> {
LwM2mPath pathIds = new LwM2mPath(convertToObjectIdFromIdVer(pathRez.toString()));
LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRez));
ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId());
if (resourceModel != null) {
this.resources.get(pathRez).setResourceModel(resourceModel);
@ -122,17 +127,17 @@ public class LwM2mClient implements Cloneable {
* @param modelProvider -
*/
public void updateResourceModel(String idVer, LwM2mModelProvider modelProvider) {
Set key = getKeysEqualsIdVer(idVer);
key.forEach(k -> this.saveResourceModel(k.toString(), modelProvider));
Set<String> key = getKeysEqualsIdVer(idVer);
key.forEach(k -> this.saveResourceModel(k, modelProvider));
}
private void saveResourceModel(String pathRez, LwM2mModelProvider modelProvider) {
LwM2mPath pathIds = new LwM2mPath(convertToObjectIdFromIdVer(pathRez));
LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRez));
ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId());
this.resources.get(pathRez).setResourceModel(resourceModel);
}
private Set getKeysEqualsIdVer(String idVer) {
private Set<String> getKeysEqualsIdVer(String idVer) {
return this.resources.keySet()
.stream()
.filter(e -> idVer.equals(e.split(LWM2M_SEPARATOR_PATH)[1]))

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

@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
public interface LwM2mClientContext {
@ -51,4 +52,6 @@ public interface LwM2mClientContext {
Map<UUID, LwM2mClientProfile> setProfiles(Map<UUID, LwM2mClientProfile> profiles);
boolean addUpdateProfileParameters(DeviceProfile deviceProfile);
Set<String> getSupportedIdVerInClient(Registration registration);
}

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

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.transport.lwm2m.server.client;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.security.EditableSecurityStore;
import org.springframework.stereotype.Service;
@ -27,11 +28,14 @@ import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler;
import org.thingsboard.server.transport.lwm2m.utils.TypeServer;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromObjectIdToIdVer;
@Service
@TbLwM2mTransportComponent
@ -90,7 +94,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
@Override
public LwM2mClient updateInSessionsLwM2MClient(Registration registration) {
if (this.lwM2mClients.get(registration.getEndpoint()) == null) {
addLwM2mClientToSession(registration.getEndpoint());
this.addLwM2mClientToSession(registration.getEndpoint());
}
LwM2mClient lwM2MClient = lwM2mClients.get(registration.getEndpoint());
lwM2MClient.setRegistration(registration);
@ -169,4 +173,21 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
}
return false;
}
/**
* if isVer - ok or default ver=DEFAULT_LWM2M_VERSION
* @param registration -
* @return - all objectIdVer in client
*/
@Override
public Set<String> getSupportedIdVerInClient(Registration registration) {
Set<String> clientObjects = ConcurrentHashMap.newKeySet();
Arrays.stream(registration.getObjectLinks()).forEach(url -> {
LwM2mPath pathIds = new LwM2mPath(url.getUrl());
if (!pathIds.isRoot()) {
clientObjects.add(convertPathFromObjectIdToIdVer(url.getUrl(), registration));
}
});
return (clientObjects.size() > 0) ? clientObjects : null;
}
}

8
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientProfile.java

@ -56,6 +56,13 @@ public class LwM2mClientProfile {
*/
private JsonArray postObserveProfile;
/**
* "attributeLwm2m": {"/3_1.0": {"ver": "currentTimeTest11"},
* "/3_1.0/0": {"gt": 17},
* "/3_1.0/0/9": {"pmax": 45}, "/3_1.2": {ver": "3_1.2"}}
*/
private JsonObject postAttributeLwm2mProfile;
public LwM2mClientProfile clone() {
LwM2mClientProfile lwM2mClientProfile = new LwM2mClientProfile();
lwM2mClientProfile.postClientLwM2mSettings = this.deepCopy(this.postClientLwM2mSettings, JsonObject.class);
@ -63,6 +70,7 @@ public class LwM2mClientProfile {
lwM2mClientProfile.postAttributeProfile = this.deepCopy(this.postAttributeProfile, JsonArray.class);
lwM2mClientProfile.postTelemetryProfile = this.deepCopy(this.postTelemetryProfile, JsonArray.class);
lwM2mClientProfile.postObserveProfile = this.deepCopy(this.postObserveProfile, JsonArray.class);
lwM2mClientProfile.postAttributeLwm2mProfile = this.deepCopy(this.postAttributeLwm2mProfile, JsonObject.class);
return lwM2mClientProfile;
}

34
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResultsAddKeyValueProto.java

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

32
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResultsResourceValue.java

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

23
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java

@ -18,14 +18,12 @@ package org.thingsboard.server.transport.lwm2m.utils;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.ResourceModel.Type;
import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.ObjectLink;
import org.eclipse.leshan.core.node.codec.CodecException;
import org.eclipse.leshan.core.node.codec.LwM2mValueConverter;
import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.core.util.StringUtils;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import java.math.BigInteger;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
@ -111,15 +109,16 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter {
case INTEGER:
log.debug("Trying to convert long value {} to date", value);
/** let's assume we received the millisecond since 1970/1/1 */
return new Date((Long) value);
return new Date(((Number) value).longValue() * 1000L);
case STRING:
log.debug("Trying to convert string value {} to date", value);
/** let's assume we received an ISO 8601 format date */
try {
DatatypeFactory datatypeFactory = DatatypeFactory.newInstance();
XMLGregorianCalendar cal = datatypeFactory.newXMLGregorianCalendar((String) value);
return cal.toGregorianCalendar().getTime();
} catch (DatatypeConfigurationException | IllegalArgumentException e) {
return new Date(Long.decode(value.toString()));
// DatatypeFactory datatypeFactory = DatatypeFactory.newInstance();
// XMLGregorianCalendar cal = datatypeFactory.newXMLGregorianCalendar((String) value);
// return cal.toGregorianCalendar().getTime();
} catch (IllegalArgumentException e) {
log.debug("Unable to convert string to date", e);
throw new CodecException("Unable to convert string (%s) to date for resource %s", value,
resourcePath);
@ -147,6 +146,8 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter {
return formatter.format(new Date(timeValue));
case OPAQUE:
return Hex.encodeHexString((byte[])value);
case OBJLNK:
return ObjectLink.decodeFromString((String) value);
default:
break;
}
@ -164,10 +165,14 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter {
}
}
break;
case OBJLNK:
if (currentType == Type.STRING) {
return ObjectLink.fromPath(value.toString());
}
default:
}
throw new CodecException("Invalid value type for resource %s, expected %s, got %s", resourcePath, expectedType,
currentType);
}
}
}

22
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.transport.mqtt.adaptors;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.protobuf.Descriptors;
import com.google.protobuf.DynamicMessage;
@ -64,9 +65,9 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
public TransportProtos.PostAttributeMsg convertToPostAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
DeviceSessionCtx deviceSessionCtx = (DeviceSessionCtx) ctx;
byte[] bytes = toBytes(inbound.payload());
Descriptors.Descriptor attributesDynamicMessage = getDescriptor(deviceSessionCtx.getAttributesDynamicMessageDescriptor());
Descriptors.Descriptor attributesDynamicMessageDescriptor = getDescriptor(deviceSessionCtx.getAttributesDynamicMessageDescriptor());
try {
return JsonConverter.convertToAttributesProto(new JsonParser().parse(dynamicMsgToJson(bytes, attributesDynamicMessage)));
return JsonConverter.convertToAttributesProto(new JsonParser().parse(dynamicMsgToJson(bytes, attributesDynamicMessageDescriptor)));
} catch (Exception e) {
throw new AdaptorException(e);
}
@ -86,8 +87,8 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException {
byte[] bytes = toBytes(inbound.payload());
String topicName = inbound.variableHeader().topicName();
int requestId = getRequestId(topicName, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX);
try {
int requestId = getRequestId(topicName, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX);
return ProtoConverter.convertToGetAttributeRequestMessage(bytes, requestId);
} catch (InvalidProtocolBufferException e) {
log.warn("Failed to decode get attributes request", e);
@ -97,10 +98,15 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
@Override
public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(MqttDeviceAwareSessionContext ctx, MqttPublishMessage mqttMsg) throws AdaptorException {
DeviceSessionCtx deviceSessionCtx = (DeviceSessionCtx) ctx;
String topicName = mqttMsg.variableHeader().topicName();
byte[] bytes = toBytes(mqttMsg.payload());
Descriptors.Descriptor rpcResponseDynamicMessageDescriptor = getDescriptor(deviceSessionCtx.getRpcResponseDynamicMessageDescriptor());
try {
return TransportProtos.ToDeviceRpcResponseMsg.parseFrom(bytes);
} catch (RuntimeException | InvalidProtocolBufferException e) {
int requestId = getRequestId(topicName, MqttTopics.DEVICE_RPC_RESPONSE_TOPIC);
JsonElement response = new JsonParser().parse(dynamicMsgToJson(bytes, rpcResponseDynamicMessageDescriptor));
return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(response.toString()).build();
} catch (Exception e) {
log.warn("Failed to decode Rpc response", e);
throw new AdaptorException(e);
}
@ -145,8 +151,10 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
@Override
public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) {
return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC + rpcRequest.getRequestId(), ProtoConverter.convertToRpcRequest(rpcRequest)));
public Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) throws AdaptorException {
DeviceSessionCtx deviceSessionCtx = (DeviceSessionCtx) ctx;
DynamicMessage.Builder rpcRequestDynamicMessageBuilder = deviceSessionCtx.getRpcRequestDynamicMessageBuilder();
return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC + rpcRequest.getRequestId(), ProtoConverter.convertToRpcRequest(rpcRequest, rpcRequestDynamicMessageBuilder)));
}
@Override

13
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java

@ -16,6 +16,7 @@
package org.thingsboard.server.transport.mqtt.session;
import com.google.protobuf.Descriptors;
import com.google.protobuf.DynamicMessage;
import io.netty.channel.ChannelHandlerContext;
import lombok.Getter;
import lombok.Setter;
@ -60,6 +61,8 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
private volatile TransportPayloadType payloadType = TransportPayloadType.JSON;
private volatile Descriptors.Descriptor attributesDynamicMessageDescriptor;
private volatile Descriptors.Descriptor telemetryDynamicMessageDescriptor;
private volatile Descriptors.Descriptor rpcResponseDynamicMessageDescriptor;
private volatile DynamicMessage.Builder rpcRequestDynamicMessageBuilder;
@Getter
@Setter
@ -102,6 +105,14 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
return attributesDynamicMessageDescriptor;
}
public Descriptors.Descriptor getRpcResponseDynamicMessageDescriptor() {
return rpcResponseDynamicMessageDescriptor;
}
public DynamicMessage.Builder getRpcRequestDynamicMessageBuilder() {
return rpcRequestDynamicMessageBuilder;
}
@Override
public void setDeviceProfile(DeviceProfile deviceProfile) {
super.setDeviceProfile(deviceProfile);
@ -136,5 +147,7 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
ProtoTransportPayloadConfiguration protoTransportPayloadConfig = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration;
telemetryDynamicMessageDescriptor = protoTransportPayloadConfig.getTelemetryDynamicMessageDescriptor(protoTransportPayloadConfig.getDeviceTelemetryProtoSchema());
attributesDynamicMessageDescriptor = protoTransportPayloadConfig.getAttributesDynamicMessageDescriptor(protoTransportPayloadConfig.getDeviceAttributesProtoSchema());
rpcResponseDynamicMessageDescriptor = protoTransportPayloadConfig.getRpcResponseDynamicMessageDescriptor(protoTransportPayloadConfig.getDeviceRpcResponseProtoSchema());
rpcRequestDynamicMessageBuilder = protoTransportPayloadConfig.getRpcRequestDynamicMessageBuilder(protoTransportPayloadConfig.getDeviceRpcRequestProtoSchema());
}
}

4
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java

@ -31,4 +31,8 @@ public class AdaptorException extends Exception {
super(cause);
}
public AdaptorException(String message, Exception cause) {
super(message, cause);
}
}

38
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java

@ -15,10 +15,13 @@
*/
package org.thingsboard.server.common.transport.adaptor;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.google.protobuf.DynamicMessage;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.util.JsonFormat;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
@ -34,6 +37,7 @@ import java.util.List;
@Slf4j
public class ProtoConverter {
public static final Gson GSON = new Gson();
public static final JsonParser JSON_PARSER = new JsonParser();
public static TransportProtos.PostTelemetryMsg convertToTelemetryProto(byte[] payload) throws InvalidProtocolBufferException, IllegalArgumentException {
@ -170,26 +174,20 @@ public class ProtoConverter {
return kvList;
}
public static byte[] convertToRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRpcRequestMsg) {
TransportProtos.ToDeviceRpcRequestMsg.Builder toDeviceRpcRequestMsgBuilder = toDeviceRpcRequestMsg.newBuilderForType();
toDeviceRpcRequestMsgBuilder.mergeFrom(toDeviceRpcRequestMsg);
toDeviceRpcRequestMsgBuilder.setParams(parseParams(toDeviceRpcRequestMsg));
TransportProtos.ToDeviceRpcRequestMsg result = toDeviceRpcRequestMsgBuilder.build();
return result.toByteArray();
}
private static String parseParams(TransportProtos.ToDeviceRpcRequestMsg toDeviceRpcRequestMsg) {
public static byte[] convertToRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRpcRequestMsg, DynamicMessage.Builder rpcRequestDynamicMessageBuilder) throws AdaptorException {
rpcRequestDynamicMessageBuilder.clear();
JsonObject rpcRequestJson = new JsonObject();
rpcRequestJson.addProperty("method", toDeviceRpcRequestMsg.getMethodName());
rpcRequestJson.addProperty("requestId", toDeviceRpcRequestMsg.getRequestId());
String params = toDeviceRpcRequestMsg.getParams();
JsonElement jsonElementParams = JSON_PARSER.parse(params);
if (!jsonElementParams.isJsonPrimitive()) {
return params;
} else {
JsonPrimitive primitiveParams = jsonElementParams.getAsJsonPrimitive();
if (jsonElementParams.getAsJsonPrimitive().isString()) {
return primitiveParams.getAsString();
} else {
return params;
}
try {
JsonElement paramsElement = JSON_PARSER.parse(params);
rpcRequestJson.add("params", paramsElement);
JsonFormat.parser().ignoringUnknownFields().merge(GSON.toJson(rpcRequestJson), rpcRequestDynamicMessageBuilder);
DynamicMessage dynamicRpcRequest = rpcRequestDynamicMessageBuilder.build();
return dynamicRpcRequest.toByteArray();
} catch (Exception e) {
throw new AdaptorException("Failed to convert ToDeviceRpcRequestMsg to Dynamic Rpc request message due to: ", e);
}
}
}

2
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigServer.java

@ -207,6 +207,8 @@ public class LwM2MTransportConfigServer {
FULL_FILE_PATH = Paths.get(BASE_DIR_PATH.replaceAll("bin$", ""));
} else if (BASE_DIR_PATH.endsWith("conf")) {
FULL_FILE_PATH = Paths.get(BASE_DIR_PATH.replaceAll("conf$", ""));
} else if (BASE_DIR_PATH.endsWith("application")) {
FULL_FILE_PATH = Paths.get(BASE_DIR_PATH.substring(0, BASE_DIR_PATH.length() - "application".length()));
} else {
FULL_FILE_PATH = Paths.get(BASE_DIR_PATH);
}

4
dao/pom.xml

@ -227,10 +227,6 @@
<groupId>org.elasticsearch.client</groupId>
<artifactId>rest</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.leshan</groupId>
<artifactId>leshan-core</artifactId>
</dependency>
</dependencies>
<build>
<plugins>

54
dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java

@ -15,6 +15,8 @@
*/
package org.thingsboard.server.dao.device;
import com.google.protobuf.Descriptors;
import com.google.protobuf.DynamicMessage;
import com.squareup.wire.Syntax;
import com.squareup.wire.schema.Field;
import com.squareup.wire.schema.Location;
@ -87,6 +89,8 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
private static final Location LOCATION = new Location("", "", -1, -1);
private static final String ATTRIBUTES_PROTO_SCHEMA = "attributes proto schema";
private static final String TELEMETRY_PROTO_SCHEMA = "telemetry proto schema";
private static final String RPC_REQUEST_PROTO_SCHEMA = "rpc request proto schema";
private static final String RPC_RESPONSE_PROTO_SCHEMA = "rpc response proto schema";
private static String invalidSchemaProvidedMessage(String schemaName) {
return "[Transport Configuration] invalid " + schemaName + " provided!";
@ -358,9 +362,10 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
if (transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) {
MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration;
if (mqttTransportConfiguration.getTransportPayloadTypeConfiguration() instanceof ProtoTransportPayloadConfiguration) {
ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration =
ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration =
(ProtoTransportPayloadConfiguration) mqttTransportConfiguration.getTransportPayloadTypeConfiguration();
validateProtoSchemas(protoTransportPayloadTypeConfiguration);
validateProtoSchemas(protoTransportPayloadConfiguration);
validateRpcRequestDynamicMessageFields(protoTransportPayloadConfiguration);
}
} else if (transportConfiguration instanceof CoapDeviceProfileTransportConfiguration) {
CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration;
@ -371,6 +376,7 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
if (transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration) {
ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration;
validateProtoSchemas(protoTransportPayloadConfiguration);
validateRpcRequestDynamicMessageFields(protoTransportPayloadConfiguration);
}
}
}
@ -418,6 +424,8 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
try {
validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceAttributesProtoSchema(), ATTRIBUTES_PROTO_SCHEMA);
validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceTelemetryProtoSchema(), TELEMETRY_PROTO_SCHEMA);
validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceRpcRequestProtoSchema(), RPC_REQUEST_PROTO_SCHEMA);
validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceRpcResponseProtoSchema(), RPC_RESPONSE_PROTO_SCHEMA);
} catch (Exception exception) {
throw new DataValidationException(exception.getMessage());
}
@ -540,6 +548,48 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
};
private void validateRpcRequestDynamicMessageFields(ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration) {
DynamicMessage.Builder rpcRequestDynamicMessageBuilder = protoTransportPayloadTypeConfiguration.getRpcRequestDynamicMessageBuilder(protoTransportPayloadTypeConfiguration.getDeviceRpcRequestProtoSchema());
Descriptors.Descriptor rpcRequestDynamicMessageDescriptor = rpcRequestDynamicMessageBuilder.getDescriptorForType();
if (rpcRequestDynamicMessageDescriptor == null) {
throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Failed to get rpcRequestDynamicMessageDescriptor!");
} else {
if (CollectionUtils.isEmpty(rpcRequestDynamicMessageDescriptor.getFields()) || rpcRequestDynamicMessageDescriptor.getFields().size() != 3) {
throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " " + rpcRequestDynamicMessageDescriptor.getName() + " message should always contains 3 fields: method, requestId and params!");
}
Descriptors.FieldDescriptor methodFieldDescriptor = rpcRequestDynamicMessageDescriptor.findFieldByName("method");
if (methodFieldDescriptor == null) {
throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Failed to get field descriptor for field: method!");
} else {
if (!Descriptors.FieldDescriptor.Type.STRING.equals(methodFieldDescriptor.getType())) {
throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'method' has invalid data type. Only string type is supported!");
}
if (methodFieldDescriptor.isRepeated()) {
throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'method' has invalid label!");
}
}
Descriptors.FieldDescriptor requestIdFieldDescriptor = rpcRequestDynamicMessageDescriptor.findFieldByName("requestId");
if (requestIdFieldDescriptor == null) {
throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Failed to get field descriptor for field: requestId!");
} else {
if (!Descriptors.FieldDescriptor.Type.INT32.equals(requestIdFieldDescriptor.getType())) {
throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'requestId' has invalid data type. Only int32 type is supported!");
}
if (requestIdFieldDescriptor.isRepeated()) {
throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'requestId' has invalid label!");
}
}
Descriptors.FieldDescriptor paramsFieldDescriptor = rpcRequestDynamicMessageDescriptor.findFieldByName("params");
if (paramsFieldDescriptor == null) {
throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Failed to get field descriptor for field: params!");
} else {
if (paramsFieldDescriptor.isRepeated()) {
throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + "Field 'params' has invalid label!");
}
}
}
}
private PaginatedRemover<TenantId, DeviceProfile> tenantDeviceProfilesRemover =
new PaginatedRemover<TenantId, DeviceProfile>() {

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save