Skip to content

Commit

Permalink
feat: Add expose as controller
Browse files Browse the repository at this point in the history
  • Loading branch information
zachowj committed Sep 24, 2023
1 parent 5ecb523 commit a50590c
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 10 deletions.
28 changes: 28 additions & 0 deletions src/common/controllers/EposeAsController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { EntityConfigNode } from '../../nodes/entity-config';
import { BaseNode } from '../../types/nodes';
import { TriggerPayload } from '../integration/BidirectionalEntityIntegration';
import OutputController, {
OutputControllerConstructor,
} from './OutputController';

export interface ExposeAsControllerConstructor<T extends BaseNode>
extends OutputControllerConstructor<T> {
exposeAsConfigNode?: EntityConfigNode;
}

export default abstract class ExposeAsController<
T extends BaseNode = BaseNode
> extends OutputController<T> {
protected readonly exposeAsConfigNode?: EntityConfigNode;

constructor(props: ExposeAsControllerConstructor<T>) {
super(props);
this.exposeAsConfigNode = props.exposeAsConfigNode;
}

get isEnabled(): boolean {
return this.exposeAsConfigNode?.state?.isEnabled() ?? false;
}

public abstract onTriggered(data: TriggerPayload): void;
}
47 changes: 46 additions & 1 deletion src/editor/convert-entity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// TODO: Can be removed after ha-entity is removed
// TODO: Remove for version 1.0
import { EditorRED } from 'node-red';

import { EntityType, NodeType } from '../const';
Expand Down Expand Up @@ -183,3 +183,48 @@ export const convertEntityNode = (node: EntityProperties) => {
}
RED.view.redraw(true);
};

export const convertEventNode = (node: any) => {
// Save the wires so we can add them to the new node
const wires = {
// @ts-expect-error - function is not defined in types
source: RED.nodes.getNodeLinks(node.id),
// @ts-expect-error - function is not defined in types
target: RED.nodes.getNodeLinks(node.id, 1),
};

const newId = generateId();
// If the node is in a group remove it so NR doesn't think the new config node is in the group
if (node.g) {
const oldEntityNode = RED.nodes.node(node.id);
if (oldEntityNode) {
RED.group.removeFromGroup(RED.nodes.group(node.g), oldEntityNode);
}
}
RED.nodes.remove(node.id);
RED.nodes.import({
type: NodeType.EntityConfig,
id: node.id,
server: node.server,
deviceConfig: '',
name: `exposed as for ${node.name || node.id}`,
version: RED.settings.get('haEntityConfigVersion', 0),
entityType: EntityType.Switch,
haConfig: node.haConfig ?? [],
resend: false,
});
RED.nodes.import({ ...node, id: newId, exposeAsEntityConfig: node.id });
addLinks(newId, wires);
const entityNode = RED.nodes.node(newId);
if (entityNode) {
RED.nodes.moveNodeToTab(entityNode, node.z);
if (node.g) {
RED.group.addToGroup(RED.nodes.group(node.g), entityNode);
}
// @ts-expect-error - changed defined as readonly
entityNode.changed = true;
}
RED.view.redraw(true);

return newId;
};
4 changes: 3 additions & 1 deletion src/editor/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@ export interface HassNodeProperties
debugenabled?: boolean;
server?: string;
entityConfigNode?: string;
exposeToHomeAssistant?: boolean;
outputs?: number | undefined;
haConfig?: HassExposedConfig[];

// TODO: remove after controllers are converted to TypeScript
exposeToHomeAssistant?: boolean;
}

