homebridge-homeconnect
Version:
A Homebridge plugin that connects Home Connect appliances to Apple HomeKit
130 lines • 6.11 kB
JavaScript
// Homebridge plugin for Home Connect home appliances
// Copyright © 2023-2026 Alexander Thoukydides
import { setTimeout as setTimeoutP } from 'timers/promises';
import { MockAppliance } from './mock-appliance.js';
import { MockDishwasher } from './mock-dishwasher.js';
import { MockHob } from './mock-hob.js';
import { MockHood } from './mock-hood.js';
import { MockOven } from './mock-oven.js';
import { MockFridgeFreezer } from './mock-fridgefreezer.js';
import { MockCoffeeMaker } from './mock-coffeemaker.js';
import { MockDryer } from './mock-dryer.js';
import { MockWasher } from './mock-washer.js';
import { detached } from '../log-error.js';
// Random delay before completing API requests
const MOCK_MIN_DELAY = 1; // (milliseconds)
const MOCK_MAX_DELAY = 20; // (milliseconds)
// Low-level access to the Home Connect API with mocked appliances
export class MockAPI {
// Create a new API object
constructor(log, config, persist) {
this.log = log;
this.config = config;
this.persist = persist;
// The mock appliances
this.appliances = new Map();
// Create the mock appliances
this.addMock(MockCoffeeMaker);
this.addMock(MockDishwasher);
this.addMock(MockDryer);
this.addMock(MockFridgeFreezer);
this.addMock(MockHob);
this.addMock(MockHood);
this.addMock(MockOven);
this.addMock(MockWasher);
}
// Instantiate a mock appliance
addMock(constructor) {
const mockAppliance = new constructor(this.log);
this.appliances.set(mockAppliance.haid, mockAppliance);
}
// Delay requests and events
async delay() {
await setTimeoutP(MOCK_MIN_DELAY + Math.random() * (MOCK_MAX_DELAY - MOCK_MIN_DELAY));
}
// Wrap an API request
async request(method, haid, ...args) {
await this.delay();
const mockAppliance = this.appliances.get(haid);
if (!mockAppliance)
throw MockAppliance.statusCodeError(404, `Unknown appliance "${haid}"`);
const fn = mockAppliance[method];
try {
const result = fn.bind(mockAppliance)(...args);
await this.delay();
return result;
}
catch (err) {
await this.delay();
throw err;
}
}
// Check whether a particular scope has been authorised
hasScope(_scope) {
return true;
}
// Get authorisation status updates
async getAuthorisationStatus() {
return Promise.resolve({ state: 'success' });
}
// Trigger a retry of Device Flow authorisation
retryAuthorisation() { }
// Get a list of paired home appliances
async getAppliances() {
return Promise.resolve([...this.appliances.values()].map(appliance => appliance.getAppliance()));
}
// Forward most methods to the appropriate mock appliance
/* eslint-disable max-len */
getAppliance(...args) { return this.request('getAppliance', ...args); }
getPrograms(...args) { return this.request('getPrograms', ...args); }
getAvailablePrograms(...args) { return this.request('getAvailablePrograms', ...args); }
getAvailableProgram(...args) { return this.request('getAvailableProgram', ...args); }
getActiveProgram(...args) { return this.request('getActiveProgram', ...args); }
setActiveProgram(...args) { return this.request('setActiveProgram', ...args); }
stopActiveProgram(...args) { return this.request('stopActiveProgram', ...args); }
getActiveProgramOptions(...args) { return this.request('getActiveProgramOptions', ...args); }
setActiveProgramOptions(...args) { return this.request('setActiveProgramOptions', ...args); }
getActiveProgramOption(...args) { return this.request('getActiveProgramOption', ...args); }
setActiveProgramOption(...args) { return this.request('setActiveProgramOption', ...args); }
getSelectedProgram(...args) { return this.request('getSelectedProgram', ...args); }
setSelectedProgram(...args) { return this.request('setSelectedProgram', ...args); }
getSelectedProgramOptions(...args) { return this.request('getSelectedProgramOptions', ...args); }
setSelectedProgramOptions(...args) { return this.request('setSelectedProgramOptions', ...args); }
getSelectedProgramOption(...args) { return this.request('getSelectedProgramOption', ...args); }
setSelectedProgramOption(...args) { return this.request('setSelectedProgramOption', ...args); }
getStatus(...args) { return this.request('getStatus', ...args); }
getStatusSpecific(...args) { return this.request('getStatusSpecific', ...args); }
getSettings(...args) { return this.request('getSettings', ...args); }
getSetting(...args) { return this.request('getSetting', ...args); }
setSetting(...args) { return this.request('setSetting', ...args); }
getCommands(...args) { return this.request('getCommands', ...args); }
setCommand(...args) { return this.request('setCommand', ...args); }
/* eslint-enable max-len */
// Get events for a single appliance or all appliances
async *getEvents(haid) {
// Select the appliances
const appliances = haid ? [this.appliances.get(haid)] : this.appliances.values();
// Receive the event streams from all of the selected appliances
let eventPromise;
let eventResolve;
const receiveEvents = detached(this.log, 'Receive events', async (events) => {
for await (const event of events) {
await this.delay();
eventResolve(event);
}
});
for (const mockAppliance of appliances) {
if (mockAppliance)
receiveEvents(mockAppliance.getEvents());
}
// Merge the event streams
yield { event: 'START' };
eventPromise = new Promise(resolve => eventResolve = resolve);
for (;;) {
const event = await eventPromise;
eventPromise = new Promise(resolve => eventResolve = resolve);
yield event;
}
}
}
//# sourceMappingURL=index.js.map