homebridge-homeconnect
Version:
A Homebridge plugin that connects Home Connect appliances to Apple HomeKit
124 lines • 6.93 kB
JavaScript
// Homebridge plugin for Home Connect home appliances
// Copyright © 2019-2026 Alexander Thoukydides
import { assertIsNumber } from './utils.js';
import { DoorState, DoorStateRefrigeration } from './api-value-types.js';
// Add an appliance door to an accessory
export function HasDoor(Base, hasLock = false) {
return class HasDoor extends Base {
// Accessory services
doorService = {};
// Door settings that may be supported by the appliance
doors = {};
// Mixin constructor
constructor(...args) {
super(...args);
// Continue initialisation asynchronously
this.asyncInitialise('Door', this.initHasDoor());
}
// Asynchronous initialisation
async initHasDoor() {
// Check whether a single appliance door should be supported
if (this.hasOptionalFeature('Door', 'Door', 'Doors')) {
// Add the door
const service = this.addDoor('BSH.Common.Status.DoorState', 'Door');
this.doorService['BSH.Common.Status.DoorState'] = service;
// Check whether the appliance supports door control commands
if (this.device.hasScope('Control')) {
const commands = await this.getCached('commands', () => this.device.getCommands());
const supports = (key) => commands.some(command => command.key === key);
const supportsOpen = supports('BSH.Common.Command.OpenDoor');
const supportsPartly = supports('BSH.Common.Command.PartlyOpenDoor');
if (supportsOpen || supportsPartly) {
const positions = [];
if (supportsOpen)
positions.push('fully');
if (supportsPartly)
positions.push('partly');
this.log.info(`Can open door ${positions.join(' and ')}`);
}
// Add open and/or partly open door support as appropriate
this.addDoorControl(service, supportsOpen, supportsPartly);
}
}
// Add services for additional doors that are supported and enabled
await this.device.waitConnected(true);
for (const [key, name] of Object.entries(this.doors)) {
if (this.device.getItem(key) !== undefined
&& this.hasOptionalFeature('Door', name, 'Doors', false)) {
this.doorService[key] = this.addDoor(key, name, `door ${name}`);
}
}
}
// Define an additional door that may be supported by the appliance
hasDoor(statusKey, name) {
this.doors[statusKey] = name;
}
// Add a door
addDoor(key, name, subtype) {
// Create a Door service for this door
const service = this.makeService(this.Service.Door, name, subtype);
// The door starts stationary
const { STOPPED } = this.Characteristic.PositionState;
service.updateCharacteristic(this.Characteristic.PositionState, STOPPED);
// Add the lock current state characteristic
const { UNSECURED, SECURED } = this.Characteristic.LockCurrentState;
if (hasLock) {
service.addOptionalCharacteristic(this.Characteristic.LockCurrentState);
service.getCharacteristic(this.Characteristic.LockCurrentState)
.setProps({ validValues: [UNSECURED, SECURED] });
}
// Update the door status
this.device.on(key, doorState => {
const isOpen = doorState === DoorState.Open || doorState === DoorStateRefrigeration.Open;
const isLocked = doorState === DoorState.Locked;
this.log.info(`${name} ${isOpen ? 'open' : 'closed'}${isLocked ? ' and locked' : ''}`);
const targetPosition = service.getCharacteristic(this.Characteristic.TargetPosition).value;
assertIsNumber(targetPosition);
if (isOpen && 0 < targetPosition) {
// Assume door has reached its target position
service.updateCharacteristic(this.Characteristic.CurrentPosition, targetPosition);
}
else {
// Door has been closed, or opened manually
service.updateCharacteristic(this.Characteristic.CurrentPosition, isOpen ? 100 : 0);
service.updateCharacteristic(this.Characteristic.TargetPosition, isOpen ? 100 : 0);
}
service.updateCharacteristic(this.Characteristic.PositionState, STOPPED);
// If the door can be locked then update its status
if (hasLock) {
service.updateCharacteristic(this.Characteristic.LockCurrentState, isLocked ? SECURED : UNSECURED);
}
});
return service;
}
// Add the ability to open or partially open the door
addDoorControl(service, supportsOpen, supportsPartly) {
// Set the door position step size appropriately
const currentPositionCharacteristic = service.getCharacteristic(this.Characteristic.CurrentPosition);
const targetPositionCharacteristic = service.getCharacteristic(this.Characteristic.TargetPosition);
const minStep = supportsPartly ? 50 : 100;
currentPositionCharacteristic.setProps({ minStep: minStep });
targetPositionCharacteristic.setProps({ minStep: minStep });
// Allow the target position to be controlled, if supported
if (supportsOpen || supportsPartly) {
// Door can be opened and/or partly opened
targetPositionCharacteristic.setProps({ perms: ["pr" /* Perms.PAIRED_READ */, "pw" /* Perms.PAIRED_WRITE */, "ev" /* Perms.NOTIFY */] });
this.onSetNumber(targetPositionCharacteristic, async (value) => {
if (0 < value) {
const fullyOpen = !supportsPartly || 50 < value;
this.log.info(`${fullyOpen ? '' : 'PARTLY '}OPEN Door`);
service.updateCharacteristic(this.Characteristic.PositionState, this.Characteristic.PositionState.INCREASING);
await this.device.openDoor(fullyOpen);
}
});
}
else {
// Door cannot be opened remotely
targetPositionCharacteristic.setProps({ perms: ["pr" /* Perms.PAIRED_READ */, "ev" /* Perms.NOTIFY */] });
}
}
};
}
// Add a lockable appliance door to an accessory
export const HasLockableDoor = (Base) => HasDoor(Base, true);
//# sourceMappingURL=has-door.js.map