Can node red update HA config using update config node?

i have several entities that i would like to streamline updating for and not have to change HA config if id like those states to stay stable in event of shutdown or restart / power failure…etc …

i can see how to do it with one entity but not to streamline multiple through the node and make it work … seems like each entity would need its own node and then it just looks muddy… to me i guess

tried sending information using render template directly to update which should work but cant figure out how to make it so each entity sent updates without multiple update config nodes

any and all guesses or direction are welcome, thanks for your time

[
    {
        "id": "4aba2ad66198c381",
        "type": "api-render-template",
        "z": "60d173737cba92fb",
        "name": "get state",
        "server": "ed8abe17.255eb",
        "version": 0,
        "template": "phUp: {{ states(\"input_number.ph_up\") }},\nphD: {{states(\"input_number.ph_down\") }},\ndose: {{states(\"input_number.ph_dose\") }},\nlow: {{states(\"input_number.ph_low\")}},\nhigh: {{states(\"input_number.ph_high\")}},\nwarn: {{states(\"input_number.ph_warn\")}},\ntime: {{states(\"sensor.water_time\")}},\nph: {{states(\"sensor.water_ph\")}},\ntds: {{states(\"sensor.water_ppm\")}},\ntemp: {{states(\"sensor.water_temp\")}}\n",
        "resultsLocation": "payload",
        "resultsLocationType": "msg",
        "templateLocation": "template",
        "templateLocationType": "flow",
        "x": 939.9999389648438,
        "y": 283.33331298828125,
        "wires": [
            [
                "87749f6d6beee021",
                "581a56df917c498a"
            ]
        ]
    },
    {
        "id": "87749f6d6beee021",
        "type": "function",
        "z": "60d173737cba92fb",
        "name": "A>O",
        "func": "var inputString = msg.payload;\nvar inputArray = inputString.split(',');\nvar outputObject = {};\n\nfor (var i = 0; i < inputArray.length; i++) {\n    var trimmedValue = inputArray[i].trim();\n    var parts = trimmedValue.split(':');\n    var columnName = parts[0].trim();\n    var columnValue = parts.slice(1).join(':').trim(); // Join all parts after the first ':' to include the entire timestamp\n    outputObject[columnName] = columnValue;\n}\n\nmsg.payload = outputObject;\nreturn msg;\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1110,
        "y": 200,
        "wires": [
            [
                "581a56df917c498a"
            ]
        ]
    },
    {
        "id": "581a56df917c498a",
        "type": "ha-update-config",
        "z": "60d173737cba92fb",
        "name": "",
        "server": "",
        "entityConfig": "",
        "version": 0,
        "outputProperties": [],
        "x": 1240,
        "y": 360,
        "wires": [
            []
        ]
    },
    {
        "id": "ed8abe17.255eb",
        "type": "server",
        "name": "Home Assistant",
        "addon": true,
        "rejectUnauthorizedCerts": true,
        "ha_boolean": "",
        "connectionDelay": false,
        "cacheJson": false,
        "heartbeat": false,
        "heartbeatInterval": "",
        "statusSeparator": "",
        "statusYear": "numeric",
        "statusMonth": "numeric",
        "statusDay": "numeric",
        "statusHourCycle": "default",
        "statusTimeFormat": "h:m:s",
        "enableGlobalContextStore": true
    }
]

I am confused at what you are trying to accomplish. I see you are getting the states of the entities but what do you mean by update home assistant configuration?

1 Like

@Mikefila

Is your goal to create a sensor inside HA and be able to update it’s value? The server config is what lets nodered talk to HA. Only one is needed unless you are trying to connect to another server aside from HA.

The server, once it’s configured, that’s it. It’s not something that should be updated. Sensor/binary/input/etc have config nodes as well. These again once they are set you shouldn’t need to up date them.

To update a sensor with new data once it is created is done by sending a message that conforms to how the sensor was configured. For instance I create a sensor and the state is set as payload

image

Whatever value is in the msg.payload position will update the state of that sensor. You can further define attributes and then the same method applies.

msg.last_delivery = attribute last_delivery

msg.company = attribute company

