Use MQTT for Shelly Thermostatic Radiator Valves

BTW after reading Devil's article, it indeed seems that the Shelly API contains similar information:

So it is all in the API documenation, and if you know the correct keywords you can find it...

1 Like

Don't bother about it if that is not the route you intend to go. In fact the likelihood of me actually using this device in the near future is pretty low, there are just so many things to play around with that I never get round to doing half of the things I dream of.

1 Like

Found this in the Shelly API documentation:

Device state is reported periodically, every 30 seconds by default. This can be changed by setting a new period for updates: mqtt_update_period under /settings. A value of 0 will disable periodic updates.

1 Like

The Shelly API Documentation is my 'goto' for all things API, I am also registered in the Forum, which I then look through if I can't find/understand things in the API documentation, sometimes the nudge is just enough to make the fog clear.

I see how to upload a profile, thank you, and will have a look through the implementation in any 'quiet times' available to me, may take a while. Status is not something I need all the time, other than for archival purposes, but that is more the Engineer in me with the Data being available, rather than it being needed (if it is available, archive it!). In fact, most messages to the TRV I only send when things change, although I may check flow parameters every 30 secs, they will only be sent if something has changed.

The flow I originally posted (referenced above) was one to show that operation was possible and contained belt and braces logic as far as I understood at the time, the original 'original' included HTTP commands because one of my Shellys would not respond to MQTT (firmware problem, now fixed).

@Colin In that case, I will leave the PID part in the flow for now so that it is available to re-implement. You never know, I might get some time to go back to it. I do want to resolve the apparent close/open situation on any change in output from the PID - whether PID based (Persistence?) or Shelly based, just because it i there!

Please be aware that if f I get a problem in things like this, it is usually because I have only understood 90% of the requirements, and not because of any bad node/flow. That 10% can be a 'Killer' for me! :rofl:

FWIW, current state with 'original' basic test flows...

TRV flow.json (73.7 KB)

(Too big to show as </>)

ADDED:
There has been an historic problem with http/API calls. You will often see this (or similar)...
http://XXX.XXX.XXX.XXX/settings/thermostats/0

What you need is...
http://XXX.XXX.XXX.XXX/settings/thermostat/0
*Note the removed 's' of 'thermostats'

So, this HTTP request is confirmed as working...
http://XXX.XXX.XXX.XXX/settings/thermostat/0?schedule_profile=1&schedule_rules=0830-0123456-18.5,1030-0123456-20.5

1 Like

Does this also apply to TRV's ie. battery powered devices ? sounds like overkill/battery killer.

@bakman2,
It is indeed not what I experience with my TRV. So like you say it probably won't be applicable to battery powered devices like their TRV's. But still not sure what the trigger is for those devices to send messages. And also not sure if I can get somehow the current mqtt_update_period value.

Found some time and had a play (gets me out of Xmas present wrapping!). It would seem (in my sphere of knowledge) that you cannot send two commands to the Shelly with MQTT sequentially and have them work.

This is the HTTP request with two commands/topics...
http://XXX.XXX.XXX.XXX/settings/thermostat/0?schedule_profile=1&schedule_rules=0830-0123456-18.5,1030-0123456-20.5
which is really two commands being sent at the same time.

Device: http://XXX.XXX.XXX.XXX/settings/thermostat/0?

  1. schedule_profile=1
  2. schedule_rules=0830-0123456-18.5,1030-0123456-20.5

To drive the single command valve position using MQTT Node I use this...
msg.topic = "shellies/shellytrv-/thermostat/0/command/valve_pos"
msg.payload = 100
But the profile messages don't work like this, and even when sent individually sequentially, they don't see to work.

This isn't really a problem as it would only be required infrequently, but thought I would just give a heads up. It possibly boils down to the fact that I cannot find a way to tie these commands together using equivalent MQTT.

1 Like

So, things have gone well and I have managed to get some more time!

I have to drive my Diverter Valve 'manually' due to the way the boiler was plumbed in, so I still need NR to control the valve. Early days, but a flow was created that has access to five Profile files, these are on the same computer as NR and from here you can download the files both to the TRV and the running profile for NR so that the setpoint can be extracted. This can then be used the same as it has been,in the past - simple comparison >=<

