homebridge-homeconnect
Version:
A Homebridge plugin that connects Home Connect appliances to Apple HomeKit
141 lines • 5.05 kB
JavaScript
// Homebridge plugin for Home Connect home appliances
// Copyright © 2023-2025 Alexander Thoukydides
import { setImmediate as setImmediateP } from 'timers/promises';
import { logError } from '../log-error.js';
// Appliance data required for the configuration schema generator
export class ConfigSchemaData {
// Create a new schema generator
constructor(log, persist) {
this.log = log;
this.persist = persist;
// Details of known appliances, indexed by haId
this.appliances = new Map();
}
// Update the active plugin configuration
async setConfig(config) {
await this.applyUpdate(() => {
this.config = config;
});
}
// Update the list of accessories
async setAppliances(newAppliances) {
await this.applyUpdate(() => {
const appliances = new Map();
for (const ha of newAppliances) {
const appliance = {
programs: [],
features: [],
...this.appliances.get(ha.haId),
...ha
};
appliances.set(ha.haId, appliance);
}
this.appliances = appliances;
});
}
// Set whether the Control scope has been authorised for an appliance
async setHasControl(haId, control) {
await this.applyUpdate(() => {
const appliance = this.appliances.get(haId);
if (appliance)
appliance.hasControl = control;
});
}
// Add the list of optional features for an appliance to the schema
async setOptionalFeatures(haId, features) {
await this.applyUpdate(() => {
const appliance = this.appliances.get(haId);
if (appliance)
appliance.features = features;
});
}
// Add the list of programs for an appliance to the schema
async setPrograms(haId, newPrograms) {
await this.applyUpdate(() => {
const appliance = this.appliances.get(haId);
if (!appliance)
return;
const findProgram = (key) => appliance.programs.find(p => p.key === key);
appliance.programs = newPrograms.map(program => ({ ...findProgram(program.key), ...program }));
});
}
// Add the options for an appliance program to the schema
async setProgramOptions(haId, programKey, options) {
await this.applyUpdate(() => {
const appliance = this.appliances.get(haId);
const program = appliance?.programs.find(p => p.key === programKey);
if (program)
program.options = options;
});
}
// Apply an update to the schema data
async applyUpdate(update) {
// Load the old schema data
await this.load();
// Perform the required update
update();
// Save the updated data
const save = async () => {
// Coalesce updates from the same event loop
await setImmediateP();
// Perform the write
this.exclusive(async () => {
delete this.savePromise;
this.log.debug('Saving configuration schema data');
await this.trySet();
});
};
this.savePromise ?? (this.savePromise = save());
await this.savePromise;
}
// Read any previously saved data
async load(reload = false) {
if (reload || !this.loadPromise)
this.loadPromise = this.exclusive(() => this.tryGet());
await this.loadPromise;
}
// Perform an operation that must be exclusive
async exclusive(operation) {
// Wait for any previous operation to complete
while (this.busyPromise)
await this.busyPromise;
// Perform the requested operation
const busyOperation = async () => {
try {
await operation();
}
finally {
delete this.busyPromise;
}
};
this.busyPromise = busyOperation();
await this.busyPromise;
}
// Attempt to read previously saved data
async tryGet() {
try {
const persist = await this.persist.getItem('config.schema.json');
if (persist) {
this.config = persist.config;
this.appliances = new Map(Object.entries(persist.appliances ?? {}));
}
}
catch (err) {
logError(this.log, 'Failed to load configuration schema data', err);
}
}
// Attempt to write new data
async trySet() {
try {
const persist = {
config: this.config,
appliances: Object.fromEntries(this.appliances)
};
await this.persist.setItem('config.schema.json', persist);
}
catch (err) {
logError(this.log, 'Failed to save configuration schema data', err);
}
}
}
//# sourceMappingURL=schema-data.js.map