mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
home assistant always turn lights on at full brightness trying to fix weird bulb
This commit is contained in:
@@ -80,6 +80,27 @@ function normalizeRgbColor(color) {
|
|||||||
function createRuntimeEngine(deps) {
|
function createRuntimeEngine(deps) {
|
||||||
const { logger, enabled, haConfig, callHomeAssistantService } = deps;
|
const { logger, enabled, haConfig, callHomeAssistantService } = deps;
|
||||||
|
|
||||||
|
async function turnOnLightAtFullBrightness(entityId, serviceData = {}) {
|
||||||
|
/*
|
||||||
|
Every server-owned interaction that turns on or changes a light must
|
||||||
|
also restore it to full brightness. Home Assistant remembers a bulb's
|
||||||
|
previous brightness, so sending only a color or color temperature can
|
||||||
|
otherwise make a light appear unexpectedly dim even though this service
|
||||||
|
requested an on-state.
|
||||||
|
|
||||||
|
Keeping this rule in one helper makes it apply consistently to ordinary
|
||||||
|
on commands, RGB changes, white-temperature changes, bulk operations,
|
||||||
|
random scenes, and lock-on behavior. brightness_pct is deliberately
|
||||||
|
written after the caller's service data so future call sites cannot
|
||||||
|
accidentally override the service-wide 100 percent requirement.
|
||||||
|
*/
|
||||||
|
await callHomeAssistantService('light', 'turn_on', {
|
||||||
|
entity_id: entityId,
|
||||||
|
...serviceData,
|
||||||
|
brightness_pct: 100,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function emitUpdate(getState) {
|
function emitUpdate(getState) {
|
||||||
events.emit('update', getState());
|
events.emit('update', getState());
|
||||||
}
|
}
|
||||||
@@ -143,7 +164,14 @@ function createRuntimeEngine(deps) {
|
|||||||
const domain = String(meta.domain || (meta.type === 'light' ? 'light' : 'switch')).toLowerCase();
|
const domain = String(meta.domain || (meta.type === 'light' ? 'light' : 'switch')).toLowerCase();
|
||||||
const service = nextState === 'on' ? 'turn_on' : 'turn_off';
|
const service = nextState === 'on' ? 'turn_on' : 'turn_off';
|
||||||
const source = String(options?.source || 'unknown');
|
const source = String(options?.source || 'unknown');
|
||||||
await callHomeAssistantService(domain, service, { entity_id: entityId });
|
if (domain === 'light' && service === 'turn_on') {
|
||||||
|
await turnOnLightAtFullBrightness(entityId);
|
||||||
|
} else {
|
||||||
|
// Off commands and non-light domains do not accept a meaningful light
|
||||||
|
// brightness value, so their existing Home Assistant payload stays
|
||||||
|
// intentionally unchanged.
|
||||||
|
await callHomeAssistantService(domain, service, { entity_id: entityId });
|
||||||
|
}
|
||||||
logger.info('Issued Home Assistant command', { entityId, domain, service, source });
|
logger.info('Issued Home Assistant command', { entityId, domain, service, source });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,7 +495,7 @@ function createRuntimeEngine(deps) {
|
|||||||
if (!runtime.connection) throw new Error('Home Assistant not connected');
|
if (!runtime.connection) throw new Error('Home Assistant not connected');
|
||||||
|
|
||||||
const normalized = normalizeRgbColor(color);
|
const normalized = normalizeRgbColor(color);
|
||||||
await callHomeAssistantService('light', 'turn_on', { entity_id: entityId, rgb_color: normalized });
|
await turnOnLightAtFullBrightness(entityId, { rgb_color: normalized });
|
||||||
logger.info('Issued Home Assistant color command', { entityId, rgbColor: normalized });
|
logger.info('Issued Home Assistant color command', { entityId, rgbColor: normalized });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,7 +509,7 @@ function createRuntimeEngine(deps) {
|
|||||||
const normalizedKelvin = Number.isFinite(nextKelvin)
|
const normalizedKelvin = Number.isFinite(nextKelvin)
|
||||||
? Math.max(2000, Math.min(6500, Math.round(nextKelvin)))
|
? Math.max(2000, Math.min(6500, Math.round(nextKelvin)))
|
||||||
: DEFAULT_WHITE_KELVIN;
|
: DEFAULT_WHITE_KELVIN;
|
||||||
await callHomeAssistantService('light', 'turn_on', { entity_id: entityId, color_temp_kelvin: normalizedKelvin });
|
await turnOnLightAtFullBrightness(entityId, { color_temp_kelvin: normalizedKelvin });
|
||||||
logger.info('Issued Home Assistant white command', { entityId, colorTempKelvin: normalizedKelvin });
|
logger.info('Issued Home Assistant white command', { entityId, colorTempKelvin: normalizedKelvin });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
// Home Assistant Runtime Engine Tests
|
||||||
|
// Purpose: Verifies the service-wide full-brightness rule for light commands.
|
||||||
|
// Scope: Exercises injected Home Assistant calls without opening a real connection or starting the server.
|
||||||
|
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const test = require('node:test');
|
||||||
|
const { createRuntimeEngine } = require('./runtimeEngine');
|
||||||
|
const { entityConfig, entityState, runtime } = require('./state');
|
||||||
|
|
||||||
|
function createHarness() {
|
||||||
|
const calls = [];
|
||||||
|
const engine = createRuntimeEngine({
|
||||||
|
enabled: true,
|
||||||
|
haConfig: { whiteKelvin: 4000 },
|
||||||
|
callHomeAssistantService: async (domain, service, serviceData) => {
|
||||||
|
calls.push({ domain, service, serviceData });
|
||||||
|
},
|
||||||
|
// These tests only verify outbound service payloads. A no-op logger keeps
|
||||||
|
// the harness faithful to the runtime dependency contract without adding
|
||||||
|
// unrelated output to the test run.
|
||||||
|
logger: {
|
||||||
|
info() {},
|
||||||
|
warn() {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { calls, engine };
|
||||||
|
}
|
||||||
|
|
||||||
|
test('light interactions force full brightness without changing switches or off commands', async (t) => {
|
||||||
|
const { calls, engine } = createHarness();
|
||||||
|
|
||||||
|
/*
|
||||||
|
runtimeEngine uses the shared entity registry populated from configuration
|
||||||
|
in production. Seed the smallest representative registry here and restore
|
||||||
|
the shared state afterward so this focused unit test cannot leak state into
|
||||||
|
other Home Assistant tests added later.
|
||||||
|
*/
|
||||||
|
entityConfig.clear();
|
||||||
|
entityState.clear();
|
||||||
|
entityConfig.set('light.room', { id: 'light.room', type: 'light', domain: 'light' });
|
||||||
|
entityConfig.set('switch.lamp', { id: 'switch.lamp', type: 'switch', domain: 'switch' });
|
||||||
|
runtime.connection = {};
|
||||||
|
t.after(() => {
|
||||||
|
entityConfig.clear();
|
||||||
|
entityState.clear();
|
||||||
|
runtime.connection = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
await engine.setEntityState('light.room', 'on');
|
||||||
|
await engine.setLightColor('light.room', [12, 34, 56]);
|
||||||
|
await engine.setLightWhite('light.room', 4500);
|
||||||
|
await engine.setEntityState('light.room', 'off');
|
||||||
|
await engine.setEntityState('switch.lamp', 'on');
|
||||||
|
|
||||||
|
assert.deepEqual(calls, [
|
||||||
|
{
|
||||||
|
domain: 'light',
|
||||||
|
service: 'turn_on',
|
||||||
|
serviceData: { entity_id: 'light.room', brightness_pct: 100 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
domain: 'light',
|
||||||
|
service: 'turn_on',
|
||||||
|
serviceData: { entity_id: 'light.room', rgb_color: [12, 34, 56], brightness_pct: 100 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
domain: 'light',
|
||||||
|
service: 'turn_on',
|
||||||
|
serviceData: { entity_id: 'light.room', color_temp_kelvin: 4500, brightness_pct: 100 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
domain: 'light',
|
||||||
|
service: 'turn_off',
|
||||||
|
serviceData: { entity_id: 'light.room' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
domain: 'switch',
|
||||||
|
service: 'turn_on',
|
||||||
|
serviceData: { entity_id: 'switch.lamp' },
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user