Skip to content

Commit

Permalink
refactor: Limit data sent to the client to prevent exceeding websocke…
Browse files Browse the repository at this point in the history
…t message size limits
  • Loading branch information
zachowj committed Oct 23, 2024
1 parent fb9175c commit ae0a878
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 52 deletions.
32 changes: 21 additions & 11 deletions src/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,27 @@ import ZoneEditor from './nodes/zone/editor';

declare const RED: EditorRED;

RED.comms.subscribe('homeassistant/areas/#', updateAreas);
RED.comms.subscribe('homeassistant/devices/#', updateDevices);
RED.comms.subscribe('homeassistant/entity/#', updateEntity);
RED.comms.subscribe('homeassistant/entities/#', updateEntities);
RED.comms.subscribe('homeassistant/floors/#', updateFloors);
RED.comms.subscribe('homeassistant/labels/#', updateLabels);
RED.comms.subscribe('homeassistant/integration/#', updateIntegration);
RED.comms.subscribe('homeassistant/services/#', updateServices);
RED.comms.subscribe('homeassistant/entityRegistry/#', updateEntityRegistry);
RED.comms.subscribe('homeassistant/issues', updateIssues);
RED.comms.subscribe(PRINT_TO_DEBUG_TOPIC, printToDebugPanel);
const subscriptions = {
'homeassistant/areas/#': updateAreas,
'homeassistant/devices/#': updateDevices,
'homeassistant/entity/#': updateEntity,
'homeassistant/entities/#': updateEntities,
'homeassistant/floors/#': updateFloors,
'homeassistant/labels/#': updateLabels,
'homeassistant/integration/#': updateIntegration,
'homeassistant/services/#': updateServices,
'homeassistant/entityRegistry/#': updateEntityRegistry,
'homeassistant/issues': updateIssues,
[PRINT_TO_DEBUG_TOPIC]: printToDebugPanel,
};
// Introduce a delay in the subscription process to prevent Node-RED from sending all updates simultaneously.
// This issue primarily arises when accessing the editor via the Home Assistant ingress proxy,
// which has a default message size limit of 4MB.
Object.entries(subscriptions).forEach(([topic, updateFunction], index) => {
setTimeout(() => {
RED.comms.subscribe(topic, updateFunction);
}, index * 150);
});
setupMigrations();
setupEditors();
RED.events.on('nodes:add', onNodesAdd);
Expand Down
15 changes: 7 additions & 8 deletions src/editor/components/idSelector/IdSelector.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { HassEntity } from 'home-assistant-js-websocket';

import { IdSelectorType } from '../../../common/const';
import { ActionTargetFilter } from '../../../nodes/action/editor/targets';
import {
HassArea,
HassAreas,
HassDevice,
HassFloor,
HassLabel,
SlimHassDevice,
SlimHassEntity,
} from '../../../types/home-assistant';
import * as haServer from '../../haserver';
import { i18n } from '../../i18n';
Expand All @@ -22,8 +21,8 @@ interface EditableListButton {
click: (evt: any) => void;
}
export interface TargetData {
entities: HassEntity[];
devices: HassDevice[];
entities: SlimHassEntity[];
devices: SlimHassDevice[];
areas: HassAreas;
floors: HassFloor[];
labels: HassLabel[];
Expand Down Expand Up @@ -74,8 +73,8 @@ export default class IdSelector {
};
}

const filteredEntities: HassEntity[] = [];
const filteredDevices: HassDevice[] = [];
const filteredEntities: SlimHassEntity[] = [];
const filteredDevices: SlimHassDevice[] = [];
const filteredAreas: HassAreas = [];
const filteredFloors: HassFloor[] = [];
const filteredLabels: HassLabel[] = [];
Expand Down Expand Up @@ -146,7 +145,7 @@ export default class IdSelector {
}

