Found the answer, it is possible using the MatterLightbulb library, with which you can create multiple virtual Matter switches, also of different kinds (binary and gradual). I put together an example sketch how to utilize that, with the use case of remotely controlling a motor over Matter. I tried it with Home Assistant and it does what it is supposed to do. Please let me know if you see some issues in the code or opportunities for improvement, so I can adjust it. Cheers!
/*
Matter motor control, using the Arduino Nano Matter board and an L298N motor driver module
With the use case of controlling a motor in direction and speed, this example shows how to:
- remotely control multiple pins on the Arduino Nano Matter board over Matter
- use pins to provide either a binary LOW/HIGH output, or a continuous range of values
The communication over Matter is done with the MatterLightbulb library, which allows:
- generation of multiple light bulbs
- generation of on/off switches for light bulbs, but also of dimmable controls of light bulbs
As done here, this library can also be used to control a motor (or for anything else).
Also contained are the functions to commission (= connect) the Arduino Nano Matter to your network,
and to decommission (= disconnect) it again. Commissioning is done by generation of a QR code,
which can then be scanned. Decommissioning is done by long-pressing the onboard user button.
See the Arduino Nano Matter User Manual for further details on Matter communication.
This example lets users control one motor via an L298N motor driver module remotely over Matter.
Code can easily be duplicated at the respective positions to also provide control of two motors.
Logic input for the L298N (driving one motor):
IN1 and IN2 to control the direction:
- both LOW or both HIGH stops the motor
- only one or the other HIGH yields forward or reverse
ENA to control the speed via PWM:
- PWM of zero stops the motor
- PWM of >0 provides gradual control of the motor speed
Author: mooseonmars, Sep 2025 (largely based on work by Tamas Jozsi from Silicon Labs)
*/
#include <Matter.h>
#include <MatterLightbulb.h>
// Assign L298N pins to Arduino pins
#define pin_ENA D4 // ENA on pin D4
#define pin_IN1 D5 // IN1 on pin D5
#define pin_IN2 D6 // IN2 on pin D6
/*
Generate instances of multiple virtual switches from the MatterLightbulb library.
These will be shown in your home automation user interface, like Home Assistant.
matter_bulb_XYZ
will yield a virtual binary ON/OFF switch
can be used to switch an Arduino pin LOW or HIGH
matter_dimmable_bulb_XYZ
will yield a virtual switch with ON/OFF, but also with a 1-100% gradient
can be used to switch a pin LOW or HIGH
can be used to apply PWM to a pin
(can also be used for other controls, like providing input for a digipot)
Each instance will be used at 4 positions in the sketch:
- here where it is instantiated
- in setup() where it is initialized with .begin()
- within the corresponding function run in loop()
- within the commissioning function at .is_online()
*/
// Binary ON/OFF switches to control IN1 and IN2
MatterLightbulb matter_bulb_IN1;
MatterLightbulb matter_bulb_IN2;
// Continuous switch to control ENA, comes with ON/OFF information and an additional gradient value when ON
MatterDimmableLightbulb matter_dimmable_bulb_ENA;
// Declare functions
void commission_handler();
void decommission_handler();
void handle_IN1();
void handle_IN2();
void handle_ENA();
// Setup --------------------------------------------------
void setup()
{
Serial.begin(115200);
Matter.begin();
matter_bulb_IN1.begin();
matter_bulb_IN2.begin();
matter_dimmable_bulb_ENA.begin();
// Onboard button as input, pulled up, button press will result in LOW (used for decommissioning)
pinMode(BTN_BUILTIN, INPUT_PULLUP);
// Onboard LED as output (used during decommissioning)
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LED_BUILTIN_INACTIVE);
// Pins IN1 and IN2 for motor direction
pinMode(pin_IN1, OUTPUT);
digitalWrite(pin_IN1, LOW);
pinMode(pin_IN2, OUTPUT);
digitalWrite(pin_IN2, LOW);
// Pin ENA for motor speed control via PWM
pinMode(pin_ENA, OUTPUT);
analogWrite(pin_ENA, 0);
// Startup message
Serial.println("Matter motor control");
// Function to initiate and check on commissioning of Arduino Nano Matter
commission_handler();
}
// Loop --------------------------------------------------
void loop()
{
// Function to monitor whether the signal for decommissioning has been given
decommission_handler();
// Functions to read the state of the virtual Matter switches and to adjust the Arduino pins accordingly
handle_IN1();
handle_IN2();
handle_ENA();
}
// Functions --------------------------------------------------
void handle_IN1()
{
static bool last_state = false;
bool current_state = matter_bulb_IN1.get_onoff(); // Reads the ON/OFF state of the binary Matter switch
// If the current state is ON and the previous was OFF - turn ON
if (current_state && !last_state) {
last_state = current_state;
digitalWrite(pin_IN1, HIGH);
Serial.println("Pin IN1 set to HIGH");
}
// If the current state is OFF and the previous was ON - turn OFF
if (!current_state && last_state) {
last_state = current_state;
digitalWrite(pin_IN1, LOW);
Serial.println("Pin IN1 set to LOW");
}
}
void handle_IN2()
{
static bool last_state = false;
bool current_state = matter_bulb_IN2.get_onoff(); // Reads the ON/OFF state of the binary Matter switch
// If the current state is ON and the previous was OFF - turn ON
if (current_state && !last_state) {
last_state = current_state;
digitalWrite(pin_IN2, HIGH);
Serial.println("Pin IN2 set to HIGH");
}
// If the current state is OFF and the previous was ON - turn OFF
if (!current_state && last_state) {
last_state = current_state;
digitalWrite(pin_IN2, LOW);
Serial.println("Pin IN2 set to LOW");
}
}
void handle_ENA()
{
static bool last_state = false;
bool current_state = matter_dimmable_bulb_ENA.get_onoff(); // Reads the ON/OFF state of the contiuous Matter switch
static uint8_t last_speed = matter_dimmable_bulb_ENA.get_brightness();
uint8_t current_speed = matter_dimmable_bulb_ENA.get_brightness(); // Reads range of values from the contiuous Matter switch (when used with Home Assistant it returned values from 4 to 254)
uint8_t pwmValue = map(current_speed, 4, 254, 0, 255); // Translate to 0 to 255 scale
// If the current state is ON and the previous was OFF - switch ON and set it to the last speed value
if (current_state && !last_state) {
last_state = current_state;
analogWrite(pin_ENA, pwmValue);
Serial.printf("Motor A ON, speed set to: %u%%\n", matter_dimmable_bulb_ENA.get_brightness_percent()); // Reads percentage value from the contiuous Matter switch
}
// If the current state is OFF and the previous was ON - turn OFF
if (!current_state && last_state) {
last_state = current_state;
analogWrite(pin_ENA, 0);
Serial.println("Motor A OFF");
}
// If the speed changes - update the speed accordingly
if (last_speed != current_speed) {
last_speed = current_speed;
analogWrite(pin_ENA, pwmValue);
Serial.printf("Motor A speed changed to: %u%%\n", matter_dimmable_bulb_ENA.get_brightness_percent());
}
}
// Commissioning: Connects Arduino Nano Matter to Matter network and stores details on chip
void commission_handler()
{
if (!Matter.isDeviceCommissioned()) {
Serial.println("Matter device is not commissioned");
Serial.println("Commission it to your Matter hub with the manual pairing code or QR code");
Serial.printf("Manual pairing code: %s\n", Matter.getManualPairingCode().c_str());
Serial.printf("QR code URL: %s\n", Matter.getOnboardingQRCodeUrl().c_str());
}
while (!Matter.isDeviceCommissioned()) {
delay(200);
}
Serial.println("Waiting for Thread network...");
while (!Matter.isDeviceThreadConnected()) {
decommission_handler(); // Enable decommissioning if something goes wrong during commissioning
delay(200);
}
Serial.println("Connected to Thread network");
Serial.println("Waiting for Matter device discovery...");
// All Matter instances need to be listed here:
while (!matter_bulb_IN1.is_online() || \
!matter_bulb_IN2.is_online() || \
!matter_dimmable_bulb_ENA.is_online()) {
decommission_handler(); // Enable decommissioning if something goes wrong during commissioning
delay(200);
}
Serial.println("Matter devices are now online");
}
// Decommissioning: Erases the Matter connection details stored on chip so the device can be newly connected
void decommission_handler()
{
// If the button is not pressed or the device is not commissioned - return
if (digitalRead(BTN_BUILTIN) != LOW || !Matter.isDeviceCommissioned()) {
return;
}
// Store the time when the button was first pressed
uint32_t start_time = millis();
while (digitalRead(BTN_BUILTIN) == LOW) { // While the button is being pressed
uint32_t elapsed_time = millis() - start_time; // Calculate the elapsed time
if (elapsed_time < 10000u) { // If the button has been pressed for less than 10 seconds - continue
yield();
continue;
}
// Blink the LED to indicate the start of the decommissioning process
for (uint8_t i = 0u; i < 10u; i++) {
digitalWrite(LED_BUILTIN, !(digitalRead(LED_BUILTIN)));
delay(100);
}
Serial.println("Starting decommissioning process, device will reboot...");
Serial.println();
digitalWrite(LED_BUILTIN, LED_BUILTIN_INACTIVE);
// This function will not return
// The device will restart once decommissioning has finished
Matter.decommission();
}
}