msg.price = attribute price

If I inject this

image

image

I get this in home assistant.

image

yes i can make it work for one, but i want to streamline this process and deliver information to be updated. theres several i would like to update at once but think for each it would need an update node to know which one was being updated or just make config for each one to be updated? if so how do you call them all
input_number.ph_up
input_number.ph_down
input_number.ph_dose
input_number.ph_low
input_number.ph_high
input_number.ph_warn

id like to be able to change value of entity within home assistant UI and then use node red to save the config of that value so a restart does not wipe the information…

  • make entity with node-red
  • pull state value for node-red to use as update value

got those but now saving multiple values in single swipe seems difficult

I see. You want to send the config value as a variable this way you only have one empty sensor node that is fed that data from those inputs?

Off the top of my head I don’t know if that is possible. I will have to look and see what I can find.

1 Like

i was actually thinking it would change home assistant config to make the nodes inital stat whatever i saved it as, im its looking like it actually just changes displayed value of UI startup value

First off input numbers should by all accounts retain their value after a restart. Sounds like the additional sensors are not really needed? You just want to save data and not display it? You can write directly to a file. Then read the file to update the input numbers should it be necessary.

1 Like

that just might do the trick i was thinking this one would do that for me to change the initial state

[
    {
        "id": "15babaedbfcd8a5b",
        "type": "api-render-template",
        "z": "60d173737cba92fb",
        "name": "get state",
        "server": "ed8abe17.255eb",
        "version": 0,
        "template": "phUp: {{ states(\"input_number.ph_up\") }},\nphD: {{states(\"input_number.ph_down\") }},\ndose: {{states(\"input_number.ph_dose\") }},\nlow: {{states(\"input_number.ph_low\")}},\nhigh: {{states(\"input_number.ph_high\")}},\nwarn: {{states(\"input_number.ph_warn\")}},\ntime: {{states(\"sensor.water_time\")}},\nph: {{states(\"sensor.water_ph\")}},\ntds: {{states(\"sensor.water_ppm\")}},\ntemp: {{states(\"sensor.water_temp\")}}\n",
        "resultsLocation": "payload",
        "resultsLocationType": "msg",
        "templateLocation": "template",
        "templateLocationType": "flow",
        "x": 500,
        "y": 1200,
        "wires": [
            [
                "8c64b3714cc15de2"
            ]
        ]
    },
    {
        "id": "24b6d70e.5d5e7c",
        "type": "file in",
        "z": "60d173737cba92fb",
        "name": "readC",
        "filename": "/homeassistant/configuration.yaml",
        "filenameType": "str",
        "format": "utf8",
        "chunk": false,
        "sendError": false,
        "encoding": "utf8",
        "allProps": true,
        "x": 1010,
        "y": 1200,
        "wires": [
            [
                "2842b3110ec5afbf",
                "42da38dd0d10ae45"
            ]
        ]
    },
    {
        "id": "7e527029.62f6b8",
        "type": "file",
        "z": "60d173737cba92fb",
        "name": "",
        "filename": "/config/configuration.yaml",
        "filenameType": "str",
        "appendNewline": false,
        "createDir": false,
        "overwriteFile": "true",
        "encoding": "none",
        "x": 1310,
        "y": 1200,
        "wires": [
            []
        ]
    },
    {
        "id": "b1f7a92d583e9c03",
        "type": "trigger",
        "z": "60d173737cba92fb",
        "name": "wait",
        "op1": "",
        "op2": "trigger",
        "op1type": "nul",
        "op2type": "str",
        "duration": "100",
        "extend": false,
        "overrideDelay": false,
        "units": "ms",
        "reset": "",
        "bytopic": "all",
        "topic": "topic",
        "outputs": 1,
        "x": 890,
        "y": 1200,
        "wires": [
            [
                "24b6d70e.5d5e7c"
            ]
        ]
    },
    {
        "id": "f7ed37abf7961dbf",
        "type": "inject",
        "z": "60d173737cba92fb",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 360,
        "y": 1200,
        "wires": [
            [
                "15babaedbfcd8a5b"
            ]
        ]
    },
    {
        "id": "30dc88ad6c98f9ea",
        "type": "function",
        "z": "60d173737cba92fb",
        "name": "globalV",
        "func": "const phUp = parseFloat(msg.payload.phUp);\nconst phD = parseFloat(msg.payload.phD);\nconst dose = parseFloat(msg.payload.dose);\nconst low = parseFloat(msg.payload.low);\nconst high = parseFloat(msg.payload.high);\nconst warn = parseFloat(msg.payload.warn);\n\nflow.set('phUp', phUp);\nflow.set('phD', phD);\nflow.set('dose', dose);\nflow.set('low', low);\nflow.set('high', high);\nflow.set('warn', warn);\n\nreturn msg;\n",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 760,
        "y": 1200,
        "wires": [
            [
                "b1f7a92d583e9c03"
            ]
        ]
    },
    {
        "id": "42da38dd0d10ae45",
        "type": "function",
        "z": "60d173737cba92fb",
        "name": "update",
        "func": "var phUp = flow.get('phUp');\nvar phD = flow.get('phD');\nvar high = flow.get('high');\nvar warn = flow.get('warn');\n\nif (phUp !== undefined && phD !== undefined && high !== undefined && warn !== undefined) {\n\n    var configFileContent = msg.payload;\n\n    if (configFileContent) {\n      \n        var sections = configFileContent.split('\\n\\n');\n\n        sections.forEach(function(section, index) {\n            \n            if (section.includes('ph_up:')) {  \n                sections[index] = section.replace(/initial: \\d+(\\.\\d+)?/, 'initial: ' + phUp);\n            } else if (section.includes('ph_down:')) {   \n                sections[index] = section.replace(/initial: \\d+(\\.\\d+)?/, 'initial: ' + phD);\n            } else if (section.includes('ph_high:')) {\n                sections[index] = section.replace(/initial: \\d+(\\.\\d+)?/, 'initial: ' + high);\n            } else if (section.includes('ph_warn:')) {\n                sections[index] = section.replace(/initial: \\d+(\\.\\d+)?/, 'initial: ' + warn);\n            }\n\n            node.warn('Updated section: ' + sections[index]);\n        });\n\n        var updatedConfigFileContent = sections.join('\\n\\n');\n\n        msg.payload = updatedConfigFileContent;\n        return msg;\n    } else {\n        node.error('Configuration file content is empty');\n        return null;\n    }\n} else {\n    node.error('One or more required values are undefined');\n    return null;\n}\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1130,
        "y": 1200,
        "wires": [
            [
                "71842989b369d448",
                "7e527029.62f6b8"
            ]
        ]
    },
    {
        "id": "8c64b3714cc15de2",
        "type": "function",
        "z": "60d173737cba92fb",
        "name": "A>O",
        "func": "var inputString = msg.payload;\nvar inputArray = inputString.split(',');\nvar outputObject = {};\n\nfor (var i = 0; i < inputArray.length; i++) {\n    var trimmedValue = inputArray[i].trim();\n    var parts = trimmedValue.split(':');\n    var columnName = parts[0].trim();\n    var columnValue = parts.slice(1).join(':').trim(); \n    outputObject[columnName] = columnValue;\n}\n\nmsg.payload = outputObject;\nreturn msg;\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 630,
        "y": 1200,
        "wires": [
            [
                "30dc88ad6c98f9ea"
            ]
        ]
    },
    {
        "id": "ed8abe17.255eb",
        "type": "server",
        "name": "Home Assistant",
        "addon": true,
        "rejectUnauthorizedCerts": true,
        "ha_boolean": "",
        "connectionDelay": false,
        "cacheJson": false,
        "heartbeat": false,
        "heartbeatInterval": "",
        "statusSeparator": "",
        "statusYear": "numeric",
        "statusMonth": "numeric",
        "statusDay": "numeric",
        "statusHourCycle": "default",
        "statusTimeFormat": "h:m:s",
        "enableGlobalContextStore": true
    }
]

