guard against NaN brightness from incomplete MQTT state, sync cached accessory display names

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 19:32:01 +01:00
parent 2011974c96
commit 494a184866
16 changed files with 345 additions and 294 deletions

28
dist/accessory.d.ts vendored Normal file
View File

@@ -0,0 +1,28 @@
import { PlatformAccessory, CharacteristicValue } from 'homebridge';
import { ESPHomeRGBWWPlatform } from './platform';
export declare class ESPHomeRGBWWAccessory {
private readonly platform;
private readonly accessory;
private readonly config;
private service;
private mqttClient;
private static readonly MAX_BRIGHTNESS;
private currentState;
constructor(platform: ESPHomeRGBWWPlatform, accessory: PlatformAccessory, config: any);
private rgbToHsv;
private hsvToRgb;
private handleStateUpdate;
private publishCommand;
setOn(value: CharacteristicValue): Promise<void>;
getOn(): Promise<CharacteristicValue>;
setBrightness(value: CharacteristicValue): Promise<void>;
getBrightness(): Promise<CharacteristicValue>;
setHue(value: CharacteristicValue): Promise<void>;
getHue(): Promise<CharacteristicValue>;
setSaturation(value: CharacteristicValue): Promise<void>;
getSaturation(): Promise<CharacteristicValue>;
setColorTemperature(value: CharacteristicValue): Promise<void>;
getColorTemperature(): Promise<CharacteristicValue>;
private updateRGBColor;
}
//# sourceMappingURL=accessory.d.ts.map

1
dist/accessory.d.ts.map vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"accessory.d.ts","sourceRoot":"","sources":["../src/accessory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAelD,qBAAa,qBAAqB;IAc9B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAfzB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAO;IAE7C,OAAO,CAAC,YAAY,CAMlB;gBAGiB,QAAQ,EAAE,oBAAoB,EAC9B,SAAS,EAAE,iBAAiB,EAC5B,MAAM,EAAE,GAAG;IAkD9B,OAAO,CAAC,QAAQ;IAiChB,OAAO,CAAC,QAAQ;IA+BhB,OAAO,CAAC,iBAAiB;IA+BzB,OAAO,CAAC,cAAc;IAOhB,KAAK,CAAC,KAAK,EAAE,mBAAmB;IAMhC,KAAK,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAIrC,aAAa,CAAC,KAAK,EAAE,mBAAmB;IAOxC,aAAa,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAI7C,MAAM,CAAC,KAAK,EAAE,mBAAmB;IAMjC,MAAM,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAItC,aAAa,CAAC,KAAK,EAAE,mBAAmB;IAMxC,aAAa,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAI7C,mBAAmB,CAAC,KAAK,EAAE,mBAAmB;IAM9C,mBAAmB,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAIzD,OAAO,CAAC,cAAc;CAevB"}

480
dist/accessory.js vendored
View File

