diff --git a/docs/node/images/sentence_dynamic_example_01.png b/docs/node/images/sentence_dynamic_example_01.png
new file mode 100644
index 0000000000..b592f9e705
Binary files /dev/null and b/docs/node/images/sentence_dynamic_example_01.png differ
diff --git a/docs/node/images/sentence_dynamic_example_02.png b/docs/node/images/sentence_dynamic_example_02.png
new file mode 100644
index 0000000000..32006c26b5
Binary files /dev/null and b/docs/node/images/sentence_dynamic_example_02.png differ
diff --git a/docs/node/sentence.md b/docs/node/sentence.md
index 0ba5638c9d..74df7cab67 100644
--- a/docs/node/sentence.md
+++ b/docs/node/sentence.md
@@ -1,47 +1,125 @@
# Sentence
-The Sentence node triggers when the Home Assistant [Assist](https://www.home-assistant.io/voice_control/) feature matches a sentence from a voice assistant using the default [conversation agent](https://www.home-assistant.io/integrations/conversation/). This node is used for voice control integrations, allowing specific voice commands to trigger automations within Node-RED.
+The **Sentence** node is triggered when the Home Assistant [Assist](https://www.home-assistant.io/voice_control/) feature matches a sentence from a voice assistant using the default [conversation agent](https://www.home-assistant.io/integrations/conversation/). This node enables voice control integrations, allowing specific voice commands to trigger automations within Node-RED.
-Sentences are allowed to use some basic template syntax. Check Home Assistant [documentation](https://www.home-assistant.io/docs/automation/trigger/#sentence-trigger) for more information.
+Sentences support basic template syntax. Refer to the Home Assistant [documentation](https://www.home-assistant.io/docs/automation/trigger/#sentence-trigger) for more details.
::: warning
-_Needs [Custom Integration](https://github.com/zachowj/hass-node-red) installed
-in Home Assistant for this node to function_
+This node requires the [Custom Integration](https://github.com/zachowj/hass-node-red) to be installed in Home Assistant.
:::
-## Configuration
+## Configuration Options
+
+### Mode
+
+- **Type**: `string`
+- **Options**:
+ - `trigger`
+ - `response`
+
+Defines the node’s function.
+If set to **trigger**, the node activates when a sentence is matched.
+If set to **response**, the node sends a dynamic response back to Home Assistant.
### Sentences
-- Type: `string`
+- **Type**: `string`
+
+A list of sentences to match. Supports basic template syntax. For more details, see the Home Assistant [documentation](https://www.home-assistant.io/docs/automation/trigger/#sentence-trigger).
+
+### Response Type
+
+- **Type**: `string`
+- **Options**:
+ - `dynamic`
+ - `fixed`
+
+Specifies the type of response sent to Home Assistant:
+
+- **Fixed**: The same response is sent for all matched sentences.
+- **Dynamic**: The response is determined by the node set to response mode.
+
+### Response
+
+- **Type**: `string`
+
+The message sent to Home Assistant when a sentence is matched. This option is used only if the **Response Type** is set to `fixed`.
+
+### Fallback Response
+
+- **Type**: `string`
+
+The message sent to Home Assistant when a timeout occurs. This is used when the **Response Type** is set to `dynamic`.
+
+### Response Timeout
+
+- **Type**: `number`
+
+Specifies the time in milliseconds to wait for a response before sending the **Fallback Response**. This option is used only when the **Response Type** is set to `dynamic`.
-A list of sentences to match. Sentences are allowed to use some basic template syntax. Check Home Assistant [documentation](https://www.home-assistant.io/docs/automation/trigger/#sentence-trigger) for more information.
+### Expose As
-### Expose as
+- **Type**: `string`
-- Type: `string`
+Select an entity to create a switch in Home Assistant. Turning the switch on or off will enable or disable this node in Node-RED.
-When an entity is selected a switch entity will be created in Home Assistant. Turning on and off this switch will disable/enable the nodes in Node-RED.
+## Inputs
+
+All properties must be provided in `msg.payload`.
+
+Example input:
+
+```json
+{
+ "response": "The light is now on"
+}
+```
+
+### Response
+
+- **Type**: `string`
+
+The response sent to Home Assistant when a sentence is matched. This is used only when the **Response Type** is set to `dynamic`.
## Outputs
Value types:
-- `trigger id`: sentence that triggered the flow
-- `config`: config properties of the node
+- `trigger id`: The sentence that triggered the flow.
+- `config`: The node's configuration properties.
## Examples
-[link](https://zachowj.github.io/node-red-contrib-home-assistant-websocket/node/sentence.html#examples)
+For detailed usage examples, see the [Examples](https://zachowj.github.io/node-red-contrib-home-assistant-websocket/node/sentence.html#examples) section.
+### Basic Usage
+
![screenshot](./images/sentence_01.png)
@[code](@examples/node/sentence/sentence_usage.json)
+### Dynamic Response Examples
+
+#### Example 1: Count Lights On and Off
+
+This example shows how to use the Sentence node to provide a dynamic response based on the number of lights that are on or off in the house. It also turns on any lights that were off.
+
+![screenshot](./images/sentence_dynamic_example_01.png)
+
+@[code](@examples/node/sentence/sentence_dynamic_response_01.json)
+
+#### Example 2: Check Garage Sensor
+
+This example checks the status of a binary sensor to determine whether the car is in the garage. If the car is present, a response is sent confirming its location. If the car is not present, the response states that the car is not in the garage.
+
+![screenshot](./images/sentence_dynamic_example_02.png)
+
+@[code](@examples/node/sentence/sentence_dynamic_response_02.json)
+
diff --git a/examples/node/sentence/sentence_dynamic_response_01.json b/examples/node/sentence/sentence_dynamic_response_01.json
new file mode 100644
index 0000000000..52b114e1fb
--- /dev/null
+++ b/examples/node/sentence/sentence_dynamic_response_01.json
@@ -0,0 +1 @@
+[{"id":"5b44e487c5795df9","type":"ha-get-entities","z":"01b6599a0739a237","name":"lights off count","server":"","version":1,"rules":[{"condition":"state_object","property":"entity_id","logic":"starts_with","value":"light.","valueType":"str"},{"condition":"state_object","property":"state","logic":"is","value":"off","valueType":"str"}],"outputType":"array","outputEmptyResults":true,"outputLocationType":"msg","outputLocation":"lightsOff","outputResultsCount":1,"x":312,"y":800,"wires":[["99c90d5fd17832c9","7c588d1510201532"]]},{"id":"99c90d5fd17832c9","type":"ha-get-entities","z":"01b6599a0739a237","name":"lights on count","server":"","version":1,"rules":[{"condition":"state_object","property":"entity_id","logic":"starts_with","value":"light.","valueType":"str"},{"condition":"state_object","property":"state","logic":"is","value":"on","valueType":"str"}],"outputType":"count","outputEmptyResults":false,"outputLocationType":"msg","outputLocation":"lightsOnCount","outputResultsCount":1,"x":504,"y":800,"wires":[["f6e701c97c731979"]]},{"id":"09cb88520209e712","type":"inject","z":"01b6599a0739a237","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":132,"y":752,"wires":[["0b2ef68df13c5b94"]]},{"id":"0b2ef68df13c5b94","type":"api-call-service","z":"01b6599a0739a237","name":"","server":"","version":7,"debugenabled":false,"action":"conversation.process","floorId":[],"areaId":[],"deviceId":[],"entityId":[],"labelId":[],"data":"{\"text\": \"let there be light\"}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"results"}],"queue":"none","blockInputOverrides":true,"domain":"conversation","service":"process","x":332,"y":752,"wires":[["edfa883350eca925"]]},{"id":"edfa883350eca925","type":"debug","z":"01b6599a0739a237","name":"output","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload.response.speech.plain.speech","targetType":"msg","statusVal":"","statusType":"auto","x":686,"y":752,"wires":[]},{"id":"b389cf7e343fc938","type":"api-call-service","z":"01b6599a0739a237","name":"","server":"","version":7,"debugenabled":false,"action":"light.turn_on","floorId":[],"areaId":[],"deviceId":[],"entityId":[],"labelId":[],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","blockInputOverrides":true,"domain":"light","service":"turn_on","x":890,"y":848,"wires":[[]]},{"id":"28191039a0c43c00","type":"change","z":"01b6599a0739a237","name":"build entity list","rules":[{"t":"set","p":"payload","pt":"msg","to":"{ \"target\": { \"entity_id\": lightsOff.entity_id}}","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":710,"y":848,"wires":[["b389cf7e343fc938"]]},{"id":"794a0664ef0e9ab0","type":"ha-sentence","z":"01b6599a0739a237","name":"let there be light","server":"5d205f70b1e41e9f","version":2,"inputs":0,"outputs":1,"exposeAsEntityConfig":"","mode":"trigger","sentences":["let there be light"],"response":"Something took to long","responseType":"jsonata","triggerResponseType":"dynamic","responseTimeout":1000,"outputProperties":[{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"},{"property":"payload","propertyType":"msg","value":"","valueType":"triggerId"}],"x":128,"y":800,"wires":[["5b44e487c5795df9"]]},{"id":"f6e701c97c731979","type":"ha-sentence","z":"01b6599a0739a237","name":"","server":"5d205f70b1e41e9f","version":2,"inputs":1,"outputs":1,"exposeAsEntityConfig":"","mode":"response","sentences":[],"response":"lightsOnCount & \" lights were already on turning on \" & $count(lightsOffCount) & \" lights that were off\"\t","responseType":"jsonata","triggerResponseType":"fixed","responseTimeout":1000,"outputProperties":[],"x":692,"y":800,"wires":[[]]},{"id":"7c588d1510201532","type":"switch","z":"01b6599a0739a237","name":"any lights off?","property":"lightsOff.length","propertyType":"msg","rules":[{"t":"gt","v":"0","vt":"num"}],"checkall":"true","repair":false,"outputs":1,"x":506,"y":848,"wires":[["28191039a0c43c00"]]}]
diff --git a/examples/node/sentence/sentence_dynamic_response_02.json b/examples/node/sentence/sentence_dynamic_response_02.json
new file mode 100644
index 0000000000..99c7cdfcde
--- /dev/null
+++ b/examples/node/sentence/sentence_dynamic_response_02.json
@@ -0,0 +1 @@
+[{"id":"d140cd86c26cbbe5","type":"inject","z":"01b6599a0739a237","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":204,"y":960,"wires":[["c18458aa0fda5201"]]},{"id":"c18458aa0fda5201","type":"api-call-service","z":"01b6599a0739a237","name":"is the car in the garage","server":"","version":7,"debugenabled":false,"action":"conversation.process","floorId":[],"areaId":[],"deviceId":[],"entityId":[],"labelId":[],"data":"{\"text\": \"is the car in the garage\"}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"results"}],"queue":"none","blockInputOverrides":true,"domain":"conversation","service":"process","x":462,"y":960,"wires":[["080f0fda9ce2ebc9"]]},{"id":"080f0fda9ce2ebc9","type":"debug","z":"01b6599a0739a237","name":"output","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload.response.speech.plain.speech","targetType":"msg","statusVal":"","statusType":"auto","x":674,"y":960,"wires":[]},{"id":"aac0b36dca0c47b4","type":"ha-sentence","z":"01b6599a0739a237","name":"is the car in the garage","server":"5d205f70b1e41e9f","version":2,"inputs":0,"outputs":1,"exposeAsEntityConfig":"","mode":"trigger","sentences":["is the car in the garage"],"response":"Something took to long","responseType":"jsonata","triggerResponseType":"dynamic","responseTimeout":1000,"outputProperties":[{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"},{"property":"payload","propertyType":"msg","value":"","valueType":"triggerId"}],"x":220,"y":1008,"wires":[["6a2a0e27afac616e"]]},{"id":"2dd86795094ba553","type":"ha-sentence","z":"01b6599a0739a237","name":"","server":"5d205f70b1e41e9f","version":2,"inputs":1,"outputs":1,"exposeAsEntityConfig":"","mode":"response","sentences":[],"response":"payload","responseType":"msg","triggerResponseType":"fixed","responseTimeout":1000,"outputProperties":[],"x":940,"y":1008,"wires":[[]]},{"id":"6a2a0e27afac616e","type":"api-current-state","z":"01b6599a0739a237","name":"","server":"","version":3,"outputs":2,"halt_if":"on","halt_if_type":"str","halt_if_compare":"is","entity_id":"binary_sensor.car_in_garage","state_type":"str","blockInputOverrides":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"}],"for":"0","forType":"num","forUnits":"minutes","override_topic":false,"state_location":"payload","override_payload":"msg","entity_location":"data","override_data":"msg","x":522,"y":1008,"wires":[["fa724a7fe743faaa"],["30171db069e062f0"]]},{"id":"fa724a7fe743faaa","type":"change","z":"01b6599a0739a237","name":"yes","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"response\": \"Yes the car is in the garage\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":770,"y":1008,"wires":[["2dd86795094ba553"]]},{"id":"30171db069e062f0","type":"change","z":"01b6599a0739a237","name":"no","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"response\": \"No the car is not in the garage\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":772,"y":1056,"wires":[["2dd86795094ba553"]]},{"id":"b4d9680db1056a02","type":"inject","z":"01b6599a0739a237","name":"set car in garage","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":224,"y":1056,"wires":[["0c940aece619684d"]]},{"id":"0c940aece619684d","type":"ha-binary-sensor","z":"01b6599a0739a237","name":"","entityConfig":"c9ce588e6f25ce5d","version":0,"state":"payload","stateType":"msg","attributes":[],"inputOverride":"allow","outputProperties":[],"x":496,"y":1056,"wires":[[]]},{"id":"763df26767e56a92","type":"inject","z":"01b6599a0739a237","name":"remove car from garage","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"false","payloadType":"bool","x":254,"y":1104,"wires":[["0c940aece619684d"]]},{"id":"c9ce588e6f25ce5d","type":"ha-entity-config","server":"5d205f70b1e41e9f","deviceConfig":"","name":"car in garage","version":"6","entityType":"binary_sensor","haConfig":[{"property":"name","value":"car in garage"},{"property":"icon","value":""},{"property":"entity_picture","value":""},{"property":"entity_category","value":""},{"property":"device_class","value":""}],"resend":true,"debugEnabled":false}]
diff --git a/examples/node/sentence/sentence_usage.json b/examples/node/sentence/sentence_usage.json
index 2b14a3a770..e154678d15 100644
--- a/examples/node/sentence/sentence_usage.json
+++ b/examples/node/sentence/sentence_usage.json
@@ -1 +1 @@
-[{"id":"2f10cb78ab2f486f","type":"debug","z":"39a6dba4b503e011","name":"debug","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload","statusType":"msg","x":382,"y":384,"wires":[]},{"id":"5c563c29d19f4aab","type":"ha-sentence","z":"39a6dba4b503e011","name":"","server":"bf5874816710d0c7","version":0,"outputs":1,"sentences":["[it's ]party time","happy (new year|birthday)","hello world"],"outputProperties":[{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"},{"property":"payload","propertyType":"msg","value":"","valueType":"triggerId"},{"property":"sentences","propertyType":"msg","value":"sentences","valueType":"config"}],"x":176,"y":384,"wires":[["2f10cb78ab2f486f"]]},{"id":"4868b36e0e57db4f","type":"api-call-service","z":"39a6dba4b503e011","name":"process sentence","server":"","version":5,"debugenabled":false,"domain":"conversation","service":"process","areaId":[],"deviceId":[],"entityId":[],"data":" {\"text\": payload}\t","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":422,"y":336,"wires":[[]]},{"id":"0bd711e6ab321cf9","type":"inject","z":"39a6dba4b503e011","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"happy new year","payloadType":"str","x":200,"y":336,"wires":[["4868b36e0e57db4f"]]},{"id":"7b19f1bcd79bf7ea","type":"inject","z":"39a6dba4b503e011","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"happy birthday","payloadType":"str","x":204,"y":288,"wires":[["4868b36e0e57db4f"]]},{"id":"fdec3314ea56130d","type":"inject","z":"39a6dba4b503e011","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"party time","payloadType":"str","x":184,"y":240,"wires":[["4868b36e0e57db4f"]]},{"id":"92aa9c7e046ebba3","type":"inject","z":"39a6dba4b503e011","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"it's party time","payloadType":"str","x":198,"y":192,"wires":[["4868b36e0e57db4f"]]},{"id":"4c1ecc1396d9eddf","type":"inject","z":"39a6dba4b503e011","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"hello world","payloadType":"str","x":188,"y":144,"wires":[["4868b36e0e57db4f"]]}]
+[{"id":"2f10cb78ab2f486f","type":"debug","z":"01b6599a0739a237","name":"debug","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload","statusType":"msg","x":418,"y":1136,"wires":[]},{"id":"5c563c29d19f4aab","type":"ha-sentence","z":"01b6599a0739a237","name":"","server":"5d205f70b1e41e9f","version":2,"inputs":0,"outputs":1,"exposeAsEntityConfig":"","mode":"trigger","sentences":["[it's ]party time","happy (new year|birthday)","hello world"],"response":"","responseType":"fixed","responseTimeout":1000,"outputProperties":[{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"},{"property":"payload","propertyType":"msg","value":"","valueType":"triggerId"},{"property":"sentences","propertyType":"msg","value":"sentences","valueType":"config"}],"x":212,"y":1136,"wires":[["2f10cb78ab2f486f"]]},{"id":"4868b36e0e57db4f","type":"api-call-service","z":"01b6599a0739a237","name":"process sentence","server":"","version":7,"debugenabled":false,"action":"conversation.process","floorId":[],"areaId":[],"deviceId":[],"entityId":[],"labelId":[],"data":" {\"text\": payload}\t","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","blockInputOverrides":false,"domain":"conversation","service":"process","x":458,"y":1088,"wires":[[]]},{"id":"0bd711e6ab321cf9","type":"inject","z":"01b6599a0739a237","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"happy new year","payloadType":"str","x":236,"y":1088,"wires":[["4868b36e0e57db4f"]]},{"id":"7b19f1bcd79bf7ea","type":"inject","z":"01b6599a0739a237","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"happy birthday","payloadType":"str","x":240,"y":1040,"wires":[["4868b36e0e57db4f"]]},{"id":"fdec3314ea56130d","type":"inject","z":"01b6599a0739a237","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"party time","payloadType":"str","x":220,"y":992,"wires":[["4868b36e0e57db4f"]]},{"id":"92aa9c7e046ebba3","type":"inject","z":"01b6599a0739a237","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"it's party time","payloadType":"str","x":234,"y":944,"wires":[["4868b36e0e57db4f"]]},{"id":"4c1ecc1396d9eddf","type":"inject","z":"01b6599a0739a237","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"hello world","payloadType":"str","x":224,"y":896,"wires":[["4868b36e0e57db4f"]]}]
diff --git a/src/common/controllers/helpers.ts b/src/common/controllers/helpers.ts
index bbb2379bb1..ca2405d13a 100644
--- a/src/common/controllers/helpers.ts
+++ b/src/common/controllers/helpers.ts
@@ -64,6 +64,7 @@ export function createExposeAsControllerDependences({
return {
...controllerDeps,
+ clientEvents,
exposeAsConfigNode,
status,
};
diff --git a/src/common/integration/Integration.ts b/src/common/integration/Integration.ts
index 28b60672a1..51d7fc1f76 100644
--- a/src/common/integration/Integration.ts
+++ b/src/common/integration/Integration.ts
@@ -25,6 +25,7 @@ export enum MessageType {
Entity = 'nodered/entity',
RemoveDevice = 'nodered/device/remove',
SentenceTrigger = 'nodered/sentence',
+ SentenceResponse = 'nodered/sentence_response',
UpdateConfig = 'nodered/entity/update_config',
Webhook = 'nodered/webhook',
}
diff --git a/src/const.ts b/src/const.ts
index f4e68fd0a6..879fcffa23 100644
--- a/src/const.ts
+++ b/src/const.ts
@@ -133,6 +133,7 @@ export enum TypedInputTypes {
PreviousValue = 'previousValue',
CalendarItem = 'calendarItem',
DeviceId = 'deviceId',
+ Enviroment = 'env',
}
export enum TimeUnit {
diff --git a/src/editor/exposenode.ts b/src/editor/exposenode.ts
index 1e9dba274c..72a4a9b46d 100644
--- a/src/editor/exposenode.ts
+++ b/src/editor/exposenode.ts
@@ -1,6 +1,7 @@
import { EditorRED } from 'node-red';
import { NO_VERSION, NodeType, ValueIntegrationMode } from '../const';
+import { SentenceMode } from '../nodes/sentence/const';
import { HassExposedConfig, HassNodeProperties } from './types';
import * as haUtils from './utils';
@@ -88,6 +89,14 @@ export function init(n: HassNodeProperties) {
renderAlert(type);
break;
case NodeType.Sentence:
+ {
+ const mode = $('#node-input-mode').val() as SentenceMode;
+ $('#exposed-as-row').toggle(mode === SentenceMode.Trigger);
+ if (!isAddNodeSelected('server')) {
+ renderAlert(type);
+ }
+ }
+ break;
case NodeType.Webhook:
if (!isAddNodeSelected('server')) {
renderAlert(type);
@@ -236,7 +245,7 @@ const NodeMinIntegraionVersion = {
[NodeType.Device]: '4.0.2',
[NodeType.Number]: '1.3.0',
[NodeType.Select]: '1.4.0',
- [NodeType.Sentence]: '2.2.0',
+ [NodeType.Sentence]: '4.1.0',
[NodeType.Sensor]: '1.1.0',
[NodeType.Switch]: '1.1.0',
[NodeType.Tag]: '0.5.0',
diff --git a/src/nodes/sentence/SentenceInputController.ts b/src/nodes/sentence/SentenceInputController.ts
new file mode 100644
index 0000000000..7d7eea5c42
--- /dev/null
+++ b/src/nodes/sentence/SentenceInputController.ts
@@ -0,0 +1,60 @@
+import InputOutputController, {
+ InputProperties,
+} from '../../common/controllers/InputOutputController';
+import InputError from '../../common/errors/InputError';
+import NoConnectionError from '../../common/errors/NoConnectionError';
+import { MessageType } from '../../common/integration/Integration';
+import { DataSource } from '../../common/services/InputService';
+import { SentenceNode, SentenceNodeProperties } from '.';
+
+export default class SentenceController extends InputOutputController<
+ SentenceNode,
+ SentenceNodeProperties
+> {
+ public async onInput({
+ done,
+ message,
+ parsedMessage,
+ send,
+ }: InputProperties): Promise {
+ if (!this.integration?.isConnected) {
+ throw new NoConnectionError();
+ }
+ if (!this.integration?.isIntegrationLoaded) {
+ throw new InputError(
+ 'home-assistant.error.integration_not_loaded',
+ 'home-assistant.status.error',
+ );
+ }
+
+ const response =
+ parsedMessage.response.source === DataSource.Config
+ ? await this.typedInputService.getValue(
+ parsedMessage.response.value,
+ parsedMessage.responseType.value,
+ {
+ message,
+ },
+ )
+ : parsedMessage.response.value;
+
+ this.status.setSending();
+
+ await this.homeAssistant.websocket.send({
+ type: MessageType.SentenceResponse,
+ response,
+ response_id: parsedMessage.responseId.value,
+ });
+
+ await this.setCustomOutputs(
+ this.node.config.outputProperties,
+ message,
+ {
+ config: this.node.config,
+ },
+ );
+ this.status.setSuccess();
+ send(message);
+ done();
+ }
+}
diff --git a/src/nodes/sentence/SentenceIntegration.ts b/src/nodes/sentence/SentenceIntegration.ts
index c253129dad..48b725c38b 100644
--- a/src/nodes/sentence/SentenceIntegration.ts
+++ b/src/nodes/sentence/SentenceIntegration.ts
@@ -3,25 +3,34 @@ import BidirectionalIntegration, {
} from '../../common/integration/BidrectionalIntegration';
import { MessageType } from '../../common/integration/Integration';
import { SentenceNode } from '.';
+import { SentenceResponseType } from './const';
export interface SentenceDiscoveryPayload extends DiscoveryMessage {
type: MessageType.SentenceTrigger;
sentences: string[];
response?: string;
+ response_type?: SentenceResponseType;
+ response_timeout?: number;
}
export default class SentenceIntegration extends BidirectionalIntegration {
protected getDiscoveryPayload(): SentenceDiscoveryPayload {
const response =
- this.node.config.response === ''
+ this.node.config.triggerResponse === ''
? undefined
- : this.node.config.response;
+ : this.node.config.triggerResponse;
+
+ // set the timeout to 1000 milliseconds if not set and convert to seconds
+ const timeout = (this.node.config.responseTimeout ?? 1000) / 1000;
+
return {
type: MessageType.SentenceTrigger,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
server_id: this.node.config.server!,
sentences: this.node.config.sentences,
response,
+ response_type: this.node.config.triggerResponseType,
+ response_timeout: timeout,
};
}
}
diff --git a/src/nodes/sentence/SentenceController.ts b/src/nodes/sentence/SentenceOutputController.ts
similarity index 88%
rename from src/nodes/sentence/SentenceController.ts
rename to src/nodes/sentence/SentenceOutputController.ts
index e9a67752cd..b9abd5659b 100644
--- a/src/nodes/sentence/SentenceController.ts
+++ b/src/nodes/sentence/SentenceOutputController.ts
@@ -8,10 +8,11 @@ interface SentenceResponse {
sentence: string;
result: Record | null;
deviceId: string | null;
+ responseId: number;
}
const ExposeAsController = ExposeAsMixin(OutputController);
-export default class SentenseController extends ExposeAsController {
+export default class SentenceController extends ExposeAsController {
public async onReceivedMessage(data: SentenceResponse) {
if (!this.isEnabled) return;
@@ -26,6 +27,7 @@ export default class SentenseController extends ExposeAsController {
[TypedInputTypes.DeviceId]: data.deviceId,
},
);
+ message._sentence_response_id = data.responseId;
this.status.setSuccess('home-assistant.status.triggered');
this.node.send(message);
}
diff --git a/src/nodes/sentence/const.ts b/src/nodes/sentence/const.ts
new file mode 100644
index 0000000000..40f9cb99f1
--- /dev/null
+++ b/src/nodes/sentence/const.ts
@@ -0,0 +1,9 @@
+export enum SentenceMode {
+ Trigger = 'trigger',
+ Response = 'response',
+}
+
+export enum SentenceResponseType {
+ Dynamic = 'dynamic',
+ Fixed = 'fixed',
+}
diff --git a/src/nodes/sentence/editor.html b/src/nodes/sentence/editor.html
index 65eb657a81..c269ae03db 100644
--- a/src/nodes/sentence/editor.html
+++ b/src/nodes/sentence/editor.html
@@ -1,4 +1,5 @@
+