NodeRed 17.0.4 what happened?

Hello
I’m on HA Core 2023.11.3 and NodeRed addon 16.0.2 (NodeRed v3.1.0)

Two strange things happened when trying to up the NodeRed

A newer version appears incompatible with my HA
After update to 17.0.4, NR was not able to connect HA. It was showing an error message: Extra keys not allowed @ data['return_response].
I noticed, that node-red-contrib-home-assistant-websocket distributed with this package is outdated. After the update I just got not connected or something similar. Simply dead-end. I’m not aware of any breaking changes.

Rollback doesn’t work
I rolled back to the previous version from partial/automatic backup. NodeRed was restored without any Home Assistant-related nodes. node-red-contrib-home-assistant-websocket was missing. I opted for installation, all nodes re-appeared. Bit strange that the package appeared as as version 0.59.0 while I was installing the most recent 0.62.3. But probably it’s better for me since the newest module might be a root cause I was facing after NR update.

But why NR after rollback not contain HA extension?

I’m just trying to learn about what is happening and how to track dependencies to avoid such situations in the future.

The very first time you install the NR addon it installs the most recent node-red-contrib-home-assistant-websocket . You can see "0.62.3" in the dependency list.

However once you have node red installed, home assistant nodes do not get updated automatically. You need to go to the palette manger and update there. You will see a yellow outline around the HA nodes.

There will also be another button next to deploy. You CAN press it to update ALL the home assistant nodes at once. OR any new node you bring out will be an updated version while the other will stay outlined in yellow(older version). If you click on any yellow node, it will update that node only.

It’s hard to say what happened as you did not supply the original error logs. In general installing an addon version that is newer that the OS is not advised. There is no guarantee that there will be backwards compatibility. In other word a newer NR is not going to realize that your OS is older.

Sometimes this button is only shown on the first login after an upgrade (or maybe its until you make a first deploy), but if the button goes away without being pressed the older versions stay and only the yellow outline is to be seen as a sign.

The world has turned many times and much water has passed beneath many bridges between Home Assistant NR addon 16.0.2 and NR addon 17.0.4

HA now provides service call returns, and WebSocket nodes now have to deal with this (basic cause of your error message I guess - you have a miss-match between your versions in how they expect and deal or don’t expect and don’t deal with the new service call returns).

Node-RED has moved to v3.1.1, and the HA NR add-on has finally been updated to include the latest NR.

Due to several recent breaking changes between WebSocket nodes and variously HA, and NR versions, the WebSocket nodes automatically check the NR version and the HA version at start up, and take the best course of action by deactivating where prerequisites are not met. Thus, if you have the latest WebSocket node set but you do not have the required HA version and NR version, they will ‘go missing’ on purpose.

As commented above, you have to manually update the WebSocket nodes, but it is HIGHLY advisable to check dependencies beforehand. There has recently been much consternation all round over the latest version being incompatible with the (then but now fixed) current installed NR addon.

As you asked - I will point you to this discussion which has most of the details, including the inter-dependencies and where to look for them.

https://community.home-assistant.io/t/warning-do-not-update-websocket-to-0-61-1-with-add-on-version-16-0-2/653887/1

Oops.

https://github.com/zachowj/node-red-contrib-home-assistant-websocket?tab=readme-ov-file#prerequisites

Well, I think that there is your answer!

I’m on NR 17.0.4 and HA Core 2024.1.6. I’m getting a new error after the upgrade to 17.0.4:

TypeError: Cannot read properties of undefined (reading 'replace')

This is appearing for all of my iOS actionable notifications, using the subflow originally put together by zachowj:

[{"id":"3586791854cfef41","type":"function","z":"6dc0247c.d7210c","name":"create service call","func":"\nmsg._originalPayload = msg.payload;\nflow.set('latestMessage', msg);\n\nvar xTitle = \"\";\nvar xSubtitle = \"\";\nvar xMessage = \"\";\nvar xServices = \"\";\nvar xTag = \"\";\nvar xUrl = \"\";\n\n// overrides\nif (typeof msg.notificationOverride !== 'undefined' && msg.notificationOverride !== null) {\n    if (typeof msg.notificationOverride.title !== 'undefined' && msg.notificationOverride.title !=='' && msg.notificationOverride.title !== null){\n        xTitle = msg.notificationOverride.title;\n    } else {\n        xTitle = env.get('title');\n    }\n    if (typeof msg.notificationOverride.subtitle !== 'undefined' && msg.notificationOverride.subtitle !==''  && msg.notificationOverride.subtitle !== null){\n        xSubtitle = msg.notificationOverride.subtitle;\n    } else {\n        xSubtitle = env.get('subtitle');\n    }\n    if (typeof msg.notificationOverride.message !== 'undefined' && msg.notificationOverride.message !=='' &&  msg.notificationOverride.message !== null){\n        xMessage = msg.notificationOverride.message;\n    } else {\n        xMessage = env.get('message');\n    }\n    if (typeof msg.notificationOverride.url !== 'undefined' && msg.notificationOverride.url !=='' &&  msg.notificationOverride.url !== null){\n        xUrl = msg.notificationOverride.url;\n    } else {\n        xUrl = env.get('notificationUrl');\n    }\n    if (typeof msg.notificationOverride.services !== 'undefined' && msg.notificationOverride.services !=='' && msg.notificationOverride.services !== null){\n        xServices = msg.notificationOverride.services;\n    } else {\n        xServices = env.get('service');\n    }\n    flow.set('service', xServices);\n    if (typeof msg.notificationOverride.tag !== 'undefined' && msg.notificationOverride.tag !=='' && msg.notificationOverride.tag !== null){\n        xTag = msg.notificationOverride.tag;\n    } else {\n        if (env.get('tag') !== '') {\n            xTag = `${env.get('tag').replace(/[^\\w\\s]/gi, '').replace(/\\s+/g,'_').toUpperCase()}`;\n        } else {\n            // need to still set this to something in case clear_notification is sent.\n            xTag = `${env.get('title').replace(/[^\\w\\s]/gi, '').replace(/\\s+/g,'_').toUpperCase()}_${flow.get('random')}`;\n        } \n    }\n    flow.set('tag',xTag);\n} else {\n    // If no override is sent in...\n    if (env.get('tag') !== '') {\n        xTag = `${env.get('tag').replace(/[^\\w\\s]/gi, '').replace(/\\s+/g,'_').toUpperCase()}`;\n    } else {\n        // need to still set this to something in case clear_notification is sent.\n        xTag = `${env.get('title').replace(/[^\\w\\s]/gi, '').replace(/\\s+/g,'_').toUpperCase()}_${flow.get('random')}`;\n    } \n    flow.set('tag',xTag);\n    \n    xServices = env.get('service');\n    flow.set('service', xServices);\n    \n    xTitle = env.get('title');\n    xSubtitle = env.get('subtitle');\n    xMessage = env.get('message');\n    xUrl = env.get('notificationUrl');\n}\n\n\n\nconst services = flow.get('service');\nif(!services) {\n    node.status({\n        text: 'no services defined',\n        shape: 'ring',\n        fill: 'red'\n    });\n    return;    \n} else if (services == \"NOONE\"){\n    node.status({\n        text: 'No one to send to',\n        shape: 'ring',\n        fill: 'yellow'\n    });\n    return;\n}\n\n\n\n// create actions\nconst actions = [];\n[1,2,3,4].forEach(i => {\n    const name = `action${i}`\n    if (env.get(`${name}Title`) !== ''){\n        const action = env.get(`${name}Title`).replace(/[^\\w\\s]/gi, '').replace(/\\s+/g,'_').toUpperCase();\n        const title = env.get(`${name}Title`);\n        const activationMode = env.get(`${name}ActivationMode`);  \n        \n        const uri = env.get(`${name}Uri`);\n        const textInputButtonTitle = env.get(`${name}TextInputButtonTitle`);\n        const textInputPlaceholder = env.get(`${name}TextInputPlaceHolder`);\n        const authenticationRequired = env.get(`${name}AuthenticationRequired`);\n        const destructive = env.get(`${name}Destructive`);\n        const behavior = env.get(`${name}Behavior`);\n        const icon = env.get(`${name}Icon`);\n        \n        const actionObject = {};\n        actionObject.action = action;\n        actionObject.title = title;\n        actionObject.activationMode = activationMode;\n        \n        if (uri != \"\") actionObject.uri = uri;\n        if (textInputButtonTitle != \"\") actionObject.textInputButtonTitle = textInputButtonTitle;\n        if (textInputPlaceholder != \"\") actionObject.textInputPlaceholder = textInputPlaceholder;\n        if (authenticationRequired != \"\") actionObject.authenticationRequired = authenticationRequired;\n        if (destructive != \"\") actionObject.destructive = destructive;\n        if (behavior != \"\") actionObject.behavior = behavior;\n        if (icon != \"\") actionObject.icon = icon;\n        \n        actions.push(actionObject);\n    }\n});\n\n// create msg object\n\nmsg.payload = {\n        domain: 'notify',\n        data: {\n            title: xTitle,\n            message: xMessage,\n            data: {\n                push: {},\n                tag: xTag\n            }\n        }\n    };\n\nif (actions.length > 0) {\n    msg.payload.data.data.action_data = {tag: xTag};\n    msg.payload.data.data.actions = actions;\n    \n}\n\n\n// notification url\nif (xUrl !== \"\") {\n    msg.payload.data.data.url = {};\n    msg.payload.data.data.url = xUrl;\n}\n\n// subtitle\nif (xSubtitle !== '') {\n    msg.payload.data.data.subtitle = xSubtitle;\n}\n\n// sound information\nmsg.payload.data.data.push.sound = {};\nif (env.get('customSound').length > 0){\n    msg.payload.data.data.push.sound.name = env.get('customSound');\n} else if (env.get('customSoundPreInstalled').length > 0) {\n    msg.payload.data.data.push.sound.name = env.get('customSoundPreInstalled');\n} else {\n    msg.payload.data.data.push.sound.name = 'default';\n}\nif (env.get('isCriticalNotification')) {\n    msg.payload.data.data.push.sound.critical = 1;\n    msg.payload.data.data.push.sound.volume = 1.0;\n}\n\n// group thread id\nif (env.get('group') !== '') {\n    msg.payload.data.data.group = env.get('group');\n}\n\n// Map Information\nif (env.get('latitudeFirst') !== 0 && env.get('longitudeFirst') !== 0){\n    msg.payload.data.data.action_data.latitude = env.get('latitudeFirst');\n    msg.payload.data.data.action_data.longitude = env.get('longitudeFirst');\n    if (env.get('latitudeSecond') !== 0 && env.get('longitudeSecond') !== 0){\n        msg.payload.data.data.action_data.second_latitude = env.get('latitudeSecond');\n        msg.payload.data.data.action_data.second_longitude = env.get('longitudeSecond');\n        // add in all the extras, which default to false.\n        msg.payload.data.data.action_data.shows_line_between_points = env.get('showLineBetweenPoints');\n        msg.payload.data.data.action_data.shows_compass = env.get('showCompass');\n        msg.payload.data.data.action_data.shows_points_of_interest = env.get('showPointsOfInterest');\n        msg.payload.data.data.action_data.shows_scale = env.get('showScale');\n        msg.payload.data.data.action_data.shows_traffic = env.get('showTraffic');\n        msg.payload.data.data.action_data.shows_user_location = env.get('showUserLocation');\n    }\n}\n// camera information\nif (env.get('cameraEntity') !== '') {\n    msg.payload.data.data.entity_id = env.get('cameraEntity');\n}\n// media information\nif (env.get('contentUrl') !== '') {\n\n} else {\n    if (env.get('imagePath') !== '') {\n        msg.payload.data.data.image = env.get('imagePath');\n    }\n    if (env.get('videoPath') !== '') {\n        msg.payload.data.data.video = env.get('videoPath');\n    }\n    if (env.get('audioPath') !== '') {\n        msg.payload.data.data.audio = env.get('audioPath');\n    }\n}\nif (env.get('contentUrl') !== '' || env.get('imagePath') !== '' || env.get('videoPath') !== '' || env.get('audioPath') !== '') {\n    if (env.get('lazyLoading')) {\n        msg.payload.data.data.lazy = env.get('lazyLoading');\n    }\n    if (env.get('hideThumbnail')) {\n        msg.payload.data.data.attachment = {};\n        msg.payload.data.data.attachment['hide-thumbnail'] = env.get('hideThumbnail');\n    }\n}\n\nservices.trim().split(/,\\s*/).forEach(service => {\n    if(!service) return;\n    msg.payload.service = service;\n    node.send(msg);\n});\n\nnode.done();","outputs":1,"noerr":0,"initialize":"flow.set('random',Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5).toUpperCase());","finalize":"","libs":[],"x":430,"y":80,"wires":[["84115d3979a0b0b6"]]},{"id":"90ab222038404541","type":"switch","z":"6dc0247c.d7210c","name":"which action?","property":"responseIndex","propertyType":"flow","rules":[{"t":"eq","v":"1","vt":"num"},{"t":"eq","v":"2","vt":"num"},{"t":"eq","v":"3","vt":"num"},{"t":"eq","v":"4","vt":"num"}],"checkall":"true","repair":false,"outputs":4,"x":1280,"y":380,"wires":[[],[],[],[]]},{"id":"f3d74762ed30ae54","type":"status","z":"6dc0247c.d7210c","name":"","scope":["3586791854cfef41","b1e2e20511543565","fcf9f9c27469a607","6fdf5748e207aa7e"],"x":100,"y":320,"wires":[[]]},{"id":"b1e2e20511543565","type":"function","z":"6dc0247c.d7210c","name":"build message","func":"const latestMessage = flow.get('latestMessage');\nconst event = msg.payload.event;\n\n\nlatestMessage.payload = latestMessage._originalPayload;\nlatestMessage.eventData = msg.payload;\ndelete latestMessage._originalPayload;\n\nif(env.get('userInfo')) {\n    const userData = msg.userData.find(u => u.id === msg.payload.context.user_id);\n    latestMessage.userData = userData;\n}\nnode.warn('title: ' + env.get('title'));\nnode.warn('1: ' + env.get(`action1Title`));\nnode.warn('2: ' + env.get(`action2Title`));\nnode.warn('3: ' + env.get(`action3Title`));\nnode.warn('4: ' + env.get(`action4Title`));\n\nconst index = [1,2,3,4].find(i => env.get(`action${i}Title`).replace(/[^\\w\\s]/gi, '').replace(/\\s+/g,'_').toUpperCase() === event.actionName);\nflow.set(\"responseIndex\",index);\nnode.status({\n    text: `${env.get(`action${index}Title`).replace(/[^\\w\\s]/gi, '').replace(/\\s+/g,'_').toUpperCase()} at: ${getPrettyDate()}`,\n    shape: 'dot',\n    fill: 'green'\n});\n\nreturn latestMessage;\n\n\nfunction getPrettyDate() {\n    return new Date().toLocaleDateString('en-US', {\n        month: 'short',\n        day: 'numeric',\n        hour12: false,\n        hour: 'numeric',\n        minute: 'numeric',\n    });\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1080,"y":380,"wires":[["90ab222038404541"]]},{"id":"a7d52acc08b671e0","type":"switch","z":"6dc0247c.d7210c","name":"belongs here?","property":"payload.event.action_data.tag","propertyType":"msg","rules":[{"t":"eq","v":"tag","vt":"flow"}],"checkall":"true","repair":false,"outputs":1,"x":412,"y":256,"wires":[["a69ed312811b3a6d"]]},{"id":"7bc5c379b10a2fbf","type":"ha-api","z":"6dc0247c.d7210c","name":"get user info","server":"296c0678.b5f9ca","version":1,"debugenabled":false,"protocol":"websocket","method":"get","path":"","data":"{\"type\": \"config/auth/list\"}","dataType":"json","responseType":"json","outputProperties":[{"property":"userData","propertyType":"msg","value":"","valueType":"results"}],"x":950,"y":320,"wires":[["b1e2e20511543565"]]},{"id":"c25a82298fbaad78","type":"switch","z":"6dc0247c.d7210c","name":"fetch user info?","property":"userInfo","propertyType":"env","rules":[{"t":"true"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":800,"y":380,"wires":[["7bc5c379b10a2fbf"],["b1e2e20511543565"]]},{"id":"b738bc2e474ec4a5","type":"server-events","z":"6dc0247c.d7210c","name":"ios.notification_action_fired","server":"296c0678.b5f9ca","version":3,"exposeAsEntityConfig":"","eventType":"ios.notification_action_fired","waitForRunning":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"},{"property":"event_type","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"}],"x":164,"y":256,"wires":[["a7d52acc08b671e0"]]},{"id":"fcf9f9c27469a607","type":"api-call-service","z":"6dc0247c.d7210c","name":"Send Notifications","server":"296c0678.b5f9ca","version":5,"debugenabled":false,"domain":"notify","service":"","areaId":[],"deviceId":[],"entityId":[],"data":"","dataType":"json","mergeContext":"callServiceData","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":830,"y":80,"wires":[[]]},{"id":"6fdf5748e207aa7e","type":"function","z":"6dc0247c.d7210c","name":"create CLEAR service call","func":"msg._originalPayload = msg.payload;\nflow.set('latestMessage', msg);\n\nvar clearAll = false;\nvar services = \"\";\n\nif (typeof msg.notificationOverride !== 'undefined') {\n    if (typeof msg.notificationOverride.clear !== 'undefined' && msg.notificationOverride.clear !== null && msg.notificationOverride.clear) {\n        clearAll = true;\n    }\n    \n    if (typeof msg.notificationOverride.services !== 'undefined' && msg.notificationOverride.services !== null && msg.notificationOverride.services){\n        services = msg.notificationOverride.services;\n    } else {\n        services = flow.get('service');\n    }\n} else {\n    services = flow.get('service');\n}\n\nif(!services) {\n    node.status({\n        text: 'no services defined',\n        shape: 'ring',\n        fill: 'red'\n    });\n    return;    \n}\n\n// create ios msg object\n\nmsg.payload = {\n        domain: 'notify',\n        data: {\n            message: \"clear_notification\",\n            data: {\n                tag: flow.get('tag'),\n            }\n        }\n    };\n\nfunction getPrettyDate() {\n    return new Date().toLocaleDateString('en-US', {\n        month: 'short',\n        day: 'numeric',\n        hour12: false,\n        hour: 'numeric',\n        minute: 'numeric',\n    });\n}\n\nif (clearAll){\n    delete msg.notificationOverride;\n}\n    \nvar xCountCleared = 0;\nservices.trim().split(/,\\s*/).forEach(service => {\n    if(!service) return;\n    // only clear on devices that didn't send the event.\n    \n    if (clearAll){\n        msg.payload.service = service;\n        node.send(msg);\n        xCountCleared++;\n    } else if (!service.includes(msg._originalPayload.event.sourceDeviceID)){\n        msg.payload.service = service;\n        node.send(msg);\n        xCountCleared++;\n    }\n});\nif (xCountCleared > 0) {\n    node.status({\n        text: `${xCountCleared} messages cleared at: ${getPrettyDate()}`,\n        shape: 'dot',\n        fill: 'blue'\n    });\n} else {\n    node.status({\n        text: `No messages cleared: ${getPrettyDate()}`,\n        shape: 'dot',\n        fill: 'red'\n    });\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":750,"y":220,"wires":[["05b8370d35105d3c"]]},{"id":"a69ed312811b3a6d","type":"switch","z":"6dc0247c.d7210c","name":"Clear Notification on Action?","property":"isClearNotificationsOnAction","propertyType":"env","rules":[{"t":"true"},{"t":"false"}],"checkall":"true","repair":false,"outputs":2,"x":460,"y":320,"wires":[["6fdf5748e207aa7e","227a6ab8de989095"],["c25a82298fbaad78"]]},{"id":"e229e2d8efb85d22","type":"api-call-service","z":"6dc0247c.d7210c","name":"Clear Notifications","server":"296c0678.b5f9ca","version":5,"debugenabled":false,"domain":"notify","service":"","areaId":[],"deviceId":[],"entityId":[],"data":"","dataType":"json","mergeContext":"callServiceData","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1190,"y":140,"wires":[[]]},{"id":"227a6ab8de989095","type":"delay","z":"6dc0247c.d7210c","name":"","pauseType":"delay","timeout":"10","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"outputs":1,"x":720,"y":320,"wires":[["c25a82298fbaad78"]]},{"id":"6b940f2bfba5188c","type":"switch","z":"6dc0247c.d7210c","name":"clear?","property":"notificationOverride.clear","propertyType":"msg","rules":[{"t":"istype","v":"undefined","vt":"undefined"},{"t":"null"},{"t":"false"},{"t":"true"}],"checkall":"false","repair":false,"outputs":4,"x":210,"y":80,"wires":[["3586791854cfef41"],["3586791854cfef41"],["3586791854cfef41"],["6fdf5748e207aa7e"]]},{"id":"05b8370d35105d3c","type":"delay","z":"6dc0247c.d7210c","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"outputs":1,"x":970,"y":160,"wires":[["e229e2d8efb85d22"]]},{"id":"84115d3979a0b0b6","type":"delay","z":"6dc0247c.d7210c","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"outputs":1,"x":630,"y":80,"wires":[["fcf9f9c27469a607"]]},{"id":"296c0678.b5f9ca","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}]

I’m not sure what to update in order to get those working again, though. I get the impression it has something to do with this .replace in the build message function node:

const index = [1,2,3,4].find(i => env.get(`action${i}Title`).replace(/[^\w\s]/gi, '').replace(/\s+/g,'_').toUpperCase() === event.actionName);
flow.set("responseIndex",index);
node.status({
    text: `${env.get(`action${index}Title`).replace(/[^\w\s]/gi, '').replace(/\s+/g,'_').toUpperCase()} at: ${getPrettyDate()}`,
    shape: 'dot',
    fill: 'green'
});

EDIT: It looks like the subflow is still working for the most part, but I’m getting warn debug messages every time they run.

I believe that needs to be just /g and not /gi

Edit: or /g/i This is probably what it’s supposed to be.

(/[^\w\s]/g/i, '')

This is wrong, I’m confused as to the difference between this and this

One is js which is javascript vs jsref which is javascript Reference. Which is the right language to use in a function node?

That line probably produce another output than earlier and now the result is an object, where the function replace act differently.

The debug confirms that. Any thoughts on a fix?

I do not have any iOS devices, so I do not know what the result of that action is.

This is strange, becasue the most recent version in changelog states:

  • call-service: Allow call service to work prior to HA 2023.12 (92c7a3f)

Anyway, thank you all for the detailed answers. It all falls into the fact, that the NodeRed update brought newer version of node-red-contrib-home-assistant-websocket without warning about breaking change.
Not sure if it’s incidental (the location of NR has been changed at the same time) or intentional.

However, the rollback to the previous working version should bring the system to a working state without any hassle. existence of ha websocket module after the rollback is something I cannot understand.

I am of the opinion that it helps to remember that this is open source and mostly community driven. Like any community notice board in the local parish hall, we are expected to go and read it, not for it to come and knock on our doors.

The Node-RED as an addon is dependent on Node-RED. There are well documented updates, and the breaking changes are included.

Here is the breaking change specifically for the file move, which did indeed catch quite a few unawares, particularly as it did not work correctly at first release. As far as I understand this stuff, this is a non-reversable change, but designed to make addon backups actually work correctly in future. Historically, the NR flow node-set was stored in the HA configuration folder, which was backed up when you did an HA backup. The Addon backup did not include this, hence restoring an addon on its own did not restore the addon configuration, and restoring an HA configuration did restore the addon configuration. Therefore a welcome change for the better, but yes if you tried to restore back over the old way or over the changeover it did not work.

https://github.com/hassio-addons/addon-node-red/releases/tag/v16.0.0

The Home Assistant WebSocket nodes sit on top of Node-RED and Home Assistant, and therefore have a more complex dependency set. If I am correct in this, when first installing Node-RED as an addon, it auto-installs an associated WebSocket node-set, tied to the latest dependency. After that, it is up to us to keep it up to date.

Since HA is also changing, the WebSocket nodes have to deal with HA as a dependency, and Node-RED (addon) as a dependency. Recently Node-RED has changed from v2+ to v3+, HA has made significant changes to the addon file structure, and to the way service calls work.

The WebSocket breaking changes are all listed.

Here is the breaking change for the nodes requiring Node-RED v3.1.1+. The impact of this is that, if you use NR as an addon, that comes with a given NR version, which needs to be checked before updating the WebSocket nodes. This caused issues with updating the WebSocket nodes when the NR addon was still using an older NR version.

https://github.com/zachowj/node-red-contrib-home-assistant-websocket/releases/tag/v0.61.0

Here is the breaking change for the nodes requiring HA 2023.12+. This is down to the phased in changes in HA from service calls returning nothing, to service calls returning something optional, to service calls returning something that you have to accept. Hence a phased change in the WebSocket nodes to try and keep up with this.

https://github.com/zachowj/node-red-contrib-home-assistant-websocket/releases/tag/v0.60.0

Going forward the HA team appear to have more changes planned, including perhaps making entities devices, and attributes entities. I am sure that will bring a great deal of fun.

Apparently so, but if you look at the previous release that that one

Bug Fixes

  • Throw error if HA version doesn’t meet requirement (6d84f0c)

So I am still working on the assumption that the WebSocket nodes check the version and don’t work if you don’t have 2023.12 or +.