@@ -1,247 +1,239 @@
"use strict";
const mqtt = require("mqtt");
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ESPHomeRGBWWAccessory = void 0;
const mqtt = __importStar(require("mqtt"));
class ESPHomeRGBWWAccessory {
static MAX_BRIGHTNESS = 255;
constructor(platform, accessory, config) {
this.platform = platform;
this.accessory = accessory;
this.config = config;
this.currentState = {
on: false,
brightness: 100,
hue: 0,
saturation: 0,
colorTemperature: 300,
};
this.accessory
.getService(this.platform.Service.AccessoryInformation)
.setCharacteristic(this.platform.Characteristic.Manufacturer, config.manufacturer || "ESPHome")
.setCharacteristic(this.platform.Characteristic.Model, config.model || "RGBWW Light")
.setCharacteristic(this.platform.Characteristic.SerialNumber, config.id);
this.service =
this.accessory.getService(this.platform.Service.Lightbulb) ||
this.accessory.addService(this.platform.Service.Lightbulb);
this.service.setCharacteristic(this.platform.Characteristic.Name, config.name);
this.service
.getCharacteristic(this.platform.Characteristic.On)
.onSet(this.setOn.bind(this))
.onGet(this.getOn.bind(this));
this.service
.getCharacteristic(this.platform.Characteristic.Brightness)
.onSet(this.setBrightness.bind(this))
.onGet(this.getBrightness.bind(this));
this.service
.getCharacteristic(this.platform.Characteristic.Hue)
.onSet(this.setHue.bind(this))
.onGet(this.getHue.bind(this));
this.service
.getCharacteristic(this.platform.Characteristic.Saturation)
.onSet(this.setSaturation.bind(this))
.onGet(this.getSaturation.bind(this));
this.service
.getCharacteristic(this.platform.Characteristic.ColorTemperature)
.onSet(this.setColorTemperature.bind(this))
.onGet(this.getColorTemperature.bind(this));
this.mqttClient = mqtt.connect(config.mqtt_broker || "mqtt://localhost:1883");
this.mqttClient.on("connect", () => {
this.platform.log.info("Connected to MQTT broker");
this.mqttClient.subscribe(config.state_topic);
});
this.mqttClient.on("message", (topic, message) => {
if (topic === config.state_topic) {
this.handleStateUpdate(message.toString());
}
});
this.mqttClient.on("error", (error) => {
this.platform.log.error("MQTT error:", error);
});
}
rgbToHsv(r, g, b) {
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const delta = max - min;
let h = 0;
let s = 0;
const v = max;
if (delta > 0) {
s = delta / max;
if (max === r) {
h = ((g - b) / delta) % 6;
} else if (max === g) {
h = (b - r) / delta + 2;
} else {
h = (r - g) / delta + 4;
}
h *= 60;
if (h < 0) {
h += 360;
}
}
return { h, s: s * 100, v: v * 100 };
}
hsvToRgb(h, s, v) {
s /= 100;
v /= 100;
const c = v * s;
const x = c * (1 - Math.abs((h / 60) % 2 - 1));
const m = v - c;
let r = 0;
let g = 0;
let b = 0;
if (h >= 0 && h < 60) {
r = c;
g = x;
b = 0;
} else if (h < 120) {
r = x;
g = c;
b = 0;
} else if (h < 180) {
r = 0;
g = c;
b = x;
} else if (h < 240) {
r = 0;
g = x;
b = c;
} else if (h < 300) {
r = x;
g = 0;
b = c;
} else {
r = c;
g = 0;
b = x;
}
return {
r: Math.round((r + m) * 255),
g: Math.round((g + m) * 255),
b: Math.round((b + m) * 255),
};
}
handleStateUpdate(message) {
try {
const state = JSON.parse(message);
this.currentState.on = state.state === "ON";
this.currentState.brightness = Math.round((state.brightness / ESPHomeRGBWWAccessory.MAX_BRIGHTNESS) * 100);
if (state.color && state.color.r !== undefined) {
const hsv = this.rgbToHsv(state.color.r, state.color.g, state.color.b);
this.currentState.hue = hsv.h;
this.currentState.saturation = hsv.s;
}
if (state.color_temp) {
this.currentState.colorTemperature = state.color_temp;
}
this.service.updateCharacteristic(this.platform.Characteristic.On, this.currentState.on);
this.service.updateCharacteristic(this.platform.Characteristic.Brightness, this.currentState.brightness);
this.service.updateCharacteristic(this.platform.Characteristic.Hue, this.currentState.hue);
this.service.updateCharacteristic(this.platform.Characteristic.Saturation, this.currentState.saturation);
this.service.updateCharacteristic(this.platform.Characteristic.ColorTemperature, this.currentState.colorTemperature);
} catch (error) {
this.platform.log.error("Failed to parse state update:", error);
}
}
publishCommand(payload) {
this.mqttClient.publish(this.config.command_topic, JSON.stringify(payload));
}
async setOn(value) {
this.currentState.on = value;
this.publishCommand({ state: value ? "ON" : "OFF" });
this.platform.log.debug("Set On ->", value);
}
async getOn() {
return this.currentState.on;
}
async setBrightness(value) {
this.currentState.brightness = value;
const brightness = Math.round(((value) / 100) * ESPHomeRGBWWAccessory.MAX_BRIGHTNESS);
this.publishCommand({ brightness });
this.platform.log.debug("Set Brightness ->", value);
}
async getBrightness() {
return this.currentState.brightness;
}
async setHue(value) {
this.currentState.hue = value;
this.updateRGBColor();
this.platform.log.debug("Set Hue ->", value);
}
async getHue() {
return this.currentState.hue;
}
async setSaturation(value) {
this.currentState.saturation = value;
this.updateRGBColor();
this.platform.log.debug("Set Saturation ->", value);
}
async getSaturation() {
return this.currentState.saturation;
}
async setColorTemperature(value) {
this.currentState.colorTemperature = value;
this.publishCommand({ color_temp: value });
this.platform.log.debug("Set ColorTemperature ->", value);
}
async getColorTemperature() {
return this.currentState.colorTemperature;
}
updateRGBColor() {
const rgb = this.hsvToRgb(this.currentState.hue, this.currentState.saturation, this.currentState.brightness);
this.publishCommand({
color: {
r: rgb.r,
g: rgb.g,
b: rgb.b,
},
});
}
constructor(platform, accessory, config) {
this.platform = platform;
this.accessory = accessory;
this.config = config;
this.currentState = {
on: false,
brightness: 100,
hue: 0,
saturation: 0,
colorTemperature: 300,
};
this.accessory.getService(this.platform.Service.AccessoryInformation)
.setCharacteristic(this.platform.Characteristic.Manufacturer, config.manufacturer || 'ESPHome')
.setCharacteristic(this.platform.Characteristic.Model, config.model || 'RGBWW Light')
.setCharacteristic(this.platform.Characteristic.SerialNumber, config.id);
this.service = this.accessory.getService(this.platform.Service.Lightbulb)
|| this.accessory.addService(this.platform.Service.Lightbulb);
this.service.setCharacteristic(this.platform.Characteristic.Name, config.name);
this.service.getCharacteristic(this.platform.Characteristic.On)
.onSet(this.setOn.bind(this))
.onGet(this.getOn.bind(this));
this.service.getCharacteristic(this.platform.Characteristic.Brightness)
.onSet(this.setBrightness.bind(this))
.onGet(this.getBrightness.bind(this));
this.service.getCharacteristic(this.platform.Characteristic.Hue)
.onSet(this.setHue.bind(this))
.onGet(this.getHue.bind(this));
this.service.getCharacteristic(this.platform.Characteristic.Saturation)
.onSet(this.setSaturation.bind(this))
.onGet(this.getSaturation.bind(this));
this.service.getCharacteristic(this.platform.Characteristic.ColorTemperature)
.onSet(this.setColorTemperature.bind(this))
.onGet(this.getColorTemperature.bind(this));
this.mqttClient = mqtt.connect(config.mqtt_broker || 'mqtt://localhost:1883');
this.mqttClient.on('connect', () => {
this.platform.log.info('Connected to MQTT broker');
this.mqttClient.subscribe(config.state_topic);
});
this.mqttClient.on('message', (topic, message) => {
if (topic === config.state_topic) {
this.handleStateUpdate(message.toString());
}
});
this.mqttClient.on('error', (error) => {
this.platform.log.error('MQTT error:', error);
});
}
rgbToHsv(r, g, b) {
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const delta = max - min;
let h = 0;
let s = 0;
const v = max;
if (delta > 0) {
s = delta / max;
if (max === r) {
h = ((g - b) / delta) % 6;
}
else if (max === g) {
h = (b - r) / delta + 2;
}
else {
h = (r - g) / delta + 4;
}
h *= 60;
if (h < 0) {
h += 360;
}
}
return { h, s: s * 100, v: v * 100 };
}
hsvToRgb(h, s, v) {
s /= 100;
v /= 100;
const c = v * s;
const x = c * (1 - Math.abs((h / 60) % 2 - 1));
const m = v - c;
let r = 0, g = 0, b = 0;
if (h >= 0 && h < 60) {
r = c;
g = x;
b = 0;
}
else if (h < 120) {
r = x;
g = c;
b = 0;
}
else if (h < 180) {
r = 0;
g = c;
b = x;
}
else if (h < 240) {
r = 0;
g = x;
b = c;
}
else if (h < 300) {
r = x;
g = 0;
b = c;
}
else {
r = c;
g = 0;
b = x;
}
return {
r: Math.round((r + m) * 255),
g: Math.round((g + m) * 255),
b: Math.round((b + m) * 255),
};
}
handleStateUpdate(message) {
try {
const state = JSON.parse(message);
this.currentState.on = state.state === 'ON';
if (state.brightness != null && isFinite(state.brightness)) {
this.currentState.brightness = Math.round((state.brightness / ESPHomeRGBWWAccessory.MAX_BRIGHTNESS) * 100);
}
if (state.color && state.color.r != null && state.color.g != null && state.color.b != null) {
const hsv = this.rgbToHsv(state.color.r, state.color.g, state.color.b);
this.currentState.hue = hsv.h;
this.currentState.saturation = hsv.s;
}
if (state.color_temp) {
this.currentState.colorTemperature = state.color_temp;
}
this.service.updateCharacteristic(this.platform.Characteristic.On, this.currentState.on);
this.service.updateCharacteristic(this.platform.Characteristic.Brightness, this.currentState.brightness);
this.service.updateCharacteristic(this.platform.Characteristic.Hue, this.currentState.hue);
this.service.updateCharacteristic(this.platform.Characteristic.Saturation, this.currentState.saturation);
this.service.updateCharacteristic(this.platform.Characteristic.ColorTemperature, this.currentState.colorTemperature);
}
catch (error) {
this.platform.log.error('Failed to parse state update:', error);
}
}
publishCommand(payload) {
this.mqttClient.publish(this.config.command_topic, JSON.stringify(payload));
}
async setOn(value) {
this.currentState.on = value;
this.publishCommand({ state: value ? 'ON' : 'OFF' });
this.platform.log.debug('Set On ->', value);
}
async getOn() {
return this.currentState.on;
}
async setBrightness(value) {
this.currentState.brightness = value;
const brightness = Math.round((value / 100) * ESPHomeRGBWWAccessory.MAX_BRIGHTNESS);
this.publishCommand({ brightness });
this.platform.log.debug('Set Brightness ->', value);
}
async getBrightness() {
return this.currentState.brightness;
}
async setHue(value) {
this.currentState.hue = value;
this.updateRGBColor();
this.platform.log.debug('Set Hue ->', value);
}
async getHue() {
return this.currentState.hue;
}
async setSaturation(value) {
this.currentState.saturation = value;
this.updateRGBColor();
this.platform.log.debug('Set Saturation ->', value);
}
async getSaturation() {
return this.currentState.saturation;
}
async setColorTemperature(value) {
this.currentState.colorTemperature = value;
this.publishCommand({ color_temp: value });
this.platform.log.debug('Set ColorTemperature ->', value);
}
async getColorTemperature() {
return this.currentState.colorTemperature;
}
updateRGBColor() {
const rgb = this.hsvToRgb(this.currentState.hue, this.currentState.saturation, this.currentState.brightness);
this.publishCommand({
color: {
r: rgb.r,
g: rgb.g,
b: rgb.b,
},
});
}
}
module.exports = { ESPHomeRGBWWAccessory };
exports.ESPHomeRGBWWAccessory = ESPHomeRGBWWAccessory;
ESPHomeRGBWWAccessory.MAX_BRIGHTNESS = 255;
//# sourceMappingURL=accessory.js.map

