Skip to content

Zigbee remote over MQTT

Reads the button presses of a Zigbee remote straight from Zigbee2MQTT, and turns a light on, off, brighter or dimmer. No Home Assistant automation is involved, and presses are handled as soon as they arrive.

You need Zigbee2MQTT and an MQTT server that Stewart is connected to.

apps/ZigbeeRemote.php
<?php
declare(strict_types=1);
namespace App;
use Stewart\Contracts\App;
use Stewart\Contracts\Automation;
use Stewart\Contracts\HaContext;
use Stewart\Contracts\Mqtt\Mqtt;
use Stewart\Contracts\Mqtt\MqttMessage;
use Stewart\Contracts\Service\ServiceTarget;
#[Automation(id: 'zigbee-remote')]
final readonly class ZigbeeRemote implements App
{
public function __construct(
private HaContext $ha,
private Mqtt $mqtt,
private string $remote = 'living_room_remote',
private string $light = 'light.living_room',
) {}
public function initialize(): void
{
$this->mqtt->watchMessages("zigbee2mqtt/{$this->remote}/action")->subscribe(function (MqttMessage $message): void {
[$service, $data] = match ($message->payload) {
'on' => ['turn_on', []],
'off' => ['turn_off', []],
'brightness_up' => ['turn_on', ['brightness_step_pct' => 10]],
'brightness_down' => ['turn_on', ['brightness_step_pct' => -10]],
default => [null, []],
};
if ($service !== null) {
$this->ha->callService('light', $service, $data, ServiceTarget::forEntities($this->light));
}
});
}
public function dispose(): void {}
}
apps:
zigbee-remote:
options:
remote: living_room_remote # the remote's friendly name in Zigbee2MQTT
light: light.living_room
  • Zigbee2MQTT publishes each press on zigbee2mqtt/<name>/action, with the action as the payload.
  • A match turns each action into a service and its data. Unknown actions are ignored.
  • brightness_step_pct changes the brightness relative to where it is.
tests/ZigbeeRemoteTest.php
<?php
declare(strict_types=1);
namespace App\Tests;
use App\ZigbeeRemote;
use PHPUnit\Framework\TestCase;
use Stewart\Contracts\Mqtt\MqttMessage;
use Stewart\Testing\App\AppHarness;
final class ZigbeeRemoteTest extends TestCase
{
private AppHarness $harness;
protected function setUp(): void
{
$this->harness = new AppHarness();
$this->harness->startApp(ZigbeeRemote::class);
}
public function testBrightnessUpBrightensTheLight(): void
{
$this->harness->mqtt->pushMessage(new MqttMessage('zigbee2mqtt/living_room_remote/action', 'brightness_up'));
$call = $this->harness->ha->getLastCall();
self::assertSame('light.turn_on', $call->getServiceName());
self::assertSame(['brightness_step_pct' => 10], $call->data);
}
public function testUnknownActionsAreIgnored(): void
{
$this->harness->mqtt->pushMessage(new MqttMessage('zigbee2mqtt/living_room_remote/action', 'arrow_left_click'));
self::assertTrue($this->harness->ha->calls->isEmpty());
}
}
  • Find your remote’s actions: watch zigbee2mqtt/<name>/action with mosquitto_sub, and press each button.
  • Long presses: many remotes send brightness_move_up and brightness_stop; start a runEvery() on the first and cancel it on the second.