Skip to content

Commit

Permalink
feat(text): Add text node
Browse files Browse the repository at this point in the history
  • Loading branch information
zachowj committed May 8, 2023
1 parent 64d3ce6 commit 2474a44
Show file tree
Hide file tree
Showing 17 changed files with 389 additions and 8 deletions.
33 changes: 33 additions & 0 deletions docs/node/text.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
::: warning
_Needs [Custom Integration](https://github.com/zachowj/hass-node-red) installed
in Home Assistant for this node to function_
:::

# Text

Creates a text entity in Home Assistant which can be manipulated from this node.

## Configuration

### State <Badge text="required"/>

- Type: `text`

The state of the entity should be updated to

## Inputs

properties of `msg.payload`

### state

- Type: `text`

The state of the text entity should be updated to

## Outputs

Value types:

- `entity state`: entity state of the text entity
- `config`: config properties of the node
1 change: 1 addition & 0 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ const nodeMap = {
sensor: { doc: 'sensor', type: 'ha-sensor' },
switch: { doc: 'switch', type: 'ha-switch' },
tag: { doc: 'tag', type: 'ha-tag' },
text: { doc: 'text', type: 'ha-text' },
time: { doc: 'time', type: 'ha-time' },
'trigger-state': { doc: 'trigger-state', type: 'trigger-state' },
'update-config': { doc: 'update-config', type: 'ha-update-config' },
Expand Down
10 changes: 8 additions & 2 deletions locales/en-US/entity-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,21 @@
"entity_picture": "Entity picture",
"icon": "Icon",
"last_reset": "Last reset",
"max_length": "Max length",
"max_value": "Max value",
"min_length": "Min length",
"min_value": "Min value",
"mode": "Mode",
"mode_options": {
"auto": "auto",
"box": "box",
"slider": "slider"
"password": "password",
"slider": "slider",
"text": "text"
},
"name": "Friendly name",
"output_debug": "Output debug information",
"pattern": "Pattern",
"resend": "Resend state and attributes",
"state_class": "State class",
"state_class_options": {
Expand All @@ -110,7 +115,8 @@
"button": "button",
"number": "number",
"sensor": "sensor",
"switch": "switch"
"switch": "switch",
"text": "text"
},
"unit_of_measurement": "Unit of measurement"
}
Expand Down
7 changes: 7 additions & 0 deletions locales/en-US/text.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"ha-text": {
"label": {
"text": "Text"
}
}
}
13 changes: 8 additions & 5 deletions src/common/integration/BidirectionalEntityIntegration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,19 +114,22 @@ export default class BidirectionalIntegration extends Integration {
}

