mirror of
https://github.com/adrianjagielak/home-assistant-futurehome.git
synced 2026-07-07 13:23:03 +00:00
Fix Modeswitch scene sensor stuck at 'unknown'
Scene controllers such as the Futurehome Modeswitch/Modusbryter (a Z-Wave device) only ever emit `evt.scene.report` on a button press and never appear in the periodic Vinculum state poll. Their address therefore never got an entry in the HA state cache, so live scene events were dropped by the `if (!payload[addr]) continue` guard in haUpdateStateValueReport, leaving the "Scene" entity permanently 'unknown'. - Register an address -> state-topic mapping from the device config so live events can be routed even when the service is absent from the state poll. - Seed the cache entry from that mapping when a report arrives for an otherwise-unknown address. - Preserve previously known values across state polls so a scene value set by a live event is not wiped by a later poll that omits the service. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BRMavpbsGAcY2ctkKvcoNR
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
<!-- https://developers.home-assistant.io/docs/add-ons/presentation#keeping-a-changelog -->
|
||||
|
||||
## 1.6.3 (01.07.2026)
|
||||
|
||||
- Fixed Modeswitch/Modusbryter (and other scene controllers) staying stuck at `unknown`. Live `evt.scene.report` button presses are now delivered to the "Scene" entity even for services that never appear in the periodic state poll.
|
||||
|
||||
## 1.6.2 (16.05.2026)
|
||||
|
||||
- Fix invalid device_class + unit_of_measurement combinations for HA 2026.5 (#32).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# https://developers.home-assistant.io/docs/add-ons/configuration#add-on-config
|
||||
name: Futurehome
|
||||
version: '1.6.2'
|
||||
version: '1.6.3'
|
||||
slug: futurehome
|
||||
description: Local Futurehome Smarthub integration
|
||||
url: 'https://github.com/adrianjagielak/home-assistant-futurehome'
|
||||
|
||||
@@ -38,6 +38,7 @@ import { abbreviateHaMqttKeys } from './abbreviate_ha_mqtt_keys';
|
||||
import { ha } from './globals';
|
||||
import { HaDeviceConfig } from './ha_device_config';
|
||||
import { HaMqttComponent } from './mqtt_components/_component';
|
||||
import { registerServiceStateTopic } from './update_state';
|
||||
|
||||
export type ServiceComponentsCreationResult = {
|
||||
components: { [key: string]: HaMqttComponent };
|
||||
@@ -228,6 +229,12 @@ export function haPublishDevice(parameters: {
|
||||
if (!svc.enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Register the address→state-topic mapping so that live `evt.*.report`
|
||||
// events for services that never appear in the periodic state poll (e.g. a
|
||||
// Modeswitch/Modusbryter scene controller) can still be routed to this
|
||||
// device's state topic instead of being dropped.
|
||||
registerServiceStateTopic(svc.addr, `${topicPrefix}/state`);
|
||||
// Skip publishing services that are already represented by higher-level MQTT entities
|
||||
if (
|
||||
!shouldPublishService(
|
||||
|
||||
@@ -175,6 +175,36 @@ const haStateCache: Record<
|
||||
Record<string, Record<string, any>> // payload (addr → { attr → value })
|
||||
> = {};
|
||||
|
||||
/**
|
||||
* Maps a FIMP service address
|
||||
* (e.g. "/rt:dev/rn:zw/ad:1/sv:scene_ctrl/ad:86_0") to the Home Assistant
|
||||
* device state topic that should carry its value.
|
||||
*
|
||||
* This is required because some services never appear in the periodic Vinculum
|
||||
* `state` poll and therefore never get an entry in `haStateCache`. The most
|
||||
* prominent example is a momentary scene controller such as the Futurehome
|
||||
* Modeswitch / Modusbryter (a Z-Wave device): it only ever emits
|
||||
* `evt.scene.report` events when a button is pressed, and its `scene`
|
||||
* attribute is not part of the polled state.
|
||||
*
|
||||
* Without this mapping those live events would be dropped in
|
||||
* `haUpdateStateValueReport` (because the address is not present in any cached
|
||||
* payload), leaving the "Scene" sensor stuck at "unknown" forever.
|
||||
*
|
||||
* The mapping is populated both from the device config (see
|
||||
* `haPublishDevice`) and from every state poll below.
|
||||
*/
|
||||
const addrToStateTopic: Record<string, string> = {};
|
||||
|
||||
/**
|
||||
* Registers the Home Assistant device state topic that owns a given FIMP
|
||||
* service address, so that live `evt.*.report` events for services missing
|
||||
* from the periodic state poll can still be routed to the correct state topic.
|
||||
*/
|
||||
export function registerServiceStateTopic(addr: string, stateTopic: string) {
|
||||
addrToStateTopic[addr] = stateTopic;
|
||||
}
|
||||
|
||||
const attributeTypeKeyMap: Record<string, string> = {
|
||||
alarm: 'event',
|
||||
meter: 'props.unit',
|
||||
@@ -271,12 +301,17 @@ export function haUpdateState(parameters: {
|
||||
}) {
|
||||
const stateTopic = `homeassistant/device/futurehome_${parameters.hubId}_${parameters.deviceState.id?.toString()}/state`;
|
||||
|
||||
const haState: Record<string, Record<string, any>> = {};
|
||||
// Start from the previously cached payload so that values learned from live
|
||||
// `evt.*.report` events (e.g. a scene controller's last reported scene) are
|
||||
// preserved across polls that do not include that service. The poll only
|
||||
// ever refreshes/adds attributes, it never drops previously known ones.
|
||||
const previous = haStateCache[stateTopic] ?? {};
|
||||
const haState: Record<string, Record<string, any>> = { ...previous };
|
||||
|
||||
for (const service of parameters.deviceState.services || []) {
|
||||
if (!service.addr) continue;
|
||||
|
||||
const serviceState: Record<string, any> = {};
|
||||
const serviceState: Record<string, any> = { ...(previous[service.addr] ?? {}) };
|
||||
|
||||
for (const attr of service.attributes || []) {
|
||||
const processedValue = processAttributeValues(
|
||||
@@ -289,6 +324,7 @@ export function haUpdateState(parameters: {
|
||||
}
|
||||
|
||||
haState[service.addr] = serviceState;
|
||||
registerServiceStateTopic(service.addr, stateTopic);
|
||||
}
|
||||
|
||||
log.debug(`Publishing HA state "${stateTopic}"`);
|
||||
@@ -328,6 +364,20 @@ export function haUpdateStateValueReport(parameters: {
|
||||
const addr = parameters.topic.replace(/^pt:j1\/mt:evt/, '');
|
||||
const typeKey = getTypeKey(parameters.attrName);
|
||||
|
||||
// Some services (most notably momentary scene controllers such as the
|
||||
// Futurehome Modeswitch / Modusbryter) never appear in the periodic state
|
||||
// poll, so their address has no entry in any cached payload yet. Seed one
|
||||
// from the address→topic mapping registered from the device config, so the
|
||||
// live event below is applied instead of being silently dropped.
|
||||
if (
|
||||
!Object.values(haStateCache).some((payload) => payload[addr]) &&
|
||||
addrToStateTopic[addr]
|
||||
) {
|
||||
const stateTopic = addrToStateTopic[addr];
|
||||
haStateCache[stateTopic] ??= {};
|
||||
haStateCache[stateTopic][addr] ??= {};
|
||||
}
|
||||
|
||||
for (const [stateTopic, payload] of Object.entries(haStateCache)) {
|
||||
if (!payload[addr]) continue;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user