Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/dataHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,23 @@ export class DataHandler {
*
* @param device - The device configuration
* @param message - The raw message
* @returns Array of message paths that were updated
*/
handleDeviceData(device: Device, message: string): void {
handleDeviceData(device: Device, message: string): string[] {
logger.debug(`Received new device data for device ${device.deviceType}:${device.deviceId}`);
logger.trace(`Raw message: ${message}`);

try {
const parsedData = parseMessage(message, device.deviceType, device.deviceId);
const updatedPaths: string[] = [];
for (const [path, data] of Object.entries(parsedData)) {
this.deviceManager.updateDeviceState(device, path, () => data);
updatedPaths.push(path);
}
return updatedPaths;
} catch (error) {
logger.error(`Error handling device data for ${device.deviceId}:`, error);
return [];
}
}
}
4 changes: 2 additions & 2 deletions src/device/venus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -623,7 +623,7 @@ function registerRuntimeInfoMessage(message: BuildMessageFn) {
icon: 'mdi:lan',
command: 'local-api-enabled',
}),
{ enabled: state => (state.deviceVersion ?? 0) >= 153 },
{ enabled: state => (state.deviceVersion == null ? undefined : state.deviceVersion >= 153) },
);

field({
Expand All @@ -642,7 +642,7 @@ function registerRuntimeInfoMessage(message: BuildMessageFn) {
max: 65535,
step: 1,
}),
{ enabled: state => (state.deviceVersion ?? 0) >= 153 },
{ enabled: state => (state.deviceVersion == null ? undefined : state.deviceVersion >= 153) },
);

command('local-api-enabled', {
Expand Down
2 changes: 1 addition & 1 deletion src/deviceDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export type RegisterCommandDefinitionFn<T extends BaseDeviceData> = (
export type AdvertiseComponentFn<T extends BaseDeviceData> = <KP extends KeyPath<T> | []>(
keyPath: KP,
component: HaStatefulAdvertiseBuilder<KP extends KeyPath<T> ? TypeAtPath<T, KP> : void>,
options?: { enabled?: (state: T) => boolean },
options?: { enabled?: (state: T) => boolean | undefined },
) => void;

export type BuildMessageDefinitionArgs<T extends BaseDeviceData> = {
Expand Down
17 changes: 13 additions & 4 deletions src/generateDiscoveryConfigs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { Device } from './types';
export interface HaAdvertisement<T, KP extends KeyPath<T> | []> {
keyPath: KP;
advertise: HaStatefulAdvertiseBuilder<KP extends KeyPath<T> ? TypeAtPath<T, KeyPath<T>> : void>;
enabled?: (state: T) => boolean;
enabled?: (state: T) => boolean | undefined;
}

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

if (field.enabled && !field.enabled(deviceState)) {
configs.push({ topic, config: null });
continue;
if (field.enabled) {
const enabledResult = field.enabled(deviceState);
if (enabledResult === undefined) {
// Defer decision - don't publish anything yet
continue;
}
if (enabledResult === false) {
// Explicitly disabled
configs.push({ topic, config: null });
continue;
}
}

// Component is enabled (or has no enabled check)
configs.push({
topic,
config: {
Expand Down
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,9 @@ async function main() {
}

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

case 'control':
Expand Down
22 changes: 22 additions & 0 deletions src/mqttClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export class MqttClient {
private discoveryInterval: NodeJS.Timeout | null = null;
private timeoutCounters: Map<string, number> = new Map();
private allowedConsecutiveTimeouts: number;
private devicePathsWithData: Set<string> = new Set();

constructor(
private config: MqttConfig,
Expand Down Expand Up @@ -212,6 +213,27 @@ export class MqttClient {
}
}

/**
* Called when device data is received to potentially re-publish discovery configs
* on first data receipt (to update enabled states that depend on device data)
*
* @param device - The device that received data
* @param publishPath - The message path that received data (e.g., 'data', 'bms')
*/
onDeviceDataReceived(device: Device, publishPath: string): void {
const devicePathKey = `${device.deviceType}:${device.deviceId}:${publishPath}`;

// If this is the first time we're receiving data for this device+path,
// re-publish discovery configs now that we have device state
if (!this.devicePathsWithData.has(devicePathKey)) {
logger.debug(
`First data received for ${device.deviceType}:${device.deviceId} on path ${publishPath}, re-publishing discovery configs`,
);
this.devicePathsWithData.add(devicePathKey);
this.publishDiscoveryConfigs(device);
}
}

private lastRequestTime: Map<string, number> = new Map();

/**
Expand Down
Loading