ok so i made this , it allows node red to read and change home assistant configuration.yaml if any of the inputs within the flow have been changed. it will set that to their initial state so at startup it will be at that position. now just have to add a button on lovelace UI

I wouldn’t do this. If you want to modify the config, I’d suggest breaking out what you want to modify and use include in configuration.yaml. Another suggestion, if you are going to use data stores in nodered consider enabling context storage to file, rather than memory.

There are a couple of other ways to get entity data in NR in liu of templating. The get entities node can be used with a regex search to get all the input numbers (state and att) at once. It can be output to an array or split. You can also specify all switches that are currently on.

All HA states are stored in global context and available through jsonata, js in function nodes, and global path in switch/change/etc nodes.

The global path

global.get('homeassistant.homeAssistant.states["sensor.abc123"].state');

In HA nodes you have a lot of options available to manipulate data. It can be used to make conditional tests as well.

There are several additional Home Assistant functions added for use in JSONata expressions, and these can only be used within the Home Assistant nodes.

$entity() returns the entity that triggered the node

$prevEntity() returns the previous state entity if the node is an event node

$areaDevices(areaId) returns all devices associated with a specific area ID.

$areaEntities(areaId) returns all entities associated with a specific area ID.

$areas(lookup) returns an area based on a provided lookup value, or all areas if no lookup value is provided. The lookup value can be an area ID, an entity ID, or a device ID.