protected getStateData(state?: State): Partial<EntityMessage> {
if (!state) {
const lastPayload = state?.getLastPayload();
if (!state || !lastPayload) {
return {};
}

const data: Partial<EntityMessage> = {};
let data: Partial<EntityMessage> = {};

switch (this.entityConfigNode.config.entityType) {
case EntityType.Number:
case EntityType.Text: {
data = { ...lastPayload };
break;
}
case EntityType.Switch:
data.state = state.isEnabled();
break;
case EntityType.Number:
data.state = state.getLastPayload();
break;
}

return data;
Expand Down
4 changes: 4 additions & 0 deletions src/common/integration/UnidirectionalEntityIntegration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,4 +314,8 @@ export default class UnidirectionalIntegration extends Integration {
protected debugToClient(topic: string, message: any) {
debugToClient(this.entityConfigNode, message, topic);
}

public getEntityConfigNode(): EntityConfigNode {
return this.entityConfigNode;
}
}
2 changes: 2 additions & 0 deletions src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export enum EntityType {
Number = 'number',
Sensor = 'sensor',
Switch = 'switch',
Text = 'text',
}

export enum EntityFilterType {
Expand Down Expand Up @@ -85,6 +86,7 @@ export enum NodeType {
Number = 'ha-number',
Sensor = 'ha-sensor',
Switch = 'ha-switch',
Text = 'ha-text',
UpdateConfig = 'ha-update-config',
}

Expand Down
2 changes: 2 additions & 0 deletions src/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import RenderTemplateEditor from './nodes/render-template/editor';
import SensorEditor from './nodes/sensor/editor';
import SwitchEditor from './nodes/switch/editor';
import TagEditor from './nodes/tag/editor';
import TextEditor from './nodes/text/editor';
import TimeEditor from './nodes/time/editor';
import TriggerStateEditor from './nodes/trigger-state/editor';
import UpdateConfigEditor from './nodes/update-config/editor';
Expand Down Expand Up @@ -93,4 +94,5 @@ RED.nodes.registerType(NodeType.Button, ButtonEditor);
RED.nodes.registerType(NodeType.Number, NumberEditor);
RED.nodes.registerType(NodeType.Sensor, SensorEditor);
RED.nodes.registerType(NodeType.Switch, SwitchEditor);
RED.nodes.registerType(NodeType.Text, TextEditor);
RED.nodes.registerType(NodeType.UpdateConfig, UpdateConfigEditor);
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import renderTemplateNode from './nodes/render-template';
import sensorNode from './nodes/sensor';
import switchNode from './nodes/switch';
import tagNode from './nodes/tag';
import textNode from './nodes/text';
import timeNode from './nodes/time';
import triggerStateNode from './nodes/trigger-state';
import updateConfigNode from './nodes/update-config';
Expand Down Expand Up @@ -64,6 +65,7 @@ const nodes: Record<NodeType, any> = {
[NodeType.Number]: numberNode,
[NodeType.Sensor]: sensorNode,
[NodeType.Switch]: switchNode,
[NodeType.Text]: textNode,
[NodeType.UpdateConfig]: updateConfigNode,
};

Expand Down
4 changes: 4 additions & 0 deletions src/nodes/entity-config/editor.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@
value="switch"
data-i18n="ha-entity-config.label.type_options.switch"
></option>
<option
value="text"
data-i18n="ha-entity-config.label.type_options.text"
></option>
</select>
</div>

Expand Down
2 changes: 2 additions & 0 deletions src/nodes/entity-config/editor/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import button from './button';
import number from './number';
import sensor from './sensor';
import switchData from './switch';
import text from './text';

export const defaultHaConfigOptions = [
{ id: 'name', type: 'string' },
Expand All @@ -21,4 +22,5 @@ export const haConfigOptions = {
number,
sensor,
switch: switchData,
text,
};
19 changes: 19 additions & 0 deletions src/nodes/entity-config/editor/data/text.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export default [
{
id: 'mode',
type: 'select',
values: ['text', 'password'],
},
{
id: 'min_length',
type: 'number',
},
{
id: 'max_length',
type: 'number',
},
{
id: 'pattern',
type: 'string',
},
];
3 changes: 2 additions & 1 deletion src/nodes/entity-config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ export default function entityConfigNode(
}
case EntityType.Button:
case EntityType.Number:
case EntityType.Switch: {
case EntityType.Switch:
case EntityType.Text: {
this.integration = new BidirectionalIntegration(props);
break;
}
Expand Down
92 changes: 92 additions & 0 deletions src/nodes/text/TextController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import InputOutputController, {
InputProperties,
} from '../../common/controllers/InputOutputController';
import InputError from '../../common/errors/InputError';
import NoConnectionError from '../../common/errors/NoConnectionError';
import BidirectionalEntityIntegration, {
StateChangePayload,
} from '../../common/integration/BidirectionalEntityIntegration';
import { EntityBaseNodeProperties, OutputProperty } from '../../types/nodes';
import { TextNode } from '.';

export interface TextNodeProperties extends EntityBaseNodeProperties {
state: string;
stateType: string;
outputProperties: OutputProperty[];
}

export default class TextController extends InputOutputController<
TextNode,
TextNodeProperties
> {
protected integration?: BidirectionalEntityIntegration;

protected async onInput({
done,
message,
parsedMessage,
send,
}: InputProperties) {
if (!this.integration?.isConnected) {
throw new NoConnectionError();
}
if (!this.integration?.isIntegrationLoaded) {
throw new InputError(
'home-assistant.error.integration_not_loaded',
'home-assistant.error.error'
);
}

const state = this.typedInputService.getValue(
parsedMessage.state.value,
parsedMessage.stateType.value,
{
message,
}
);

if (this.#validState(state) === false) {
throw new InputError(
'home-assistant.error.pattern_not_matched',
'home-assistant.error.error'
);
}

this.state?.setLastPayload({ state, attributes: {} });
await this.integration.updateHomeAssistant(state);

this.status.setSuccess(state.toString());
this.setCustomOutputs(this.node.config.outputProperties, message, {
config: this.node.config,
entityState: state,
});

send(message);
done();
}

public async onValueChange(payload: StateChangePayload) {
if (typeof payload.state !== 'string') return;

if (this.#validState(payload.state)) {
this.state?.setLastPayload({
state: payload.state,
attributes: {},
});
}
}

#validState(text: string): boolean {
const haConfig =
this.integration?.getEntityConfigNode().config.haConfig;
const pattern = haConfig?.find((item) => item.property === 'pattern')
?.value as string;

if (pattern) {
const regex = new RegExp(pattern);
return regex.test(text);
}

return true;
}
}
25 changes: 25 additions & 0 deletions src/nodes/text/editor.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<input type="hidden" id="node-input-version" />

<div class="form-row">
<label for="node-input-name">
<span data-i18n="home-assistant.label.name"></span>
</label>
<input type="text" id="node-input-name" placeholder="Name" />
</div>

<div class="form-row">
<label for="node-input-entityConfig">
<span data-i18n="home-assistant.label.entity_config"></span>
</label>
<input type="text" id="node-input-entityConfig" />
</div>

<div class="form-row">
<label for="node-input-text">
<span data-i18n="ha-text.label.text"></span>
</label>
<input type="text" id="node-input-state" style="width: 70%" />
<input type="hidden" id="node-input-stateType" />
</div>

<div class="form-row"><ol id="custom-outputs"></ol></div>
Loading

0 comments on commit 2474a44

Please sign in to comment.