Comforting music with the google home hub ( + Home Assistant)

Overview

This routine will allow you to add the comfort music to your daily routine. In this case the music served as a noice generation for improving the comfort of a person that uses the private spaces. It can be used as well as a comfort music for the time when you wake up / entering the house / clicking the switch. The main routine will be the same.

Home Assistant as the infrastructure

The home assistant is used here as the core infrastructure. The nodered retrieves the devices configuration from the homeassistant webhook. As you can see further on, the config of some nodes contains the home assistant's path in it.

Prerequsites

In order to run the flow you will need

  • the google assistant device that will play the music.
  • music storage device ( the home assistant location in my case )
  • trigger device ( the wall switch in my case )

Flow overview

The flow contains 2 subflows

  • Trigger the mp3 file play on the google-home activated node ( google hub )
  • Identify the number of songs/examples/mp3 files by iterating over them 1 by 1.

File locations and naming

File considered to be located under homeassistant "media" dir. The naming of the files are considered to be

  • example1.mp3
  • example2.mp3
  • etc Also it is considered that empty mp3 file with the name of empty.mp3 is located in the same dir to break the routine in the mid of execution with the silence.

Check the README node

The README node will contain the list of changes required for the routine to start and proceed

[{"id":"f1d2b34fed117145","type":"tab","label":"Switch_party_time","disabled":false,"info":"","env":[]},{"id":"74cf84455f8ad442","type":"cast-to-client","z":"f1d2b34fed117145","name":"google_hub_silence_mode","url":"http://home-assistant.local:8123/local/media/empty.mp3","contentType":"","message":"","language":"","ip":"insert the local ip address of google hub speaker","port":"","volume":"10","x":540,"y":580,"wires":[[]]},{"id":"4ba19b59560fec5e","type":"cast-to-client","z":"f1d2b34fed117145","name":"google_hub_playit","url":"","contentType":"","message":"","language":"en","ip":"insert the local ip address of google home speaker","port":"","volume":"","x":1110,"y":480,"wires":[[]]},{"id":"b81c89c61c954965","type":"function","z":"f1d2b34fed117145","name":"media url population","func":"//define constants\nconst EXAMPLE_ITERATOR_NAME = 'example_iterator';\nconst EXAMPLE_MAX_NUMBER_NAME = 'example_max_number';\nconst EXAMPLE_ARRAY_NAME = 'example_array';\n// replace me with the actual link to homeassistant \nconst BASE_URL = 'http://home-assistant.local:8123/local/media/';\n\nconst REQUEST_ARRAY_UPDATE = 'request_arr_update';\n\n// retrieve initial global values\nlet maxNumberOfSongs = global.get(EXAMPLE_MAX_NUMBER_NAME);\nlet example_iterator = global.get(EXAMPLE_ITERATOR_NAME);\nlet example_arr = global.get(EXAMPLE_ARRAY_NAME);\n\n// if the number of songs was not populated yet\nif (maxNumberOfSongs == undefined) {\n    // at least a single song is required\n    maxNumberOfSongs = 1;\n}\n\n/* uncomment for the debug lines\nif (example_iterator == undefined) {\n    throw 'example_iter is undefined';\n}\n\nif (example_arr == undefined) {\n    throw 'example_arr is undefined';\n}\n*/\n\nif (example_iterator == undefined ||\n    example_arr == undefined ||\n    example_iterator >= maxNumberOfSongs ) {\n    // if we need to update iterator and reshuffle the array of songs/examples    \n    msg.request_arr_update = 1;\n    // if we need to proceed with this operation again once the reshuffle is done\n    msg.cumback_is_required = 1;\n    return msg;\n}\n\n//set return values\nmsg.request_arr_update = 0;\nmsg.cumback_is_required = 0;\nmsg.url = BASE_URL + 'example' + example_arr[example_iterator] + '.mp3';\n\n// iterate example iterator\nglobal.set(EXAMPLE_ITERATOR_NAME, example_iterator + 1)\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":400,"y":480,"wires":[["19613a1014bb2f38","45849be51e54577e"]]},{"id":"cecb18f8302861c1","type":"inject","z":"f1d2b34fed117145","name":"Google silence","props":[{"p":"topic","vt":"str"},{"p":"command","v":"stop","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":160,"y":600,"wires":[["74cf84455f8ad442"]]},{"id":"aa417d95c318805f","type":"server-state-changed","z":"f1d2b34fed117145","name":"Switch_on","server":"1b0904cc.c3fa8b","version":5,"outputs":2,"exposeAsEntityConfig":"","entityId":"light.bathroom2_dimmer","entityIdType":"exact","outputInitially":false,"stateType":"str","ifState":"on","ifStateType":"str","ifStateOperator":"is","outputOnlyOnStateChange":true,"for":"50","forType":"num","forUnits":"milliseconds","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":120,"y":520,"wires":[["b81c89c61c954965"],["74cf84455f8ad442"]]},{"id":"16afb2800c225872","type":"inject","z":"f1d2b34fed117145","name":"manual main flow run","props":[{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":160,"y":420,"wires":[["b81c89c61c954965"]]},{"id":"5f9cbee47f95585b","type":"inject","z":"f1d2b34fed117145","name":"Check if any files were added every minute","props":[{"p":"payload"}],"repeat":"60","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":230,"y":720,"wires":[["89798e238a12439e"]]},{"id":"c71bc451a5481317","type":"debug","z":"f1d2b34fed117145","name":"Reached the limit of examples/songs","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"example_iterator","targetType":"msg","statusVal":"","statusType":"auto","x":910,"y":920,"wires":[]},{"id":"25e3772be3afbede","type":"http request","z":"f1d2b34fed117145","name":"https request to verify file existence","method":"GET","ret":"txt","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":520,"y":860,"wires":[["53ff39aa4b2941fd"]]},{"id":"89798e238a12439e","type":"function","z":"f1d2b34fed117145","name":"request generation. Need for example iterator","func":"\nlet url_prefix = 'http://home-assistant.local:8123/local/media/example';\n// local value in the loop\n// not the global example_iterator\nif (msg.example_iterator === undefined) {\n    msg.example_iterator = 1;\n} else {\n    msg.example_iterator ++;\n}\n\n// preparing values for the http call for the next node/block\ntry {\n    msg.url = url_prefix + msg.example_iterator + '.mp3'; \n    msg.method = \"GET\";\n} catch (error) {\n    \n}\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":340,"y":800,"wires":[["25e3772be3afbede"]]},{"id":"2e32208bd1513a0a","type":"inject","z":"f1d2b34fed117145","name":"Check global val","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":180,"y":1100,"wires":[["448e11483a393a0f"]]},{"id":"448e11483a393a0f","type":"function","z":"f1d2b34fed117145","name":"global -> local","func":"msg.example_max_number = global.get('example_max_number');\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":380,"y":1100,"wires":[["cebd81a76d1ba36d"]]},{"id":"6bd9599c8f4fcb9a","type":"change","z":"f1d2b34fed117145","name":"number of songs updated ( global value)","rules":[{"t":"set","p":"example_max_number","pt":"global","to":"example_iterator","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1100,"y":1080,"wires":[["c6658b2623dfab3d","73913e2a42f1e053"]]},{"id":"53ff39aa4b2941fd","type":"switch","z":"f1d2b34fed117145","name":"Repeat the request on 200, continue on 404","property":"statusCode","propertyType":"msg","rules":[{"t":"eq","v":"200","vt":"str"},{"t":"eq","v":"404","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":410,"y":960,"wires":[["89798e238a12439e"],["c71bc451a5481317","9b11f99af8623db3"]]},{"id":"9b11f99af8623db3","type":"function","z":"f1d2b34fed117145","name":"Settings the global example_iterator ","func":"let previousMaxNumber = global.get('example_max_number');\n\nlet currentMax = msg.example_iterator - 1;\nmsg.example_iterator = currentMax;\n\nmsg.forwardChange = 0;\n\n// if number of songs raised up: forward the change to the global wars\n// and regeneration of the selection array\nif (previousMaxNumber == undefined || \n    currentMax != previousMaxNumber) {\n    msg.forwardChange = 1;\n\n}\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":910,"y":960,"wires":[["a03958497208107e"]]},{"id":"cebd81a76d1ba36d","type":"debug","z":"f1d2b34fed117145","name":"Global val check manual","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":610,"y":1100,"wires":[]},{"id":"b90057c4996963b6","type":"time-range-switch","z":"f1d2b34fed117145","name":"Split day and night","lat":"0","lon":"0","startTime":"07:28","endTime":"22:00","startOffset":0,"endOffset":0,"x":710,"y":480,"wires":[["d0de85b11f8d8ea4"],["b51f8f8c789f6719"]]},{"id":"b51f8f8c789f6719","type":"change","z":"f1d2b34fed117145","name":"Volume 15% ( night )","rules":[{"t":"set","p":"volume","pt":"msg","to":"15","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":920,"y":540,"wires":[["4ba19b59560fec5e"]]},{"id":"d0de85b11f8d8ea4","type":"change","z":"f1d2b34fed117145","name":"Volume 60% ( day )","rules":[{"t":"set","p":"volume","pt":"msg","to":"60","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":910,"y":420,"wires":[["4ba19b59560fec5e"]]},{"id":"c7ed3522116e44eb","type":"comment","z":"f1d2b34fed117145","name":"Check every minute if the music lib was updated","info":"Here we calling a single .mp3 file as \"example\", \nmeaning the number of examples \nis the number of files located in the dir.\n\nThe routine checks the number \nof files in the appropriate dir once a minute\nand updates the global parameter\nif required\n","x":220,"y":680,"wires":[]},{"id":"ab64d0864398d9ab","type":"inject","z":"f1d2b34fed117145","name":"manual array shuffle","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":1150,"y":340,"wires":[["c6658b2623dfab3d"]]},{"id":"c6658b2623dfab3d","type":"function","z":"f1d2b34fed117145","name":"shuffle_array_and_reset_iterator","func":"\n//creating array from the scratch, regenerating the iterator\n// function to shuffle array\n\n//define constants\nconst EXAMPLE_ITERATOR_NAME = 'example_iterator';\nconst EXAMPLE_MAX_NUMBER_NAME = 'example_max_number';\nconst EXAMPLE_ARRAY_NAME = 'example_array';\n\n//generate random int: [0, max)\nfunction getRandomInt(max) {\n    return Math.floor(Math.random() * max);\n}\n\n//swap function\nfunction swap(arr, i, j) {\n    let temp = arr[i];\n    arr[i] = arr[j];\n    arr[j] = temp;\n\n}\n// shuffle the array\nfunction shuffleArray(arr) {\n    for (let i = arr.length - 1; i >= 0; i--) {\n        swap(arr, getRandomInt(i), i);\n    }\n}\n\n//creating the array\nlet arr = [];\nlet songQuantity = global.get(EXAMPLE_MAX_NUMBER_NAME);\n\n//if no example if set_ default is 1\nif (songQuantity == undefined) {\n    songQuantity = 1;\n}\n\nfor (var i = 0; i < songQuantity; i++) {\n    // as the example suffix numbers goes 1->n not 0->n-1\n    arr[i] = i + 1;\n}\n\n//actual shuffling the array\nshuffleArray(arr);\n// outputing the result\nconsole.log(arr)\n\n//setting iterator to the index 0\nglobal.set(EXAMPLE_ITERATOR_NAME, 0);\n\n// attaching array reference to global value\nglobal.set(EXAMPLE_ARRAY_NAME, arr);\n\n//msg values set\nmsg.arr = arr;\nmsg.example_iterator = global.get(EXAMPLE_ITERATOR_NAME);\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1430,"y":400,"wires":[["83b30955c8c97853","498d106d1ba696e0"]]},{"id":"83b30955c8c97853","type":"debug","z":"f1d2b34fed117145","name":"Reshuffle results","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1590,"y":480,"wires":[]},{"id":"a03958497208107e","type":"switch","z":"f1d2b34fed117145","name":"If number of songs has changed","property":"forwardChange","propertyType":"msg","rules":[{"t":"eq","v":"1","vt":"str"},{"t":"eq","v":"0","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":970,"y":1020,"wires":[["6bd9599c8f4fcb9a"],["854d1c950d21a421"]]},{"id":"854d1c950d21a421","type":"debug","z":"f1d2b34fed117145","name":"no forward change is requested","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1070,"y":1120,"wires":[]},{"id":"abd094567177cfac","type":"comment","z":"f1d2b34fed117145","name":"global: example_max_number","info":"","x":180,"y":100,"wires":[]},{"id":"5817f6db256e5f2f","type":"comment","z":"f1d2b34fed117145","name":"global: example_iterator","info":"index going through array \"example_array\" from index 0 to length - 1","x":160,"y":140,"wires":[]},{"id":"409ae3220b99009e","type":"comment","z":"f1d2b34fed117145","name":"global: example_array","info":"","x":160,"y":180,"wires":[]},{"id":"73913e2a42f1e053","type":"debug","z":"f1d2b34fed117145","name":"max number is updated","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1450,"y":1080,"wires":[]},{"id":"a8a5e02bc501cfd0","type":"comment","z":"f1d2b34fed117145","name":"msg: request_arr_update","info":"","x":170,"y":220,"wires":[]},{"id":"19613a1014bb2f38","type":"switch","z":"f1d2b34fed117145","name":"if array update is required","property":"request_arr_update","propertyType":"msg","rules":[{"t":"eq","v":"1","vt":"str"},{"t":"eq","v":"0","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":570,"y":400,"wires":[["c6658b2623dfab3d"],["b90057c4996963b6"]]},{"id":"498d106d1ba696e0","type":"switch","z":"f1d2b34fed117145","name":"if comback is required","property":"comback_is_required","propertyType":"msg","rules":[{"t":"eq","v":"1","vt":"str"},{"t":"eq","v":"0","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":1540,"y":440,"wires":[["d5a1338e7843ebfa"],["83b30955c8c97853"]],"info":"If parameter set to 1\nmeaning the reshuffle operation was a part of \nplaysong routine\nwe require to come back to the \nstart of the flow \nand play the routine again. \nThe example/song will play this time"},{"id":"4c3ed5ede75cda10","type":"link in","z":"f1d2b34fed117145","name":"cumback_to_song","links":["d5a1338e7843ebfa"],"x":285,"y":400,"wires":[["b81c89c61c954965"]]},{"id":"d5a1338e7843ebfa","type":"link out","z":"f1d2b34fed117145","name":"CUMBACK_TO_SONG","mode":"link","links":["4c3ed5ede75cda10"],"x":1725,"y":400,"wires":[]},{"id":"45849be51e54577e","type":"debug","z":"f1d2b34fed117145","name":"init_run","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":820,"y":320,"wires":[]},{"id":"b27cf2c47eba9395","type":"function","z":"f1d2b34fed117145","name":"cleanup global vals","func":"\n//creating array from the scratch, regenerating the iterator\n// function to shuffle array\n\n//define constants\nconst EXAMPLE_ITERATOR_NAME = 'example_iterator';\nconst EXAMPLE_MAX_NUMBER_NAME = 'example_max_number';\nconst EXAMPLE_ARRAY_NAME = 'example_array';\n\n//setting iterator to the index 0\nglobal.set(EXAMPLE_ITERATOR_NAME, undefined);\nglobal.set(EXAMPLE_ARRAY_NAME, undefined);\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":370,"y":1140,"wires":[[]]},{"id":"e707cc82599012c6","type":"inject","z":"f1d2b34fed117145","name":"manual wipe","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":170,"y":1140,"wires":[["b27cf2c47eba9395"]]},{"id":"278b39eb5267bb6a","type":"comment","z":"f1d2b34fed117145","name":"List of global parameters in this flow (expandme):","info":"## example_max_number\n\nKeeps the total number of songs/examples/.mp3 \nfiles \n\n\n## example_array\n\nKeeps the name of the array \nas a global variable\n\n## example_iterator\n\nKeeps the index value \nof the example/song/.mp3 file\nthat will play next","x":200,"y":60,"wires":[]},{"id":"07125ac6f1596411","type":"comment","z":"f1d2b34fed117145","name":"README: Update the variables for nodes","info":"## Update home assistant url in : \n\n- media url population node\n - https request to verify file existance\n\n##  Update google hub local ip\n\n- google_hub_playit node\n- google_hub_silence_mode node\n\n## Add the empty.mp3 file to the local HA storage\n\nAdd the empty.mp3\ninto homeassistant local \nin order to propagate the empty sound to google hub\n\n## Split day and night node\nUpdate latitude and logtitude of your location\n\n## Switch_on\nConfugure your device switch ON operation trigger","x":180,"y":280,"wires":[]},{"id":"1b0904cc.c3fa8b","type":"server","name":"Home Assistant","version":5,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":30,"areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m","enableGlobalContextStore":true}]

Flow Info

Created 9 months ago
Rating: not yet rated

Owner

Actions

Rate:

Node Types

Core
  • change (x3)
  • comment (x7)
  • debug (x6)
  • function (x6)
  • http request (x1)
  • inject (x6)
  • link in (x1)
  • link out (x1)
  • switch (x4)
Other

Tags

  • #homeassistant
  • #google
  • #google-assistant
  • #googleassistant
  • #music
  • #songs
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option