1
dist/accessory.js.map vendored Normal file

File diff suppressed because one or more lines are too long

4
dist/index.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
import { API } from 'homebridge';
declare const _default: (api: API) => void;
export = _default;
//# sourceMappingURL=index.d.ts.map

1
dist/index.d.ts.map vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;yBAGvB,KAAK,GAAG;AAAlB,kBAEE"}

6
dist/index.js vendored
View File

@@ -1,6 +1,6 @@
"use strict";
const { ESPHomeRGBWWPlatform } = require("./platform");
const platform_1 = require("./platform");
module.exports = (api) => {
api.registerPlatform("homebridge-esphome-rgbww", "ESPHomeRGBWW", ESPHomeRGBWWPlatform);
api.registerPlatform('homebridge-esphome-rgbww', 'ESPHomeRGBWW', platform_1.ESPHomeRGBWWPlatform);
};
//# sourceMappingURL=index.js.map

1
dist/index.js.map vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,yCAAkD;AAElD,iBAAS,CAAC,GAAQ,EAAE,EAAE;IACpB,GAAG,CAAC,gBAAgB,CAAC,0BAA0B,EAAE,cAAc,EAAE,+BAAoB,CAAC,CAAC;AACzF,CAAC,CAAC"}

13
dist/platform.d.ts vendored Normal file
View File

