Skip to content

Devices

Portenta H7, Opta and GIGA R1 WiFi

Connect an Arduino Portenta H7, Opta or GIGA R1 WiFi to Thinger.io on the Zephyr core, or on the Mbed OS core with Ethernet and OTA updates.

Reference9 min read

The Arduino Portenta H7, the Arduino Opta and the Arduino GIGA R1 WiFi share the same microcontroller, the dual-core STM32H747 (an Arm Cortex-M7 at 480 MHz and a Cortex-M4 at 240 MHz). The Thinger.io Arduino library connects them on two Arduino cores, always with TLS:

  • Arduino Zephyr core, the recommended one: ThingerZephyr connects the three boards over Wi-Fi and validates the server certificate. OTA updates are not available on the Zephyr core yet.
  • Arduino Mbed OS core: ThingerMbed connects the Portenta H7 and the Opta over Wi-Fi and ThingerMbedEth over Ethernet, with OTA updates (ThingerPortentaOTA) and the connection in its own thread. Arduino ended the maintenance of this core in July 2026; use it when you need Ethernet or OTA updates.
BoardCoreNetworkIncludeClassExampleOTA
Portenta H7, GIGA R1 WiFiZephyrWi-FiThingerZephyr.hThingerZephyrArduinoZephyr—
Opta WiFiZephyrWi-FiThingerZephyr.hThingerZephyrArduinoOptaZephyr—
Portenta H7Mbed OSWi-FiThingerMbed.hThingerMbedArduinoPortentaH7ThingerPortentaOTA
Portenta H7 (with an Ethernet carrier or shield)Mbed OSEthernetThingerMbedEth.hThingerMbedEthArduinoPortentaH7EthernetThingerPortentaOTA
Opta WiFiMbed OSWi-FiThingerMbed.hThingerMbedArduinoOptaWiFiThingerPortentaOTA
Opta (all models)Mbed OSEthernetThingerMbedEth.hThingerMbedEthArduinoOptaEthernetThingerPortentaOTA

Set up the board ​

  1. Install the board package

    In the Arduino IDE, open Tools > Board > Boards Manager and install the core you chose: Arduino Zephyr Boards for the Zephyr core, or Arduino Mbed OS Portenta Boards (Portenta H7) or Arduino Mbed OS Opta Boards (Opta) for the Mbed OS core. The Wi-Fi and Ethernet libraries come with the package.

  2. Install the library

    Install thinger.io from the Library Manager.

  3. Create the credentials file

    The sketches read the credentials from arduino_secrets.h, in the sketch folder:

    arduino_secrets.h
    cpp
    #define USERNAME "YOUR_USER"
    #define DEVICE_ID "YOUR_DEVICE"
    #define DEVICE_CREDENTIAL "YOUR_TOKEN"
    
    #define SSID "wifi-network"
    #define SSID_PASSWORD "wifi-password"

    DEVICE_CREDENTIAL holds the device credentials. The Ethernet sketches do not use SSID and SSID_PASSWORD.

  4. Prepare the board

    On the Zephyr core, install the Zephyr loader once, as in Install the Zephyr loader. On the Mbed OS core, update the bootloader, format the flash and update the Wi-Fi firmware once, as in Prepare the board; without these steps, the TLS connection or the OTA updates can fail.

Each example is in File > Examples > thinger.io > Arduino.

Portenta H7 and GIGA R1 WiFi ​

Arduino Portenta H7 board with its high-density connectors and the Wi-Fi antenna connector
Arduino Portenta H7

Portenta H7 and GIGA R1 WiFi on the Zephyr core ​

Example ArduinoZephyr. It runs on every Wi-Fi board of the Zephyr core in this page, the Opta WiFi included:

ArduinoZephyr.ino
cpp
#define THINGER_SERIAL_DEBUG

// install the Zephyr loader once with Tools > Burn Bootloader
#include <ThingerZephyr.h>
#include "arduino_secrets.h"

ThingerZephyr thing(USERNAME, DEVICE_ID, DEVICE_CREDENTIAL);

