AI Master Class

This is a unique utilization of GPT within Home Assistant which enables you to not only ask your chatbot questions about your home, but also gives your AI bot the ability to make changes such as toggling lights and other devices.

GPT-3 makes it very easy to create an AI chatbot however there are some challenges we need to overcome to get it to work in a meaningful way within our smarhome. Here is what we must overcome:

  • Integrating Telegram into Home Assistant

  • Integrate GPT-3 into Home Assistant

  • Enable IA Chatbot to run simple commands

  • Enable IA Chatbot to run complex commands

NOTE: The OpenAI node can go out of date and cause errors since OpenAI is experimental and can deprecate their API. If you run into errors check their API documentation and update this code accordingly


1. Integrating Telegram into Home Assistant

In the video below, I explain how create a telegram bot and connect it to home assistant. I also explain how the Telegram events work and how you can see them in your automations. This is the first step in creating a personal AI chatbot capable of controlling your smart home.

Once you you add Telegram to your config yaml file, you should have new services available to you inside node-reds Call-Service node. If you followed the video you should have a similar setup of nodes you see below. However, if you were unable to follow or simply did not feel like it, you can import the JSON below to get the same exact setup and nodes I have.

[{"id":"554a3ca9a8d55a2c","type":"api-call-service","z":"62022061b503e626","name":"","server":"228d3d53.df8e02","version":5,"debugenabled":false,"domain":"homeassistant","service":"{{action}}","areaId":[],"deviceId":[],"entityId":["{{entities}}"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":610,"y":360,"wires":[["76c5ddaad67262cf"]]},{"id":"b61dbeed8f51ba67","type":"server-events","z":"62022061b503e626","name":"From Telegram","server":"228d3d53.df8e02","version":2,"eventType":"telegram_text","exposeToHomeAssistant":false,"eventData":"","haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"}],"event_type":"","x":120,"y":360,"wires":[["5e4d9d4799961728"]]},{"id":"bc1be606f8697887","type":"debug","z":"62022061b503e626","name":"debug 43","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1040,"y":360,"wires":[]},{"id":"5e4d9d4799961728","type":"change","z":"62022061b503e626","name":"set property","rules":[{"t":"set","p":"telegramMessage","pt":"msg","to":"payload.event.text","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":330,"y":360,"wires":[["554a3ca9a8d55a2c"]]},{"id":"76c5ddaad67262cf","type":"api-call-service","z":"62022061b503e626","name":"respond","server":"228d3d53.df8e02","version":5,"debugenabled":false,"domain":"telegram_bot","service":"send_message","areaId":[],"deviceId":[],"entityId":[],"data":"{\"message\": msg.gptAnswer}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":860,"y":360,"wires":[["bc1be606f8697887"]]},{"id":"2785df25da60b6fc","type":"inject","z":"62022061b503e626","name":"","props":[{"p":"payload"},{"p":"action","v":"toggle","vt":"str"},{"p":"entities","v":"[\"light.office_light_left\", \"light.office_light_right\"]","vt":"jsonata"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":180,"y":160,"wires":[["554a3ca9a8d55a2c"]]},{"id":"228d3d53.df8e02","type":"server","name":"Home Assistant","addon":true}]

2. Add GPT to Home Assistant in 10 Minutes! Make it EPIC in 10 more!

There are several integrations out there that allows you to utilize GPT in Home Assistant but i've never seen people use it used in an automation. At the time of writing, the OpenAI integration only allows you talk to GPT on a dashboard. But my version HITS DIFFERENT. I believe it is where Home Assistant will go after a few month of testing and development and it's where I believe the smart home community expects it to go. At the end of this lesson you will be able to use GPT in an automation flow and get answers about your smart home as well as any other general knowledge GPT has.

NOTICE: Many of you started reporting that the OpenAI Node is failing. This is because OpenAI has deprecated the version that I referenced. Stuff like this happen all the time when working with code. You will need to go to the OpenAI developer site, navigate to the Playground, Click View Code and update the node to look similar to the Node.JS example they provide. I will create a video teaching you how to figure this out for yourself as this will most-likely happen again.

When a message comes in from Telegram the next node sets the a property called "prompt" to the text sent from telegram. the "getEntities" node fetches all the entities in your home assistant environment and the "Filter for specific entities" will search for any entity with an id that starts with "light." The following node updates the "prompt" text to with information about your smart home and the question you want to ask GPT. The "OpenAI" node will send the message and return an answer which will be cleaned up and formatted byt the "Sanitize response" node. Lastly the message from OpenAI is sent back to Telegram.

[{"id":"63f8cdadbe364b21","type":"server-events","z":"dcb1a4b9bbc7cb30","name":"From Telegram","server":"228d3d53.df8e02","version":2,"eventType":"telegram_text","eventData":"","waitForRunning":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"}],"event_type":"","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"x":220,"y":300,"wires":[["0f1e0fdd6ca5e3c0"]]},{"id":"f2b9d7c758523b48","type":"debug","z":"dcb1a4b9bbc7cb30","name":"debug 43","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1160,"y":460,"wires":[]},{"id":"0f1e0fdd6ca5e3c0","type":"change","z":"dcb1a4b9bbc7cb30","name":"set property","rules":[{"t":"set","p":"prompt","pt":"msg","to":"payload.event.text","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":430,"y":300,"wires":[["a128b094820e013f"]]},{"id":"9bd54e1b12f8eb47","type":"api-call-service","z":"dcb1a4b9bbc7cb30","name":"respond","server":"228d3d53.df8e02","version":5,"debugenabled":false,"domain":"telegram_bot","service":"send_message","areaId":[],"deviceId":[],"entityId":[],"data":"{\"message\": msg.gptAnswer}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1000,"y":460,"wires":[["f2b9d7c758523b48"]]},{"id":"a128b094820e013f","type":"ha-get-entities","z":"dcb1a4b9bbc7cb30","name":"","server":"228d3d53.df8e02","version":0,"rules":[{"property":"entity_id","logic":"is_not","value":"undefined","valueType":"jsonata"}],"output_type":"array","output_empty_results":false,"output_location_type":"msg","output_location":"payload","output_results_count":1,"x":670,"y":300,"wires":[["631249911b32d5a9"]]},{"id":"631249911b32d5a9","type":"function","z":"dcb1a4b9bbc7cb30","name":"Filter for specific entities","func":"const {payload} = msg;\nconst filters = [\"light.\"]\nconst entities = payload.reduce((aggregate, entity) => {\n    const state = entity.state\n    const name = entity?.attributes?.friendly_name \n    const id = entity.entity_id\n    const canShow = filters.some(filter => {\n        return id.includes(filter)\n    })\n\n    if(name && canShow){\n        aggregate[name] = {\n            state,\n            id: entity.entity_id,\n            name\n        }\n    }\n   \n    return aggregate\n}, {})\n\nmsg.entities = entities;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":710,"y":340,"wires":[["324ad6b1fce764dc"]]},{"id":"324ad6b1fce764dc","type":"function","z":"dcb1a4b9bbc7cb30","name":"Template: Answer questions","func":"const { entities, prompt} = msg\nlet entitySummary = \"\"\n\nObject.keys(entities).forEach((key) => {\n    const entity = entities[key];\n    entitySummary += `${entity.name} has the entity id of ${entity.id} and is is currently ${entity.state}\\n`\n})\n\nconst template = `\nThis smart home named Ke is powered by Home Assistant.\n\nAn overview of the areas and the devices in this smart home:\n${entitySummary}\n\nAnswer the question below  in a conversational tone:\n\n${prompt}\n`\nmsg.prompt = template\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":720,"y":380,"wires":[["6adb22296a85611d"]]},{"id":"4bb194833cbe1f35","type":"function","z":"dcb1a4b9bbc7cb30","name":"sanitize response","func":"const message = msg.payload.choices.reduce((aggregate, choice) => {\n    return aggregate + `${choice.message.content} `;\n}, \"\")\n\nmsg.gptAnswer = message\n.trim()\n.split(\"\\n\")\n.filter(Boolean)\n.join(\" \")\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":690,"y":460,"wires":[["9bd54e1b12f8eb47"]]},{"id":"6adb22296a85611d","type":"function","z":"dcb1a4b9bbc7cb30","name":"Open AI","func":"const openai = new OpenAI({\n    apiKey: \"<use your own api key!>\"\n});\n\nconst messages = []\n\n\nif(msg.prompt){\n    messages.push({\n        role: \"user\",\n        content: msg.prompt\n    })\n}\n\nif(messages.length){\n    openai.chat.completions.create({\n        model: \"gpt-3.5-turbo\",\n        messages,\n        temperature: 0.7,\n        max_tokens: 256,\n        top_p: 1,\n        frequency_penalty: 0,\n        presence_penalty: 0,\n    }).then(answer => {\n        msg.payload = answer\n        node.send([msg, null]);\n        node.done();\n    })\n    return;\n}else{\n    return[null, new Error(\"No data sent to OpenAI\")]\n}\n\n\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[{"var":"OpenAI","module":"openai"}],"x":660,"y":420,"wires":[["4bb194833cbe1f35"]]},{"id":"228d3d53.df8e02","type":"server","name":"Home Assistant","addon":false,"rejectUnauthorizedCerts":true,"ha_boolean":"","connectionDelay":false,"cacheJson":false,"heartbeat":false,"heartbeatInterval":"","statusSeparator":"","enableGlobalContextStore":false}]

3. Get GPT to Trigger Your Automations!

After some digging, I think I'm the first person to figure out how to get GPT to control your Home Automations without actually having physical control or connection to your system. This is experimental and I am certain that in time, a better integration will be available to enable a more seamless and tightly coupled experience. Until that time, the content below shows how you can use your AI Chatbot to control your smart home with GPT-3's help.

NOTICE: Many of you started reporting that the OpenAI Node is failing. This is because OpenAI has deprecated the version that I referenced. Stuff like this happen all the time when working with code. You will need to go to the OpenAI developer site, navigate to the Playground, Click View Code and update the node to look similar to the Node.JS example they provide. I will create a video teaching you how to figure this out for yourself as this will most-likely happen again.

GPT is able to control our automations by providing the entities and the action to perform on those entities based on the context give. We provide OpenAI relevant Devices and actions and ask it to return a JSON Object that satisfies the criteria. This mean that we do not need to provide any new code or alternate way of saying "Turn of the lights" as GPT is able to infer what we want. It should be noted that this implementation takes 2 trips to OpenAI's server to complete any request. Currently, there is a contest to see if this can be reduced to a single succinct request.

[{"id":"e03985c8faa42d51","type":"server-events","z":"b7166ab4bf2d497e","name":"From Telegram","server":"228d3d53.df8e02","version":3,"exposeAsEntityConfig":"","eventType":"telegram_text","eventData":"","waitForRunning":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"}],"event_type":"","x":180,"y":260,"wires":[["a5c90ef8b641706d"]]},{"id":"74857e67438792d0","type":"debug","z":"b7166ab4bf2d497e","name":"debug 43","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1280,"y":360,"wires":[]},{"id":"a5c90ef8b641706d","type":"change","z":"b7166ab4bf2d497e","name":"set property","rules":[{"t":"set","p":"prompt","pt":"msg","to":"payload.event.text","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":390,"y":260,"wires":[["fad68243dd065b67"]]},{"id":"6931151bf4ad1b62","type":"api-call-service","z":"b7166ab4bf2d497e","name":"respond","server":"228d3d53.df8e02","version":5,"debugenabled":false,"domain":"telegram_bot","service":"send_message","areaId":[],"deviceId":[],"entityId":[],"data":"{\"message\": msg.gptAnswer}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1120,"y":360,"wires":[["74857e67438792d0"]]},{"id":"668bc5cd04351cbd","type":"ha-get-entities","z":"b7166ab4bf2d497e","name":"","server":"228d3d53.df8e02","version":0,"rules":[{"property":"entity_id","logic":"is_not","value":"undefined","valueType":"jsonata"}],"output_type":"array","output_empty_results":false,"output_location_type":"msg","output_location":"payload","output_results_count":1,"x":670,"y":200,"wires":[["c5262a97822c3c86"]]},{"id":"c5262a97822c3c86","type":"function","z":"b7166ab4bf2d497e","name":"Filter for specific entities","func":"const {payload} = msg;\nconst filters = [\"light.\"]\nconst entities = payload.reduce((aggregate, entity) => {\n    const state = entity.state\n    const name = entity?.attributes?.friendly_name \n    const id = entity.entity_id\n    const canShow = filters.some(filter => {\n        return id.includes(filter)\n    })\n\n    if(name && canShow){\n        aggregate[name] = {\n            state,\n            id: entity.entity_id,\n            name\n        }\n    }\n   \n    return aggregate\n}, {})\n\nmsg.entities = entities;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":710,"y":240,"wires":[["3133c8a4758a1938"]]},{"id":"3133c8a4758a1938","type":"function","z":"b7166ab4bf2d497e","name":"Template: Answer questions","func":"const { entities, prompt} = msg\nlet entitySummary = \"\"\n\nObject.keys(entities).forEach((key) => {\n    const entity = entities[key];\n    entitySummary += `${entity.name} has the entity id of ${entity.id} and is is currently ${entity.state}\\n`\n})\n\nconst template = `\nThis smart home named Ke is powered by Home Assistant.\n\nAn overview of the areas and the devices in this smart home:\n${entitySummary}\n\nAnswer the question below  in a conversational tone. If you are asked to do a task tell the user to rephrase it as a command.:\n\n\n${prompt}\n`\nmsg.prompt = template\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":720,"y":280,"wires":[["7e2cf9e6f6d3719b"]]},{"id":"aabebc23f040b9ee","type":"api-call-service","z":"b7166ab4bf2d497e","name":"","server":"228d3d53.df8e02","version":5,"debugenabled":false,"domain":"homeassistant","service":"{{action}}","areaId":[],"deviceId":[],"entityId":["{{_entities}}"],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":990,"y":660,"wires":[["6931151bf4ad1b62"]]},{"id":"7beced65d9e701bb","type":"switch","z":"b7166ab4bf2d497e","name":"choose","property":"gptAnswer","propertyType":"msg","rules":[{"t":"eq","v":"true","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":400,"y":420,"wires":[["668bc5cd04351cbd"],["487f70ae27fd7da8"]]},{"id":"fad68243dd065b67","type":"function","z":"b7166ab4bf2d497e","name":"Template: Is Question","func":"const {prompt} = msg\nconst template = `\nIf the following sentence is a question return true otherwise return false:\n${prompt}\n`\nmsg.template = template\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":360,"y":300,"wires":[["ce59ad07c2c4ad85"]]},{"id":"487f70ae27fd7da8","type":"ha-get-entities","z":"b7166ab4bf2d497e","name":"","server":"228d3d53.df8e02","version":0,"rules":[{"property":"entity_id","logic":"is_not","value":"undefined","valueType":"jsonata"}],"output_type":"array","output_empty_results":false,"output_location_type":"msg","output_location":"payload","output_results_count":1,"x":670,"y":460,"wires":[["509613dc25064c11"]]},{"id":"509613dc25064c11","type":"function","z":"b7166ab4bf2d497e","name":"Filter for specific entities","func":"const {payload} = msg;\nconst filters = [\"light.\"]\nconst entities = payload.reduce((aggregate, entity) => {\n    const state = entity.state\n    const name = entity?.attributes?.friendly_name \n    const id = entity.entity_id\n    const canShow = filters.some(filter => {\n        return id.includes(filter)\n    })\n\n    if(name && canShow){\n        aggregate[name] = {\n            state,\n            id: entity.entity_id,\n            name\n        }\n    }\n   \n    return aggregate\n}, {})\n\nmsg.entities = entities;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":710,"y":500,"wires":[["2045ad327bf07b03"]]},{"id":"2045ad327bf07b03","type":"function","z":"b7166ab4bf2d497e","name":"Template: Optimized Commands","func":"const { entities, prompt} = msg\nlet entitySummary = \"\"\n\nObject.keys(entities).forEach((key) => {\n    const entity = entities[key];\n    entitySummary += `${entity.name} has the entity id of ${entity.id} and is is currently ${entity.state}\\n`\n})\n\nconst template = `\nThis smart home named Ke powered by Home Assistant.\n\nAn overview of the areas and the devices in this smart home:\n${entitySummary}\n\nAction List: turn_off, turn_on, toggle\n\nJSON Template: {\"action\": \"\", \"entities\": []}\n\nRespond to the following sentence below with the JSON object only by updating the action value with one of the Action List item that best relates to the sentence below and filling the entities array with only the ids  of the entities that relates to the sentence below:\n\n${prompt}\n`\nmsg.template = template\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":740,"y":540,"wires":[["b8de2a0e3c70047c"]]},{"id":"82cd51e1c43bc0ab","type":"function","z":"b7166ab4bf2d497e","name":"handle optimized response","func":"const index = msg.gptAnswer.indexOf(\"{\")\nconst sanitized = msg.gptAnswer.slice(index)\n\nconst {action, entities} = JSON.parse(sanitized)\n\nmsg.action = action;\nmsg._entities = entities\nmsg.gptAnswer = \"on my honor\"\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":720,"y":660,"wires":[["aabebc23f040b9ee"]]},{"id":"690e5879f8b13a9c","type":"function","z":"b7166ab4bf2d497e","name":"sanitize response","func":"const message = msg.payload.choices.reduce((aggregate, choice) => {\n    return aggregate + `${choice.message.content} `;\n}, \"\")\n\nmsg.gptAnswer = message\n.trim()\n.split(\"\\n\")\n.filter(Boolean)\n.join(\" \")\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":370,"y":380,"wires":[["7beced65d9e701bb"]]},{"id":"ce59ad07c2c4ad85","type":"function","z":"b7166ab4bf2d497e","name":"Open AI","func":"const openai = new OpenAI({\n    apiKey: \"<use your own api key!>\"\n});\n\nconst messages = []\n\n\nif(msg.template){\n    messages.push({\n        role: \"user\",\n        content: msg.template\n    })\n}\n\nif(messages.length){\n    openai.chat.completions.create({\n        model: \"gpt-3.5-turbo\",\n        messages,\n        temperature: 0.7,\n        max_tokens: 256,\n        top_p: 1,\n        frequency_penalty: 0,\n        presence_penalty: 0,\n    }).then(answer => {\n        msg.payload = answer\n        node.send([msg, null]);\n        node.done();\n    })\n    return;\n}else{\n    return[null, new Error(\"No data sent to OpenAI\")]\n}\n\n\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[{"var":"OpenAI","module":"openai"}],"x":400,"y":340,"wires":[["690e5879f8b13a9c"]]},{"id":"235676cfd1ec3e50","type":"function","z":"b7166ab4bf2d497e","name":"sanitize response","func":"const message = msg.payload.choices.reduce((aggregate, choice) => {\n    return aggregate + `${choice.message.content} `;\n}, \"\")\n\nmsg.gptAnswer = message\n.trim()\n.split(\"\\n\")\n.filter(Boolean)\n.join(\" \")\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":690,"y":360,"wires":[["6931151bf4ad1b62"]]},{"id":"7e2cf9e6f6d3719b","type":"function","z":"b7166ab4bf2d497e","name":"Open AI","func":"const openai = new OpenAI({\n    apiKey: \"<use your own api key!>\"\n});\n\nconst messages = []\n\n\nif(msg.prompt){\n    messages.push({\n        role: \"user\",\n        content: msg.prompt\n    })\n}\n\nif(messages.length){\n    openai.chat.completions.create({\n        model: \"gpt-3.5-turbo\",\n        messages,\n        temperature: 0.7,\n        max_tokens: 256,\n        top_p: 1,\n        frequency_penalty: 0,\n        presence_penalty: 0,\n    }).then(answer => {\n        msg.payload = answer\n        node.send([msg, null]);\n        node.done();\n    })\n    return;\n}else{\n    return[null, new Error(\"No data sent to OpenAI\")]\n}\n\n\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[{"var":"OpenAI","module":"openai"}],"x":660,"y":320,"wires":[["235676cfd1ec3e50"]]},{"id":"b8de2a0e3c70047c","type":"function","z":"b7166ab4bf2d497e","name":"Open AI","func":"const openai = new OpenAI({\n    apiKey: \"<use your own api key!>\"\n});\n\nconst messages = []\n\n\nif(msg.template){\n    messages.push({\n        role: \"user\",\n        content: msg.template\n    })\n}\n\nif(messages.length){\n    openai.chat.completions.create({\n        model: \"gpt-3.5-turbo\",\n        messages,\n        temperature: 0.7,\n        max_tokens: 256,\n        top_p: 1,\n        frequency_penalty: 0,\n        presence_penalty: 0,\n    }).then(answer => {\n        msg.payload = answer\n        node.send([msg, null]);\n        node.done();\n    })\n    return;\n}else{\n    return[null, new Error(\"No data sent to OpenAI\")]\n}\n\n\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[{"var":"OpenAI","module":"openai"}],"x":660,"y":580,"wires":[["b3d2ec18cc4464a5"]]},{"id":"b3d2ec18cc4464a5","type":"function","z":"b7166ab4bf2d497e","name":"sanitize response","func":"const message = msg.payload.choices.reduce((aggregate, choice) => {\n    return aggregate + `${choice.message.content} `;\n}, \"\")\n\nmsg.gptAnswer = message\n.trim()\n.split(\"\\n\")\n.filter(Boolean)\n.join(\" \")\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":690,"y":620,"wires":[["82cd51e1c43bc0ab"]]},{"id":"228d3d53.df8e02","type":"server","name":"Home Assistant","addon":false,"rejectUnauthorizedCerts":true,"ha_boolean":"","connectionDelay":false,"cacheJson":false,"heartbeat":false,"heartbeatInterval":"","statusSeparator":"","enableGlobalContextStore":false}]
Previous
Previous

This simple automation could've prevented this nightmare

Next
Next

Avoid this lame trigger and use this one instead