$deviceEntities(device_id) returns all entities associated with a specific device.

$device(lookup) returns a device based on a provided lookup value. The lookup value can be an entity ID or a device name.

$entities() returns all entities in the cache

$entities(entity_id) returns a single entity from cache matching the given entity_id
2 Likes

You are indeed a far braver person than me. Personally I would always start with the clear understanding that the Home Assistant configuration file should never, ever be updated like this. Just too easy for one of your values to become “unavailable” and then your config to become “initial: unavailable”. Not sure how HA would like that. However, each to his own…

Thoughts and considerations:

Point 1. As @Mikefila has pointed out, the ‘input_number’ entities are special in two ways. First, unlike most entities in HA, we can update their state values (both via the dashboard and via service calls) and second, HA automatically restores them to the last state value at restarts. This happens automatically, it just does not happen if you set an initial value as you are doing, as that initial value is used instead. I would suggest that you only need to remove the ‘initial’ value setting from your configuration (or just use the Helper setup which does not include this feature) and HA will restore all your input_numbers on a restart for you. Job done. No need to read any further…

Point 2. As per your OP, it is not usually possible to do more than one thing in a node per message, so working through a list of entities to get, save, and write requires either a lot of nodes, or a sequence of messages passed as input to one node. Easy enough to do using an array (list) and the split and join nodes. That is how Node-RED can execute iteration.

Point 3. Yes you can use the template node to execute Jinja to get the state values, then a function node to pick them out of the string and put them into an object, then save them to context. However the standard Nodes can be used to do much of this work for you.

So, here is my suggestion. Two flows

  • the first reads a given list of input_number entities, builds a JSON object of all them with their state values, and saves this to context.
  • the second reads back the context variable, and then for each input_number entity / value pair it will call the input_number:set_value service call to write the value back again.

The advantage of this, apart from not having to mess with the HA configuration file, is that it can be run at any point to save the input_number entity set, and run at any point to restore this set.

You could trigger this from an Event: state node to perform the save-to-context on any change to one or all of your input_numbers.
You could write another flow to update / change the stored context value, or even just one field (for one input_number), thus maintaining your saved data set.
You could extend this to save blocks of data to context (object within an object) so that you could, for example save the current settings to ‘usual’ and another set to ‘holiday’, much as scenes do in HA.

In terms of saving over a restart, the Node-RED context store is normally to volatile memory only, however it is possible to change this or add an extra store that saves to file. This allows for context variables to be persistent over NR restarts.

(@Mikefila has given you the link above)

Note that I have set this flow up with just input_numbers, and set the Current state node for state type ‘number’, but it is easy to change this to collect states as strings and to deal with the necessary change to a number for write-back as required.