export interface HassTargetDomains {
Expand Down
28 changes: 24 additions & 4 deletions src/editor/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { EditorNodeInstance, EditorRED } from 'node-red';

import { NodeType } from '../const';
import { migrate } from '../helpers/migrate';
import { convertEntityNode, EntityProperties } from './convert-entity';
import {
convertEntityNode,
convertEventNode,
EntityProperties,
} from './convert-entity';
import { i18n } from './i18n';
import { HassNodeProperties } from './types';

Expand All @@ -29,25 +33,39 @@ export function versionCheckOnEditPrepare(
) {
if (!isHomeAssistantNode(node) || isCurrentVersion(node)) return;

node = migrateNode(node);

// the close event will not fire if the editor was already opened
if (!isHomeAssistantConfigNode(node)) {
RED.events.on('editor:close', function reopen() {
RED.events.off('editor:close', reopen);
RED.editor.edit(node);
});
}
migrateNode(node);

RED.nodes.dirty(true);
RED.tray.close();
RED.notify(i18n('home-assistant.ui.migrations.node_schema_updated'));
}

const exposedEventNodes: NodeType[] = [];

function migrateNode(node: EditorNodeInstance<HassNodeProperties>) {
const data = RED.nodes.convertNode(node, false);
let data = RED.nodes.convertNode(node, false);

// TODO: Remove for version 1.0
if (
exposedEventNodes.includes(node.type as unknown as NodeType) &&
node.exposeToHomeAssistant === true
) {
const newId = convertEventNode(data as unknown as EntityProperties);
node = RED.nodes.node(newId) as EditorNodeInstance<HassNodeProperties>;
data = RED.nodes.convertNode(node, false);
}

const migratedData: HassNodeProperties = migrate(data);

// TODO: Can be removed after ha-entity is removed
// TODO: Remove for version 1.0
if (migratedData.type === NodeType.Entity) {
convertEntityNode(migratedData as unknown as EntityProperties);
}
Expand All @@ -70,6 +88,8 @@ function migrateNode(node: EditorNodeInstance<HassNodeProperties>) {
if ($upgradeHaNode.is(':visible') && getOldNodeCount() === 0) {
$upgradeHaNode.hide();
}

return node;
}

function migrateAllNodes() {
Expand Down
6 changes: 6 additions & 0 deletions src/helpers/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ export function getConfigNodes(node: EntityNode) {
};
}

export function getExposeAsConfigNode(
nodeId?: string
): EntityConfigNode | undefined {
return getNode<EntityConfigNode>(nodeId) as EntityConfigNode;
}

function isConfigNode(node: BaseNode | EntityNode): boolean {
return (
node.type === NodeType.DeviceConfig ||
Expand Down
11 changes: 8 additions & 3 deletions src/nodes/entity-config/editor/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,14 @@ export const createRow = (
return $row;
};

export const saveEntityType = (type: EntityType) => {
$('#node-input-lookup-entityConfig').on('click', function () {
if ($('#node-input-entityConfig').val() === '_ADD_') {
type EntityTypeSelector = 'entityConfig' | 'exposeAsEntityConfig';

export const saveEntityType = (
type: EntityType,
selector: EntityTypeSelector = 'entityConfig'
) => {
$(`#node-input-lookup-${selector}`).on('click', function () {
if ($(`#node-input-${selector}`).val() === '_ADD_') {
$('body').data('haEntityType', type);
}
});
Expand Down
7 changes: 7 additions & 0 deletions src/types/home-assistant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,13 @@ export type HassEntity = Omit<HomeAssistantEntity, 'state'> & {
timeSinceChangedMs: number;
};

export type HassEvent = HassEventBase & {
event_type: string;
event: {
[key: string]: any;
};
};

export type HassStateChangedEvent = HassEventBase & {
event_type: string;
entity_id: string;
Expand Down
3 changes: 2 additions & 1 deletion src/types/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@ export interface BaseNodeConfig extends NodeProperties {
debugenabled?: boolean;
server?: string;
entityConfigNode?: string;
exposeToHomeAssistant?: boolean;
outputs?: number;
// TODO: Can be removed when controllers are converted to TypeScript
exposeToHomeAssistant?: boolean;
}

export interface BaseNode extends Node {
Expand Down

0 comments on commit a50590c

Please sign in to comment.