@@ -0,0 +1,13 @@
import { API, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service, Characteristic } from 'homebridge';
export declare class ESPHomeRGBWWPlatform implements DynamicPlatformPlugin {
readonly log: Logger;
readonly config: PlatformConfig;
readonly api: API;
readonly Service: typeof Service;
readonly Characteristic: typeof Characteristic;
readonly accessories: PlatformAccessory[];
constructor(log: Logger, config: PlatformConfig, api: API);
configureAccessory(accessory: PlatformAccessory): void;
discoverDevices(): void;
}
//# sourceMappingURL=platform.d.ts.map

1
dist/platform.d.ts.map vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,GAAG,EACH,qBAAqB,EACrB,MAAM,EACN,iBAAiB,EACjB,cAAc,EACd,OAAO,EACP,cAAc,EACf,MAAM,YAAY,CAAC;AAGpB,qBAAa,oBAAqB,YAAW,qBAAqB;aAM9C,GAAG,EAAE,MAAM;aACX,MAAM,EAAE,cAAc;aACtB,GAAG,EAAE,GAAG;IAP1B,SAAgB,OAAO,EAAE,OAAO,OAAO,CAAwB;IAC/D,SAAgB,cAAc,EAAE,OAAO,cAAc,CAA+B;IACpF,SAAgB,WAAW,EAAE,iBAAiB,EAAE,CAAM;gBAGpC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,GAAG;IAS1B,kBAAkB,CAAC,SAAS,EAAE,iBAAiB;IAK/C,eAAe;CAuBhB"}