[{"id":"0e47621e9ad92442","type":"comment","z":"0d80ab0dc9580192","name":"http://172.27.123.251/settings/thermostats/0?schedule_profile=1&schedule_rules=0600-0123456-20, 1000-0123456-19, 1500-0123456-21, 1800-0123456-22, 2100-0123456-16","info":"","x":860,"y":40,"wires":[]},{"id":"e55ff2a0097c1601","type":"comment","z":"0d80ab0dc9580192","name":"filepath","info":"// file path with / at the end\nvar filepath = \"/mnt/array1/NodeRed/AutoBackups/\"; // This is the path\nvar localfilepath = \"/home/pi/node-red-storage/\";\n//\"220129_nodered_backup.zip\"","x":370,"y":180,"wires":[]},{"id":"d22324c7f6658b15","type":"inject","z":"0d80ab0dc9580192","name":"","props":[{"p":"filename","v":"Profile1.csv","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":"1","topic":"","x":170,"y":60,"wires":[["236e0b92934aa646"]]},{"id":"236e0b92934aa646","type":"file in","z":"0d80ab0dc9580192","name":"Profile","filename":"filename","filenameType":"msg","format":"utf8","chunk":false,"sendError":false,"encoding":"none","allProps":false,"x":350,"y":220,"wires":[["2c5e99851d92fd90"]]},{"id":"2c5e99851d92fd90","type":"csv","z":"0d80ab0dc9580192","name":"","sep":",","hdrin":false,"hdrout":"none","multi":"mult","ret":"\\n","temp":"","skip":"0","strings":true,"include_empty_strings":"","include_null_values":"","x":470,"y":220,"wires":[["79c4935ebc75c29b"]]},{"id":"2a17101cae4bbbb2","type":"function","z":"0d80ab0dc9580192","name":"Frost/Shower Temps","func":"global.set('tempFrost', 14);\nglobal.set('tempHWAway', 22);\nglobal.set('tempShower', 42); \nglobal.set('tempBoilerDesired', 52);","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":620,"y":100,"wires":[[]]},{"id":"7c1c4aa16c5ea0de","type":"inject","z":"0d80ab0dc9580192","d":true,"name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":"0.7","topic":"","payload":"","payloadType":"date","x":430,"y":100,"wires":[["2a17101cae4bbbb2"]]},{"id":"79c4935ebc75c29b","type":"function","z":"0d80ab0dc9580192","name":"split file","func":"global.set('profileCurrent', msg.payload[0].col1, \"storeInFile\");\nglobal.set('profileName', msg.payload[1].col1, \"storeInFile\");\n\nlet length = msg.payload.length;\nlet arr = msg.payload.slice(2)\nglobal.set('profileEvents', arr);\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":600,"y":220,"wires":[["a62630aa5523a604","1f06132e71a9db59"]]},{"id":"a62630aa5523a604","type":"debug","z":"0d80ab0dc9580192","name":"debug 118","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":690,"y":180,"wires":[]},{"id":"1f06132e71a9db59","type":"function","z":"0d80ab0dc9580192","name":"set CH time/deg/days","func":"let eventTime;\nlet eventTemp;\nlet eventDays;\nlet length;\nlet eventString = \"\";\nlet arr = msg.payload;\n\nlet profileNumber = arr[0].col1;\nlet profileName = arr[1].col1;\n\nfor (let i = 2; i < arr.length; i++) {\n    eventTime = arr[i].col1;\n    //eventTime = eventTime.toString();\n    eventTemp = arr[i].col2;\n    eventDays = arr[i].col3;\n    eventString = eventString + eventTime + \"-\" + eventDays + \"-\" + eventTemp\n    if (i < arr.length){\n        eventString = eventString + \",\";\n    }\n}\n\nlet messageString = \"http://172.27.123.251/settings/thermostats/0?schedule_profile=\" + profileNumber + \"&profile_name=\" + profileName + \"&schedule_rules=\" + eventString\nmsg.url = messageString;\nreturn msg","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":800,"y":220,"wires":[["1ab3183c161818fc","027cbeba6b1c6284"]]},{"id":"1ab3183c161818fc","type":"debug","z":"0d80ab0dc9580192","name":"debug 120","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":890,"y":180,"wires":[]},{"id":"027cbeba6b1c6284","type":"http request","z":"0d80ab0dc9580192","name":"","method":"GET","ret":"txt","paytoqs":false,"url":"","persist":false,"insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":1010,"y":220,"wires":[["be0d47aade8f41b7","2a322bc99fb6d87c"]]},{"id":"be0d47aade8f41b7","type":"debug","z":"0d80ab0dc9580192","name":"debug 121","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1090,"y":180,"wires":[]},{"id":"cef284c5dcd4dac6","type":"inject","z":"0d80ab0dc9580192","name":"","props":[{"p":"filename","v":"Profile2.csv","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":"1","topic":"","x":170,"y":100,"wires":[["236e0b92934aa646"]]},{"id":"8d13df490a229a0a","type":"inject","z":"0d80ab0dc9580192","name":"","props":[{"p":"filename","v":"Profile3.csv","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":"1","topic":"","x":170,"y":140,"wires":[["236e0b92934aa646"]]},{"id":"4740aa084ccf5b56","type":"inject","z":"0d80ab0dc9580192","name":"","props":[{"p":"filename","v":"Profile4.csv","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":"1","topic":"","x":170,"y":180,"wires":[["236e0b92934aa646"]]},{"id":"78bd9619fe660f2e","type":"inject","z":"0d80ab0dc9580192","name":"","props":[{"p":"filename","v":"Profile5.csv","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":"1","topic":"","x":170,"y":220,"wires":[["236e0b92934aa646"]]},{"id":"347f7f877f9020c9","type":"catch","z":"0d80ab0dc9580192","name":"","scope":null,"uncaught":false,"x":1080,"y":80,"wires":[["1835d45f1fd96af9"]]},{"id":"1835d45f1fd96af9","type":"debug","z":"0d80ab0dc9580192","name":"debug 98","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1220,"y":80,"wires":[]},{"id":"2a322bc99fb6d87c","type":"function","z":"0d80ab0dc9580192","name":"set CH time/deg/days","func":"delete msg.payload;\ndelete msg.topic;\ndelete msg.filename;\ndelete msg.columns;\n\nlet length;\nlet setHours;\nlet setMinutes;\nlet eventTime;\nlet timeEvent;\nlet arr = [];\narr = global.get('profileEvents');\nlet tempDesired = 27 || 0;\n\n//Get current day start and end timestamp\nlet dateNow = Date.now();\nlet timeFrom = new Date(dateNow).setHours(0, 0, 0, 0);\nlet timeTo = new Date(dateNow).setHours(23, 59, 59, 0)\n\n//Add offset, iterate until file time < current time\nfor (let i = arr.length - 1; i >= 0; i--) {\n    eventTime = arr[i].col1;\n    eventTime = eventTime.toString();\n    msg.test1 = eventTime;\n    setHours = eventTime.slice(0, 2);\n    setMinutes = eventTime.slice(2);\n    timeEvent = new Date(timeFrom).setHours(setHours, setMinutes, 0, 0); //Date from midnight this morning\n    tempDesired = parseFloat(arr[i].col2);\n    msg.tempDesired = tempDesired;\n    msg.dateNow = dateNow;\n    msg.timeEvent = timeEvent;\n\n    if (dateNow > timeEvent) {\n        global.set('tempDesiredCH', tempDesired, 'storeInFile');\n        node.status({ text: timeEvent + \" | \" + tempDesired });\n        return msg;\n    }\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":360,"y":280,"wires":[["c5980b7598462543"]]},{"id":"c5980b7598462543","type":"debug","z":"0d80ab0dc9580192","name":"debug 119","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":590,"y":280,"wires":[]},{"id":"9dfc059a6c2db66a","type":"inject","z":"0d80ab0dc9580192","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":120,"y":280,"wires":[["2a322bc99fb6d87c"]]}]

Profiles.zip.txt (1.1 KB)

The profile is only loaded once and also goes to global.context. The times and setpoint are then extracted along with the current time and the setpoint set appropriately.

Comments/coding advice welcome!

Have you thought of using cron-plus for the schedules? That takes care of any timing etc. All you need to do is load the schedule profiles (suitably adjusted for cron-plus).

Having seen what you were doing I thought I could use some of your ideas in a different direction (I also use TRVs, although not Shellys). This is the result, hopefully it will give you some ideas also.

Happy Christmas

A JSON object would be easier to deal with than the CSV, something like this

[
    {
        "profile_name": "Living Room Weekdays",
        "rules": [
            {
                "days": "12345",
                "time": "10:30",
                "temperature": 18
            },
            {
                "days": "12345",
                "time": "14:30",
                "temperature": 20
            },
            {
                "days": "12345",
                "time": "22:30",
                "temperature": 15
            }
        ]
    },
    {
        "profile_name": "Living Room Weekend",
        "rules": [
            {
                "days": "06",
                "time": "11:30",
                "temperature": 18
            },
            {
                "days": "06",
                "time": "14:30",
                "temperature": 20
            },
            {
                "days": "06",
                "time": "22:30",
                "temperature": 15
            }
        ]
    }
]

As an exercise for myself I put together this flow which uses the JSON object above to produce an Object for use with a Shelly TRV (as part of /settings/thermostats/0 [ schedule_profile_names & schedule_rules]) and a payload which can be used by the node-red-contrib-cron-plus node.

Oops edit: forgot to put a 0 in seconds of cron expression

[{"id":"c5980b7598462543","type":"debug","z":"f859c7434a67ede7","name":"Shelly Out","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":690,"y":160,"wires":[]},{"id":"9dfc059a6c2db66a","type":"inject","z":"f859c7434a67ede7","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"profile_name\":\"Living Room Weekdays\",\"rules\":[{\"days\":\"12345\",\"time\":\"10:30\",\"temperature\":18},{\"days\":\"12345\",\"time\":\"14:30\",\"temperature\":20},{\"days\":\"12345\",\"time\":\"22:30\",\"temperature\":15}]},{\"profile_name\":\"Living Room Weekend\",\"rules\":[{\"days\":\"06\",\"time\":\"11:30\",\"temperature\":18},{\"days\":\"06\",\"time\":\"14:30\",\"temperature\":20},{\"days\":\"06\",\"time\":\"22:30\",\"temperature\":15}]}]","payloadType":"json","x":190,"y":160,"wires":[["3c1cab6a40a0d9c4","5128e2c3c6f15a0c"]]},{"id":"3c1cab6a40a0d9c4","type":"function","z":"f859c7434a67ede7","name":"Shelly Msg","func":"const schedules = msg.payload\n\nlet shellySchedule = {\n        schedule_profile_names: [],\n        schedule_rules: []\n\n}\n\nschedules.forEach((schedule) => {\n    shellySchedule.schedule_profile_names.push(schedule.profile_name)\n    schedule.rules.forEach(rule => {\n        shellySchedule.schedule_rules.push(`${rule.time.replace(':', '')}-${rule.days}-${rule.temperature}`)\n        })  \n})\n\nmsg.shelly = shellySchedule\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":450,"y":160,"wires":[["c5980b7598462543"]]},{"id":"5128e2c3c6f15a0c","type":"function","z":"f859c7434a67ede7","name":"CRON Msg","func":"function strToDays(string) {\n    const daysArray = ['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT']\n    let days = ''\n\n    for (let i = 0; i < string.length; i++) {\n        days = days + ',' + daysArray[string.charAt(i)]\n\n    }\n\n    return days.slice(1)\n}\n\nconst schedules = msg.payload\nconst cron = ['0', '*', '*', '*', '*', '*']\nlet cronExpression = ''\nlet cronExpressions = []\nlet cronSchedule = []\n\nschedules.forEach((schedule) => {\n    schedule.rules.forEach((rule, index) => {\n        cron[5] = strToDays(rule.days)\n        cron[1] = rule.time.split(':')[1]\n        cron[2] = rule.time.split(':')[0]\n        cronExpression = cron.reduce((accumulator, currentValue) => accumulator + ' ' + currentValue)\n        cronExpressions.push(cronExpression)\n\n        cronSchedule.push({ command: 'add', \n                            name: schedule.profile_name + ' ' + index, \n                            topic: 'schedule/' + schedule.profile_name, \n                            expression: cronExpression,\n                            expressionType: 'cron',\n                            payloadType: 'default',\n                            payload: rule.temperature,\n                            limit: null })\n    })\n    \n})\n\nmsg.payload = cronSchedule\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":450,"y":220,"wires":[["c5980b7598462543","0cef1eca98cb9705"]]},{"id":"e0f3f3d0e46771d5","type":"debug","z":"f859c7434a67ede7","name":"CRON Out","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1030,"y":220,"wires":[]},{"id":"eac62bbb6d723009","type":"inject","z":"f859c7434a67ede7","name":"List All to CRON Out","props":[{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"list-all","x":510,"y":280,"wires":[["0cef1eca98cb9705"]]},{"id":"0cef1eca98cb9705","type":"cronplus","z":"f859c7434a67ede7","name":"","outputField":"payload","timeZone":"","persistDynamic":true,"commandResponseMsgOutput":"output1","outputs":1,"options":[],"x":740,"y":220,"wires":[["e0f3f3d0e46771d5"]]}]

Having looked at the Shelly TRV data I do not quite see what the benefit of schedule_profile_names gives to controlling the TRV as there is no link to the schedule_rules but they make a handy way of adding information to data in Node-red

1 Like

@mudwalker,
Thanks a lot for sharing your solution!!!

Similar to the feedback from @Buckskin, I would prefer to have the schedule as json in my Inject nodes:

image

Don't have any other suggestions. Looks nice.

Just two questions:

  1. What is the purpose of the function node after the http request node?

  2. Am I correct that we use http instead of mqtt, only because we don't know how this works with mqtt? I am now wondering if http has disadvantages compared to mqtt? Just to know whether it is required that we spend more time on looking for an mqtt based solution...

1 Like

Do you mean to let the cron-plug node send commands to the TRV, at the moments where there are switchpoints in the profile. So that Node-RED tells the TRV when to go to another temperature?

If so then you need to measure very often the temperature, which means a lot of communication with the TRV. As a result the TRV battery will be empty much faster.

Therefore the idea was to send once a day the entire schedule to the TRV, and let the TRV itself decide when it needs to switch to another temperature.

But perhaps I misunderstood your proposal.

No. In @mudwalkers function, that you highlighted, he was creating a schedule for his boiler (at least that is what I think it was there for), so I created the cron output for that as that did seem to require a command for each schedule change.

My proposal (with the shelly output) is to send the schedule data to the TRV and let it get on with it. The idea suggested by @mudwalker of a schedule file (or context) was a good one so I wanted to come up with a way of converting my idea of the schedule into something that could be sent to a Shelly TRV.

I have a different TRV but I only have to send a schedule if it changes (and the schedule covers weekdays & weekends so it almost never gets changed). I do not think I have sent the TRVs a command since I set the attributes how I wanted them when I first installed them.

2 Likes

@Buckskin With JSON - I seem to hit a brick wall, or at least, get taken out of my comfort zone! I seem to ge ton well, and then have a horrendous crash, which knocks my confidence! BUT, your's and Bart's observation regarding JSON from the inject Node looks good. I will have to try it.

I did experiment with a CRON PLUS job as an 'active' driver to adjust temperature, but ended up 'developing my own' as I was not 100% sure about CRON after a power off. (I say my own, but it is not rocket science!! :rofl:) That is a solution that I know works, and can easily be included in a 'parameter refresh' loop but now I can see the logic behind your comments with regard to JSON. Don't like fetching files anyway if I can help it, always seems a bit clunky!

Agreed, the schedule_profile_names does not add anything other than convenience of identification.

@BartButenaers Thank you, always worried at what people will say with regard to code, but I also treat it as 'Training Opportunity' for someone to 'hedjucate' me!!

The function node you have indicated injects the current value for the desired temperature of the Living Room into context without accessing the TRV. This is then used by the rest of my flow to

  1. Switch the boiler ON/OFF
  2. Control the Diverter Valve

I used HTTP as I could not find the equivalent MQTT command string. iirc, all Shelly API calls for the TRV were developed for HTTP and MQTT has gradually caught up, but the documentation has been well behind the curve either incorrect (thermostat/s). Now, maybe, you can find something I missed, in which case, please let me know. (Would JSON work?)

I don't know that MQTT is better than HTTP from a power standpoint, I would assume yes, but don't know.


The idea is to load all the profiles onto the TRV and let that run autonomously, then be able to select the profile by changing the Profile Number. That then requires the Boiler to know what is going on, I pull the current profile into a function node (without communicating with the TRV) to strip out the current required temperature so that I can control the boiler and the Diverter Valve.

I will look at a way of using the cronplus node to load the profile schedules so that I can get the current data, but only send those schedules to the TRV if there is a change. Or, more likely, use the above, but instead of using a file, use JSON.

My concern is that after a Power Cut, the Boiler Control flow needs to hit the ground running with the correct values and maintain them as each step of the schedule changes and account for any change in profile. No good having a power cut for a couple of days and then Boiler operating in 'Night Mode' while the Radiator is running in 'Day Mode'.

Thanking you for your comments and observations, Any questions, ask away!

4 Likes

Hey guys,
To start with: a Merry Christmas :santa: :gift: :christmas_tree:

I am going to start with my TRV today, in a desperate attempt to give my body some time to digest the food and drinks from yesterday evening...

Don't think I understand the message structure correctly. In the API documentation Shelly gives this example:

    "schedule_profile": 2,
    "schedule_profile_names": [
        "Livingroom",
        "Livingroom 1",
        "Bedroom",
        "Bedroom 1",
        "Holiday"
    ],
    "schedule_rules": [
        "0600-0123456-23",
        "0830-0123456-18",
        "1630-0123456-23",
        "2300-0123456-18"
    ],

Question 1 - schedule_profile_names

The schedule_profiles_names is only used for visual identification in the web interface of the TRV if I understand correctly. Anyway would like to have it clear and self explaining, in case I need to do troubleshooting in the future...

The schedule rules of the above example specify that this profile (consisting out of 4 setpoints) should be run every day of the week (due to 0123456). But why on earth are here 5 different profile names?

I thought that if the schedule rules would contain N setpoint profiles, that you would have N setpoint profile names. For example a profile for the weekend (06) and one for the weekdays (12345):

    "schedule_profile": 2,
    "schedule_profile_names": [
        "Bathroom profile for the week",
        "Bathroom profile for the weekend"
    ],
    "schedule_rules": [
        # Setpoint profiles for the week
        "0700-12345-20",
        "0820-12345-19",
        "1730-12345-25",
        "2200-12345-17"
        # Setpoint profiles for the weekend
        "0600-06-23",
        "0830-06-18",
        "1630-06-23",
        "2300-06-18"
    ],

But seems NOT to be working like that. Of course a bit normal, because the Shelly doesn't know this way anyway which profile name would correspond to which profile setpoint values...

But instead their example gives profile names like "Bedroom" and "Living room". Don't understand why a TRV - which is installed in a single room - returns the profile names of multiple rooms...

Question 2 - schedule_profile

The schedule_profile allows you to "Choose an active schedule profile with the provided id 1..5". Again I don't think I understand this correctly. You can't have 7 profiles, in case you want a different setpoint profile every day. Which is of course again a bit normal I think, because the schedule_rules can contain the setpoints of multiple days (via 0123456).

My plan was to send via Node-RED every morning a schedule for today to the TRV. Am I correct that this is always schedule_profile "1", i.e. the profile of today? Not sure if I would need multiple different profiles.

Question 3 - schedule_rules

Via the schedule_rules I can set the temperature at a specific time of the day, according to the Shelly documentation:

I thought that I had read somewhere that this is the desired temperature at that time. So then the TRV valve will need to open earlier to reach my desired temperature in time. Not sure how he does that, because he doesn't know the outside temperature (which determines how much earlier he has to start heating my room).

Suppose this is indeed the desired temperature. Let's say I want to have my bathroom at 20 degrees (Celsius of course :wink: ) between 07:00 and 08:00:

image

I can easily specify a schedule rule "0700-0123456-20" to make sure it is warm when I enter the room at 07:00. But which schedule rule do I need to enter for 08:00? Do I have to repeat the first rule like "0800-0123456-20" to make sure he keeps the room warm until that time. But I don't know how fast the room cools down to 15 degrees. So should I say "0815-0123456-15", or "0820-0123456-15", or "0830-0123456-15", or ...

Would be nice if somebody could clarify a bit. Perhaps stupid questions, but I don't get it...

Yes, Merry Christmas to All!

My understanding of Profiles/Schedules:
There can be up to 5 Profiles numbered 1-5
You can select your desired Profile 1-5 with schedule_profile
schedule_profile_names only seems to be used by us Humans to aid easier identification
I have added 7 events to a profile with no problem. (Is there a limit?)

The examples I feel sure, are just examples, probably 'developed' by the software engineer to test different variables with names to suit their test (you know how ridiculous developers get when developing, everything for convenience to test a scenario, not for actual real world practicality).

My Living Room TRV is staying where it is and won't be used elsewhere, but I can select a profile to suit. Visitors? - a little warmer (if they are welcome, a little cooler if not! :rofl:)

I am going to try a profile that has events that occur on different days, say, at the weekend turn the heating off later, so the next to last event switches heating off at 21:30, but at the weekend, not until 22:30...

{
    "schedule_profile": 5,
    "profile_name": "Different off times",
    "rules": [
        {
            "days": "0123456",
            "time": "11:30",
            "temperature": 18
        },
        {
            "days": "01234",
            "time": "21:30",
            "temperature": 15
        },
        {
            "days": "56",
            "time": "22:30",
            "temperature": 15
        }
    ]
}

Not sure how the 'predictive' algorithm works, but agree, without outside temperature it is at best a guess. I do have a form of 'weather compensation'. At the moment, I change the temperature of the boiler to give faster response if colder, but this affects the efficiency. My next plan is to work on the time, so that I can predict when the boiler needs to come on to heat the room effectively, but need data on the boiler/house response/inertia

FWIW, I am now feeding the TRV schedules in using JSON.

I do not use the Shelly but I imagine that most TRVs are similar. The Shelly knows the room temperature

    "tmp": {
        "value": 17.4,
        "units": "C",
        "is_valid": true
    },

but I would think that, like most thermostats, there is no anticipation of scheduled temperatures so the valve only tries to adjust once a set time has arrived. From looking at the documentation I think

ext_t.enabled bool If temperature correction from external temp sensor is enabled

that you can use an external temperature sensor as a control if you wish.

The other issue to bear in mind is that the TRV has no idea of the current temperature of the water flowing in the radiator, so if the heating is not on it can be wide open, with no actual effect. :thinking:

Yes indeed that was what I was also thinking the first time I read that sentence. However afterwards I read somewhere that this can be used if you have a temperature sensor somewhere in your room, which you want to use instead of the temperature sensor inside the TRV. So it is to measure the temperature inside your room, not outdoors...

That would be indeed a useful experiment. My confusion is caused because it "feels" to me that Shelly is mixing setpoint profiles (for a single day) with schedules, by the days parameter...

But this can be manipulated in Node-red using both internal & external sensors because all the valve is after is a temperature, source is irrelevant.

I haven't though through how this can happen but what is required is a value that causes the valve to open or close dependant on the weighting given to each sensor.

So, for example, if the room temperature is as scheduled the only action is making sure that the temperature sent to the valve is above the scheduled temperature.
However, if the room temperature is below the scheduled temperature then some calculated value, dependent on how far below scheduled temperature it is and what impact you are allowing for the external temperature to have, should be send to the valve.

1 Like

Just a suggestion: did you consider to drop shelly an email with your findings instead ?