void setup() {
  // open serial for debugging
  Serial.begin(115200);

  pinMode(LED_BUILTIN, OUTPUT);

  // configure wifi network
  thing.add_wifi(SSID, SSID_PASSWORD);

  // pin control example (i.e. turning on/off a light, a relay, etc)
  thing["led"] << digitalPin(LED_BUILTIN);

  // resource output example (i.e. reading a sensor value, a variable, etc)
  thing["millis"] >> outputValue(millis());
}

void loop() {
  thing.handle();
}

On the Zephyr core, call thing.handle() in loop(), as on most boards: the connection does not run in its own thread. While the board joins the Wi-Fi network, thing.handle() returns at once, so loop() keeps running.

Portenta H7 over Wi-Fi ​

Mbed OS core. Example ArduinoPortentaH7, with OTA updates:

ArduinoPortentaH7.ino
cpp
#define THINGER_SERIAL_DEBUG

#include <ThingerMbed.h>
#include <ThingerPortentaOTA.h>
#include "arduino_secrets.h"

ThingerMbed thing(USERNAME, DEVICE_ID, DEVICE_CREDENTIAL);
ThingerPortentaOTA ota(thing);

void setup() {
  // configure LED_BUILTIN for output
  pinMode(LED_BUILTIN, OUTPUT);

  // open serial for debugging
  Serial.begin(115200);

  // configure wifi network
  thing.add_wifi(SSID, SSID_PASSWORD);

  // pin control example (i.e. turning on/off a light, a relay, etc)
  thing["led"] << digitalPin(LED_BUILTIN);

  // resource output example (i.e. reading a sensor value, a variable, etc)
  thing["millis"] >> outputValue(millis());

  // start the connection in its own thread
  thing.start();
}

void loop() {
  // your code here: no thing.handle() needed
  // wrap variables shared with device resources in thing.lock() and thing.unlock()
  delay(1000);
}

Portenta H7 over Ethernet ​

Mbed OS core. Example ArduinoPortentaH7Ethernet, with OTA updates. The Portenta H7 has no Ethernet connector of its own: it needs a carrier or shield with one, such as the Portenta Vision Shield Ethernet or the Portenta Breakout. The board gets its address over DHCP.

ArduinoPortentaH7Ethernet.ino
cpp
#define THINGER_SERIAL_DEBUG

#include <ThingerMbedEth.h>
#include <ThingerPortentaOTA.h>
#include "arduino_secrets.h"

ThingerMbedEth thing(USERNAME, DEVICE_ID, DEVICE_CREDENTIAL);
ThingerPortentaOTA ota(thing);

void setup() {
  // configure LED_BUILTIN for output
  pinMode(LED_BUILTIN, OUTPUT);

  // open serial for debugging
  Serial.begin(115200);

  // pin control example (i.e. turning on/off a light, a relay, etc)
  thing["led"] << digitalPin(LED_BUILTIN);

  // resource output example (i.e. reading a sensor value, a variable, etc)
  thing["millis"] >> outputValue(millis());

  // start the connection in its own thread
  thing.start();
}

void loop() {
  // your code here: no thing.handle() needed
  // wrap variables shared with device resources in thing.lock() and thing.unlock()
  delay(1000);
}

Opta ​

The Arduino Opta is a micro PLC made with Finder for industrial automation: four relay outputs, inputs that read 0-10 V or digital signals, Ethernet and, in the Opta WiFi, Wi-Fi and Bluetooth. Each relay has a status LED.

Arduino Opta micro PLC with its status LEDs, the USER button, the Ethernet port and the relay output terminals
Arduino Opta

The pins have different names on each core:

FunctionZephyr coreMbed OS core
Relays 1 to 4D0 to D3D0 to D3
Status LEDs of the relaysD4 to D7LED_D0 to LED_D3
USER buttonD11BTN_USER
Inputs I1 to I8A0 to A7A0 to A7

Opta WiFi on the Zephyr core ​

Example ArduinoOptaZephyr. One input resource per relay switches the relay and its status LED and, when you read it, returns the state of the relay. led controls the USER LED (LED_BUILTIN), button tells whether the USER button is pressed, and inputs returns the voltage of the eight inputs, from 0 to 10 V.