85
dist/platform.js vendored
View File

@@ -1,45 +1,46 @@
"use strict";
const { ESPHomeRGBWWAccessory } = require("./accessory");
Object.defineProperty(exports, "__esModule", { value: true });
exports.ESPHomeRGBWWPlatform = void 0;
const accessory_1 = require("./accessory");
class ESPHomeRGBWWPlatform {
constructor(log, config, api) {
this.log = log;
this.config = config;
this.api = api;
this.Service = this.api.hap.Service;
this.Characteristic = this.api.hap.Characteristic;
this.accessories = [];
this.log.debug("Finished initializing platform:", this.config.name);
this.api.on("didFinishLaunching", () => {
this.discoverDevices();
});
}
configureAccessory(accessory) {
this.log.info("Loading accessory from cache:", accessory.displayName);
this.accessories.push(accessory);
}
discoverDevices() {
const devices = this.config.lights || [];
for (const device of devices) {
const uuid = this.api.hap.uuid.generate(device.id);
const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid);
if (existingAccessory) {
this.log.info("Restoring existing accessory from cache:", existingAccessory.displayName);
new ESPHomeRGBWWAccessory(this, existingAccessory, device);
} else {
this.log.info("Adding new accessory:", device.name);
const accessory = new this.api.platformAccessory(device.name, uuid);
accessory.context.device = device;
new ESPHomeRGBWWAccessory(this, accessory, device);
this.api.registerPlatformAccessories("homebridge-esphome-rgbww", "ESPHomeRGBWW", [accessory]);
}
}
}
constructor(log, config, api) {
this.log = log;
this.config = config;
this.api = api;
this.Service = this.api.hap.Service;
this.Characteristic = this.api.hap.Characteristic;
this.accessories = [];
this.log.debug('Finished initializing platform:', this.config.name);
this.api.on('didFinishLaunching', () => {
this.discoverDevices();
});
}
configureAccessory(accessory) {
this.log.info('Loading accessory from cache:', accessory.displayName);
this.accessories.push(accessory);
}
discoverDevices() {
const devices = this.config.lights || [];
for (const device of devices) {
const uuid = this.api.hap.uuid.generate(device.id);
const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid);
if (existingAccessory) {
this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName);
if (existingAccessory.displayName !== device.name) {
existingAccessory.displayName = device.name;
this.api.updatePlatformAccessories([existingAccessory]);
}
new accessory_1.ESPHomeRGBWWAccessory(this, existingAccessory, device);
}
else {
this.log.info('Adding new accessory:', device.name);
const accessory = new this.api.platformAccessory(device.name, uuid);
accessory.context.device = device;
new accessory_1.ESPHomeRGBWWAccessory(this, accessory, device);
this.api.registerPlatformAccessories('homebridge-esphome-rgbww', 'ESPHomeRGBWW', [accessory]);
}
}
}
}
module.exports = { ESPHomeRGBWWPlatform };
exports.ESPHomeRGBWWPlatform = ESPHomeRGBWWPlatform;
//# sourceMappingURL=platform.js.map

