homebridge-unifi-ap-light
Version:
Control the blue LED ring on your UniFi APs!
153 lines • 8 kB
JavaScript
import { getAccessPoint } from './unifi.js';
/**
* This class represents a single platform accessory (e.g., a UniFi access point) for Homebridge.
* It handles the lifecycle and HomeKit interactions for individual accessories.
*/
export class UniFiAP {
constructor(platform, accessory) {
this.platform = platform;
this.accessory = accessory;
this.accessPoint = this.accessory.context.accessPoint;
// Fallback: Patch missing site property for cached accessories
if (!this.accessPoint.site) {
const configuredSites = this.platform.config.sites;
let fallbackSite = 'default';
if (Array.isArray(configuredSites) && configuredSites.length === 1) {
// Try to resolve the internal site name if possible
const resolved = this.platform.sessionManager.getSiteName(configuredSites[0]);
fallbackSite = resolved || configuredSites[0] || 'default';
}
this.platform.log.warn(`Patching missing site for ${this.accessPoint.name}: using "${fallbackSite}"`);
this.accessPoint.site = fallbackSite;
this.accessory.context.accessPoint = this.accessPoint;
}
// Initialize accessory information from the configuration.
const accessoryInformationService = this.accessory.getService(this.platform.Service.AccessoryInformation);
if (accessoryInformationService) {
accessoryInformationService
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'Ubiquiti')
.setCharacteristic(this.platform.Characteristic.Model, this.accessPoint.model)
.setCharacteristic(this.platform.Characteristic.SerialNumber, this.accessPoint.serial)
.setCharacteristic(this.platform.Characteristic.FirmwareRevision, this.accessPoint.version);
}
else {
// Handle the case where the service is not available
this.platform.log.warn('Accessory Information Service not found.');
}
// Create or retrieve the LightBulb service.
this.service =
this.accessory.getService(this.platform.Service.Lightbulb) ||
this.accessory.addService(this.platform.Service.Lightbulb);
// Set default HomeKit name based on the name stored in `accessory.context` from the `discoverDevices` method.
this.service.setCharacteristic(this.platform.Characteristic.Name, this.accessPoint.name);
// Register handlers for the On/Off Characteristic.
this.service.getCharacteristic(this.platform.Characteristic.On)
.onSet(this.setOn.bind(this)) // SET - bind to the `setOn` method below
.onGet(this.getOn.bind(this)); // GET - bind to the `getOn` method below
}
/**
* Handles "SET" requests from HomeKit to change the state of the accessory.
* @param {CharacteristicValue} value - The new state from HomeKit.
*/
async setOn(value) {
var _a;
// Determine if this is a UDM-based device with nested LED settings
const isUdmDevice = this.accessPoint.type === 'udm';
const site = (_a = this.accessPoint.site) !== null && _a !== void 0 ? _a : 'default';
// Choose the correct API payload based on device type
const data = isUdmDevice
? { ledSettings: { enabled: value } }
: { led_override: value ? 'on' : 'off' };
// Define API endpoints to try in sequence (some UniFi setups use different URL structures)
const endpoints = [
`/api/s/${site}/rest/device/${this.accessPoint._id}`,
`/proxy/network/api/s/${site}/rest/device/${this.accessPoint._id}`
];
// Try each endpoint until one works or all fail
for (const endpoint of endpoints) {
try {
const response = await this.platform.sessionManager.request({
method: 'put',
url: endpoint,
data: data,
});
if (response.status === 200) {
this.platform.log.debug(`Successfully set LED state for ${this.accessPoint.name} to ${value ? 'on' : 'off'}.`);
return;
}
else {
this.platform.log.error(`Failed to set LED state for ${this.accessPoint.name}: Unexpected response status ${response.status}`);
}
}
catch (error) {
const axiosError = error;
// Try next fallback endpoint if 404
if (axiosError.response && axiosError.response.status === 404 && endpoint !== endpoints[endpoints.length - 1]) {
continue;
}
else {
// Log full error details for debugging
this.platform.log.error(`Failed to set LED state for ${this.accessPoint.name}: ${error}`);
if (axiosError.response) {
this.platform.log.error(`Response status: ${axiosError.response.status}`);
this.platform.log.error(`Response data: ${JSON.stringify(axiosError.response.data)}`);
}
break;
}
}
}
}
/**
* Handles "GET" requests from HomeKit to retrieve the current state of the accessory.
* @returns {Promise<CharacteristicValue>} - The current state of the accessory.
*/
async getOn() {
try {
// Use the site name already attached to the AP context
const site = this.accessPoint.site;
if (!site) {
this.platform.log.error(`Access point ${this.accessPoint.name} is missing site information.`);
return false;
}
// Re-fetch the latest AP state using the current site
const accessPoint = await getAccessPoint(this.accessPoint._id, this.platform.sessionManager.request.bind(this.platform.sessionManager), [site], this.platform.log);
// Process valid AP response
if (accessPoint) {
if (accessPoint.type === 'udm') {
// UDM devices use nested `ledSettings.enabled`
if (accessPoint.ledSettings) {
if (typeof accessPoint.ledSettings.enabled !== 'undefined') {
const isOn = accessPoint.ledSettings.enabled;
this.platform.log.debug(`Retrieved LED state for ${this.accessPoint.name}: ${isOn ? 'on' : 'off'}`);
return isOn;
}
else {
this.platform.log.error(`The 'enabled' property in 'ledSettings' is undefined for ${this.accessPoint.name}`);
return false;
}
}
else {
this.platform.log.error(`The 'ledSettings' property is undefined for ${this.accessPoint.name}`);
return false;
}
}
else {
// Standard APs use the flat `led_override` field
const isOn = accessPoint.led_override === 'on';
this.platform.log.debug(`Retrieved LED state for ${this.accessPoint.name}: ${isOn ? 'on' : 'off'}`);
return isOn;
}
}
else {
this.platform.log.error(`Failed to retrieve LED state for ${this.accessPoint.name}: Access point not found`);
return false;
}
}
catch (error) {
// Handle network or API errors gracefully
this.platform.log.error(`Failed to retrieve LED state for ${this.accessPoint.name}: ${error}`);
return false;
}
}
}
//# sourceMappingURL=platformAccessory.js.map