[{"id":"6c0af22021cbdcb6","type":"api-current-state","z":"92ed4c92bf112b10","name":"Read state (numbers)","server":"","version":3,"outputs":1,"halt_if":"","halt_if_type":"str","halt_if_compare":"is","entity_id":"","state_type":"num","blockInputOverrides":false,"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":740,"y":820,"wires":[["76d3e4092163bec9"]]},{"id":"da4bce88a8848620","type":"inject","z":"92ed4c92bf112b10","name":"Run to save state values to context","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[\"input_number.help_fc_power\",\"input_number.help_in_battery_cycles\",\"input_number.help_temp_number\"]","payloadType":"json","x":220,"y":820,"wires":[["dccac1f4373dde9d"]]},{"id":"4b89f3ba2935375e","type":"debug","z":"92ed4c92bf112b10","name":"debug 13","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1260,"y":820,"wires":[]},{"id":"dccac1f4373dde9d","type":"split","z":"92ed4c92bf112b10","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":430,"y":820,"wires":[["575c24e171f0ef4b"]]},{"id":"575c24e171f0ef4b","type":"change","z":"92ed4c92bf112b10","name":"Entity ID","rules":[{"t":"set","p":"topic","pt":"msg","to":"payload","tot":"msg"},{"t":"set","p":"payload","pt":"msg","to":"{\"entityId\": payload}","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":560,"y":820,"wires":[["6c0af22021cbdcb6"]]},{"id":"76d3e4092163bec9","type":"join","z":"92ed4c92bf112b10","name":"","mode":"custom","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":910,"y":820,"wires":[["d2361b949b499ecf"]]},{"id":"d2361b949b499ecf","type":"change","z":"92ed4c92bf112b10","name":"Save object to context","rules":[{"t":"set","p":"SaveInputs","pt":"flow","to":"payload","tot":"msg","dc":true}],"action":"","property":"","from":"","to":"","reg":false,"x":1080,"y":820,"wires":[["4b89f3ba2935375e"]]},{"id":"2788765ab6b8cf84","type":"inject","z":"92ed4c92bf112b10","name":"Run to reset state values","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":190,"y":880,"wires":[["6da32448695ba68d"]]},{"id":"6da32448695ba68d","type":"change","z":"92ed4c92bf112b10","name":"Read object from context","rules":[{"t":"set","p":"payload","pt":"msg","to":"SaveInputs","tot":"flow","dc":true},{"t":"set","p":"payload","pt":"msg","to":"$spread(payload)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":430,"y":880,"wires":[["982750997a6f2398"]]},{"id":"982750997a6f2398","type":"split","z":"92ed4c92bf112b10","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":610,"y":880,"wires":[["928b8cedad382b07"]]},{"id":"080860e921b7f8c4","type":"debug","z":"92ed4c92bf112b10","name":"debug 14","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1260,"y":880,"wires":[]},{"id":"5b78f62a513063d3","type":"api-call-service","z":"92ed4c92bf112b10","name":"Write state (input number)","server":"","version":5,"debugenabled":false,"domain":"input_number","service":"set_value","areaId":[],"deviceId":[],"entityId":[],"data":"","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"all","x":950,"y":880,"wires":[["080860e921b7f8c4"]]},{"id":"928b8cedad382b07","type":"change","z":"92ed4c92bf112b10","name":"Set for Call","rules":[{"t":"set","p":"payload","pt":"msg","to":"(\t    $id:=$keys(payload)[0];\t    $value:=$lookup(payload,$id);\t    {\"target\": {\"entity_id\": $id},\t     \"data\": {\"value\": $value}\t    }\t\t\t)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":750,"y":880,"wires":[["5b78f62a513063d3"]]}]
3 Likes

i appreciate all your input and i will take it all into consideration, as far as braver soul than you. Sir, im afraid im am not as brave as youve given me credit for. As th last save file in my flow does not save over the actual config file, it just saves over a file named configuration.yaml that is inside the nodered addon folder incase this stuff doesnt work correctly! it gives me time to deal with things that may pop up or better ideas from peope such as yourself and @Mikefila .

Again i aprreciate both your insight in this matter and will get back to attackng this project tonight.

Also thank you i did not know if i removed initial state from config yaml it would automatically save