1
dist/platform.js.map vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":";;;AASA,2CAAoD;AAEpD,MAAa,oBAAoB;IAK/B,YACkB,GAAW,EACX,MAAsB,EACtB,GAAQ;QAFR,QAAG,GAAH,GAAG,CAAQ;QACX,WAAM,GAAN,MAAM,CAAgB;QACtB,QAAG,GAAH,GAAG,CAAK;QAPV,YAAO,GAAmB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC;QAC/C,mBAAc,GAA0B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC;QACpE,gBAAW,GAAwB,EAAE,CAAC;QAOpD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,iCAAiC,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAEpE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE;YACrC,IAAI,CAAC,eAAe,EAAE,CAAC;QACzB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,kBAAkB,CAAC,SAA4B;QAC7C,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,+BAA+B,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC;QACtE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACnC,CAAC;IAED,eAAe;QACb,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC;QAEzC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACnD,MAAM,iBAAiB,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;YAEtF,IAAI,iBAAiB,EAAE,CAAC;gBACtB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,0CAA0C,EAAE,iBAAiB,CAAC,WAAW,CAAC,CAAC;gBACzF,IAAI,iBAAiB,CAAC,WAAW,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC;oBAClD,iBAAiB,CAAC,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC;oBAC5C,IAAI,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC;gBAC1D,CAAC;gBACD,IAAI,iCAAqB,CAAC,IAAI,EAAE,iBAAiB,EAAE,MAAM,CAAC,CAAC;YAC7D,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,uBAAuB,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;gBACpD,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;gBACpE,SAAS,CAAC,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC;gBAClC,IAAI,iCAAqB,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;gBACnD,IAAI,CAAC,GAAG,CAAC,2BAA2B,CAAC,0BAA0B,EAAE,cAAc,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;YAChG,CAAC;QACH,CAAC;IACH,CAAC;CACF;AA7CD,oDA6CC"}

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "homebridge-esphome-rgbww",
"version": "2026.02.23",
"version": "1.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "homebridge-esphome-rgbww",
"version": "2026.02.23",
"version": "1.0.1",
"license": "MIT",
"dependencies": {
"mqtt": "^5.3.5"

View File

@@ -1,6 +1,6 @@
{
"name": "homebridge-esphome-rgbww",
"version": "1.0.0",
"version": "1.0.1",
"description": "Homebridge plugin for ESPHome RGBWW lights with HSV support",
"main": "dist/index.js",
"files": [

View File

@@ -149,9 +149,12 @@ export class ESPHomeRGBWWAccessory {
const state: LightState = JSON.parse(message);
this.currentState.on = state.state === 'ON';
this.currentState.brightness = Math.round((state.brightness / ESPHomeRGBWWAccessory.MAX_BRIGHTNESS) * 100);
if (state.color && state.color.r !== undefined) {
if (state.brightness != null && isFinite(state.brightness)) {
this.currentState.brightness = Math.round((state.brightness / ESPHomeRGBWWAccessory.MAX_BRIGHTNESS) * 100);
}
if (state.color && state.color.r != null && state.color.g != null && state.color.b != null) {
const hsv = this.rgbToHsv(state.color.r, state.color.g, state.color.b);
this.currentState.hue = hsv.h;
this.currentState.saturation = hsv.s;

View File

@@ -40,6 +40,10 @@ export class ESPHomeRGBWWPlatform implements DynamicPlatformPlugin {
if (existingAccessory) {
this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName);
if (existingAccessory.displayName !== device.name) {
existingAccessory.displayName = device.name;
this.api.updatePlatformAccessories([existingAccessory]);
}
new ESPHomeRGBWWAccessory(this, existingAccessory, device);
} else {
this.log.info('Adding new accessory:', device.name);