matterbridge-roborock-vacuum-plugin
Version:
Matterbridge Roborock Vacuum Plugin
166 lines • 9.46 kB
JavaScript
import { bridgedNode } from 'matterbridge';
import { debugStringify } from 'matterbridge/logger';
import { UINT16_MAX, UINT32_MAX, VendorId } from 'matterbridge/matter';
import { BridgedDeviceBasicInformation, Descriptor, Identify } from 'matterbridge/matter/clusters';
import { isValidNumber, isValidString } from 'matterbridge/utils';
import { RoomMap } from '../core/application/models/index.js';
import { HomeEntity } from '../core/domain/entities/Home.js';
import { configureBehavior } from '../share/behaviorFactory.js';
import { RoborockVacuumCleaner } from '../types/roborockVacuumCleaner.js';
/**
* Handles device configuration: local network setup, room mapping,
* behavior configuration, and Matterbridge device registration.
*/
export class DeviceConfigurator {
platform;
configManager;
registry;
getPlatformRunner;
snackbarMessage;
log;
rrHomeId;
constructor(platform, configManager, registry, getPlatformRunner, snackbarMessage, log) {
this.platform = platform;
this.configManager = configManager;
this.registry = registry;
this.getPlatformRunner = getPlatformRunner;
this.snackbarMessage = snackbarMessage;
this.log = log;
}
async onConfigureDevice(roborockService) {
this.log.info('onConfigureDevice start');
if (!this.registry.hasDevices()) {
this.log.error('Initializing: No supported devices found');
return;
}
const configureSuccess = new Map();
roborockService.setDeviceNotify(async (payload) => {
await this.getPlatformRunner().updateRobotWithPayload(payload);
});
for (const vacuum of this.registry.getAllDevices()) {
const success = await this.configureDevice(vacuum, roborockService);
configureSuccess.set(vacuum.duid, success);
if (success) {
this.rrHomeId = vacuum.rrHomeId;
}
}
for (const [duid, robot] of this.registry.robotsMap) {
if (!configureSuccess.get(duid)) {
continue;
}
roborockService.activateDeviceNotify(robot.device);
}
try {
await this.getPlatformRunner().requestHomeData();
}
catch (error) {
this.log.error(`requestHomeData (initial) failed: ${error instanceof Error ? error.message : String(error)}`);
}
// During initial configuration, we delay activating handlers until all devices are configured.
this.log.notice('Activating device notify handlers');
this.getPlatformRunner().activateHandlerFunctions();
this.log.info('onConfigureDevice finished');
}
async configureDevice(vacuum, roborockService) {
const connectedToLocalNetwork = await roborockService.initializeMessageClientForLocal(vacuum);
if (!connectedToLocalNetwork) {
this.log.warn(`Device ${vacuum.name} (${vacuum.duid}) could not connect to local network, using MQTT only`);
this.snackbarMessage(`Vacuum: ${vacuum.name} does not appear to be connected to local network, please double-check`, 5000, 'error');
}
// if vacuum does not response serial number in general response, try to get it via mqtt
if (!vacuum.serialNumber) {
vacuum.serialNumber = await roborockService.getSerialNumber(vacuum.duid);
}
const { activeMapId, mapInfo, roomMap } = await RoomMap.fromMapInfo(vacuum, { roborockService, log: this.log });
this.log.debug('Initializing - roomMap: ', debugStringify(roomMap));
const homeData = vacuum.store.homeData;
const homeInfo = new HomeEntity(homeData.id, homeData.name, roomMap, mapInfo, activeMapId);
const robot = new RoborockVacuumCleaner(vacuum, homeInfo, this.configManager, roborockService, this.log);
const behaviorHandler = configureBehavior(vacuum.specs.model, vacuum.duid, roborockService, this.configManager.isCustomCleanModeMappingEnabled, this.configManager.cleanModeSettings, this.configManager.forceRunAtDefault, this.log, () => this.getPlatformRunner().burstPolling.startBurstPolling(vacuum.duid));
robot.configureHandler(behaviorHandler);
this.log.info('vacuum:', debugStringify(vacuum));
if (this.platform.validateDevice(robot.deviceName ?? '')) {
await this.addDevice(robot);
}
return true;
}
async addDevice(rvc) {
if (!rvc.serialNumber || !rvc.deviceName) {
this.log.warn('Cannot add device: missing rvc or deviceName');
return undefined;
}
this.platform.setSelectDevice(rvc.serialNumber, rvc.deviceName, undefined, 'hub');
const vacuumData = rvc.device.specs;
const hardwareVersionString = vacuumData.firmwareVersion ?? rvc.device.fv ?? this.platform.matterbridge.matterbridgeVersion;
if (this.platform.validateDevice(rvc.deviceName)) {
this.applyVersionInfo(rvc, hardwareVersionString);
if (this.configManager.overrideMatterConfiguration) {
const customMatterConfiguration = this.configManager.matterOverrideSettings;
this.platform.log.debug(`customMatterConfiguration: ${debugStringify(customMatterConfiguration)}`);
const configChanged = this.overrideMatterConfiguration(rvc, vacuumData, customMatterConfiguration);
if (configChanged) {
await this.platform.onConfigChanged(this.configManager.rawConfig);
}
}
const options = rvc.getClusterServerOptions(BridgedDeviceBasicInformation.Cluster.id);
if (options) {
options.softwareVersion = rvc.softwareVersion ?? 1;
options.softwareVersionString = rvc.softwareVersionString ?? '1.0.0';
options.hardwareVersion = rvc.hardwareVersion ?? 1;
options.hardwareVersionString = rvc.hardwareVersionString ?? '1.0.0';
}
rvc.createDefaultIdentifyClusterServer(5, Identify.IdentifyType.AudibleBeep);
// We need to add bridgedNode device type and BridgedDeviceBasicInformation cluster for single class devices that doesn't add it in childbridge mode.
if (rvc.mode === undefined && !rvc.deviceTypes.has(bridgedNode.code)) {
rvc.deviceTypes.set(bridgedNode.code, bridgedNode);
const options = rvc.getClusterServerOptions(Descriptor.Cluster.id);
if (options) {
const deviceTypeList = options.deviceTypeList;
if (!deviceTypeList.find((dt) => dt.deviceType === bridgedNode.code)) {
deviceTypeList.push({ deviceType: bridgedNode.code, revision: bridgedNode.revision });
}
}
rvc.createDefaultBridgedDeviceBasicInformationClusterServer(rvc.deviceName, rvc.serialNumber, rvc.vendorId, rvc.vendorName, rvc.productName, rvc.softwareVersion, rvc.softwareVersionString, rvc.hardwareVersion, rvc.hardwareVersionString);
}
await this.platform.registerDevice(rvc);
this.registry.registerRobot(rvc);
return rvc;
}
else {
return undefined;
}
}
applyVersionInfo(rvc, hardwareVersionString) {
rvc.softwareVersion = parseInt(this.platform.version.replace(/\D/g, ''));
rvc.softwareVersionString = this.platform.version === '' ? 'Unknown' : this.platform.version;
rvc.hardwareVersion = parseInt(hardwareVersionString.replace(/\D/g, ''));
rvc.hardwareVersionString = hardwareVersionString;
rvc.softwareVersion = isValidNumber(rvc.softwareVersion, 0, UINT32_MAX) ? rvc.softwareVersion : undefined;
rvc.softwareVersionString = isValidString(rvc.softwareVersionString)
? rvc.softwareVersionString.slice(0, 64)
: undefined;
rvc.hardwareVersion = isValidNumber(rvc.hardwareVersion, 0, UINT16_MAX) ? rvc.hardwareVersion : undefined;
rvc.hardwareVersionString = isValidString(rvc.hardwareVersionString)
? rvc.hardwareVersionString.slice(0, 64)
: undefined;
}
overrideMatterConfiguration(rvc, vacuumData, customMatterConfiguration) {
rvc.vendorName =
customMatterConfiguration.matterVendorName?.length > 0
? customMatterConfiguration.matterVendorName
: 'Matterbridge';
const perDeviceProductName = this.configManager.getProductNameForDevice(rvc.serialNumber ?? '');
rvc.productName =
perDeviceProductName && perDeviceProductName.length > 0
? perDeviceProductName
: customMatterConfiguration.matterProductName?.length > 0
? customMatterConfiguration.matterProductName
: vacuumData.model;
const matterVendorId = customMatterConfiguration.matterVendorId;
rvc.vendorId = VendorId.isValid(matterVendorId) ? VendorId(matterVendorId) : 65521;
rvc.productId = customMatterConfiguration.matterProductId > 0 ? customMatterConfiguration.matterProductId : 32768;
rvc.productUrl = 'https://github.com/RinDevJunior/matterbridge-roborock-vacuum-plugin';
return this.configManager.ensureDeviceProductNameEntry(rvc.serialNumber ?? '', vacuumData.model);
}
}
//# sourceMappingURL=deviceConfigurator.js.map