Skip to content

Devices

Remote tools

Open a shell, browse files and read the log of a connected device from the Thinger.io console, without VPNs or open ports.

Feature7 min read

The remote tools reach a device through its own Thinger.io connection, so they work behind NAT and firewalls without opening any port on the device. Terminal opens a shell or a serial-like console, and Explorer lets you manage its files.

When to use it ​

  • To diagnose a Linux gateway in the field without SSH access.
  • To read the log of an Arduino device or send it text commands.
  • To copy configuration files or logs to and from a device.

Which devices support them ​

ToolWhat it doesDevice side
TerminalInteractive shell on a Linux device, or a serial-like console on a microcontroller.The Linux client (shell), or the ThingerConsole class of the Arduino library on ESP32 and ESP8266 (see Console on Arduino devices).
ExplorerLists, uploads, downloads, renames and deletes files, with a terminal next to them.The Linux client, inside the folder set with --fs-path.

Both need an IOTMP device that is connected. MQTT, HTTP, virtual devices and subdevices do not offer them.

The device opens the connection to the server, and the server relays the Terminal and Explorer traffic over it. Nothing connects to the device from outside:

Your browserThinger.io consoleTerminalExplorerThinger.io serverrelays both directionsHTTPSDevice networkNAT or firewall, no open portsLinux deviceLinux clientShell (Terminal)Files in --fs-path (Explorer)Arduino deviceArduino libraryThingerConsole (Terminal)Log and your commandsIOTMP connectionopened by the device
How the remote tools reach a device

Open a terminal ​

Console

Open the device in Devices and choose Terminal in its menu. On a Linux device you get a shell with the permissions of the user that runs the client; on an Arduino device you see what the sketch writes to ThingerConsole and can type commands to it.

Terminal of a Linux device, connected, with the prompt pi@raspberrypi:~$ and the output of ls -l: README.txt and the folders config, data, logs and scripts
A shell on a Linux device, run as the user pi

API

The terminal is interactive and needs the console. To run a single command on a Linux device from the API, call its cmd device resource:

POST/v3/users/{user}/devices/{device}/resources/{resource}API reference
bash
curl -X POST "https://YOUR_SERVER/v3/users/YOUR_USER/devices/YOUR_DEVICE/resources/cmd" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"cmd": "df -h /"}'

YOUR_SERVER is iot.thinger.io on the free Cloud account, or your own domain. YOUR_TOKEN is an access token of your account.

CLI

The CLI has no interactive terminal. Run a single command on a Linux device through its cmd device resource:

bash
thinger device resource YOUR_DEVICE cmd -i cmd='"df -h /"'

AI

Ask your AI agent

Run df -h / on my Linux device YOUR_DEVICE and tell me how much disk space is left.

Browse files ​

Console

Open the device and choose Explorer in its menu. Navigate the folders, and upload, download, rename or delete files.

Explorer of a Linux device with the folders config, data, logs and scripts and the file README.txt, the config folder open and gateway.yaml open in the editor
The --fs-path folder of a Linux device in Explorer, with a file open

API

List a folder of the device:

GET/v3/users/{user}/devices/{device}/files{file}API reference
bash
curl "https://YOUR_SERVER/v3/users/YOUR_USER/devices/YOUR_DEVICE/files/" \
  -H "Authorization: Bearer YOUR_TOKEN"

The answer lists each entry with name, type, size, mode and modified. The same path with PUT, PATCH and DELETE uploads, renames and deletes files: see the REST API reference.

The CLI and the MCP tools do not browse device files.

Console on Arduino devices ​

With the ThingerConsole class of the Arduino library, the Terminal of a device works like the serial monitor of the Arduino IDE, over the Internet: the sketch writes logs to it and reads what you type. Create the console after the thing object:

main.cpp
cpp
#define THINGER_SERIAL_DEBUG

#include <ThingerESP32.h>
#include <ThingerConsole.h>

ThingerESP32 thing("YOUR_USER", "YOUR_DEVICE", "YOUR_TOKEN");
ThingerConsole console(thing);

unsigned long last_log = 0;

void setup() {
  Serial.begin(115200);
  thing.add_wifi("wifi-network", "wifi-password");
}

void loop() {
  thing.handle();

  // write a line every second
  unsigned long now = millis();
  if (now - last_log >= 1000) {
    last_log = now;
    console.printf("uptime: %lu ms\r\n", now);
  }
}

YOUR_TOKEN is the device credentials. Open the device and choose Terminal to see the lines.

Terminal of the esp32_terminal device, connected, with an uptime line every second
The sketch writing to the Terminal

Boards

ThingerConsole is meant for ESP32 and ESP8266 boards. It is not available on AVR boards, such as the UNO and the Mega. On other boards the sketch compiles, but printf is not available and the console's own messages (prompt, help, errors) do not reach the Terminal.

Write to the console ​

console has the same functions as Serial: print, println, printf and write. Text is sent at the end of each line, or every 128 bytes; call console.flush() to send text that does not end in a new line, such as a question. End lines with \r\n in printf.

The device sends nothing while no Terminal is open: whatever the sketch writes is discarded. Check if (console) to skip work that only feeds the log, such as reading a sensor:

main.cpp
cpp
void loop() {
  thing.handle();

  // only when a Terminal is open
  if (console) {
    unsigned long now = millis();
    if (now - last_log >= 1000) {
      last_log = now;
      console.printf("uptime: %lu ms\r\n", now);
    }
  }
}

Read from the console ​

What you type appears in the Terminal and reaches the sketch when you press Enter. The line ends with a carriage return ('\r'). Read it as you would read Serial:

main.cpp
cpp
void loop() {
  thing.handle();

  if (console.available()) {
    String input = console.readStringUntil('\r');
    console.print("> I received: ");
    console.println(input);
  }
}
Terminal of the esp32_terminal device with two typed lines, each followed by the device's answer starting with > I received:
Lines typed in the Terminal, echoed by the sketch

A line holds up to 128 characters, and Backspace deletes the last one.

Add commands to the console ​

Register commands to turn the Terminal into an interactive shell: it shows a prompt, runs the command you type and lists them all with help. Register each command in setup() with a name, the function to run and an optional description that help shows:

main.cpp
cpp
void setup() {
  // ...

  console.command("millis", [](int argc, char* argv[]) {
    console.println(millis());
  }, "show the time since the board started");
}
Terminal of the esp32_terminal device with the esp32_terminal$ prompt, the millis command and its output
A command typed at the prompt

The function receives the arguments as in the main function of a C program: argc is how many there are and argv holds them, with the command name in argv[0]. It can be a lambda, as above, or any function void name(int argc, char* argv[]). A name that is not registered answers command not found.

Once a command is registered, the line you type goes to the command interpreter, not to console.available(). While a command runs, what you type goes to the command.

Arguments ​

Arguments are separated by spaces. Quotes keep spaces inside one argument (print "hello world"), and a backslash (\) takes the next character as is. A line that ends in \, or with a quote still open, continues on the next one, after a > prompt. A command receives up to 10 arguments, its name included.

This command turns a pin on or off, and uses console.error() to print a message in red:

main.cpp
cpp
void setup() {
  // ...

  console.command("gpio", [](int argc, char* argv[]) {
    if (argc < 3) {
      console.error("usage: gpio <pin> <on|off>");
      return;
    }
    int pin = atoi(argv[1]);
    pinMode(pin, OUTPUT);
    digitalWrite(pin, strcmp(argv[2], "on") == 0 ? HIGH : LOW);
    console.printf("pin %d turned %s\r\n", pin, argv[2]);
  }, "<pin> <on|off> turn a pin on or off");
}
Terminal with gpio 2 on answered by pin 2 turned on, and gpio without arguments answered by the usage message in red
A command with arguments, and its error in red

Long-running commands ​

A command can run until you stop it, for example to follow a log. Loop while console.command_running() is true: it turns false when you press Ctrl+C or close the Terminal. The loop does not need to call thing.handle(), because command_running() keeps the connection alive:

main.cpp
cpp
void setup() {
  // ...

  console.command("log", [](int argc, char* argv[]) {
    while (console.command_running()) {
      console.printf("running at %lu ms\r\n", millis());
      delay(1000);
    }
  }, "print a line every second until Ctrl+C");
}
Terminal running the log command, with a line every second until ^C stops it and the prompt returns
A long-running command stopped with Ctrl+C

Ctrl+C with no command running clears the line you are typing.

Ask for input ​

A command can ask a question and wait for the answer. Send the question with flush(), since it does not end in a new line, and wait for console.available(), which also keeps the connection alive:

main.cpp
cpp
void setup() {
  // ...

  console.command("hello", [](int argc, char* argv[]) {
    console.print("Please enter your name: ");
    console.flush();
    while (console.command_running() && !console.available());
    if (!console.command_running()) return;
    String name = console.readStringUntil('\r');
    console.printf("Hello %s\r\n", name.c_str());
  }, "ask for your name");
}
Terminal running hello: it asks Please enter your name, receives Ada and answers Hello Ada
A command that asks for input

Prompt ​

The prompt is the device ID followed by $. Change it in setup() with a text that lasts for the whole program, such as a literal:

main.cpp
cpp
void setup() {
  // ...

  console.set_prompt("Arduino");
}
Terminal with the Arduino$ prompt and the millis command
A custom prompt

Built-in commands ​

Once you register a command, these come with it:

CommandWhat it does
helpLists the commands with their descriptions.
clearClears the Terminal.
rebootPrints device will reboot now... and restarts the device.
exitListed by help as "close terminal", but it does nothing: close the Terminal from the console.
Terminal with the output of help: the built-in commands clear, reboot, exit and help, and the commands of the sketch with their descriptions
The output of help

ThingerConsole functions ​

ThingerConsole is an Arduino Stream, so it also has read(), peek(), readString(), parseInt(), setTimeout() and the rest of the Stream functions.

FunctionDescription
ThingerConsole(ThingerClient& client)Creates the console on the thing object.
operator bool()true while a Terminal is open on the device.
size_t write(uint8_t value)Writes one byte. print, println and printf write through it.
void flush()Sends the pending text without waiting for the end of the line.
int available()Bytes of the lines received and not read yet.
int read()Reads one byte, or -1 if there is none.
int peek()Returns the next byte without reading it, or -1.
void command(const char* cmd, std::function<void(int argc, char* argv[])> callback, const char* desc = "")Registers a command, with an optional description for help.
bool command_running()true while a command runs and has not been cancelled with Ctrl+C or by closing the Terminal.
bool is_running()true while a Terminal is open.
void error(const char* message)Prints a message in red, followed by a new line.
void set_prompt(const char* prompt)Replaces the prompt; the text must last for the whole program.

Permissions ​

The remote tools act with the permissions of whoever opens them. Anyone with access to the device resources of a device can open its Terminal and Explorer, so give access tokens, project members and agents only the devices they need: see Access Tokens.

Limits ​

  • The device must be connected. Otherwise the console reports that it is not connected.
  • On Linux, the shell and the files have the permissions of the user that runs the client.
  • Explorer cannot reach files outside the folder the client shares.
  • On Arduino devices, the console handles lines of up to 128 characters and commands of up to 10 arguments. Arrow keys and the command history are not supported.