ArduinoOptaZephyr.ino
cpp
#define THINGER_SERIAL_DEBUG

// install the Zephyr loader once with Tools > Burn Bootloader
#include <ThingerZephyr.h>
#include "arduino_secrets.h"

// pins of the Opta on the Zephyr core: relays, their status leds and user button
#define RELAY_D0 D0
#define RELAY_D1 D1
#define RELAY_D2 D2
#define RELAY_D3 D3
#define LED_D0 D4
#define LED_D1 D5
#define LED_D2 D6
#define LED_D3 D7
#define BUTTON_USER D11

ThingerZephyr thing(USERNAME, DEVICE_ID, DEVICE_CREDENTIAL);

// voltage of an input (0-10V), from its 12-bit reading and the voltage divider of the Opta
float input_voltage(int pin) {
  return analogRead(pin) * (3.249 / 4095.0) / 0.3034;
}

void setup() {
  // open serial for debugging
  Serial.begin(115200);

  // configure relays and their status leds for output
  pinMode(RELAY_D0, OUTPUT);
  pinMode(RELAY_D1, OUTPUT);
  pinMode(RELAY_D2, OUTPUT);
  pinMode(RELAY_D3, OUTPUT);
  pinMode(LED_D0, OUTPUT);
  pinMode(LED_D1, OUTPUT);
  pinMode(LED_D2, OUTPUT);
  pinMode(LED_D3, OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(BUTTON_USER, INPUT);

  // read the inputs with 12 bits
  analogReadResolution(12);

  // configure wifi network
  thing.add_wifi(SSID, SSID_PASSWORD);

  // control each relay and its status led; reading the resource returns the relay state
  thing["relay_d0"] << [](pson& in){
    if(in.is_empty()){
      in = (bool) digitalRead(RELAY_D0);
    }else{
      digitalWrite(RELAY_D0, in ? HIGH : LOW);
      digitalWrite(LED_D0, in ? HIGH : LOW);
    }
  };

  thing["relay_d1"] << [](pson& in){
    if(in.is_empty()){
      in = (bool) digitalRead(RELAY_D1);
    }else{
      digitalWrite(RELAY_D1, in ? HIGH : LOW);
      digitalWrite(LED_D1, in ? HIGH : LOW);
    }
  };

  thing["relay_d2"] << [](pson& in){
    if(in.is_empty()){
      in = (bool) digitalRead(RELAY_D2);
    }else{
      digitalWrite(RELAY_D2, in ? HIGH : LOW);
      digitalWrite(LED_D2, in ? HIGH : LOW);
    }
  };

  thing["relay_d3"] << [](pson& in){
    if(in.is_empty()){
      in = (bool) digitalRead(RELAY_D3);
    }else{
      digitalWrite(RELAY_D3, in ? HIGH : LOW);
      digitalWrite(LED_D3, in ? HIGH : LOW);
    }
  };

  // control the USER led
  thing["led"] << digitalPin(LED_BUILTIN);

  // USER button (pressed when low)
  thing["button"] >> outputValue(digitalRead(BUTTON_USER) == LOW);

  // voltage of the inputs I1 to I8
  thing["inputs"] >> [](pson& out){
    out["I1"] = input_voltage(A0);
    out["I2"] = input_voltage(A1);
    out["I3"] = input_voltage(A2);
    out["I4"] = input_voltage(A3);
    out["I5"] = input_voltage(A4);
    out["I6"] = input_voltage(A5);
    out["I7"] = input_voltage(A6);
    out["I8"] = input_voltage(A7);
  };
}

void loop() {
  thing.handle();
}

Send true or false to relay_d0 to close or open the first relay; read it to get its state. inputs returns an object with the voltage of each input, such as {"I1": 0.02, "I2": 9.87} for the first two.

Opta over Ethernet ​

Mbed OS core, on every Opta model. Example ArduinoOptaEthernet, with OTA updates. One input resource per relay: it switches the relay and its status LED and, when you read it, returns the state of the relay. Two more input resources control the USER LED (LED_BUILTIN) and the red status LED (LEDR).

ArduinoOptaEthernet.ino
cpp
// enable debug output over serial
#define THINGER_SERIAL_DEBUG

// #define THINGER_SERVER "YOUR_SERVER"  // only on a private instance

#include <ThingerMbedEth.h>
#include <ThingerPortentaOTA.h>
#include "arduino_secrets.h"

ThingerMbedEth thing(USERNAME, DEVICE_ID, DEVICE_CREDENTIAL);
ThingerPortentaOTA ota(thing);

void setup() {
  // open serial for debugging
  Serial.begin(115200);

  // configure leds for output
  pinMode(LED_D0, OUTPUT);
  pinMode(LED_D1, OUTPUT);
  pinMode(LED_D2, OUTPUT);
  pinMode(LED_D3, OUTPUT);
  pinMode(LEDR, OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT);

  // configure relays for output
  pinMode(D0, OUTPUT);
  pinMode(D1, OUTPUT);
  pinMode(D2, OUTPUT);
  pinMode(D3, OUTPUT);

  // control each relay and its status led; reading the resource returns the relay state
  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);
    }
  };

  thing["relay_d1"] << [](pson& in){
    if(in.is_empty()){
      in = (bool) digitalRead(D1);
    }else{
      digitalWrite(D1, in ? HIGH : LOW);
      digitalWrite(LED_D1, in ? HIGH : LOW);
    }
  };

  thing["relay_d2"] << [](pson& in){
    if(in.is_empty()){
      in = (bool) digitalRead(D2);
    }else{
      digitalWrite(D2, in ? HIGH : LOW);
      digitalWrite(LED_D2, in ? HIGH : LOW);
    }
  };

  thing["relay_d3"] << [](pson& in){
    if(in.is_empty()){
      in = (bool) digitalRead(D3);
    }else{
      digitalWrite(D3, in ? HIGH : LOW);
      digitalWrite(LED_D3, in ? HIGH : LOW);
    }
  };

  // control the USER led and the red status led
  thing["led"] << digitalPin(LED_BUILTIN);
  thing["led_r"] << digitalPin(LEDR);

  // resource output example (i.e. reading a sensor value, a variable, etc)
  thing["millis"] >> outputValue(millis());

  // start the connection in its own thread
  thing.start();
}

