From ade9ab7e9415943e839122895f76a095c623f5e6 Mon Sep 17 00:00:00 2001 From: Andry925 Date: Fri, 26 Dec 2025 15:49:42 +0200 Subject: [PATCH 1/2] add documentation section for multipart/form-data functionality --- .../iot-gateway/rest-converter-json-config.md | 189 +++++++++++------- 1 file changed, 120 insertions(+), 69 deletions(-) diff --git a/_includes/templates/iot-gateway/rest-converter-json-config.md b/_includes/templates/iot-gateway/rest-converter-json-config.md index 6025bca112..6ba824740c 100644 --- a/_includes/templates/iot-gateway/rest-converter-json-config.md +++ b/_includes/templates/iot-gateway/rest-converter-json-config.md @@ -23,49 +23,99 @@ Json converter is default converter, it looks for deviceName, deviceType, attrib {% endcapture %} {% include templates/info-banner.md content=difference %} +In addition, you can send **multipart/form-data** requests while still using the `json` converter - no additional configuration is required. +The REST connector merges multipart fields into a single key-value object, so you can reference them the same way as with **application/json**. -Mapping subsection looks like: +Below are two equivalent requests. The first one sends data as `application/json`: ```json - { - "endpoint": "/test_device", - "HTTPMethod": [ - "POST" - ], - "security": +{ + "name": "SensorA", + "sensorModel": "TX100", + "temp": 23.5, + "hum": 56.2, + "timestampField": "10.11.24 14:30:00.000" +} +``` +Send this request to the `/test_device` endpoint with the POST method, using Basic Auth (user / passwd): + +```bash +curl -X POST http://127.0.0.1:5000/test_device \ + -H "Content-Type: application/json" \ + -u user:passwd \ + -d '{ + "name": "SensorA", + "sensorModel": "TX100", + "temp": 23.5, + "hum": 56.2, + "timestampField": "10.11.24 14:30:00.000" + }' +``` +{: .copy-code} + +You can achieve the same result using a `multipart/form-data` request, where each field is sent as a separate part. +Note: when using `curl -F`, `curl` automatically sets the `Content-Type: multipart/form-data; boundary=...` header. + +```bash +curl -X POST "http://127.0.0.1:5000/test_device" \ + -u "user:passwd" \ + -F 'name=SensorA' \ + -F 'sensorModel=TX100' \ + -F 'temp=23.5' \ + -F 'hum=56.2' \ + -F 'timestampField=10.11.24 14:30:00.000' +``` +{: .copy-code} + +{% capture difference %} +**Notes:** +1. **File uploads are not supported** for device attributes or telemetry. If a multipart request contains file parts, the gateway will ignore them and log an info message. +2. **Multipart form fields are received as strings.** Values will be converted to boolean, integer, or double according to the configured field type in the `json` converter. +{% endcapture %} +{% include templates/info-banner.md content=difference %} + +This is how the mapping subsection for both examples above looks like: + +```json +{ + "endpoint": "/test_device", + "HTTPMethods": [ + "POST" + ], + "security": { + "type": "basic", + "username": "user", + "password": "passwd" + }, + "converter": { + "type": "json", + "deviceNameExpression": "Device ${name}", + "deviceTypeExpression": "default", + "attributes": [ { - "type": "basic", - "username": "user", - "password": "passwd" + "type": "string", + "key": "model", + "value": "${sensorModel}" + } + ], + "timeseries": [ + { + "type": "double", + "key": "temperature", + "value": "${temp}" }, - "converter": { - "type": "json", - "deviceNameExpression": "Device ${name}", - "deviceTypeExpression": "default", - "attributes": [ - { - "type": "string", - "key": "model", - "value": "${sensorModel}" - } - ], - "timeseries": [ - { - "type": "double", - "key": "temperature", - "value": "${temp}" - }, - { - "type": "double", - "key": "humidity", - "value": "${hum}", - "tsField": "${timestampField}", - "dayfirst": true - } - ] + { + "type": "double", + "key": "humidity", + "value": "${hum}", + "tsField": "${timestampField}", + "dayfirst": true } - } + ] + } +} ``` +{: .copy-code} {% capture tsField %} **Note**: In **Mapping subsection** configuration section for `humidity` timeseries key illustrates how to use the `tsField` and @@ -74,40 +124,41 @@ Mapping subsection looks like: {% include templates/info-banner.md content=tsField %} Also, you can combine values from HTTP request in attributes, telemetry and serverSideRpc section, for example: -{% highlight json %} + +```json { - "endpoint": "/test_device", - "HTTPMethod": [ - "POST" - ], - "security": + "endpoint": "/test_device", + "HTTPMethods": [ + "POST" + ], + "security": { + "type": "basic", + "username": "user", + "password": "passwd" + }, + "converter": { + "type": "json", + "deviceNameExpression": "Device ${name}", + "deviceTypeExpression": "default", + "attributes": [], + "timeseries": [ + { + "type": "double", + "key": "temperature", + "value": "${temp}" + }, { - "type": "basic", - "username": "user", - "password": "passwd" + "type": "double", + "key": "humidity", + "value": "${hum}" }, - "converter": { - "type": "json", - "deviceNameExpression": "Device ${name}", - "deviceTypeExpression": "default", - "attributes": [], - "timeseries": [ - { - "type": "double", - "key": "temperature", - "value": "${temp}" - }, - { - "type": "double", - "key": "humidity", - "value": "${hum}" - }, - { - "type": "string", - "key": "combine", - "value": "${hum}:${temp}" - } - ] + { + "type": "string", + "key": "combine", + "value": "${hum}:${temp}" } - } -{% endhighlight %} + ] + } +} +``` +{: .copy-code} From 04a9666a3eb34acaa7ca5ebcd2de336f13bd894e Mon Sep 17 00:00:00 2001 From: Andry925 Date: Mon, 5 Jan 2026 15:41:19 +0200 Subject: [PATCH 2/2] update configs for rest connector section --- ...est-connector-anonymous-security-config.md | 9 +- .../rest-connector-basic-security-config.md | 12 +- .../rest-connector-no-ssl-security-config.md | 13 ++- .../rest-connector-ssl-security-config.md | 16 ++- .../rest-converter-custom-config.md | 68 ++++++------ .../iot-gateway/rest-converter-json-config.md | 76 ++++++++----- docs/iot-gateway/config/rest.md | 104 +++++++++--------- 7 files changed, 171 insertions(+), 127 deletions(-) diff --git a/_includes/templates/iot-gateway/rest-connector-anonymous-security-config.md b/_includes/templates/iot-gateway/rest-connector-anonymous-security-config.md index c46e87e7d7..a13a734be1 100644 --- a/_includes/templates/iot-gateway/rest-connector-anonymous-security-config.md +++ b/_includes/templates/iot-gateway/rest-connector-anonymous-security-config.md @@ -11,7 +11,10 @@ Anonymous auth is the most simple option. It is useful for testing. Security subsection in configuration file will look like this: ```json - "security": { - "type": "anonymous" - } +{ + "security": { + "type": "anonymous" + } +} ``` +{: .copy-code} diff --git a/_includes/templates/iot-gateway/rest-connector-basic-security-config.md b/_includes/templates/iot-gateway/rest-connector-basic-security-config.md index c33937500d..231933a28a 100644 --- a/_includes/templates/iot-gateway/rest-connector-basic-security-config.md +++ b/_includes/templates/iot-gateway/rest-connector-basic-security-config.md @@ -12,11 +12,13 @@ The REST Connector waits for HTTP requests with the Authorization header that co Security section in configuration file will look like this: ```json - "security": { - "type": "basic", - "username": "username", - "password": "password" - } +{ + "security": { + "type": "basic", + "username": "username", + "password": "password" + } +} ``` Also, make sure that your request have `Authorization` header with provided credentials. diff --git a/_includes/templates/iot-gateway/rest-connector-no-ssl-security-config.md b/_includes/templates/iot-gateway/rest-connector-no-ssl-security-config.md index 0ec8b65a71..45034c5bac 100644 --- a/_includes/templates/iot-gateway/rest-connector-no-ssl-security-config.md +++ b/_includes/templates/iot-gateway/rest-connector-no-ssl-security-config.md @@ -8,8 +8,15 @@ Configuration section will look like: ```json { - "host": "127.0.0.1", - "port": "5000", - "SSL": false + "server": { + "host": "127.0.0.1", + "port": "5000", + "SSL": false, + "security": { + "cert": "~/ssl/cert.pem", + "key": "~/ssl/key.pem" + } + } } ``` +{: .copy-code} diff --git a/_includes/templates/iot-gateway/rest-connector-ssl-security-config.md b/_includes/templates/iot-gateway/rest-connector-ssl-security-config.md index 53534fb75e..59a8f9c4e1 100644 --- a/_includes/templates/iot-gateway/rest-connector-ssl-security-config.md +++ b/_includes/templates/iot-gateway/rest-connector-ssl-security-config.md @@ -8,14 +8,18 @@ |--- Configuration section will look like: + ```json { - "host": "127.0.0.1", - "port": "5000", - "SSL": true, - "security": { - "cert": "~/ssl/cert.pem", - "key": "~/ssl/key.pem" + "server": { + "host": "127.0.0.1", + "port": 5000, + "SSLVerify": true, + "security": { + "cert": "~/ssl/cert.pem", + "key": "~/ssl/key.pem" + } } } ``` +{: .copy-code} diff --git a/_includes/templates/iot-gateway/rest-converter-custom-config.md b/_includes/templates/iot-gateway/rest-converter-custom-config.md index 2ce6ee10d8..151b7e9a2d 100644 --- a/_includes/templates/iot-gateway/rest-converter-custom-config.md +++ b/_includes/templates/iot-gateway/rest-converter-custom-config.md @@ -2,21 +2,23 @@ A custom converter is converter written for some device: -|**Parameter**|**Default value**|**Description**| -|:-|:-|- -| type | **custom** | Provides information to connector that custom converter will be uses for converting data from request. | -| deviceNameExpression | **SuperAnonDevice** | Device name. | -| deviceTypeExpression | **default** | Devcie type. | -| extension | **CustomRESTUplinkConverter** | Name of custom converter class. | -| extension-config | | Configuration, for custom converter (You can put anything, there. It will be passed to the converter object on initialization). | -| key | **Totaliser** | | -| datatype | **float** | | -| fromByte | **0** | | -| toByte | **4** | | -| byteorder | **big** | | -| signed | **true** | | -| multiplier | **1** | | -|--- +| **Parameter** | **Default value** | **Description** | +|:------------------------------|:------------------------------|--------------------------------------------------------------------------------------------------------------------------------- +| type | **custom** | Provides information to connector that custom converter will be uses for converting data from request. | +| deviceNameExpression | **SuperAnonDevice** | Device name. | +| deviceNameExpressionSource | **constant** | Device name source. | +| deviceProfileExpressionSource | **constant** | Device profile source. | +| deviceProfileExpression | **default** | Device profile name. | +| extension | **CustomRESTUplinkConverter** | Name of custom converter class. | +| extension-config | | Configuration, for custom converter (You can put anything, there. It will be passed to the converter object on initialization). | +| key | **Totaliser** | | +| datatype | **float** | | +| fromByte | **0** | | +| toByte | **4** | | +| byteorder | **big** | | +| signed | **true** | | +| multiplier | **1** | | +| --- {% capture difference %} @@ -29,20 +31,24 @@ A custom converter is converter written for some device: Mapping subsection in the configuration looks like: ```json - "converter": { - "type": "custom", - "deviceNameExpression": "SuperAnonDevice", - "deviceTypeExpression": "default", - "extension": "CustomRestUplinkConverter", - "extension-config": [ - { - "key": "Totaliser", - "datatype": "float", - "fromByte": 0, - "toByte": 4, - "byteorder": "big", - "signed": true, - "multiplier": 1 - }] - } +"converter": { + "type": "custom", + "deviceInfo": { + "deviceNameExpression": "SuperAnonDevice", + "deviceNameExpressionSource": "constant", + "deviceProfileExpressionSource": "constant", + "deviceProfileExpression": "default" + }, + "extension": "CustomRestUplinkConverter", + "extensionConfig": { + "key": "Totaliser", + "datatype": "float", + "fromByte": 0, + "toByte": 4, + "byteorder": "big", + "signed": true, + "multiplier": 1 + } + } ``` +{: .copy-code} diff --git a/_includes/templates/iot-gateway/rest-converter-json-config.md b/_includes/templates/iot-gateway/rest-converter-json-config.md index 6ba824740c..104bdb0d12 100644 --- a/_includes/templates/iot-gateway/rest-converter-json-config.md +++ b/_includes/templates/iot-gateway/rest-converter-json-config.md @@ -1,22 +1,24 @@ Json converter is default converter, it looks for deviceName, deviceType, attributes and telemetry in the incoming request from the client, with rules, described in this subsection: -| **Parameter** | **Default value** | **Description** | -|:--------------------------|:------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -| type | **json** | Provides information to connector that default converter will be uses for converting data from the incoming request. | -| deviceNameExpression | **Device ${name}** | Simple JSON expression, uses for looking device name in the incoming message (value of the parameter "name" from the request will be used as device name). | -| deviceTypeExpression | **default** | Simple JSON expression, uses for looking device type in the incoming message (string "default" will be used as device type). | -| attributes | | This subsection contains parameters of the incoming requests, that will be interpreted as attributes for the device. | -| ... type | **string** | Type of incoming data for a current attribute. | -| ... key | **model** | Simple JSON expression, uses for looking key in the incoming data, that will send to ThingsBoard instance as attribute key. | -| ... value | **${sensorModel}** | Simple JSON expression, uses for looking value in the incoming data, that will send to ThingsBoard instance as value of key parameter. | -| timeseries | | This subsection contains parameters of the incoming message, that will be interpreted as telemetry for the device. | -| ... type | **double** | Type of incoming data for a current telemetry. | -| ... key | **temperature** | Simple JSON expression, uses for looking key in the incoming message, that will send to ThingsBoard instance as attribute key. | -| ... value | **${temp}** | Simple JSON expression, uses for looking value in the incoming message, that will send to ThingsBoard instance as value of key parameter. | -| ... tsField | **${timestampField}** | **Optional.** JSON-path expression for field that carries a datetime string. If not present, the `ts` or `timestamp` properties from incoming message will be used as timestamp for data entry. | -| ... dayfirst | **false** | **Optional.** Points out that the first number is the **day** (`DD.MM.YY HH:mm:ss.SSS`).
• `false` → `10.11.24 10:10:10.252` → **11 Oct 2024 10:10:10.252**
• `true` → `10.11.24 10:10:10.252` → **10 Nov 2024 10:10:10.252** | -| ... yearfirst | **false** | **Optional.** Points out that the first number is the **year** (`DD.MM.YY HH:mm:ss.SSS`).
• `false` → follows `dayfirst` rule
• `true` → `10.11.24 10:10:10.252` → **24 Nov 2010 10:10:10.252** | -| --- +| **Parameter** | **Default value** | **Description** | +|:------------------------------|:----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +| type | **json** | Provides information to connector that default converter will be used for converting data from the incoming request. | +| deviceNameExpression | **Device ${name}** | JSON expression used to build the device name. In this example, the connector takes the name field from the incoming request payload and produces a device name like Device SensorA | +| deviceNameExpressionSource | **request** | Defines where the connector should take values for **deviceNameExpression** from: `request` means read from the incoming request payload; `constant` means treat the expression as a fixed value (no payload parsing). | +| deviceProfileExpressionSource | **request** | Defines where the connector should take values for **deviceProfileExpression** from: `request` means read from the incoming request payload; `constant` means use the expression as a fixed value. | +| deviceProfileExpression | **default** | JSON expression used to resolve the device profile name. With `default`, the connector will assign the device to the default profile (or use the payload field if **deviceProfileExpressionSource** is `request` and the expression references a field). | +| attributes | | This subsection contains parameters of the incoming requests, that will be interpreted as attributes for the device. | +| ... type | **string** | Type of incoming data for a current attribute. | +| ... key | **model** | Simple JSON expression, uses for looking key in the incoming data, that will send to ThingsBoard instance as attribute key. | +| ... value | **${sensorModel}** | Simple JSON expression, uses for looking value in the incoming data, that will send to ThingsBoard instance as value of key parameter. | +| timeseries | | This subsection contains parameters of the incoming message, that will be interpreted as telemetry for the device. | +| ... type | **double** | Type of incoming data for a current telemetry. | +| ... key | **temperature** | Simple JSON expression, uses for looking key in the incoming message, that will send to ThingsBoard instance as attribute key. | +| ... value | **${temp}** | Simple JSON expression, uses for looking value in the incoming message, that will send to ThingsBoard instance as value of key parameter. | +| ... tsField | **${timestampField}** | **Optional.** JSON-path expression for field that carries a datetime string. If not present, the `ts` or `timestamp` properties from incoming message will be used as timestamp for data entry. | +| ... dayfirst | **false** | **Optional.** Points out that the first number is the **day** (`DD.MM.YY HH:mm:ss.SSS`).
• `false` → `10.11.24 10:10:10.252` → **11 Oct 2024 10:10:10.252**
• `true` → `10.11.24 10:10:10.252` → **10 Nov 2024 10:10:10.252** | +| ... yearfirst | **false** | **Optional.** Points out that the first number is the **year** (`DD.MM.YY HH:mm:ss.SSS`).
• `false` → follows `dayfirst` rule
• `true` → `10.11.24 10:10:10.252` → **24 Nov 2010 10:10:10.252** | +| --- {% capture difference %} **Parameters in attributes and telemetry section may differ from those presented above, but will have the same structure.** @@ -37,7 +39,7 @@ Below are two equivalent requests. The first one sends data as `application/json "timestampField": "10.11.24 14:30:00.000" } ``` -Send this request to the `/test_device` endpoint with the POST method, using Basic Auth (user / passwd): +Send this request to the `/test_device` endpoint with the POST method, using Basic Auth with credentials (user / passwd): ```bash curl -X POST http://127.0.0.1:5000/test_device \ @@ -77,7 +79,7 @@ curl -X POST "http://127.0.0.1:5000/test_device" \ This is how the mapping subsection for both examples above looks like: ```json -{ + { "endpoint": "/test_device", "HTTPMethods": [ "POST" @@ -89,24 +91,28 @@ This is how the mapping subsection for both examples above looks like: }, "converter": { "type": "json", - "deviceNameExpression": "Device ${name}", - "deviceTypeExpression": "default", + "deviceInfo": { + "deviceNameExpression": "Device ${name}", + "deviceNameExpressionSource": "request", + "deviceProfileExpressionSource": "request", + "deviceProfileExpression": "default" + }, "attributes": [ { - "type": "string", "key": "model", + "type": "string", "value": "${sensorModel}" } ], "timeseries": [ { - "type": "double", "key": "temperature", + "type": "double", "value": "${temp}" }, { - "type": "double", "key": "humidity", + "type": "double", "value": "${hum}", "tsField": "${timestampField}", "dayfirst": true @@ -138,23 +144,33 @@ Also, you can combine values from HTTP request in attributes, telemetry and serv }, "converter": { "type": "json", - "deviceNameExpression": "Device ${name}", - "deviceTypeExpression": "default", - "attributes": [], + "deviceInfo": { + "deviceNameExpression": "Device ${name}", + "deviceNameExpressionSource": "request", + "deviceProfileExpressionSource": "request", + "deviceProfileExpression": "default" + }, + "attributes": [ + { + "key": "model", + "type": "string", + "value": "${sensorModel}" + } + ], "timeseries": [ { - "type": "double", "key": "temperature", + "type": "double", "value": "${temp}" }, { - "type": "double", "key": "humidity", + "type": "double", "value": "${hum}" }, { - "type": "string", "key": "combine", + "type": "string", "value": "${hum}:${temp}" } ] diff --git a/docs/iot-gateway/config/rest.md b/docs/iot-gateway/config/rest.md index 87f5bbee56..533cd708c8 100644 --- a/docs/iot-gateway/config/rest.md +++ b/docs/iot-gateway/config/rest.md @@ -28,95 +28,98 @@ Example listed below will create a server on a localhost using 5000 port. Connector will use basic HTTP authorization using username and password. Then, connector will create endpoints from a list of endpoints using endpoints from mapping section. See more info in a description below. -{% capture restConf %} +```json { - "host": "127.0.0.1", - "port": 5000, - "SSL": false, - "security": { - "cert": "~/ssl/cert.pem", - "key": "~/ssl/key.pem" + "server": { + "host": "127.0.0.1", + "port": 5000, + "security": { + "cert": "~/ssl/cert.pem", + "key": "~/ssl/key.pem" + } }, - "mapping":[ + "mapping": [ { "endpoint": "/test_device", "HTTPMethods": [ "POST" ], - "security": - { + "security": { "type": "basic", "username": "user", "password": "passwd" }, "converter": { "type": "json", - "deviceNameExpression": "Device ${name}", - "deviceTypeExpression": "default", + "deviceInfo": { + "deviceNameExpression": "Device ${name}", + "deviceNameExpressionSource": "request", + "deviceProfileExpressionSource": "constant", + "deviceProfileExpression": "default" + }, "attributes": [ { - "type": "string", "key": "model", + "type": "string", "value": "${sensorModel}" } ], "timeseries": [ { - "type": "double", "key": "temperature", + "type": "double", "value": "${temp}" }, { - "type": "double", "key": "humidity", - "value": "${hum}", - "converter": "CustomConverter" + "type": "double", + "value": "${hum}" + }, + { + "key": "combine", + "type": "string", + "value": "${hum}:${temp}" } ] } }, { - "endpoint": "/test", + "endpoint": "/anon2", "HTTPMethods": [ - "GET", "POST" ], - "security": - { + "security": { "type": "anonymous" }, "converter": { "type": "custom", - "class": "CustomConverter", - "deviceNameExpression": "Device 2", - "deviceTypeExpression": "default", - "attributes": [ - { - "type": "string", - "key": "model", - "value": "Model2" - } - ], - "timeseries": [ - { - "type": "double", - "key": "temperature", - "value": "${temp}" - }, - { - "type": "double", - "key": "humidity", - "value": "${hum}" - } - ] + "deviceInfo": { + "deviceNameExpression": "SuperAnonDevice", + "deviceNameExpressionSource": "constant", + "deviceProfileExpressionSource": "constant", + "deviceProfileExpression": "default" + }, + "extension": "CustomRestUplinkConverter", + "extensionConfig": { + "key": "Totaliser", + "datatype": "float", + "fromByte": 0, + "toByte": 4, + "byteorder": "big", + "signed": true, + "multiplier": 1 + } } } - ] + ], + "requestsMapping": { + "attributeRequests": [], + "attributeUpdates": [], + "serverSideRpc": [] + } } - -{% endcapture %} -{% include code-toggle.liquid code=restConf params="conf|.copy-code.expandable-20" %} - +``` +{:.copy-code.expandable-15} ### General section {% capture restconnectorsecuritytogglespec %} @@ -245,6 +248,7 @@ The **attributeRequests** section will look like: } ] ``` +{: .copy-code} Also, you can request multiple attributes at once. Simply add one more JSON-path to attributeNameExpression parameter. For example, we want to request two shared attributes in one request, our config @@ -266,6 +270,7 @@ will look like: } ] ``` +{: .copy-code} ### Attribute update section @@ -318,8 +323,8 @@ The **attributeUpdates** section will look like: "valueExpression": "{\"${attributeKey}\":\"${attributeValue}\"}" } ], - ``` +{: .copy-code} ### Server side RPC section @@ -383,6 +388,7 @@ Examples for both methods provided below. } ] ``` +{: .copy-code} Also, every telemetry and attribute parameter has built-in GET and SET RPC methods out of the box, so you don’t need to configure it manually. To use them, make sure you set all required parameters (in the case of REST Connector, these are the following: