UNPKG

iobroker.frigate

Version:
145 lines 5.82 kB
/** * Tracks active events per zone and maintains aggregate object counts. * Zones are defined in Frigate config per camera. When an event enters a zone, * the zone counter increments. When the event ends, it decrements. * This gives a cross-camera view of objects per zone, split by active/stationary. */ export class ZoneAggregator { ctx; /** zone → Map of eventId → TrackedEvent */ zoneEvents = new Map(); /** Known zone names from Frigate config */ knownZones = new Set(); constructor(ctx) { this.ctx = ctx; } /** Initialize zones from Frigate config and create device/state objects */ async initZones(configData) { if (!configData?.cameras) { return; } const zones = new Set(); for (const camKey in configData.cameras) { const camZones = configData.cameras[camKey].zones; if (camZones) { for (const zoneName of Object.keys(camZones)) { zones.add(zoneName); } } } this.knownZones = zones; for (const zone of zones) { this.ctx.adapter.log.info(`Create zone device for: ${zone}`); await this.ctx.adapter.extendObjectAsync(zone, { type: 'device', common: { name: `Zone ${zone}` }, native: {}, }); // Pre-create the summary states await this.createZoneState(`${zone}.total_objects`, `Total objects in zone ${zone}`, 'number', 'value', 0); await this.createZoneState(`${zone}.active`, `Objects detected in zone ${zone}`, 'boolean', 'indicator', false); } } /** Process an event update and recalculate zone counts */ async processEvent(data) { if (this.knownZones.size === 0) { return; } const eventData = data.after || data.before; if (!eventData?.id) { return; } const eventId = eventData.id; const label = eventData.label; if (!label) { return; } const eventType = data.type; const zones = eventData.entered_zones || []; const isStationary = eventData.stationary === true; if (eventType === 'end') { for (const [, events] of this.zoneEvents) { events.delete(eventId); } } else { // Remove from zones this event is no longer in for (const [zoneName, events] of this.zoneEvents) { if (!zones.includes(zoneName)) { events.delete(eventId); } } // Add/update in current zones for (const zone of zones) { if (!this.knownZones.has(zone)) { continue; } if (!this.zoneEvents.has(zone)) { this.zoneEvents.set(zone, new Map()); } this.zoneEvents.get(zone).set(eventId, { label, stationary: isStationary }); } } await this.updateZoneStates(); } async updateZoneStates() { for (const zone of this.knownZones) { const events = this.zoneEvents.get(zone); // Aggregate counts: label → { total, active, stationary } const counts = new Map(); let totalAll = 0; if (events) { for (const [, ev] of events) { if (!counts.has(ev.label)) { counts.set(ev.label, { total: 0, active: 0, stationary: 0 }); } const c = counts.get(ev.label); c.total++; totalAll++; if (ev.stationary) { c.stationary++; } else { c.active++; } } } // Write per-label states for (const [label, c] of counts) { await this.createZoneState(`${zone}.${label}`, `${label} in zone ${zone} (total)`, 'number', 'value', 0); await this.ctx.adapter.setStateAsync(`${zone}.${label}`, c.total, true); await this.createZoneState(`${zone}.${label}_active`, `${label} actively moving in zone ${zone}`, 'number', 'value', 0); await this.ctx.adapter.setStateAsync(`${zone}.${label}_active`, c.active, true); await this.createZoneState(`${zone}.${label}_stationary`, `${label} stationary in zone ${zone}`, 'number', 'value', 0); await this.ctx.adapter.setStateAsync(`${zone}.${label}_stationary`, c.stationary, true); } // Reset labels that had events before but now have 0 if (events) { // Find labels we've written before by checking existing states // We track this by checking if count is 0 for previously seen labels for (const [, ev] of events) { // events still exist, handled above void ev; } } // Write summary states await this.ctx.adapter.setStateAsync(`${zone}.total_objects`, totalAll, true); await this.ctx.adapter.setStateAsync(`${zone}.active`, totalAll > 0, true); } } async createZoneState(id, name, type, role, def) { await this.ctx.adapter.extendObjectAsync(id, { type: 'state', common: { name, type, role, def, read: true, write: false, }, native: {}, }); } } //# sourceMappingURL=zoneAggregator.js.map