// Add devices that the entity is part of
let device: HassDevice | undefined;
let device: SlimHassDevice | undefined;
if (entity.device_id) {
device = devices.find((d) => d.id === entity.device_id);
if (device) {
Expand Down
45 changes: 26 additions & 19 deletions src/editor/data.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
import {
HassEntities,
HassEntity,
HassServices,
} from 'home-assistant-js-websocket';
import { HassServices } from 'home-assistant-js-websocket';

import {
HassArea,
HassDevice,
HassEntityRegistryEntry,
HassFloor,
HassLabel,
SlimHassDevice,
SlimHassEntity,
SlimHassEntityRegistryEntry,
} from '../types/home-assistant';
import { i18n } from './i18n';
import { deepFind } from './utils';

const areas: { [serverId: string]: HassArea[] } = {};
const devices: { [serverId: string]: HassDevice[] } = {};
const entities: { [serverId: string]: HassEntities } = {};
const devices: { [serverId: string]: SlimHassDevice[] } = {};
const entities: {
[serverId: string]: { [entity_id: string]: SlimHassEntity };
} = {};
const floors: { [serverId: string]: HassFloor[] } = {};
const labels: { [serverId: string]: HassLabel[] } = {};
const services: { [serverId: string]: HassServices } = {};
const entityRegistry: { [serverId: string]: HassEntityRegistryEntry[] } = {};
const entityRegistry: { [serverId: string]: SlimHassEntityRegistryEntry[] } =
{};

export function updateAreas(topic: string, data: HassArea[]): void {
const serverId = parseServerId(topic);
areas[serverId] = data;
}

export function updateDevices(topic: string, data: HassDevice[]): void {
export function updateDevices(topic: string, data: SlimHassDevice[]): void {
const serverId = parseServerId(topic);
devices[serverId] = data;
}

export function updateEntity(topic: string, data: HassEntity[]): void {
export function updateEntity(topic: string, data: SlimHassEntity[]): void {
const serverId = parseServerId(topic);
if (!entities[serverId]) entities[serverId] = {};
data.forEach((item) => {
Expand All @@ -42,13 +42,16 @@ export function updateEntity(topic: string, data: HassEntity[]): void {

export function updateEntityRegistry(
topic: string,
data: HassEntityRegistryEntry[],
data: SlimHassEntityRegistryEntry[],
): void {
const serverId = parseServerId(topic);
entityRegistry[serverId] = data;
}

export function updateEntities(topic: string, data: HassEntities): void {
export function updateEntities(
topic: string,
data: { [entity_id: string]: SlimHassEntity },
): void {
const serverId = parseServerId(topic);
entities[serverId] = data;
}
Expand Down Expand Up @@ -194,26 +197,30 @@ export function getAreas(serverId: string): HassArea[] {
return areas[serverId] ?? [];
}

export function getDevices(serverId: string): HassDevice[] {
export function getDevices(serverId: string): SlimHassDevice[] {
return devices[serverId] ?? [];
}

export function getEntity(serverId: string, entityId: string): HassEntity {
export function getEntity(serverId: string, entityId: string): SlimHassEntity {
return entities[serverId][entityId];
}

export function getEntityFromRegistry(
serverId: string,
registryId: string,
): HassEntityRegistryEntry | undefined {
): SlimHassEntityRegistryEntry | undefined {
return entityRegistry[serverId].find((entry) => entry.id === registryId);
}

export function getEntityRegistry(serverId: string): HassEntityRegistryEntry[] {
export function getEntityRegistry(
serverId: string,
): SlimHassEntityRegistryEntry[] {
return entityRegistry[serverId] ?? [];
}

export function getEntities(serverId: string): HassEntities {
export function getEntities(serverId: string): {
[entity_id: string]: SlimHassEntity;
} {
return entities[serverId] ?? {};
}

Expand Down
3 changes: 2 additions & 1 deletion src/editor/editors/entity-filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { HassEntity } from 'home-assistant-js-websocket';
import { EditorRED } from 'node-red';

import { byPropertiesOf } from '../../helpers/sort';
import { SlimHassEntity } from '../../types/home-assistant';
import { i18n } from '../i18n';

declare const RED: EditorRED;
Expand Down Expand Up @@ -111,7 +112,7 @@ export const openEntityFilter = ({
}: {
filter: string;
filterType: 'substring' | 'regex';
entities: HassEntity[];
entities: SlimHassEntity[];
complete: (filter: string) => void;
}) => {
RED.editor.showTypeEditor('ha_entity_filter', {
Expand Down
11 changes: 6 additions & 5 deletions src/editor/haserver.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { HassEntity, HassServices } from 'home-assistant-js-websocket';
import { HassServices } from 'home-assistant-js-websocket';
import { EditorNodeInstance, EditorRED } from 'node-red';

import { NodeType } from '../const';
import { ConfigServerEditorNodeProperties } from '../nodes/config-server/editor';
import {
HassArea,
HassAreas,
HassDevices,
HassFloor,
HassLabel,
SlimHassDevice,
SlimHassEntity,
} from '../types/home-assistant';
import * as haData from './data';
import { HassNodeProperties } from './types';
Expand Down Expand Up @@ -129,19 +130,19 @@ export const getAreas = (): HassAreas => {
return haData.getAreas(serverId);
};

export const getDevices = (): HassDevices => {
export const getDevices = (): SlimHassDevice[] => {
return haData.getDevices(serverId);
};

export const getEntity = (entityId: string): HassEntity => {
export const getEntity = (entityId: string): SlimHassEntity => {
return haData.getEntity(serverId, entityId);
};

export const getEntityRegistry = () => {
return haData.getEntityRegistry(serverId);
};

export const getEntities = (): HassEntity[] => {
export const getEntities = (): SlimHassEntity[] => {
return Object.values(haData.getEntities(serverId));
};

Expand Down
61 changes: 55 additions & 6 deletions src/nodes/config-server/Comms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import { ClientEvent } from '../../homeAssistant/Websocket';
import {
HassArea,
HassDevice,
HassEntity,
HassEntityRegistryEntry,
HassFloor,
HassLabel,
HassStateChangedEvent,
SlimHassDevice,
SlimHassEntity,
SlimHassEntityRegistryEntry,
} from '../../types/home-assistant';

export default class Comms {
Expand Down Expand Up @@ -60,7 +62,17 @@ export default class Comms {
}

onDeviceRegistryUpdate(devices: HassDevice[]): void {
this.publish('devices', devices);
const slimDevices: SlimHassDevice[] = devices.map((device) => {
return {
area_id: device.area_id,
id: device.id,
labels: device.labels,
name: device.name,
name_by_user: device.name_by_user,
};
});

this.publish('devices', slimDevices);
}

onFloorRegistryUpdate(floors: HassFloor[]): void {
Expand All @@ -72,7 +84,21 @@ export default class Comms {
}

onEntityRegistryUpdate(entities: HassEntityRegistryEntry[]): void {
this.publish('entityRegistry', entities);
const slimEntities: SlimHassEntityRegistryEntry[] = entities.map(
(entity) => {
return {
area_id: entity.area_id,
device_id: entity.device_id,
entity_id: entity.entity_id,
id: entity.id,
labels: entity.labels,
name: entity.name,
original_name: entity.original_name,
platform: entity.platform,
};
},
);
this.publish('entityRegistry', slimEntities);
}

onIntegrationEvent(eventType: string): void {
Expand All @@ -86,23 +112,46 @@ export default class Comms {
this.publish('services', services);
}

#stateChangedBatchedUpdates: Map<string, HassEntity> = new Map();
#stateChangedBatchedUpdates: Map<string, SlimHassEntity> = new Map();
#throttledStateChangedPublish = throttle(() => {
this.publish(
'entity',
Array.from(this.#stateChangedBatchedUpdates.values()),
false,
);
this.#stateChangedBatchedUpdates.clear();
}, 1000);

onStateChanged(event: HassStateChangedEvent): void {
const entity = event.event.new_state;
const entity: SlimHassEntity = {
entity_id: event.entity_id,
state: event.event.new_state?.state as string,
attributes: {
device_class: event.event.new_state?.attributes.device_class,
friendly_name: event.event.new_state?.attributes.friendly_name,
supported_features:
event.event.new_state?.attributes.supported_features,
},
};
if (!entity) return;
this.#stateChangedBatchedUpdates.set(event.entity_id, entity);
this.#throttledStateChangedPublish();
}

onStatesLoaded(entities: HassEntities): void {
this.publish('entities', entities);
const slimEntities: { [entity_id: string]: SlimHassEntity } = {};
Object.keys(entities).forEach((entityId) => {
const entity = entities[entityId];
slimEntities[entityId] = {
entity_id: entityId,
state: entity.state,
attributes: {
device_class: entity.attributes.device_class,
friendly_name: entity.attributes.friendly_name,
supported_features: entity.attributes.supported_features,
},
};
});
this.publish('entities', slimEntities);
}
}
4 changes: 2 additions & 2 deletions src/nodes/device/editor/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getAreaNameById, getDevices } from '../../../editor/data';
import * as haServer from '../../../editor/haserver';
import { i18n } from '../../../editor/i18n';
import { HassDevice } from '../../../types/home-assistant';
import { SlimHassDevice } from '../../../types/home-assistant';
import { VirtualSelectOption } from '../../../types/virtual-select';

export enum DeviceEndpoint {
Expand All @@ -18,7 +18,7 @@ export enum DeviceEndpoint {
* @returns An array of virtual select options.
*/
export function buildDevices(serverId: string) {
const devices: HassDevice[] = getDevices(serverId);
const devices: SlimHassDevice[] = getDevices(serverId);
const options: VirtualSelectOption[] = [];
devices.forEach((device) => {
options.push({
Expand Down
Loading

0 comments on commit ae0a878

Please sign in to comment.