Device API Controller

This commit is contained in:
Andrii Shvaika 2021-10-25 18:51:23 +03:00
parent 38a3ca682f
commit d626ae0364

View File

@ -17,6 +17,8 @@ package org.thingsboard.server.transport.http;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@ -68,6 +70,7 @@ import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
/**
* @author Andrew Shvayka
*/
@ -77,14 +80,70 @@ import java.util.function.Consumer;
@Slf4j
public class DeviceApiController implements TbTransportService {
private static final String MARKDOWN_CODE_BLOCK_START = "\n\n```json\n";
private static final String MARKDOWN_CODE_BLOCK_END = "\n```\n\n";
private static final String REQUIRE_ACCESS_TOKEN = "The API call is designed to be used by device firmware and requires device access token ('deviceToken'). " +
"It is not recommended to use this API call by third-party scripts, rule-engine or platform widgets (use 'Telemetry Controller' instead).\n";
private static final String ATTRIBUTE_PAYLOAD_EXAMPLE = "{\n" +
" \"stringKey\":\"value1\", \n" +
" \"booleanKey\":true, \n" +
" \"doubleKey\":42.0, \n" +
" \"longKey\":73, \n" +
" \"jsonKey\": {\n" +
" \"someNumber\": 42,\n" +
" \"someArray\": [1,2,3],\n" +
" \"someNestedObject\": {\"key\": \"value\"}\n" +
" }\n" +
"}";
protected static final String TS_PAYLOAD = "The request payload is a JSON document with three possible formats:\n\n" +
"Simple format without timestamp. In such a case, current server time will be used: \n\n" +
MARKDOWN_CODE_BLOCK_START +
"{\n" +
" \"stringKey\":\"value1\", \n" +
" \"booleanKey\":true, \n" +
" \"doubleKey\":42.0, \n" +
" \"longKey\":73, \n" +
" \"jsonKey\": {\n" +
" \"someNumber\": 42,\n" +
" \"someArray\": [1,2,3],\n" +
" \"someNestedObject\": {\"key\": \"value\"}\n" +
" }\n" +
"}" +
MARKDOWN_CODE_BLOCK_END +
"\n\n Single JSON object with timestamp: \n\n" +
MARKDOWN_CODE_BLOCK_START +
"{\"ts\":1634712287000,\"values\":{\"temperature\":26, \"humidity\":87}}" +
MARKDOWN_CODE_BLOCK_END +
"\n\n JSON array with timestamps: \n\n" +
MARKDOWN_CODE_BLOCK_START +
"[\n{\"ts\":1634712287000,\"values\":{\"temperature\":26, \"humidity\":87}}, \n{\"ts\":1634712588000,\"values\":{\"temperature\":25, \"humidity\":88}}\n]" +
MARKDOWN_CODE_BLOCK_END;
private static final String ACCESS_TOKEN_PARAM_DESCRIPTION = "Your device access token.";
@Autowired
private HttpTransportContext transportContext;
@ApiOperation(value = "Get attributes (getDeviceAttributes)",
notes = "Returns all attributes that belong to device. "
+ "Use optional 'clientKeys' and/or 'sharedKeys' parameter to return specific attributes. "
+ "\n Example of the result: "
+ MARKDOWN_CODE_BLOCK_START
+ ATTRIBUTE_PAYLOAD_EXAMPLE
+ MARKDOWN_CODE_BLOCK_END
+ REQUIRE_ACCESS_TOKEN,
produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(value = "/{deviceToken}/attributes", method = RequestMethod.GET, produces = "application/json")
public DeferredResult<ResponseEntity> getDeviceAttributes(@PathVariable("deviceToken") String deviceToken,
@RequestParam(value = "clientKeys", required = false, defaultValue = "") String clientKeys,
@RequestParam(value = "sharedKeys", required = false, defaultValue = "") String sharedKeys,
HttpServletRequest httpRequest) {
public DeferredResult<ResponseEntity> getDeviceAttributes(
@ApiParam(value = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true, defaultValue = "YOUR_DEVICE_ACCESS_TOKEN")
@PathVariable("deviceToken") String deviceToken,
@ApiParam(value = "Comma separated key names for attribute with client scope", required = true, defaultValue = "state")
@RequestParam(value = "clientKeys", required = false, defaultValue = "") String clientKeys,
@ApiParam(value = "Comma separated key names for attribute with shared scope", required = true, defaultValue = "configuration")
@RequestParam(value = "sharedKeys", required = false, defaultValue = "") String sharedKeys) {
DeferredResult<ResponseEntity> responseWriter = new DeferredResult<>();
transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
@ -106,9 +165,20 @@ public class DeviceApiController implements TbTransportService {
return responseWriter;
}
@ApiOperation(value = "Post attributes (postDeviceAttributes)",
notes = "Post client attribute updates on behalf of device. "
+ "\n Example of the request: "
+ MARKDOWN_CODE_BLOCK_START
+ ATTRIBUTE_PAYLOAD_EXAMPLE
+ MARKDOWN_CODE_BLOCK_END
+ REQUIRE_ACCESS_TOKEN,
produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(value = "/{deviceToken}/attributes", method = RequestMethod.POST)
public DeferredResult<ResponseEntity> postDeviceAttributes(@PathVariable("deviceToken") String deviceToken,
@RequestBody String json, HttpServletRequest request) {
public DeferredResult<ResponseEntity> postDeviceAttributes(
@ApiParam(value = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true, defaultValue = "YOUR_DEVICE_ACCESS_TOKEN")
@PathVariable("deviceToken") String deviceToken,
@ApiParam(value = "JSON with attribute key-value pairs. See API call description for example.")
@RequestBody String json) {
DeferredResult<ResponseEntity> responseWriter = new DeferredResult<>();
transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
@ -119,9 +189,17 @@ public class DeviceApiController implements TbTransportService {
return responseWriter;
}
@ApiOperation(value = "Post time-series data (postTelemetry)",
notes = "Post time-series data on behalf of device. "
+ "\n Example of the request: "
+ TS_PAYLOAD
+ REQUIRE_ACCESS_TOKEN,
produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(value = "/{deviceToken}/telemetry", method = RequestMethod.POST)
public DeferredResult<ResponseEntity> postTelemetry(@PathVariable("deviceToken") String deviceToken,
@RequestBody String json, HttpServletRequest request) {
public DeferredResult<ResponseEntity> postTelemetry(
@ApiParam(value = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true, defaultValue = "YOUR_DEVICE_ACCESS_TOKEN")
@PathVariable("deviceToken") String deviceToken,
@RequestBody String json, HttpServletRequest request) {
DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
@ -132,9 +210,22 @@ public class DeviceApiController implements TbTransportService {
return responseWriter;
}
@ApiOperation(value = "Save claiming information (claimDevice)",
notes = "Saves the information required for user to claim the device. " +
"See more info about claiming in the corresponding 'Claiming devices' platform documentation."
+ "\n Example of the request payload: "
+ MARKDOWN_CODE_BLOCK_START
+ "{\"secretKey\":\"value\", \"durationMs\":60000}"
+ MARKDOWN_CODE_BLOCK_END
+ "Note: both 'secretKey' and 'durationMs' is optional parameters. " +
"In case the secretKey is not specified, the empty string as a default value is used. In case the durationMs is not specified, the system parameter device.claim.duration is used.\n\n"
+ REQUIRE_ACCESS_TOKEN,
produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(value = "/{deviceToken}/claim", method = RequestMethod.POST)
public DeferredResult<ResponseEntity> claimDevice(@PathVariable("deviceToken") String deviceToken,
@RequestBody(required = false) String json, HttpServletRequest request) {
public DeferredResult<ResponseEntity> claimDevice(
@ApiParam(value = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true, defaultValue = "YOUR_DEVICE_ACCESS_TOKEN")
@PathVariable("deviceToken") String deviceToken,
@RequestBody(required = false) String json) {
DeferredResult<ResponseEntity> responseWriter = new DeferredResult<>();
transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
@ -146,10 +237,18 @@ public class DeviceApiController implements TbTransportService {
return responseWriter;
}
@ApiOperation(value = "Subscribe to RPC commands (subscribeToCommands) (Deprecated)",
notes = "Subscribes to RPC commands using http long polling. " +
"Deprecated, since long polling is resource and network consuming. " +
"Consider using MQTT or CoAP protocol for light-weight real-time updates. \n\n" +
REQUIRE_ACCESS_TOKEN,
produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(value = "/{deviceToken}/rpc", method = RequestMethod.GET, produces = "application/json")
public DeferredResult<ResponseEntity> subscribeToCommands(@PathVariable("deviceToken") String deviceToken,
@RequestParam(value = "timeout", required = false, defaultValue = "0") long timeout,
HttpServletRequest httpRequest) {
public DeferredResult<ResponseEntity> subscribeToCommands(
@ApiParam(value = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true, defaultValue = "YOUR_DEVICE_ACCESS_TOKEN")
@PathVariable("deviceToken") String deviceToken,
@ApiParam(value = "Optional timeout of the long poll. Typically less then 60 seconds, since limited on the server side.")
@RequestParam(value = "timeout", required = false, defaultValue = "0") long timeout) {
DeferredResult<ResponseEntity> responseWriter = new DeferredResult<>();
transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
@ -164,10 +263,18 @@ public class DeviceApiController implements TbTransportService {
return responseWriter;
}
@ApiOperation(value = "Reply to RPC commands (replyToCommand)",
notes = "Replies to server originated RPC command identified by 'requestId' parameter. The response is arbitrary JSON.\n\n" +
REQUIRE_ACCESS_TOKEN,
produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(value = "/{deviceToken}/rpc/{requestId}", method = RequestMethod.POST)
public DeferredResult<ResponseEntity> replyToCommand(@PathVariable("deviceToken") String deviceToken,
@PathVariable("requestId") Integer requestId,
@RequestBody String json, HttpServletRequest request) {
public DeferredResult<ResponseEntity> replyToCommand(
@ApiParam(value = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true, defaultValue = "YOUR_DEVICE_ACCESS_TOKEN")
@PathVariable("deviceToken") String deviceToken,
@ApiParam(value = "RPC request id from the incoming RPC request", required = true, defaultValue = "123")
@PathVariable("requestId") Integer requestId,
@ApiParam(value = "Reply to the RPC request, JSON. For example: {\"status\":\"success\"}", required = true)
@RequestBody String json) {
DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
@ -177,9 +284,23 @@ public class DeviceApiController implements TbTransportService {
return responseWriter;
}
@ApiOperation(value = "Send the RPC command (postRpcRequest)",
notes = "Send the RPC request to server. The request payload is a JSON document that contains 'method' and 'params'. For example:" +
MARKDOWN_CODE_BLOCK_START +
"{\"method\": \"sumOnServer\", \"params\":{\"a\":2, \"b\":2}}" +
MARKDOWN_CODE_BLOCK_END +
"The response contains arbitrary JSON with the RPC reply. For example: " +
MARKDOWN_CODE_BLOCK_START +
"{\"result\": 4}" +
MARKDOWN_CODE_BLOCK_END +
REQUIRE_ACCESS_TOKEN,
produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(value = "/{deviceToken}/rpc", method = RequestMethod.POST)
public DeferredResult<ResponseEntity> postRpcRequest(@PathVariable("deviceToken") String deviceToken,
@RequestBody String json, HttpServletRequest httpRequest) {
public DeferredResult<ResponseEntity> postRpcRequest(
@ApiParam(value = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true, defaultValue = "YOUR_DEVICE_ACCESS_TOKEN")
@PathVariable("deviceToken") String deviceToken,
@ApiParam(value = "The RPC request JSON", required = true)
@RequestBody String json) {
DeferredResult<ResponseEntity> responseWriter = new DeferredResult<ResponseEntity>();
transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
@ -196,10 +317,18 @@ public class DeviceApiController implements TbTransportService {
return responseWriter;
}
@ApiOperation(value = "Subscribe to attribute updates (subscribeToAttributes) (Deprecated)",
notes = "Subscribes to client and shared scope attribute updates using http long polling. " +
"Deprecated, since long polling is resource and network consuming. " +
"Consider using MQTT or CoAP protocol for light-weight real-time updates. \n\n" +
REQUIRE_ACCESS_TOKEN,
produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(value = "/{deviceToken}/attributes/updates", method = RequestMethod.GET, produces = "application/json")
public DeferredResult<ResponseEntity> subscribeToAttributes(@PathVariable("deviceToken") String deviceToken,
@RequestParam(value = "timeout", required = false, defaultValue = "0") long timeout,
HttpServletRequest httpRequest) {
public DeferredResult<ResponseEntity> subscribeToAttributes(
@ApiParam(value = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true, defaultValue = "YOUR_DEVICE_ACCESS_TOKEN")
@PathVariable("deviceToken") String deviceToken,
@ApiParam(value = "Optional timeout of the long poll. Typically less then 60 seconds, since limited on the server side.")
@RequestParam(value = "timeout", required = false, defaultValue = "0") long timeout) {
DeferredResult<ResponseEntity> responseWriter = new DeferredResult<>();
transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(),
new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> {
@ -214,26 +343,85 @@ public class DeviceApiController implements TbTransportService {
return responseWriter;
}
@ApiOperation(value = "Get Device Firmware (getFirmware)",
notes = "Downloads the current firmware package." +
"When the platform initiates firmware update, " +
"it informs the device by updating the 'fw_title', 'fw_version', 'fw_checksum' and 'fw_checksum_algorithm' shared attributes." +
"The 'fw_title' and 'fw_version' parameters must be supplied in this request to double-check " +
"that the firmware that device is downloading matches the firmware it expects to download. " +
"This is important, since the administrator may change the firmware assignment while device is downloading the firmware. \n\n" +
"Optional 'chunk' and 'size' parameters may be used to download the firmware in chunks. " +
"For example, device may request first 16 KB of firmware using 'chunk'=0 and 'size'=16384. " +
"Next 16KB using 'chunk'=1 and 'size'=16384. The last chunk should have less bytes then requested using 'size' parameter. \n\n" +
REQUIRE_ACCESS_TOKEN,
produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(value = "/{deviceToken}/firmware", method = RequestMethod.GET)
public DeferredResult<ResponseEntity> getFirmware(@PathVariable("deviceToken") String deviceToken,
@RequestParam(value = "title") String title,
@RequestParam(value = "version") String version,
@RequestParam(value = "size", required = false, defaultValue = "0") int size,
@RequestParam(value = "chunk", required = false, defaultValue = "0") int chunk) {
public DeferredResult<ResponseEntity> getFirmware(
@ApiParam(value = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true, defaultValue = "YOUR_DEVICE_ACCESS_TOKEN")
@PathVariable("deviceToken") String deviceToken,
@ApiParam(value = "Title of the firmware, corresponds to the value of 'fw_title' attribute.", required = true)
@RequestParam(value = "title") String title,
@ApiParam(value = "Version of the firmware, corresponds to the value of 'fw_version' attribute.", required = true)
@RequestParam(value = "version") String version,
@ApiParam(value = "Size of the chunk. Optional. Omit to download the entire file without chunks.")
@RequestParam(value = "size", required = false, defaultValue = "0") int size,
@ApiParam(value = "Index of the chunk. Optional. Omit to download the entire file without chunks.")
@RequestParam(value = "chunk", required = false, defaultValue = "0") int chunk) {
return getOtaPackageCallback(deviceToken, title, version, size, chunk, OtaPackageType.FIRMWARE);
}
@ApiOperation(value = "Get Device Software (getSoftware)",
notes = "Downloads the current software package." +
"When the platform initiates software update, " +
"it informs the device by updating the 'sw_title', 'sw_version', 'sw_checksum' and 'sw_checksum_algorithm' shared attributes." +
"The 'sw_title' and 'sw_version' parameters must be supplied in this request to double-check " +
"that the software that device is downloading matches the software it expects to download. " +
"This is important, since the administrator may change the software assignment while device is downloading the software. \n\n" +
"Optional 'chunk' and 'size' parameters may be used to download the software in chunks. " +
"For example, device may request first 16 KB of software using 'chunk'=0 and 'size'=16384. " +
"Next 16KB using 'chunk'=1 and 'size'=16384. The last chunk should have less bytes then requested using 'size' parameter. \n\n" +
REQUIRE_ACCESS_TOKEN,
produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(value = "/{deviceToken}/software", method = RequestMethod.GET)
public DeferredResult<ResponseEntity> getSoftware(@PathVariable("deviceToken") String deviceToken,
@RequestParam(value = "title") String title,
@RequestParam(value = "version") String version,
@RequestParam(value = "size", required = false, defaultValue = "0") int size,
@RequestParam(value = "chunk", required = false, defaultValue = "0") int chunk) {
public DeferredResult<ResponseEntity> getSoftware(
@ApiParam(value = ACCESS_TOKEN_PARAM_DESCRIPTION, required = true, defaultValue = "YOUR_DEVICE_ACCESS_TOKEN")
@PathVariable("deviceToken") String deviceToken,
@ApiParam(value = "Title of the software, corresponds to the value of 'sw_title' attribute.", required = true)
@RequestParam(value = "title") String title,
@ApiParam(value = "Version of the software, corresponds to the value of 'sw_version' attribute.", required = true)
@RequestParam(value = "version") String version,
@ApiParam(value = "Size of the chunk. Optional. Omit to download the entire file without using chunks.")
@RequestParam(value = "size", required = false, defaultValue = "0") int size,
@ApiParam(value = "Index of the chunk. Optional. Omit to download the entire file without using chunks.")
@RequestParam(value = "chunk", required = false, defaultValue = "0") int chunk) {
return getOtaPackageCallback(deviceToken, title, version, size, chunk, OtaPackageType.SOFTWARE);
}
@ApiOperation(value = "Provision new device (provisionDevice)",
notes = "Exchange the provision request to the device credentials. " +
"See more info about provisioning in the corresponding 'Device provisioning' platform documentation." +
"Requires valid JSON request with the following format: " +
MARKDOWN_CODE_BLOCK_START +
"{\n" +
" \"deviceName\": \"NEW_DEVICE_NAME\",\n" +
" \"provisionDeviceKey\": \"u7piawkboq8v32dmcmpp\",\n" +
" \"provisionDeviceSecret\": \"jpmwdn8ptlswmf4m29bw\"\n" +
"}" +
MARKDOWN_CODE_BLOCK_END +
"Where 'deviceName' is the name of enw or existing device which depends on the provisioning strategy. " +
"The 'provisionDeviceKey' and 'provisionDeviceSecret' matches info configured in one of the existing device profiles. " +
"The result of the successful call is the JSON object that contains new credentials:" +
MARKDOWN_CODE_BLOCK_START + "{\n" +
" \"credentialsType\":\"ACCESS_TOKEN\",\n" +
" \"credentialsValue\":\"DEVICE_ACCESS_TOKEN\",\n" +
" \"status\":\"SUCCESS\"\n" +
"}" + MARKDOWN_CODE_BLOCK_END
,
produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(value = "/provision", method = RequestMethod.POST)
public DeferredResult<ResponseEntity> provisionDevice(@RequestBody String json, HttpServletRequest httpRequest) {
public DeferredResult<ResponseEntity> provisionDevice(
@ApiParam(value = "JSON with provision request. See API call description for example.")
@RequestBody String json) {
DeferredResult<ResponseEntity> responseWriter = new DeferredResult<>();
transportContext.getTransportService().process(JsonConverter.convertToProvisionRequestMsg(json),
new DeviceProvisionCallback(responseWriter));