The Thinger.io Arduino library connects a board to Thinger.io over IOTMP legacy, the earlier version of IOTMP. This page lists its public API: the classes for each network, the connection functions, device resources, calls to the platform, macros and compile options. To learn how to use them, follow the Coding guide.
| Package | Installed from | Version | Source code |
|---|---|---|---|
thinger.io | Arduino Library Manager, PlatformIO | 2.49.0 | thinger-io/Arduino-Library |
- Install: Install, on this page. To connect a board step by step, follow Arduino.
- Changelog: Arduino library changelog: every release, with what it adds, changes and fixes.
Install
Install thinger.io from the library manager of the Arduino IDE (Sketch > Include Library > Manage Libraries), or add it to the project file of PlatformIO:
lib_deps = thinger-io/thinger.ioThe library examples are in File > Examples > thinger.io, one per board. Step-by-step setup is in Arduino.
Classes by network
Include the header of your board and create one object of its class, usually called thing. Which class each board uses, and which ones support OTA updates, is listed in Supported boards.
Every class takes the same three arguments: your username, the device ID and the device credentials. Two classes also take the serial port of the modem:
| Constructor | Notes |
|---|---|
ThingerESP32(const char* user, const char* device, const char* device_credential) | The same arguments for every board class. |
ThingerTinyGSM(const char* user, const char* device, const char* device_credential, Stream& serial) | GSM modems driven with AT commands. |
ThingerESP8266AT(const char* user, const char* device, const char* device_credential, Stream& serial) | An ESP8266 used as a Wi-Fi modem. |
ThingerSmartConfig(const char* user, const char* device, const char* device_credential, bool use_led=true) | ESP8266 that gets the Wi-Fi network from the ESP-Touch app. |
#include <ThingerESP32.h>
ThingerESP32 thing("YOUR_USER", "YOUR_DEVICE", "YOUR_TOKEN");YOUR_TOKEN is the device credentials, not an access token.
Network setup
Call these functions in setup(), before the first thing.handle(). Each one exists only in the classes listed.
| Function | Classes | Description |
|---|---|---|
void add_wifi(const char* ssid, const char* password=nullptr) | Wi-Fi classes (ThingerESP32, ThingerESP8266, ThingerWiFiNINA, ThingerWifi101, ThingerR4WiFi, ThingerPicoW, ThingerMbed, ThingerZephyr, ThingerWifi) | Adds a Wi-Fi network. On ESP32, ESP8266, Raspberry Pi Pico W and Pico 2 W, call it once per network: with two or more, the board scans and joins the strongest one, also when it reconnects; with one, it joins it without scanning, so a hidden network only works as the only one. On the other boards a second call replaces the first. On ESP32 and ESP8266, without a call the board joins the last network it used. |
void add_wifi_enterprise(const char* ssid, const char* username, const char* password, const char* identity="", const char* ca_pem=nullptr) | ThingerESP32, with version 3 of the esp32 board package | Adds a WPA2-Enterprise network with PEAP and EAP-MSCHAPv2, such as eduroam. identity is the outer identity; empty, the board uses the username. ca_pem is the certificate, in PEM format, of the authority of the authentication server: with it, the board checks the server and joins this network first, on its own, and tries the other networks if it cannot join it; without it, the server is not checked and the board joins the strongest of its networks. Only one network can carry a certificate. |
void set_apn(const char* apn, const char* apn_username="", const char* apn_password="") | ThingerMKRGSM, ThingerMKRNB | Sets the APN of the cellular network. |
void set_pin(const char* pin) | ThingerMKRGSM, ThingerMKRNB, ThingerNBCore, ThingerLinkItOneGPRS | Unlocks the SIM card with its PIN. |
void setAPN(const char* APN, const char* user=nullptr, const char* password=nullptr) | ThingerTinyGSM | Sets the APN. |
void setPIN(const char* pin) | ThingerTinyGSM | Unlocks the SIM card. |
void set_address(const char* ip, const char* gateway, const char* subnet, const char* dns1="8.8.8.8", const char* dns2="8.8.4.4") | ThingerESP32Eth | Uses a fixed IP address instead of DHCP. |
void set_hostname(const char* hostname) | ThingerESP32Eth | Sets the host name of the board on the network. |
The example of the MKR NB 1500, trimmed:
#include <ThingerMKRNB.h>
ThingerMKRNB thing(USERNAME, DEVICE_ID, DEVICE_CREDENTIAL);
void setup() {
// optional set pin number
thing.set_pin(PIN_NUMBER);
// set APN
thing.set_apn(GPRS_APN, GPRS_LOGIN, GPRS_PASSWORD);
}Run the connection in its own task
On ESP32 with #define THINGER_FREE_RTOS (ThingerESP32, ThingerESP32Eth and ThingerTinyGSM), and on the Mbed OS boards (ThingerMbed, ThingerMbedEth: Nano RP2040 Connect, and Portenta H7 and Opta on the Mbed OS core), the connection can run in its own task. Call thing.start() in setup() instead of calling thing.handle() in loop().
| Function | Classes | Description |
|---|---|---|
bool start(unsigned core=ARDUINO_RUNNING_CORE, size_t stack_size=8192) | ThingerESP32, ThingerESP32Eth and ThingerTinyGSM on ESP32, with THINGER_FREE_RTOS | Starts the task on the given core, with the given stack size in bytes. |
bool start() | ThingerMbed, ThingerMbedEth | Starts the task. |
bool stop() | Both | Stops the task. |
bool is_running() | Both | true while the task runs. |
void lock(), void unlock() | Both | Protect the variables that your device resources share with loop(), and the calls to the platform from another task, such as write_bucket or call_endpoint. On ESP32, get_state() and get_error() can be read from any task without them. |
The ESP32RTOS example, trimmed:
#define THINGER_SERIAL_DEBUG
#define THINGER_FREE_RTOS
#include <ThingerESP32.h>
ThingerESP32 thing(USERNAME, DEVICE_ID, DEVICE_CREDENTIAL);
void setup() {
thing.add_wifi(SSID, SSID_PASSWORD);
thing["millis"] >> outputValue(millis());
// start thinger on its own task
thing.start();
}
void loop() {
// use thing.lock() thing.unlock() if using variables exposed on thinger resources
}Connection
These functions are common to every class.
| Function | Description |
|---|---|
void handle() | Connects to the network and to the server when needed, answers the requests of the platform and sends the keepalive. Call it in every iteration of loop(). While the device is offline it returns at once between connection attempts, so loop() keeps running: see Reconnection. |
bool is_connected() | true while the connection to the server is open. |
void stop() | Closes the connection. The next thing.handle() connects again. |
void reboot() | Closes the connection and restarts the board. |
void set_credentials(const char* username, const char* device_id, const char* device_password) | Changes the username, device ID and device credentials. Takes effect on the next connection. |
void set_host(const char* host) | Changes the server. Takes effect on the next connection. |
const char* get_host() | The current server. |
const char* get_username() | The username in use. |
const char* get_device_id() | The device ID in use. |
void set_root_ca(const char* ca) | Sets the root certificate that validates the server, in PEM format. It takes effect on the next connection. |
const char* get_root_ca() | The root certificate in use. |
void set_state_listener(std::function<void(THINGER_STATE)> state_listener) | Calls state_listener on every change of the connection state. |
THINGER_STATE get_state() const | The last connection state, the one last passed to the state listener. |
THINGER_ERROR get_error() const | The reason of the last NETWORK_CONNECT_ERROR state, or ERROR_NONE once the network is connected. See Network errors. |
The strings you pass to set_credentials, set_host and set_root_ca are not copied: they must stay valid while the device runs (literals, globals or String objects that live as long as thing).
Connection states
set_state_listener receives one value of the ThingerClient::THINGER_STATE enumeration, in this order of a normal connection:
| State | Meaning |
|---|---|
NETWORK_CONNECTING | Joining the network (Wi-Fi, Ethernet or cellular). |
NETWORK_CONNECTED | The network is up. |
NETWORK_CONNECT_ERROR | The board could not join the network. |
SOCKET_CONNECTING | Opening the connection to the server. |
SOCKET_CONNECTED | The connection to the server is open. |
SOCKET_CONNECTION_ERROR | The server could not be reached. |
THINGER_AUTHENTICATING | Sending the device credentials. |
THINGER_AUTHENTICATED | The server accepted the device credentials. The device is online. |
THINGER_AUTH_FAILED | The server rejected the username, device ID or device credentials. |
SOCKET_TIMEOUT | The server stopped answering. |
SOCKET_ERROR | The connection failed. |
SOCKET_DISCONNECTED | The connection is closed. |
THINGER_STOP_REQUEST | The sketch called thing.stop() or thing.reboot(). |
thing.set_state_listener([](ThingerClient::THINGER_STATE state) {
if (state == ThingerClient::THINGER_AUTHENTICATED) {
Serial.println("Connected to Thinger.io");
}
});Reconnection
thing.handle() does not hold loop() while the device is offline:
- The Wi-Fi classes listed for
add_wifiin Network setup join the network step by step, one step on eachthing.handle(), with the stateNETWORK_CONNECTINGin the meantime. They give up after 30 seconds without joining the network, and after 30 more without an IP address. Two exceptions wait insidething.handle(): a board with several Wi-Fi networks scans them for a few seconds, and the WebConfig classes wait for the network. - The other classes wait for the network inside
thing.handle(): Ethernet, cellular modems,ThingerSmartConfig,ThingerCC3000,ThingerESP8266ATandThingerLinkItOneWifi. - The TLS connection and the authentication with the server take a few seconds on every attempt, inside
thing.handle().
Between two attempts, thing.handle() returns at once until the wait ends:
| Failure | Wait before the next attempt |
|---|---|
The network (NETWORK_CONNECT_ERROR) | RECONNECTION_TIMEOUT milliseconds. |
The server: socket, TLS or authentication (SOCKET_CONNECTION_ERROR, THINGER_AUTH_FAILED) | RECONNECTION_TIMEOUT after the first failure, doubled on every failure up to THINGER_RECONNECTION_MAX_TIMEOUT, plus a random margin of up to 25 %, so many devices do not reconnect at the same time. It starts again from RECONNECTION_TIMEOUT once the device connects. |
With THINGER_SERIAL_DEBUG, the serial monitor shows each wait:
[THINGER] Retrying connection in (ms): 5843Network errors
get_error() returns one value of the ThingerClient::THINGER_ERROR enumeration. Each board reports the reasons it can tell apart, and ERROR_UNKNOWN for the rest.
| Error | Meaning | Boards |
|---|---|---|
ERROR_NONE | The network is connected. | All |
ERROR_UNKNOWN | The board does not report the reason. | All |
NETWORK_NOT_CONFIGURED | No Wi-Fi network or APN is set. With WebConfig, the configuration page closed without a Wi-Fi network. | Wi-Fi and cellular |
NETWORK_NOT_FOUND | The Wi-Fi network is not available. | Wi-Fi |
NETWORK_AUTH_ERROR | The Wi-Fi network rejected the connection, usually for a wrong password. | Wi-Fi |
NETWORK_NO_IP | The board joined the Wi-Fi network but got no IP address. | Wi-Fi |
MODEM_NOT_RESPONDING | The modem does not answer. | ThingerTinyGSM |
MODEM_SIM_ERROR | The SIM card cannot be used, for example for a wrong PIN. | ThingerTinyGSM |
NETWORK_NOT_REGISTERED | The modem cannot register in the network, for example for lack of coverage. | ThingerTinyGSM, ThingerNBCore, ThingerBC66 |
NETWORK_REGISTRATION_DENIED | The operator rejected the registration, for example because the SIM card is disabled. | ThingerNBCore, ThingerBC66 |
NETWORK_APN_ERROR | The modem is registered, but the data connection does not open: check the APN. | ThingerTinyGSM, ThingerMKRNB, ThingerMKRGSM |
Device resources
A device resource is a function of the device that the platform can call. Declare it in setup() with thing["name"] and an operator that sets its direction:
| Declaration | Equivalent function | Kind |
|---|---|---|
thing["name"] << [](pson& in){ … }; | void set_input(std::function<void(pson&)> in_function) | Input: receives data from the platform. |
thing["name"] >> [](pson& out){ … }; | void set_output(std::function<void(pson&)> out_function) | Output: sends data to the platform. |
thing["name"] = [](pson& in, pson& out){ … }; | void set_input_output(std::function<void(pson& in, pson& out)> pson_in_pson_out_function) | Input and output. |
thing["name"] = [](){ … }; | void set_function(std::function<void()> run_function) | No data: runs an action. |
An input resource is called with an empty in when the platform reads its current value, for example when you open it in the console. Check in.is_empty() and fill in with the current value, as in the Opta examples:
thing["relay_d0"] << [](pson& in){
if(in.is_empty()){
in = (bool) digitalRead(D0);
}else{
digitalWrite(D0, in ? HIGH : LOW);
digitalWrite(LED_D0, in ? HIGH : LOW);
}
};An output resource fills out each time it is read:
thing["millis"] >> outputValue(millis());Device resources can be nested, as in thing["led"]["green"]: the platform calls the inner device resource by its path, led/green.
| Function | Description |
|---|---|
void then(std::function<void()> then) | Runs then after each call to the resource has been answered. Not available on AVR boards. |
void set_stream_listener(std::function<void(uint16_t, unsigned long, bool enabled)> stream_listener) | Called when the platform starts (enabled is true) or stops listening to the resource, with the stream ID and the sampling interval in milliseconds. |
Macros for common resources
| Macro | Kind | Description |
|---|---|---|
digitalPin(PIN) | Input | Sets a digital output pin on or off and reports its state. PIN must be a number or a #define. |
analogPin(PIN) | Input | Writes an analog (PWM) value to the pin. Not available on ESP32. |
outputValue(value) | Output | Sends the value of an expression or a variable. |
outputString(value) | Output | The same, for a text. |
inputValue(value) | Input | Stores the value received in a variable, and reports the current value. |
inputValue(value, callback) | Input | The same, and runs callback after each change. |
servo(servo) | Input | Moves a servo (an object of the Arduino Servo library) to the angle received, and reports its angle. |
// digital pin control example (i.e. turning on/off a light, a relay, configuring a parameter, etc)
thing["GPIO_16"] << digitalPin(16);
// resource output example (i.e. reading a sensor value)
thing["millis"] >> outputValue(millis());The pson type
pson holds the data of a device resource: a single value, an object with named fields or an array. Assign a value to write it, and assign the pson to a variable (or cast it) to read it.
| Type | Write | Read |
|---|---|---|
| Boolean | in = true; | bool value = in; |
| Integer | out = 42; | int value = in; (also long, unsigned int, unsigned long, short, char) |
| Decimal | out = 21.5; | float value = in; or double value = in; |
| Text | out = "text"; or a String | const char* text = in; or String text = in; |
| Object | out["temperature"] = 21.5; | float value = in["temperature"]; |
| Array | pson_array& array = out; then array.add(1).add(2); | — |
| Bytes | void set_bytes(void* bytes, size_t size) | bool get_bytes(const void*& bytes, size_t& size) |
| Function | Description |
|---|---|
bool is_empty() | true if the pson has no value. In an input resource, true when the platform only reads the current value. |
bool is_boolean(), bool is_number(), bool is_integer(), bool is_float(), bool is_string(), bool is_bytes(), bool is_object(), bool is_array(), bool is_null() | The type of the value. |
The wire format of pson is PSON, described on iotmp.io.
Calls to the platform
These functions send a request to the server. They need the device to be connected and return false if the request could not be sent. With the last argument (confirm_write or confirm_call) set to true, they wait for the server and return true only if the server accepted the request.
Data Buckets
| Function | Description |
|---|---|
bool write_bucket(const char* bucket_id, pson& data, bool confirm_write=false) | Stores data in the Data Bucket. |
bool write_bucket(const char* bucket_id, const char* resource_name, bool confirm_write=false) | Stores the output of a device resource. |
bool write_bucket(const char* bucket_id, thinger_resource& resource, bool confirm_write=false) | The same, with the resource object: thing["name"]. |
The Data Bucket must exist, with its data source set to the device write call. See Data Buckets.
Properties
| Function | Description |
|---|---|
bool get_property(const char* property_identifier, pson& data) | Reads a property of the device into data. Waits for the server. |
bool set_property(const char* property_identifier, pson& data, bool confirm_write=false) | Writes a property of the device. |
See Properties.
Endpoints
| Function | Description |
|---|---|
bool call_endpoint(const char* endpoint_name, bool confirm_call=false) | Calls the endpoint without data. |
bool call_endpoint(const char* endpoint_name, pson& data, bool confirm_call=false) | Calls the endpoint with data. |
bool call_endpoint(const char* endpoint_name, const char* resource_name, bool confirm_call=false) | Calls the endpoint with the output of a device resource. |
bool call_endpoint(const char* endpoint_name, thinger_resource& resource, bool confirm_call=false) | The same, with the resource object. |
See Endpoints.
Other devices
| Function | Description |
|---|---|
bool call_device(const char* device_name, const char* resource_name, bool confirm_call=false) | Calls a device resource of another device of your account. |
bool call_device(const char* device_name, const char* resource_name, pson& data, bool confirm_call=false) | The same, with data as input. |
bool call_device(const char* device_name, const char* resource_name, thinger_resource& resource, bool confirm_call=false) | The same, with the output of a local device resource as input. |
The other device must be connected.
Streams
A stream sends the value of a device resource to whoever is listening in real time, such as an open dashboard. Nothing is sent, or stored, when no one is listening.
| Function | Description |
|---|---|
bool stream(const char* resource) | Sends the current output of the resource. Returns false if no one is listening. |
bool stream(thinger_resource& resource) | The same, with the resource object. |
bool stream_data(thinger_resource& resource, pson& payload) | Sends payload on the stream of the resource instead of its output. |
The platform can also ask the device to sample an output resource at a fixed interval, for example for a dashboard widget that refreshes on its own; the library does it from thing.handle() without any code.
Over-the-air updates
The OTA classes receive firmware from the Visual Studio Code extension. Create the object after thing, with the class of your board (see Supported boards):
#include <ThingerMKRNB.h>
#include <ThingerMKRNBOTA.h>
ThingerMKRNB thing(USERNAME, DEVICE_ID, DEVICE_CREDENTIAL);
ThingerMKRNBOTA ota(thing);| Function | Description |
|---|---|
void set_enabled(bool enabled) | Accepts (true, the default) or rejects updates. |
bool is_enabled() | Whether updates are accepted. |
void set_block_size(size_t size) | Size in bytes of each block of firmware. |
void on_start(std::function<void()> on_start) | Called when an update starts. |
void on_progress(std::function<void(size_t progress, size_t total)> on_progress) | Called after each block, with the bytes received and the total. |
void on_end(std::function<void()> on_end) | Called when the update ends. |
The device reports its OTA settings in the $ota/options device resource, which the extension reads before each update.
Compatibility check
To keep a device from installing a firmware built for other hardware, such as another PCB version or a cellular modem instead of Wi-Fi, define THINGER_OTA_CHECK before the OTA include, with one entry per key and value:
#define THINGER_OTA_CHECK(FIELD) FIELD("PCB_MODEL", "SENSOR_I2C") FIELD("PCB_VERSION", "V2")
#include <ThingerESP32.h>
#include <ThingerESP32OTA.h>- The fields are stored in the firmware binary, and the device reports them in
$ota/options. - The Visual Studio Code extension compares the fields of the new firmware with those of the device, and asks for confirmation when they differ or the new firmware has none.
- The device rejects an update whose fields differ from its own or are missing, unless the user confirmed it in the extension. It answers with the error
"Firmware not compatible with this device (THINGER_OTA_CHECK)". - A device without fields accepts any firmware.
- Up to 16 fields, with keys of 1 to 32 characters and values of up to 128 characters, and no repeated keys. A sketch that breaks a limit does not compile.
- Available on every board with OTA updates.
Automatic rollback (ESP32)
On the ESP32, define THINGER_OTA_ROLLBACK before the OTA include. A new firmware must then connect to Thinger.io within THINGER_OTA_ROLLBACK_TIMEOUT seconds of its first start; otherwise the board goes back to the previous firmware. It also goes back if it restarts before connecting, for example after a crash.
#define THINGER_OTA_ROLLBACK
#define THINGER_OTA_ROLLBACK_TIMEOUT 300
#include <ThingerESP32.h>
#include <ThingerESP32OTA.h>The new firmware is the one that must define it: a firmware without it is accepted as soon as it starts. Define it in only one source file of the project, the one that includes the OTA header.
More in OTA updates.
Compile options
Define these options before the first #include of the library, or as build flags (-D) in PlatformIO.
| Option | Default | Description |
|---|---|---|
THINGER_SERVER | "iot.thinger.io" | Server to connect to. On a private instance, set your domain, YOUR_SERVER. With this option, the library validates the server with the root certificate of Let's Encrypt (ISRG Root X1) instead of the one of the Thinger.io Cloud servers (USERTrust RSA); use THINGER_ROOT_CA if your server has a certificate from another authority. |
THINGER_ROOT_CA | Not defined | Root certificate, in PEM format, that validates the server, for a private instance whose certificate comes from its own authority. Define it in the sketch, before the include, as in Server and TLS. It replaces the default certificates. ESP32, ESP8266, Raspberry Pi Pico W and Pico 2 W, Arduino UNO R4 WiFi, Arduino UNO Q, and the boards of the Arduino Zephyr core (ThingerZephyr). |
THINGER_SSL_PORT | 25202 | Server port for TLS connections. |
THINGER_PORT | 25200 | Server port for connections without TLS. |
THINGER_SERIAL_DEBUG | Not defined | Prints each connection step to the serial port. Open the serial monitor at the speed you set with Serial.begin(). |
THINGER_INSECURE_SSL | Not defined | Connects over TLS without validating the server certificate, for a private instance with a self-signed certificate. ESP32, ESP8266, Raspberry Pi Pico W and Pico 2 W, and the boards of the Arduino Zephyr core (ThingerZephyr). On the Arduino UNO R4 WiFi, the Wi-Fi module always validates the certificate: with this option, it validates it with its own certificates instead of the root certificate of the library. |
_DISABLE_TLS_ | Not defined | Connects without TLS, to THINGER_PORT. |
THINGER_FREE_RTOS | Not defined | ESP32: adds thing.start() to run the connection in its own task, with ThingerESP32, ThingerESP32Eth and ThingerTinyGSM. |
RECONNECTION_TIMEOUT | 5000 | Milliseconds to wait after a failed connection attempt, and the first wait after a failure of the server. See Reconnection. |
THINGER_RECONNECTION_MAX_TIMEOUT | 30000 | Longest wait, in milliseconds, between two attempts to connect to the server, without the random margin. |
DEFAULT_READ_TIMEOUT | 10000 | Milliseconds to wait for the rest of a message from the server. |
THINGER_OTA_COMPRESSION | 1 on ESP32, Portenta H7 and Opta; 0 on Raspberry Pi Pico W and Pico 2 W | 1 receives the OTA firmware compressed, 0 uncompressed. On the Pico boards, compression needs a recent version of the Visual Studio Code extension. |
THINGER_OTA_CHECK | Not defined | Compatibility fields of the firmware for OTA updates. See Compatibility check. |
THINGER_OTA_ROLLBACK | Not defined | ESP32: goes back to the previous firmware if a new one does not connect to Thinger.io. See Automatic rollback. |
THINGER_OTA_ROLLBACK_TIMEOUT | 300 | ESP32, with THINGER_OTA_ROLLBACK: seconds a new firmware has to connect to Thinger.io. |
THINGER_OTA_VERSION | (empty) | Firmware version the device reports for OTA updates. The Visual Studio Code extension skips devices that already run the same version. |
THINGER_DEVICE_SSID | "Thinger-Device" | WebConfig classes: name of the Wi-Fi network the board opens for its setup. |
THINGER_DEVICE_SSID_PSWD | "thinger.io" | WebConfig classes: password of that network. See WebConfig. |
THINGER_WEBCONFIG_ATTEMPTS | 3 | WebConfig classes: failed attempts to join the stored Wi-Fi network before the configuration page opens again. 0 turns it off. |
THINGER_WEBCONFIG_PORTAL_TIMEOUT | 180 | WebConfig classes: seconds the configuration page stays open when it opens for that reason. |
Some boards do not support TLS with this library: they always connect without TLS, to THINGER_PORT. The IOTMP reference lists the ports of the server.
See also
- Coding guide: how to use this API in a sketch.
- Supported boards: the class, include and OTA support of each board.
- IOTMP: the protocol; the library speaks its legacy version.
- Arduino library changelog: what each version of the library adds, changes and fixes.
- Troubleshooting: connection problems.