feat(filter): integrate exclude filter into parseStates and state_changed

Adapter now consults the exclude-pattern list (new `native.excludePatterns`
config option, multi-line string) at two points:

- `parseStates()` skips matching entities entirely. They are not registered
  as ioBroker objects and no state is written. A summary INFO line reports
  the count when the filter dropped anything from a sync.
- The `state_changed` event handler short-circuits matching live updates
  with a DEBUG line, so a filtered entity that still emits state changes
  in HA does not cause any churn in ioBroker.

Patterns are parsed once at adapter start: trimmed, empty lines and lines
starting with `#` are dropped. An empty resulting list means the filter is
inactive — adapter behaviour is then identical to the previous version.

Default config ships with example comments only, so existing installations
see no functional change after the update.
This commit is contained in:
mokusone
2026-05-14 11:40:28 +02:00
parent 9b95ece284
commit 6889664c28
2 changed files with 36 additions and 1 deletions
+34
View File
@@ -1,11 +1,13 @@
import { Adapter, type AdapterOptions } from '@iobroker/adapter-core';
import HASS from './lib/hass';
import { isExcluded } from './lib/entityFilter';
interface HassAdapterConfig {
host: string;
port: number;
password: string;
secure: boolean;
excludePatterns: string;
}
interface HassEntity {
@@ -188,6 +190,7 @@ class HassAdapter extends Adapter {
private delayTimeout: ReturnType<typeof setTimeout> | null = null;
private syncDebounceTimeout: ReturnType<typeof setTimeout> | null = null;
private stopped: boolean = false;
private excludePatterns: string[] = [];
public constructor(options: Partial<AdapterOptions> = {}) {
super({
@@ -454,6 +457,7 @@ class HassAdapter extends Adapter {
const objs: (ioBroker.ChannelObject | ioBroker.StateObject)[] = [];
const states: { id: string; lc?: number; ts?: number; val: ioBroker.StateValue; ack: boolean }[] = [];
const expectedObjects = new Set<string>();
let excludedCount = 0;
for (let e = 0; e < entities.length; e++) {
const entity = entities[e];
@@ -461,6 +465,11 @@ class HassAdapter extends Adapter {
continue;
}
if (isExcluded(entity.entity_id, this.excludePatterns)) {
excludedCount++;
continue;
}
const name = entity.name || entity.attributes?.friendly_name || entity.entity_id;
const desc = entity.attributes?.attribution || undefined;
@@ -652,12 +661,32 @@ class HassAdapter extends Adapter {
}
this.log.info(`Synchronization completed: ${changes.join(', ')}`);
}
if (excludedCount > 0) {
this.log.info(
`Entity filter excluded ${excludedCount} entit${excludedCount === 1 ? 'y' : 'ies'} from sync`,
);
}
}
private async main(): Promise<void> {
this.config.host ||= '127.0.0.1';
this.config.port = parseInt(String(this.config.port), 10) || 8123;
const rawPatterns = (this.config.excludePatterns || '').toString();
this.excludePatterns = rawPatterns
.split('\n')
.map(s => s.trim())
.filter(line => line.length > 0 && !line.startsWith('#'));
if (this.excludePatterns.length === 0) {
this.log.info('Entity filter inactive (no exclude patterns configured)');
} else {
this.log.info(
`Entity filter active: ${this.excludePatterns.length} pattern(s) loaded: ${this.excludePatterns.join(', ')}`,
);
}
await this.setStateAsync('info.connection', false, true);
this.hass = new HASS(this.config, this.log);
@@ -670,6 +699,11 @@ class HassAdapter extends Adapter {
return;
}
if (isExcluded(entity.entity_id, this.excludePatterns)) {
this.log.debug(`Entity filter: ignored state_changed for ${entity.entity_id}`);
return;
}
const id = `entities.${entity.entity_id}.`;
const lc = entity.last_changed ? new Date(entity.last_changed).getTime() : undefined;
const ts = entity.last_updated ? new Date(entity.last_updated).getTime() : undefined;