Skip to content

Commit 269db33

Browse files
committed
Merge release v1.5.2
2 parents d1bb3b4 + 45b1478 commit 269db33

File tree

10 files changed

+55
-16
lines changed

10 files changed

+55
-16
lines changed

CHANGELOG.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
# Changelog
22

3-
All notable changes to this project will be documented in this file.
3+
## [1.5.2] - 2025-10-03
44

5-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5+
### Fixed
6+
7+
- Venus: Fix race condition where local API components were incorrectly disabled on startup. Components with conditional enablement now defer their decision until required device data is available (#189)
78

89
## [1.5.1] - 2025-10-03
910

ha_addon/config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: "hm2mqtt"
2-
version: "1.5.1"
2+
version: "1.5.2"
33
slug: "hm2mqtt"
44
description: "Connect Hame energy storage devices to Home Assistant via MQTT"
55
url: "https://github.com/tomquist/hm2mqtt"

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hm2mqtt",
3-
"version": "1.5.1",
3+
"version": "1.5.2",
44
"main": "dist/index.js",
55
"scripts": {
66
"build": "tsc",

src/dataHandler.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,23 @@ export class DataHandler {
2020
*
2121
* @param device - The device configuration
2222
* @param message - The raw message
23+
* @returns Array of message paths that were updated
2324
*/
24-
handleDeviceData(device: Device, message: string): void {
25+
handleDeviceData(device: Device, message: string): string[] {
2526
logger.debug(`Received new device data for device ${device.deviceType}:${device.deviceId}`);
2627
logger.trace(`Raw message: ${message}`);
2728

2829
try {
2930
const parsedData = parseMessage(message, device.deviceType, device.deviceId);
31+
const updatedPaths: string[] = [];
3032
for (const [path, data] of Object.entries(parsedData)) {
3133
this.deviceManager.updateDeviceState(device, path, () => data);
34+
updatedPaths.push(path);
3235
}
36+
return updatedPaths;
3337
} catch (error) {
3438
logger.error(`Error handling device data for ${device.deviceId}:`, error);
39+
return [];
3540
}
3641
}
3742
}

src/device/venus.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,7 @@ function registerRuntimeInfoMessage(message: BuildMessageFn) {
623623
icon: 'mdi:lan',
624624
command: 'local-api-enabled',
625625
}),
626-
{ enabled: state => (state.deviceVersion ?? 0) >= 153 },
626+
{ enabled: state => (state.deviceVersion == null ? undefined : state.deviceVersion >= 153) },
627627
);
628628

629629
field({
@@ -642,7 +642,7 @@ function registerRuntimeInfoMessage(message: BuildMessageFn) {
642642
max: 65535,
643643
step: 1,
644644
}),
645-
{ enabled: state => (state.deviceVersion ?? 0) >= 153 },
645+
{ enabled: state => (state.deviceVersion == null ? undefined : state.deviceVersion >= 153) },
646646
);
647647

648648
command('local-api-enabled', {

src/deviceDefinition.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export type RegisterCommandDefinitionFn<T extends BaseDeviceData> = (
102102
export type AdvertiseComponentFn<T extends BaseDeviceData> = <KP extends KeyPath<T> | []>(
103103
keyPath: KP,
104104
component: HaStatefulAdvertiseBuilder<KP extends KeyPath<T> ? TypeAtPath<T, KP> : void>,
105-
options?: { enabled?: (state: T) => boolean },
105+
options?: { enabled?: (state: T) => boolean | undefined },
106106
) => void;
107107

108108
export type BuildMessageDefinitionArgs<T extends BaseDeviceData> = {

src/generateDiscoveryConfigs.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { Device } from './types';
1313
export interface HaAdvertisement<T, KP extends KeyPath<T> | []> {
1414
keyPath: KP;
1515
advertise: HaStatefulAdvertiseBuilder<KP extends KeyPath<T> ? TypeAtPath<T, KeyPath<T>> : void>;
16-
enabled?: (state: T) => boolean;
16+
enabled?: (state: T) => boolean | undefined;
1717
}
1818

1919
export function generateDiscoveryConfigs(
@@ -75,11 +75,20 @@ export function generateDiscoveryConfigs(
7575
const objectId = _objectId.replace(/[^a-zA-Z0-9_-]/g, '_');
7676
const topic = `homeassistant/${platform}/${nodeId}/${objectId}/config`;
7777

78-
if (field.enabled && !field.enabled(deviceState)) {
79-
configs.push({ topic, config: null });
80-
continue;
78+
if (field.enabled) {
79+
const enabledResult = field.enabled(deviceState);
80+
if (enabledResult === undefined) {
81+
// Defer decision - don't publish anything yet
82+
continue;
83+
}
84+
if (enabledResult === false) {
85+
// Explicitly disabled
86+
configs.push({ topic, config: null });
87+
continue;
88+
}
8189
}
8290

91+
// Component is enabled (or has no enabled check)
8392
configs.push({
8493
topic,
8594
config: {

src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,9 @@ async function main() {
224224
}
225225

226226
deviceManager.clearResponseTimeout(device);
227-
dataHandler.handleDeviceData(device, message.toString());
227+
const updatedPaths = dataHandler.handleDeviceData(device, message.toString());
228+
// Re-publish discovery configs for each message path that received data for the first time
229+
updatedPaths.forEach(path => mqttClient.onDeviceDataReceived(device, path));
228230
break;
229231

230232
case 'control':

src/mqttClient.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export class MqttClient {
1111
private discoveryInterval: NodeJS.Timeout | null = null;
1212
private timeoutCounters: Map<string, number> = new Map();
1313
private allowedConsecutiveTimeouts: number;
14+
private devicePathsWithData: Set<string> = new Set();
1415

1516
constructor(
1617
private config: MqttConfig,
@@ -212,6 +213,27 @@ export class MqttClient {
212213
}
213214
}
214215

216+
/**
217+
* Called when device data is received to potentially re-publish discovery configs
218+
* on first data receipt (to update enabled states that depend on device data)
219+
*
220+
* @param device - The device that received data
221+
* @param publishPath - The message path that received data (e.g., 'data', 'bms')
222+
*/
223+
onDeviceDataReceived(device: Device, publishPath: string): void {
224+
const devicePathKey = `${device.deviceType}:${device.deviceId}:${publishPath}`;
225+
226+
// If this is the first time we're receiving data for this device+path,
227+
// re-publish discovery configs now that we have device state
228+
if (!this.devicePathsWithData.has(devicePathKey)) {
229+
logger.debug(
230+
`First data received for ${device.deviceType}:${device.deviceId} on path ${publishPath}, re-publishing discovery configs`,
231+
);
232+
this.devicePathsWithData.add(devicePathKey);
233+
this.publishDiscoveryConfigs(device);
234+
}
235+
}
236+
215237
private lastRequestTime: Map<string, number> = new Map();
216238

217239
/**

0 commit comments

Comments
 (0)