Alexa Speech Parser

This flow simplifies the tasks of passing entity details for Alexa to speak using the brilliant Alexa speech module. It will also enable Alexa to speak to any entity or attribute value within Home assistant by just asking.

You will need to amend the flow . I have included 4 of my live flows in this export You will need to change the entity names to match your .

[{"id":"5fae6fac80277c62","type":"subflow","name":"V Bulb","info":"","category":"","in":[],"out":[{"x":560,"y":80,"wires":[{"id":"c97269f6368eee2c","port":0}]}],"env":[],"meta":{},"color":"#DDAA99"},{"id":"acb98115438430a8","type":"server-state-changed","z":"5fae6fac80277c62","name":"bulb","server":"","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"light.alexa_virtual","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"on","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":false,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":190,"y":80,"wires":[["72443bb146b4ab28"],[]]},{"id":"72443bb146b4ab28","type":"function","z":"5fae6fac80277c62","name":"Bri to %","func":"var bri = 0\nbri = msg.data.new_state.attributes.brightness\nbri = Math.floor((bri/255)*100)\nmsg.bri = bri\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":320,"y":80,"wires":[["c97269f6368eee2c"]]},{"id":"c97269f6368eee2c","type":"api-call-service","z":"5fae6fac80277c62","name":"bulb off","server":"","version":5,"debugenabled":false,"domain":"light","service":"turn_off","areaId":[],"deviceId":[],"entityId":["light.alexa_virtual"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":460,"y":80,"wires":[[]]},{"id":"5d90fc6d2f25bffb","type":"subflow","name":"Parse","info":"","category":"","in":[{"x":40,"y":80,"wires":[{"id":"c24f5a20d235d692"}]}],"out":[{"x":660,"y":80,"wires":[{"id":"bdb72ceedb5edf33","port":0}]}],"env":[],"meta":{},"color":"#DDAA99"},{"id":"eac6d966e220943e","type":"function","z":"5d90fc6d2f25bffb","name":"Parse ","func":"// loop round each entity from msg.payload\nvar entity = msg.payload\nentity.forEach(function (it, ix) \n{\n    const dc = []\n    // Add all device classes  sorted off/on\n    dc[\"vibration\"] = [\n        [\"no vibration\"],\n        [\"vibration detected\"]\n    ]\n    dc[\"moisture\"] = [\n        [\"dry\"],\n        [\"wet\"]\n    ]\n    dc[\"battery\"] = [\n        [\"normal\"],\n        [\"low\"]\n    ]\n    dc[\"cold\"] = [\n        [\"normal\"],\n        [\"cold\"]\n    ]\n    dc[\"light\"] = [\n        [\"off\"],\n        [\"on\"]\n    ]\n    dc[\"motion\"] = [\n        [\"clear\"],\n        [\"detected\"]\n    ]\n    dc[\"moving\"] = [\n        [\"stopped\"],\n        [\"moving\"]\n    ]\n    dc[\"plug\"] = [\n        [\"unplugged\"],\n        [\"plugged in\"]\n    ]\n    dc[\"power\"] = [\n        [\"no power\"],\n        [\"detected\"]\n    ]\n    dc[\"battery_charging\"] = [\n        [\"not charging\"],\n        [\"charging\"]\n    ]\n    dc[\"alarm\"] = [\n        [\"disarm\"],\n        [\"armed\"]\n    ]\n    dc[\"door\"] = [\n        [\"closed\"],\n        [\"open\"]    \n    ]\n    // set vars\n    var c = new Date(entity[ix].last_changed)\n    c = c.toDateString()\n    var u = new Date(entity[ix].last_updated)\n    u = u.toDateString()\n    entity[ix].c = c\n    entity[ix].u = u\n    entity[ix].a = entity[ix].attributes\n    entity[ix].a.device_class = entity[ix].attributes.device_class\n    entity[ix].r = entity[ix].state\n    entity[ix].o = 0\n// check if number\n    if (isNaN(entity[ix].r) == false)\n    {\n      //if number make whole number\n      entity[ix].o = Math.round(entity[ix].r)\n    }\n    entity[ix].s = entity[ix].state\n    entity[ix].e = entity[ix].entity_id\n    entity[ix].f = entity[ix].attributes.friendly_name\n    // test state convert \"no\" \"yes\"   to 0 or 1 \n    var t_on ='on'\n    var t_off = 'off'\n    var x = 0\n    if (entity[ix].r == t_off) { x  = 0}\n    if (entity[ix].r == t_on ) { x  = 1}\n    // check if device class exists and array created for device class\n    if (entity[ix].a.device_class !== undefined)//\n    {\n        if (dc[entity[ix].a.device_class] !== undefined)\n        {\n            entity[ix].s = dc[entity[ix].a.device_class][x].toString() \n        }\n    }\n    // delete all values not needed \n    delete entity[ix].state\n    delete entity[ix].attributes\n    delete entity[ix].context\n    delete entity[ix].last_changed\n    delete entity[ix].last_updated\n    delete entity[ix].entity_id \n})\n   // message out \nmsg.payload = entity\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":290,"y":80,"wires":[["bdb72ceedb5edf33"]]},{"id":"bdb72ceedb5edf33","type":"change","z":"5d90fc6d2f25bffb","name":"shorten msg","rules":[{"t":"move","p":"payload","pt":"msg","to":"e","tot":"msg"},{"t":"delete","p":"payload","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":430,"y":80,"wires":[["341f91d558551bef"]]},{"id":"c24f5a20d235d692","type":"ha-get-entities","z":"5d90fc6d2f25bffb","name":"","server":"","version":0,"rules":[{"property":"entity_id","logic":"includes","value":"entity_id","valueType":"msg"}],"output_type":"array","output_empty_results":true,"output_location_type":"msg","output_location":"payload","output_results_count":1,"x":150,"y":80,"wires":[["eac6d966e220943e"]]},{"id":"341f91d558551bef","type":"debug","z":"5d90fc6d2f25bffb","name":"","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"e","targetType":"msg","statusVal":"payload","statusType":"auto","x":570,"y":80,"wires":[]},{"id":"ddd76f64db4352b2","type":"tab","label":"TTS","disabled":false,"info":"","env":[]},{"id":"a2d01e1a597b9e64","type":"subflow:5d90fc6d2f25bffb","z":"ddd76f64db4352b2","name":"","x":550,"y":180,"wires":[["cd97e07b79990c0c"]]},{"id":"cd97e07b79990c0c","type":"template","z":"ddd76f64db4352b2","name":"Message","field":"e","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"the car is {{e.0.r}} % charged and has a range of {{e.1.r}} miles. , It is {{e.2.r}} and has a charging speed of {{e.3.r}} miles per hour ","output":"str","x":680,"y":180,"wires":[["b7d10c68937f105b"]]},{"id":"c1f44af659282ea4","type":"subflow:5fae6fac80277c62","z":"ddd76f64db4352b2","name":"V Bulb","x":150,"y":240,"wires":[["789ebc9603d4d390"]]},{"id":"fca999b9af5765df","type":"change","z":"ddd76f64db4352b2","name":"Corsa","rules":[{"t":"set","p":"entity_id","pt":"msg","to":"sensor.corsa_battery_level,sensor.corsa_battery_autonomy,sensor.corsa_charging_status,sensor.corsa_charging_speed","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":410,"y":180,"wires":[["a2d01e1a597b9e64"]]},{"id":"b7d10c68937f105b","type":"api-call-service","z":"ddd76f64db4352b2","name":"TTS","server":"","version":5,"debugenabled":false,"domain":"notify","service":"alexa_media","areaId":[],"deviceId":[],"entityId":[],"data":"{\"message\":\"{{e}}\",\"target\":\"{{states.sensor.last_alexa.state}}\",\"data\":{\"type\":\"tts\"}}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":850,"y":240,"wires":[[]]},{"id":"789ebc9603d4d390","type":"switch","z":"ddd76f64db4352b2","name":"Bri ?","property":"bri","propertyType":"msg","rules":[{"t":"eq","v":"1","vt":"str"},{"t":"eq","v":"2","vt":"str"},{"t":"eq","v":"3","vt":"str"},{"t":"eq","v":"4","vt":"str"}],"checkall":"true","repair":false,"outputs":4,"x":270,"y":240,"wires":[["fca999b9af5765df"],["981077259cdcd768"],["bf64eb5ad6fd996f"],["c692aa369ce9a9d0"]]},{"id":"d4c1d36973e35b82","type":"subflow:5d90fc6d2f25bffb","z":"ddd76f64db4352b2","name":"","x":550,"y":220,"wires":[["0218eaa46f7fb81b"]]},{"id":"0218eaa46f7fb81b","type":"template","z":"ddd76f64db4352b2","name":"Message","field":"e","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"The temperature in the living room is {{e.0.o}} °c.  In the bedroom the temperature is {{e.1.o}} °c","output":"str","x":680,"y":220,"wires":[["b7d10c68937f105b"]]},{"id":"981077259cdcd768","type":"change","z":"ddd76f64db4352b2","name":"Temp","rules":[{"t":"set","p":"entity_id","pt":"msg","to":"sensor.lr_temp_temperature,sensor.mb_temp_temperature","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":410,"y":220,"wires":[["d4c1d36973e35b82"]]},{"id":"c916b5c48ed5e6a8","type":"subflow:5d90fc6d2f25bffb","z":"ddd76f64db4352b2","name":"","x":550,"y":260,"wires":[["fe241a0c2ce84152"]]},{"id":"fe241a0c2ce84152","type":"template","z":"ddd76f64db4352b2","name":"Message","field":"e","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"The {{e.0.f}} is {{e.0.s}}. The {{e.1.f}} is {{e.1.s}}. The {{e.2.f}} is {{e.2.s}}","output":"str","x":680,"y":260,"wires":[["b7d10c68937f105b"]]},{"id":"bf64eb5ad6fd996f","type":"change","z":"ddd76f64db4352b2","name":"Door","rules":[{"t":"set","p":"entity_id","pt":"msg","to":"binary_sensor.back_door_contact,binary_sensor.front_door_contact,binary_sensor.patio_door_contact","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":410,"y":260,"wires":[["c916b5c48ed5e6a8"]]},{"id":"ba701c24956da976","type":"subflow:5d90fc6d2f25bffb","z":"ddd76f64db4352b2","name":"","x":550,"y":300,"wires":[["e8d110c0381a7733"]]},{"id":"e8d110c0381a7733","type":"template","z":"ddd76f64db4352b2","name":"Message","field":"e","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"At {{e.0.f}} there are {{e.0.r}} chargers currently available. ","output":"str","x":680,"y":300,"wires":[["b7d10c68937f105b"]]},{"id":"c692aa369ce9a9d0","type":"change","z":"ddd76f64db4352b2","name":"Podpoint","rules":[{"t":"set","p":"entity_id","pt":"msg","to":"sensor.tesco_bw","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":420,"y":300,"wires":[["ba701c24956da976"]]},{"id":"410d2f6e10346a5d","type":"comment","z":"ddd76f64db4352b2","name":"Speech Parser","info":"","x":180,"y":100,"wires":[]}]

I have used this technique in various versions of this code in Appdemon and HA automations. The code worked well but the lack of editing and storing permanent variables has proved cumbersome.

Nodered has resolved these problems with the template node. This allows me to embed the parsed code simply without having to resort to hacks using input_selects as storage.

To get Alexa to speak entity values normally requires complex Jinja2 templates to be created. Compare the following

The {{states.binary_sensor.patio_door_contact.attributes.friendly_name}} is {{states.binary_sensor.patio_door_contact.state|replace("off","closed")|replace("on","open")}

with

The {{e.0.f}} is {{e.0.s}}

Which outputs either the “patio door is open” or “patio door closed”

The code (e.0.s) will output the state of the entity with the correct device type without restoring to any code.

By using a virtual bulb, we can use the Alexa App to trigger the automation.So, we can do the following

" Alexa is the patio door open"

“The Patio door is closed”

This requires a virtual bulb The brightness levels are used to trigger the flow which in turn parses the text and outputs the results to Alexa to speak .

The parse uses the following syntax. All codes are separated by periods.

The first letter is always e (short for entity) .

The second character is always a number. This represents the entity array number. So if 3 entities need to be parsed the first entity is always zero.

The third character can be one of the following:-

s= State. This will return the state attribute and convert it to the correct device class. So a door will be “open” and “close”, moisture with be “dry” and “wet”, etc

r= State. This will return the raw state without converting to the correct device class

o=Value. This will return the numeric value rounded to the nearest whole number. This sounds better spoken than decimals .

e= Entity Id.

f= Friendly name.

c= last changed converted to a date format

u= last updated converted to a date format

a=Attribute . This is a special case. This will return all the attributes of the entity. The first attribute is always zero . so To parse the second value use a[1].

So using the previous example The {{e.0.f}} is {{e.0.s}} will return friendly name followed by the device class state so

The Patio door is open.

The flow is broken down into the following

The flow is triggered by a virtual bulb

Change the bulb node to match the virtual bulb name.

Once the virtual bulb is triggered it is converted to % brightness {0-100) then turned off ready for the next trigger.

The value is then tested and sent down the correct flow

So Brightness 1 to first flow 2 to the second, etc

The change node sets the msg.entity_ id to a list of entities to parse . In this example the entities of my electric car.

Sensor.corsa_battery_level,sensor.corsa_battery_autonomy,sensor.corsa_charging_status,sensor.corsa_charging_speed

Each entity is separated by a comma. So sensor.corsa_battery_level is entity 0, sensor.corsa_battery_autonomy is entity 1 , etc.

These entities are then parsed by the parse subflow

The results can then used by the template node

The car is {{e.0.r}} % charged and has a range of {{e.1.r}} miles. , It is {{e.2.r}} and has a charging speed of {{e.3.r}} miles per hour

This message is then sent to the

The parsed message {{e}} is sent to the target. In my case , I’m using the last_alexa sensor as I have a number of alexa devices and I need to make sure the correct device answers. Change the data to match your configuration.

The hard work is done by parse subflow.

Inside the Parse function you add any device classes I’m missing.

const dc = []

// Add all device classes sorted off/on

dc[“vibration”] = [

[“no vibration”],

[“vibration detected”]

]

dc[“moisture”] = [

[“dry”],

[“wet”]

]

Add them as required using the off condition first.

There is a debug node so you can see all variables returned.

3 Likes

FWIW, the allegedly “complex Jinja2 template” example isn’t especially complex just long-winded. In fact, the example employs the longest way possible to reference an entity (in an apparent attempt to demonstrate ‘complexity’).

To be clear, I am not attempting to detract from what you have created (which I applaud) just to point out that the examples used in the ‘complexity’ comparison weren’t evenly matched.

If e represents an entity_id then I believe this would be a fair comparison. The Jinja2 template employs more concise methods for referencing an entity’s friendly_name and converting on/off state values.

{{states[e].name}} is {{iif(states[e].state|bool,'open','closed')}}

vs

{{e.0.f}} is {{e.0.s}}

Without question, what you have created is still more compact but at least the comparison is on a more even footing.

Fair point. Your Jinja2 templating is much better than mine, it was’nt for effect honest

The point is I can never remember any code I write and every time I needed a new message , I got fed up of testing it in the template editor . JInja2 code is not friendly and my brain hurts using such a managled syntax

True I proberly spent 10 time longer writing the code , but it may help somebody , if nothing else I have learnt some new Jinja2 code :slight_smile: Thanks