void loop() {
  // your code here: no thing.handle() needed
  // wrap variables shared with device resources in thing.lock() and thing.unlock()
  delay(1000);
}

Send true or false to relay_d0 to close or open the first relay; read it to get its state.

Opta WiFi over Wi-Fi ​

Mbed OS core. Example ArduinoOptaWiFi, with OTA updates. The same device resources as the Ethernet sketch; only the include, the class and add_wifi() change.

ArduinoOptaWiFi.ino
cpp
#define THINGER_SERIAL_DEBUG

#include <ThingerMbed.h>
#include <ThingerPortentaOTA.h>
#include "arduino_secrets.h"

ThingerMbed thing(USERNAME, DEVICE_ID, DEVICE_CREDENTIAL);
ThingerPortentaOTA ota(thing);

void setup() {
  // open serial for debugging
  Serial.begin(115200);

  // configure wifi network
  thing.add_wifi(SSID, SSID_PASSWORD);

  // configure leds for output
  pinMode(LED_D0, OUTPUT);
  pinMode(LED_D1, OUTPUT);
  pinMode(LED_D2, OUTPUT);
  pinMode(LED_D3, OUTPUT);
  pinMode(LEDR, OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT);

  // configure relays for output
  pinMode(D0, OUTPUT);
  pinMode(D1, OUTPUT);
  pinMode(D2, OUTPUT);
  pinMode(D3, OUTPUT);

  // control each relay and its status led; reading the resource returns the relay state
  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);
    }
  };

  thing["relay_d1"] << [](pson& in){
    if(in.is_empty()){
      in = (bool) digitalRead(D1);
    }else{
      digitalWrite(D1, in ? HIGH : LOW);
      digitalWrite(LED_D1, in ? HIGH : LOW);
    }
  };

  thing["relay_d2"] << [](pson& in){
    if(in.is_empty()){
      in = (bool) digitalRead(D2);
    }else{
      digitalWrite(D2, in ? HIGH : LOW);
      digitalWrite(LED_D2, in ? HIGH : LOW);
    }
  };

  thing["relay_d3"] << [](pson& in){
    if(in.is_empty()){
      in = (bool) digitalRead(D3);
    }else{
      digitalWrite(D3, in ? HIGH : LOW);
      digitalWrite(LED_D3, in ? HIGH : LOW);
    }
  };

  // control the USER led and the red status led
  thing["led"] << digitalPin(LED_BUILTIN);
  thing["led_r"] << digitalPin(LEDR);

  // resource output example (i.e. reading a sensor value, a variable, etc)
  thing["millis"] >> outputValue(millis());

  // start the connection in its own thread
  thing.start();
}

