matterbridge
Version:
Matterbridge plugin manager for Matter
376 lines • 22.9 kB
JavaScript
/**
* This file contains the class MatterbridgeEndpoint that extends the Endpoint class from the Matter.js library.
*
* @file matterbridgeBehaviors.ts
* @author Luca Liguori
* @date 2024-11-07
* @version 1.0.0
*
* Copyright 2024, 2025, 2026 Luca Liguori.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. *
*/
/* eslint-disable @typescript-eslint/no-namespace */
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
// @matter
import { Behavior } from '@matter/main';
// @matter clusters
import { BooleanStateConfiguration } from '@matter/main/clusters/boolean-state-configuration';
import { ColorControl } from '@matter/main/clusters/color-control';
import { FanControl } from '@matter/main/clusters/fan-control';
import { WindowCovering } from '@matter/main/clusters/window-covering';
import { Thermostat } from '@matter/main/clusters/thermostat';
import { ValveConfigurationAndControl } from '@matter/main/clusters/valve-configuration-and-control';
import { SmokeCoAlarm } from '@matter/main/clusters/smoke-co-alarm';
import { BooleanStateConfigurationServer } from '@matter/main/behaviors/boolean-state-configuration';
// @matter behaviors
import { IdentifyServer } from '@matter/main/behaviors/identify';
import { OnOffServer } from '@matter/main/behaviors/on-off';
import { LevelControlServer } from '@matter/main/behaviors/level-control';
import { ColorControlServer } from '@matter/main/behaviors/color-control';
import { WindowCoveringServer } from '@matter/main/behaviors/window-covering';
import { DoorLockServer } from '@matter/main/behaviors/door-lock';
import { FanControlServer } from '@matter/main/behaviors/fan-control';
import { ThermostatServer } from '@matter/main/behaviors/thermostat';
import { ValveConfigurationAndControlServer } from '@matter/main/behaviors/valve-configuration-and-control';
import { ModeSelectServer } from '@matter/main/behaviors/mode-select';
import { SmokeCoAlarmServer } from '@matter/main/behaviors/smoke-co-alarm';
import { SwitchServer } from '@matter/main/behaviors/switch';
export class MatterbridgeBehaviorDevice {
log;
commandHandler;
device; // Will be a plugin device
endpointId = undefined;
endpointNumber = undefined;
constructor(log, commandHandler, device) {
this.log = log;
this.commandHandler = commandHandler;
this.device = device;
}
setEndpointId(endpointId) {
this.endpointId = endpointId;
}
setEndpointNumber(endpointNumber) {
this.endpointNumber = endpointNumber;
}
identify({ identifyTime }) {
this.log.info(`Identifying device for ${identifyTime} seconds`);
this.commandHandler.executeHandler('identify', { request: { identifyTime }, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
}
triggerEffect({ effectIdentifier, effectVariant }) {
this.log.info(`Triggering effect ${effectIdentifier} variant ${effectVariant}`);
this.commandHandler.executeHandler('triggerEffect', { request: { effectIdentifier, effectVariant }, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
}
on() {
this.log.info(`Switching device on (endpoint ${this.endpointId}.${this.endpointNumber})`);
this.commandHandler.executeHandler('on', { request: {}, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
}
off() {
this.log.info(`Switching device off (endpoint ${this.endpointId}.${this.endpointNumber})`);
this.commandHandler.executeHandler('off', { request: {}, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
}
toggle() {
this.log.info(`Toggle device on/off (endpoint ${this.endpointId}.${this.endpointNumber})`);
this.commandHandler.executeHandler('toggle', { request: {}, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
}
moveToLevel({ level, transitionTime, optionsMask, optionsOverride }) {
this.log.info(`Setting level to ${level} with transitionTime ${transitionTime} (endpoint ${this.endpointId}.${this.endpointNumber})`);
this.commandHandler.executeHandler('moveToLevel', { request: { level, transitionTime, optionsMask, optionsOverride }, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
}
moveToLevelWithOnOff({ level, transitionTime, optionsMask, optionsOverride }) {
this.log.info(`Setting level to ${level} with transitionTime ${transitionTime} (endpoint ${this.endpointId}.${this.endpointNumber})`);
this.commandHandler.executeHandler('moveToLevelWithOnOff', { request: { level, transitionTime, optionsMask, optionsOverride }, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
}
moveToHue({ optionsMask, optionsOverride, hue, direction, transitionTime }) {
this.log.info(`Setting hue to ${hue} with transitionTime ${transitionTime} (endpoint ${this.endpointId}.${this.endpointNumber})`);
this.commandHandler.executeHandler('moveToHue', { request: { optionsMask, optionsOverride, hue, direction, transitionTime }, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
}
moveToSaturation({ optionsMask, optionsOverride, saturation, transitionTime }) {
this.log.info(`Setting saturation to ${saturation} with transitionTime ${transitionTime} (endpoint ${this.endpointId}.${this.endpointNumber})`);
this.commandHandler.executeHandler('moveToSaturation', { request: { optionsMask, optionsOverride, saturation, transitionTime }, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
}
moveToHueAndSaturation({ optionsOverride, optionsMask, saturation, hue, transitionTime }) {
this.log.info(`Setting hue to ${hue} and saturation to ${saturation} with transitionTime ${transitionTime} (endpoint ${this.endpointId}.${this.endpointNumber})`);
this.commandHandler.executeHandler('moveToHueAndSaturation', { request: { optionsOverride, optionsMask, saturation, hue, transitionTime }, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
}
moveToColor({ optionsMask, optionsOverride, colorX, colorY, transitionTime }) {
this.log.info(`Setting color to ${colorX}, ${colorY} with transitionTime ${transitionTime} (endpoint ${this.endpointId}.${this.endpointNumber})`);
this.commandHandler.executeHandler('moveToColor', { request: { optionsMask, optionsOverride, colorX, colorY, transitionTime }, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
}
moveToColorTemperature({ optionsOverride, optionsMask, colorTemperatureMireds, transitionTime }) {
this.log.info(`Setting color temperature to ${colorTemperatureMireds} with transitionTime ${transitionTime} (endpoint ${this.endpointId}.${this.endpointNumber})`);
this.commandHandler.executeHandler('moveToColorTemperature', {
request: { optionsOverride, optionsMask, colorTemperatureMireds, transitionTime },
attributes: {},
endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId },
});
}
upOrOpen() {
this.log.info(`Opening cover (endpoint ${this.endpointId}.${this.endpointNumber})`);
this.commandHandler.executeHandler(`upOrOpen`, { request: {}, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
}
downOrClose() {
this.log.info(`Closing cover (endpoint ${this.endpointId}.${this.endpointNumber})`);
this.commandHandler.executeHandler(`downOrClose`, { request: {}, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
}
stopMotion() {
this.log.info(`Stopping cover (endpoint ${this.endpointId}.${this.endpointNumber})`);
this.commandHandler.executeHandler('stopMotion', { request: {}, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
}
goToLiftPercentage({ liftPercent100thsValue }) {
this.log.info(`Setting cover lift percentage to ${liftPercent100thsValue} (endpoint ${this.endpointId}.${this.endpointNumber})`);
this.commandHandler.executeHandler('goToLiftPercentage', { request: { liftPercent100thsValue }, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
}
lockDoor() {
this.log.info(`Locking door (endpoint ${this.endpointId}.${this.endpointNumber})`);
this.commandHandler.executeHandler('lockDoor', { request: {}, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
}
unlockDoor() {
this.log.info(`Unlocking door (endpoint ${this.endpointId}.${this.endpointNumber})`);
this.commandHandler.executeHandler('unlockDoor', { request: {}, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
}
step({ direction, wrap, lowestOff }) {
this.log.info(`Stepping fan with direction ${direction} (endpoint ${this.endpointId}.${this.endpointNumber})`);
this.commandHandler.executeHandler('step', { request: { direction, wrap, lowestOff }, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
}
setpointRaiseLower({ mode, amount }) {
this.log.info(`Setting setpoint to ${amount} in mode ${mode} (endpoint ${this.endpointId}.${this.endpointNumber})`);
this.commandHandler.executeHandler('setpointRaiseLower', { request: { mode, amount }, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
}
open({ openDuration, targetLevel }) {
this.log.info(`Opening valve (endpoint ${this.endpointId}.${this.endpointNumber})`);
this.commandHandler.executeHandler('open', { request: { openDuration, targetLevel }, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
}
close() {
this.log.info(`Closing valve (endpoint ${this.endpointId}.${this.endpointNumber})`);
this.commandHandler.executeHandler('close', { request: {}, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
}
changeToMode({ newMode }) {
this.log.info(`Changing mode to ${newMode}`);
this.commandHandler.executeHandler('changeToMode', { request: { newMode }, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
}
selfTestRequest() {
this.log.info(`Testing SmokeCOAlarm (endpoint ${this.endpointId}.${this.endpointNumber})`);
this.commandHandler.executeHandler('selfTestRequest', { request: {}, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
}
enableDisableAlarm({ alarmsToEnableDisable }) {
this.log.info(`Enabling/disabling alarm ${alarmsToEnableDisable}`);
this.commandHandler.executeHandler('enableDisableAlarm', { request: { alarmsToEnableDisable }, attributes: {}, endpoint: { number: this.endpointNumber, uniqueStorageKey: this.endpointId } });
}
}
export class MatterbridgeBehavior extends Behavior {
static id = 'matterbridge';
}
(function (MatterbridgeBehavior) {
class State {
deviceCommand;
}
MatterbridgeBehavior.State = State;
})(MatterbridgeBehavior || (MatterbridgeBehavior = {}));
export class MatterbridgeIdentifyServer extends IdentifyServer {
initialize() {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.setEndpointId(this.endpoint.maybeId);
device.setEndpointNumber(this.endpoint.maybeNumber);
super.initialize();
}
identify({ identifyTime }) {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.identify({ identifyTime });
super.identify({ identifyTime });
}
triggerEffect({ effectIdentifier, effectVariant }) {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.triggerEffect({ effectIdentifier, effectVariant });
super.triggerEffect({ effectIdentifier, effectVariant });
}
}
export class MatterbridgeOnOffServer extends OnOffServer {
async on() {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.on();
super.on();
}
async off() {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.off();
super.off();
}
async toggle() {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.toggle();
super.toggle();
}
}
export class MatterbridgeLevelControlServer extends LevelControlServer {
async moveToLevel({ level, transitionTime, optionsMask, optionsOverride }) {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.moveToLevel({ level, transitionTime, optionsMask, optionsOverride });
super.moveToLevel({ level, transitionTime, optionsMask, optionsOverride });
}
async moveToLevelWithOnOff({ level, transitionTime, optionsMask, optionsOverride }) {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.moveToLevelWithOnOff({ level, transitionTime, optionsMask, optionsOverride });
super.moveToLevelWithOnOff({ level, transitionTime, optionsMask, optionsOverride });
}
}
export class MatterbridgeColorControlServer extends ColorControlServer.with(ColorControl.Feature.HueSaturation, ColorControl.Feature.Xy, ColorControl.Feature.ColorTemperature) {
async moveToHue({ optionsMask, optionsOverride, hue, direction, transitionTime }) {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.moveToHue({ optionsMask, optionsOverride, hue, direction, transitionTime });
super.moveToHue({ optionsMask, optionsOverride, hue, direction, transitionTime });
}
async moveToSaturation({ optionsMask, optionsOverride, saturation, transitionTime }) {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.moveToSaturation({ optionsMask, optionsOverride, saturation, transitionTime });
super.moveToSaturation({ optionsMask, optionsOverride, saturation, transitionTime });
}
async moveToHueAndSaturation({ optionsOverride, optionsMask, saturation, hue, transitionTime }) {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.moveToHueAndSaturation({ optionsOverride, optionsMask, saturation, hue, transitionTime });
super.moveToHueAndSaturation({ optionsOverride, optionsMask, saturation, hue, transitionTime });
}
async moveToColor({ optionsMask, optionsOverride, colorX, colorY, transitionTime }) {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.moveToColor({ optionsMask, optionsOverride, colorX, colorY, transitionTime });
super.moveToColor({ optionsMask, optionsOverride, colorX, colorY, transitionTime });
}
async moveToColorTemperature({ optionsOverride, optionsMask, colorTemperatureMireds, transitionTime }) {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.moveToColorTemperature({ optionsOverride, optionsMask, colorTemperatureMireds, transitionTime });
super.moveToColorTemperature({ optionsOverride, optionsMask, colorTemperatureMireds, transitionTime });
}
}
export class MatterbridgeWindowCoveringServer extends WindowCoveringServer.with(WindowCovering.Feature.Lift, WindowCovering.Feature.PositionAwareLift) {
async upOrOpen() {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.upOrOpen();
super.upOrOpen();
}
async downOrClose() {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.downOrClose();
super.downOrClose();
}
stopMotion() {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.stopMotion();
super.stopMotion();
}
goToLiftPercentage({ liftPercent100thsValue }) {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.goToLiftPercentage({ liftPercent100thsValue });
super.goToLiftPercentage({ liftPercent100thsValue });
}
async handleMovement(type, reversed, direction, targetPercent100ths) {
// Do nothing here, as the device will handle the movement
}
}
export class MatterbridgeDoorLockServer extends DoorLockServer {
async lockDoor() {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.lockDoor();
super.lockDoor();
}
async unlockDoor() {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.unlockDoor();
super.unlockDoor();
}
}
export class MatterbridgeModeSelectServer extends ModeSelectServer {
async changeToMode({ newMode }) {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.changeToMode({ newMode });
super.changeToMode({ newMode });
}
}
export class MatterbridgeFanControlServer extends FanControlServer.with(FanControl.Feature.MultiSpeed, FanControl.Feature.Auto, FanControl.Feature.Step) {
async step({ direction, wrap, lowestOff }) {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.step({ direction, wrap, lowestOff });
const lookupStepDirection = ['Increase', 'Decrease'];
device.log.debug(`Command step called with direction: ${lookupStepDirection[direction]} wrap: ${wrap} lowestOff: ${lowestOff}`);
device.log.debug(`- current percentCurrent: ${this.state.percentCurrent}`);
if (direction === FanControl.StepDirection.Increase) {
if (wrap && this.state.percentCurrent === 100) {
this.state.percentCurrent = lowestOff ? 0 : 10;
}
else
this.state.percentCurrent = Math.min(this.state.percentCurrent + 10, 100);
}
else if (direction === FanControl.StepDirection.Decrease) {
if (wrap && this.state.percentCurrent === (lowestOff ? 0 : 10)) {
this.state.percentCurrent = 100;
}
else
this.state.percentCurrent = Math.max(this.state.percentCurrent - 10, lowestOff ? 0 : 10);
}
device.log.debug('Set percentCurrent to:', this.state.percentCurrent);
super.step({ direction, wrap, lowestOff });
}
}
export class MatterbridgeThermostatServer extends ThermostatServer.with(Thermostat.Feature.Cooling, Thermostat.Feature.Heating, Thermostat.Feature.AutoMode) {
async setpointRaiseLower({ mode, amount }) {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.setpointRaiseLower({ mode, amount });
const lookupSetpointAdjustMode = ['Heat', 'Cool', 'Both'];
device.log.debug(`Command setpointRaiseLower called with mode: ${lookupSetpointAdjustMode[mode]} amount: ${amount / 10}`);
device.log.debug(`- current occupiedHeatingSetpoint: ${this.state.occupiedHeatingSetpoint / 100}`);
device.log.debug(`- current occupiedCoolingSetpoint: ${this.state.occupiedCoolingSetpoint / 100}`);
if ((mode === Thermostat.SetpointRaiseLowerMode.Heat || mode === Thermostat.SetpointRaiseLowerMode.Both) && this.state.occupiedHeatingSetpoint !== undefined) {
const setpoint = this.state.occupiedHeatingSetpoint / 100 + amount / 10;
this.state.occupiedHeatingSetpoint = setpoint * 100;
device.log.debug('Set occupiedHeatingSetpoint to:', setpoint);
}
if ((mode === Thermostat.SetpointRaiseLowerMode.Cool || mode === Thermostat.SetpointRaiseLowerMode.Both) && this.state.occupiedCoolingSetpoint !== undefined) {
const setpoint = this.state.occupiedCoolingSetpoint / 100 + amount / 10;
this.state.occupiedCoolingSetpoint = setpoint * 100;
device.log.debug('Set occupiedCoolingSetpoint to:', setpoint);
}
super.setpointRaiseLower({ mode, amount });
}
}
export class MatterbridgeValveConfigurationAndControlServer extends ValveConfigurationAndControlServer.with(ValveConfigurationAndControl.Feature.Level) {
async open({ openDuration, targetLevel }) {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.open({ openDuration, targetLevel });
super.open({ openDuration, targetLevel });
}
async close() {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.close();
super.close();
}
}
export class MatterbridgeSmokeCoAlarmServer extends SmokeCoAlarmServer.with(SmokeCoAlarm.Feature.SmokeAlarm, SmokeCoAlarm.Feature.CoAlarm) {
async selfTestRequest() {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.selfTestRequest();
super.selfTestRequest();
}
}
export class MatterbridgeBooleanStateConfigurationServer extends BooleanStateConfigurationServer.with(BooleanStateConfiguration.Feature.Visual, BooleanStateConfiguration.Feature.Audible, BooleanStateConfiguration.Feature.SensitivityLevel) {
async enableDisableAlarm({ alarmsToEnableDisable }) {
const device = this.agent.get(MatterbridgeBehavior).state.deviceCommand;
device.enableDisableAlarm({ alarmsToEnableDisable });
super.enableDisableAlarm({ alarmsToEnableDisable });
}
}
export class MatterbridgeSwitchServer extends SwitchServer {
initialize() {
// Do nothing here, as the device will handle the switch logic
}
}
//# sourceMappingURL=matterbridgeBehaviors.js.map