UNPKG

homebridge-aeg-robot

Version:

AEG RX9 / Electrolux Pure i9 robot vacuum plugin for Homebridge

226 lines 12 kB
// Homebridge plugin for AEG RX 9 / Electrolux Pure i9 robot vacuum // Copyright © 2022-2023 Alexander Thoukydides import { AEGAccessory } from './accessory.js'; import { SimpleActivity } from './aeg-robot.js'; import { assertIsBoolean, assertIsNumber, gcd } from './utils.js'; import { PLUGIN_VERSION } from './settings.js'; import { RX9BatteryStatus, RX92PowerMode } from './aegapi-rx9-types.js'; // A Homebridge AEG RX 9 / Electrolux Pure i9 accessory handler export class AEGRobotAccessory extends AEGAccessory { robot; // Plugin configuration config = this.platform.config; // Create a new AEG RX 9 / Electrolux Pure i9 accessory constructor(platform, accessory, robot) { super(platform, accessory, robot.name); this.robot = robot; this.log = robot.log; // Add all of the required services (first to be added is the primary) const support = (service) => !this.config.hideServices.includes(service); this.addAccessoryInformation(); if (support('Switch Clean')) this.addSwitchClean(); if (support('Switch Home')) this.addSwitchHome(); if (support('Fan')) this.addFan(); if (support('Contact Sensor')) this.addContactSensor(); if (support('Occupancy Sensor')) this.addOccupancySensor(); if (support('Battery')) this.addBattery(); if (support('Filter Maintenance')) this.addFilterMaintenance(); // Set or clear long term error state this.onRobot('isError', (err) => { this.setError(err); }); // Check and tidy services after the accessory has been configured this.cleanupServices(); } // Prepare the Accessory Information service (no need to add it) addAccessoryInformation() { // Set static values const service = this.makeService(this.Service.AccessoryInformation) .updateCharacteristic(this.Characteristic.Manufacturer, this.robot.brand) .updateCharacteristic(this.Characteristic.Model, this.robot.model) .updateCharacteristic(this.Characteristic.SerialNumber, this.robot.sn) .updateCharacteristic(this.Characteristic.Name, this.robot.name) .updateCharacteristic(this.Characteristic.FirmwareRevision, PLUGIN_VERSION); // Update other characteristics when there is an update this.onRobot('hardware', (hardware) => { this.log.debug(`Hardware Revision <= ${hardware}`); service.updateCharacteristic(this.Characteristic.HardwareRevision, hardware); }).onRobot('firmware', (firmware) => { this.log.debug(`Software Revision <= ${firmware}`); service.updateCharacteristic(this.Characteristic.SoftwareRevision, firmware); }); } // Add a Battery service addBattery() { const service = this.makeService(this.Service.Battery); // Update characteristics when there is an update this.onRobot('battery', (battery) => { const percent = Math.round(100 * ((battery ?? RX9BatteryStatus.Dead) - RX9BatteryStatus.Dead) / (RX9BatteryStatus.FullyCharged - RX9BatteryStatus.Dead)); this.log.debug(`Battery Level <= ${percent}%`); service.updateCharacteristic(this.Characteristic.BatteryLevel, percent); }).onRobot('isBatteryLow', (isBatteryLow) => { const state = isBatteryLow !== false ? 'BATTERY_LEVEL_LOW' : 'BATTERY_LEVEL_NORMAL'; this.log.debug(`Status Low Battery <= ${state}`); service.updateCharacteristic(this.Characteristic.StatusLowBattery, this.Characteristic.StatusLowBattery[state]); }).onRobot('isCharging', (isCharging) => { const state = isCharging === undefined ? 'NOT_CHARGEABLE' : (isCharging ? 'CHARGING' : 'NOT_CHARGING'); this.log.debug(`Charging State <= ${state}`); service.updateCharacteristic(this.Characteristic.ChargingState, this.Characteristic.ChargingState[state]); }); } // Add a Filter Maintenance service to indicate when the dustbin is full addFilterMaintenance() { const service = this.makeService(this.Service.FilterMaintenance, 'Dustbin'); // Update characteristics when there is an update this.onRobot('isDustbinEmpty', (isDustbinEmpty) => { const state = isDustbinEmpty === false ? 'CHANGE_FILTER' : 'FILTER_OK'; this.log.debug(`Filter Change Indication <= ${state}`); service.updateCharacteristic(this.Characteristic.FilterChangeIndication, this.Characteristic.FilterChangeIndication[state]); }); } // Add an Contact Sensor service to indicate being on the charging dock addContactSensor() { const service = this.makeService(this.Service.ContactSensor, 'Docked'); // Update values when they change this.onRobot('isDocked', (isDocked) => { const state = isDocked === true ? 'CONTACT_DETECTED' : 'CONTACT_NOT_DETECTED'; this.log.debug(`Contact Sensor State <= ${state}`); service.updateCharacteristic(this.Characteristic.ContactSensorState, this.Characteristic.ContactSensorState[state]); }); this.addContactOccupancySensorCharacteristics(service, 'Contact Sensor'); } // Add an Occupancy Sensor service to indicate being on the charging dock addOccupancySensor() { const service = this.makeService(this.Service.OccupancySensor, 'Dock Occupied'); // Update values when they change this.onRobot('isDocked', (isDocked) => { const state = isDocked === true ? 'OCCUPANCY_DETECTED' : 'OCCUPANCY_NOT_DETECTED'; this.log.debug(`Occupancy Detected <= ${state}`); service.updateCharacteristic(this.Characteristic.OccupancyDetected, this.Characteristic.OccupancyDetected[state]); }); this.addContactOccupancySensorCharacteristics(service, 'Occupancy Sensor'); } // Update common Contact or Occupancy Sensor characteristics addContactOccupancySensorCharacteristics(service, type) { this.onRobot('isBatteryLow', (isBatteryLow) => { const state = isBatteryLow !== false ? 'BATTERY_LEVEL_LOW' : 'BATTERY_LEVEL_NORMAL'; this.log.debug(`Status Low Battery (${type}) <= ${state}`); service.updateCharacteristic(this.Characteristic.StatusLowBattery, this.Characteristic.StatusLowBattery[state]); }).onRobot('isActive', (isActive) => { const state = isActive === true; this.log.debug(`Status Active (${type}) <= ${state}`); service.updateCharacteristic(this.Characteristic.StatusActive, state); }).onRobot('isFault', (isFault) => { const state = isFault !== false ? 'GENERAL_FAULT' : 'NO_FAULT'; this.log.debug(`Status Fault (${type}) <= ${state}`); service.updateCharacteristic(this.Characteristic.StatusFault, this.Characteristic.StatusFault[state]); }); } // Add a Fan service to control cleaning and power mode addFan() { const service = this.makeService(this.Service.Fanv2, 'Power Mode'); // Mapping from power mode to fan rotation speed const powerPercent = { [RX92PowerMode.Quiet]: 25, [RX92PowerMode.Smart]: 50, [RX92PowerMode.Power]: 100 }; // Restrict the supported Rotation Speed values const powerPercentValues = Object.values(powerPercent); service.getCharacteristic(this.Characteristic.RotationSpeed) .setProps({ minValue: 0, maxValue: Math.max(...powerPercentValues), minStep: gcd(...powerPercentValues), validValues: [0, ...powerPercentValues] }); // Update characteristics when there is an update this.onRobot('isBusy', (isBusy) => { const state = isBusy ? 'ACTIVE' : 'INACTIVE'; this.log.debug(`Active (Fan) <= ${state}`); service.updateCharacteristic(this.Characteristic.Active, this.Characteristic.Active[state]); }).onRobot('simpleActivity', (activity) => { const state = activity === SimpleActivity.Clean ? 'BLOWING_AIR' : (activity === SimpleActivity.Pitstop ? 'IDLE' : 'INACTIVE'); this.log.debug(`Current Fan State <= ${state}`); service.updateCharacteristic(this.Characteristic.CurrentFanState, this.Characteristic.CurrentFanState[state]); }).onRobot('eco', (eco) => { // AEG RX9.1 only supports two power levels const percent = eco === undefined ? 0 : (eco ? 50 : 100); this.log.debug(`Rotation Speed <= ${percent}%`); service.updateCharacteristic(this.Characteristic.RotationSpeed, percent); }).onRobot('power', (power) => { // AEG RX9.2 adds a 'smart' power level const percent = power === undefined ? 0 : powerPercent[power]; this.log.debug(`Rotation Speed <= ${percent}%`); service.updateCharacteristic(this.Characteristic.RotationSpeed, percent); }); // Start or pause/resume cleaning service.getCharacteristic(this.Characteristic.Active).onSet((value) => { assertIsNumber(value); const command = value === this.Characteristic.Active.ACTIVE ? 'play' : 'pause'; this.log.debug(`Active => ${value} => ${command}`); this.robot.setActivity(command); }); // Change cleaning power mode service.getCharacteristic(this.Characteristic.RotationSpeed).onSet((value) => { assertIsNumber(value); if (value === 0) { this.log.debug(`Rotation Speed => ${value} => Pause`); this.robot.setActivity('pause'); } else { this.log.debug(`Rotation Speed => ${value} => Clean`); this.robot.setActivity('play'); } }); } // Add a Switch service to start or pause/resume cleaning addSwitchClean() { const service = this.makeService(this.Service.Switch, 'Cleaning'); // Update characteristics when there is an update this.onRobot('isBusy', (isBusy) => { const state = isBusy === true; this.log.debug(`On (Clean) <= ${state}`); service.updateCharacteristic(this.Characteristic.On, state); }); // Start or pause/resume cleaning service.getCharacteristic(this.Characteristic.On).onSet((value) => { assertIsBoolean(value); const command = value ? 'play' : 'pause'; this.log.debug(`On (Clean) => ${value} => ${command}`); this.robot.setActivity(command); }); } // Add a Switch service to initiate a return to Home addSwitchHome() { const service = this.makeService(this.Service.Switch, 'Return Home', 'home'); // Update characteristics when there is an update this.onRobot('simpleActivity', (activity) => { const state = activity === SimpleActivity.Return; this.log.debug(`On (Home) <= ${state}`); service.updateCharacteristic(this.Characteristic.On, state); }); // Return to the charging dock or pause returning service.getCharacteristic(this.Characteristic.On).onSet((value) => { assertIsBoolean(value); const command = value ? 'home' : 'pause'; this.log.debug(`On (Home) => ${value} => ${command}`); this.robot.setActivity(command); }); } // Install a handler for a robot event and call it immediately with the current status onRobot(event, listener) { this.robot.on(event, listener); listener(this.robot.status[event]); return this; } } //# sourceMappingURL=accessory-robot.js.map