void loop() {
  // your code here: no thing.handle() needed
  // wrap variables shared with device resources in thing.lock() and thing.unlock()
  delay(1000);
}

Zephyr core ​

Install the Zephyr loader ​

The sketches of the Zephyr core run on top of a Zephyr firmware, the loader, that the board needs once:

  1. Connect the board over USB and select it in Tools > Board > Arduino Zephyr Boards.
  2. Double-click the reset button of the board to enter bootloader mode.
  3. Click Tools > Burn Bootloader. If the Arduino IDE asks for a programmer, select any one in Tools > Programmer.
  4. Double-click the reset button again and upload your sketch.

The next sketches upload as usual.

Install the Wi-Fi firmware ​

The Wi-Fi module reads its firmware from the flash of the board. If the Wi-Fi does not start, the serial monitor shows Communication with WiFi module failed, or WiFi.status() returns WL_NO_SHIELD. Install the firmware:

  1. Open File > Examples > Storage > FlashFormat, an example of the Zephyr core, and upload it.
  2. Open the serial monitor and answer the questions of the sketch: Y to format the partitions, to restore the TLS certificates and to restore the Wi-Fi firmware. Formatting erases the data your sketches stored in the flash.
  3. Upload your sketch again.

The FAQ of the Zephyr core describes the same fix.

TLS on the Zephyr core ​

ThingerZephyr validates the server with the root certificate of the library. On a private instance, THINGER_ROOT_CA and thing.set_root_ca() set the root certificate of your server, and #define THINGER_INSECURE_SSL skips the check, as in Server and TLS.

Mbed OS core ​

The examples of the Mbed OS core are in Portenta H7 over Wi-Fi, Portenta H7 over Ethernet, Opta over Ethernet and Opta WiFi over Wi-Fi. They receive OTA updates with ThingerPortentaOTA and run the connection in its own thread.

The connection thread ​

thing.start() starts the connection in its own thread and returns at once; setup() goes on and loop() runs your code. thing.stop() ends the thread and thing.is_running() tells whether it runs.

Device resources run in the connection thread, while loop() runs in the main one. When you change a variable in loop() that a device resource also reads or writes, do it between thing.lock() and thing.unlock(), as the comment in the examples says.

Prepare the board ​

On the Mbed OS core, the Portenta H7 and the Opta need a recent bootloader for OTA updates, a formatted flash to store the new firmware, and an up-to-date Wi-Fi firmware with its certificates for TLS. Run these example sketches of the board package once, in this order, from File > Examples > STM32H747_System. Each one runs from the serial monitor: open it after the upload and follow the prompts.

Arduino IDE examples menu open at STM32H747_System, with QSPIFormat, QSPIReadPartitions, STM32H747_getBootloaderInfo, STM32H747_getResetReason, STM32H747_manageBootloader and WiFiFirmwareUpdater
The STM32H747_System examples of the board package
OrderExampleWhat it does
1STM32H747_manageBootloaderUpdates the bootloader. OTA updates need bootloader version 22 or later; with an older one, an update fails with OTA is not supported by the current bootloader. STM32H747_getBootloaderInfo shows the current version.
2QSPIFormatFormats the QSPI flash and creates its partitions. The OTA update stores the new firmware there before installing it. Formatting erases the flash.
3WiFiFirmwareUpdaterWrites the Wi-Fi firmware and the root certificates to the flash. Run it also when the TLS connection fails, over Wi-Fi or over Ethernet.

After the three sketches, upload your own sketch again.

See also ​