Trying to write a Function Node for monitoring temperatures

Hey Guys, I'm trying to write a function node which monitors a Home Assistant Event State Node output (Temperatur as string e.g. 23.6...) for temperature changes. If the temperature changes within a given time to a certain extent it should generate an output. Bu all I get in a debug node is

msg.payload : undefined

This is my node:

var previousValue = null;
var previousTimestamp = null;
var threshold = 4; // Temperaturschwelle in Grad Celsius
var timeWindow = 60; // Zeitfenster in Sekunden

function checkTemperature(msg) {

    // Add debug statements here
    console.log("Incoming Message Payload:", msg.payload);

    var currentTimestamp = Date.now();
    var currentValue = parseFloat(msg.payload); // Umwandlung in Gleitkommazahl

    if (!isNaN(currentValue) && previousValue !== null && previousTimestamp !== null) {
        var deltaTime = (currentTimestamp - previousTimestamp) / 1000;
        var deltaValue = currentValue - previousValue;

        if (deltaTime <= timeWindow && deltaValue >= threshold) {
            node.send({
                payload: "Temperatur erhöht sich um " + deltaValue + " Grad Celsius innerhalb von " + deltaTime + " Sekunden."
            });
        }
    } else {
        console.log("UngĂĽltige Payload:", msg.payload);
    }
    previousValue = currentValue;
    previousTimestamp = currentTimestamp;
}

return {

    onInput: function (msg) {
        checkTemperature(msg);
    }
};

Tried to debug it but didn't make it...

Have you any ideas for me?

try

return {
    onInput:  checkTemperature(msg)
};

and previousValue in your if conditional will be null as you set var previousValue = null; which is a global and is visible to the function

Use node.warn(var)in your code to send variable to debug, so you can see what is or is not happening.

2 Likes

The snipplet you posted neither is valid for the definition of a node nor as code within a function node.
I propose you either post the full node definition or a flow with this (function) node. Both will be a good starting point to help you solving your issue.

Okay @ralphwetzel here you go

[{"id":"df8c984ddabd30e4","type":"function","z":"65879b662c0554cb","name":"Temperatur stieg um 5° innerhab 1 Minute","func":"var previousValue = null;\nvar previousTimestamp = null;\nvar threshold = 4; // Temperaturschwelle in Grad Celsius\nvar timeWindow = 60; // Zeitfenster in Sekunden\n\nfunction checkTemperature(msg) {\n\n    // Add debug statements here\n    console.log(\"Incoming Message Payload:\", msg.payload);\n\n    var currentTimestamp = Date.now();\n    var currentValue = parseFloat(msg.payload); // Umwandlung in Gleitkommazahl\n\n    if (!isNaN(currentValue) && previousValue !== null && previousTimestamp !== null) {\n        var deltaTime = (currentTimestamp - previousTimestamp) / 1000;\n        var deltaValue = currentValue - previousValue;\n\n        if (deltaTime <= timeWindow && deltaValue >= threshold) {\n            node.send({\n                payload: \"Temperatur erhöht sich um \" + deltaValue + \" Grad Celsius innerhalb von \" + deltaTime + \" Sekunden.\"\n            });\n        }\n    } else {\n        console.log(\"Ungültige Payload:\", msg.payload);\n    }\n    previousValue = currentValue;\n    previousTimestamp = currentTimestamp;\n}\n\nreturn {\n    onInput:  checkTemperature(msg)\n };\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":440,"y":1680,"wires":[["bd638a3a8d3097ce"]]},{"id":"bd638a3a8d3097ce","type":"debug","z":"65879b662c0554cb","name":"debug 17","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":740,"y":1680,"wires":[]},{"id":"573256e42d0bd41b","type":"inject","z":"65879b662c0554cb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"23.3","payloadType":"str","x":110,"y":1680,"wires":[["df8c984ddabd30e4"]]},{"id":"93262112d7fb3756","type":"inject","z":"65879b662c0554cb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"29.4","payloadType":"str","x":110,"y":1720,"wires":[["df8c984ddabd30e4"]]}]

This is my testconstruction :slight_smile:

Short & clean. Perfect! :+1:

Now it's obvious, what's happening: You mis-interpreted the way NR is working.

The code you posted will be execute each time a msg arrives. This means, that the variables will be initialized = null each time as well.
Additionally, your return structure is simply invalid - or at least unnecessarily complex.

How to compensate for those topics:

  • Use context variables to store values between node invocations. Initialize them in the "onStart" tab of the function node.
  • Just return what you like to send to the next node.

That's how I would write this function (node):

// Get the previous values from the context store
var previousValue = context.get("pV");
var previousTimestamp = context.get("pT");

var threshold = 4; // Temperaturschwelle in Grad Celsius
var timeWindow = 60; // Zeitfenster in Sekunden

let ret;

// Add debug statements here
console.log("Incoming Message Payload:", msg.payload);

var currentTimestamp = Date.now();
var currentValue = parseFloat(msg.payload); // Umwandlung in Gleitkommazahl

if (isNaN(currentValue)) {
    console.log("UngĂĽltige Payload:", msg.payload);
} else {
    if (previousValue !== null && previousTimestamp !== null) {
        var deltaTime = (currentTimestamp - previousTimestamp) / 1000;
        var deltaValue = currentValue - previousValue;

        if (deltaTime <= timeWindow && deltaValue >= threshold) {
            ret = "Temperatur erhöht sich um " + deltaValue + " Grad Celsius innerhalb von " + deltaTime + " Sekunden."
        }
    }
} 

// Write the current values to the context store
context.set("pV", currentValue);
context.set("pT", currentTimestamp);

if (ret) return { payload: ret };
return;

Your flow - modified:

[{"id":"3108d4117384a41a","type":"tab","label":"Flow 32","disabled":false,"info":"","env":[],"_mcu":{"mcu":false}},{"id":"df8c984ddabd30e4","type":"function","z":"3108d4117384a41a","name":"Temperatur stieg um 5° innerhab 1 Minute","func":"// Get the previous values from the context store\nvar previousValue = context.get(\"pV\");\nvar previousTimestamp = context.get(\"pT\");\n\nvar threshold = 4; // Temperaturschwelle in Grad Celsius\nvar timeWindow = 60; // Zeitfenster in Sekunden\n\nlet ret;\n\n// Add debug statements here\nconsole.log(\"Incoming Message Payload:\", msg.payload);\n\nvar currentTimestamp = Date.now();\nvar currentValue = parseFloat(msg.payload); // Umwandlung in Gleitkommazahl\n\nif (isNaN(currentValue)) {\n    console.log(\"Ungültige Payload:\", msg.payload);\n} else {\n    if (previousValue !== null && previousTimestamp !== null) {\n        var deltaTime = (currentTimestamp - previousTimestamp) / 1000;\n        var deltaValue = currentValue - previousValue;\n\n        if (deltaTime <= timeWindow && deltaValue >= threshold) {\n            ret = \"Temperatur erhöht sich um \" + deltaValue + \" Grad Celsius innerhalb von \" + deltaTime + \" Sekunden.\"\n        }\n    }\n} \n\n// Write the current values to the context store\ncontext.set(\"pV\", currentValue);\ncontext.set(\"pT\", currentTimestamp);\n\nif (ret) return { payload: ret };\nreturn;","outputs":1,"timeout":"","noerr":0,"initialize":"// Code added here will be run once\n// whenever the node is started.\ncontext.set(\"pV\", null);\ncontext.set(\"pT\", null);","finalize":"","libs":[],"_mcu":{"mcu":false},"x":460,"y":200,"wires":[["bd638a3a8d3097ce"]]},{"id":"bd638a3a8d3097ce","type":"debug","z":"3108d4117384a41a","name":"debug 17","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","_mcu":{"mcu":false},"x":720,"y":200,"wires":[]},{"id":"573256e42d0bd41b","type":"inject","z":"3108d4117384a41a","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"23.3","payloadType":"str","_mcu":{"mcu":false},"x":210,"y":200,"wires":[["df8c984ddabd30e4"]]},{"id":"93262112d7fb3756","type":"inject","z":"3108d4117384a41a","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"29.4","payloadType":"str","_mcu":{"mcu":false},"x":210,"y":240,"wires":[["df8c984ddabd30e4"]]}]
1 Like

Thank you very much! Seems like it's working :heart_eyes:

I have to admit it doesn't... Maybe I committed a fallacy: If the temperature of the showerpipe rises, usually it does so quite fast. Possibly even after 10 Seconds it reaches the 4° threshold. Temperature readings coming in the node every 0.2°C change at the probe.
So e.g. we have

20:00:00 - 24°
20:00:01 - 24,7°
20:00:02 - 25.4°
20:00:03 - 26.2°
20:00:04 - 28.3°

The value change within a 1 minute timerange is reached after 10 seconds already but the flow can't know this because it takes only the last incoming value into account doesn't it? The changes between the sensor readings are aways small. Any Idea how to solve this?

Have a look at the smooth node

Feed the temperatures into it and have it smooth over (say) 10 readings and return the mean

This will give you gradual rise and declines and then feed those into your function node.

You could also look at the Hysterises node which could be used to replace most of your function node as well (but takes a bit of work to wrap your head around)

Craig

1 Like

There are several patterns you can follow.
A simple one:

  • Store the incoming value pairs in an array.
  • Run over the array to see if your condition is met.
  • Remove data when it falls out of your time window
1 Like

Thank you so much guys, I think I go with the last idea, because i want the fastest way for my function to react and detect a steep temperature curve climb.

Sideinfo: I'm trying to extract a "Shower occupied" state from the temperature Data to switch the Shower lights on and off, and for some other automations...

So me and chatgpt trying to get the Array thing running. But now I don't get any outputs anymore. And shouldn't i be able to see my array data in the Context Data Tab? Nothing appears in there. This is my code:

// Get the previous values from the context store
var previousValues = context.get("previousValues") || [];
var threshold = 4; // Temperaturschwelle in Grad Celsius
var timeWindow = 60; // Zeitfenster in Sekunden

var ret;

// Add debug statements here
console.log("Incoming Message Payload:", msg.payload);

var currentTimestamp = Date.now();
var currentValue = parseFloat(msg.payload); // Umwandlung in Gleitkommazahl

if (isNaN(currentValue)) {
    console.log("UngĂĽltige Payload:", msg.payload);
} else {
    // Holen Sie die vorherigen Werte aus dem Kontext
    if (previousValues.length > 0) {
        var previousValueBeforeMinute = previousValues[0].value;
        var previousTimestampBeforeMinute = previousValues[0].timestamp;
        var deltaTime = (currentTimestamp - previousTimestampBeforeMinute) / 1000;
        var deltaValue = currentValue - previousValueBeforeMinute;

        if (deltaTime <= timeWindow && deltaValue >= threshold) {
            ret = "Temperatur erhöht sich um " + deltaValue + " Grad Celsius innerhalb von " + deltaTime + " Sekunden."
        }
    }

    // Aktualisieren Sie die Liste der vorherigen Werte
    previousValues.unshift({ value: currentValue, timestamp: currentTimestamp });

    // Entfernen Sie alte Werte, die älter als eine Minute sind
    var cutoffTimestamp = currentTimestamp - (timeWindow * 1000);
    previousValues = previousValues.filter(function (item) {
        return item.timestamp >= cutoffTimestamp;
    });

    // Schreiben Sie die aktualisierte Liste in den Kontext
    context.set("previousValues", previousValues);
}

if (ret) return { payload: ret };
return;

and this the test flow:

[{"id":"df8c984ddabd30e4","type":"function","z":"65879b662c0554cb","name":"Temperatur stieg um 5° innerhab 1 Minute","func":"// Get the previous values from the context store\nvar previousValues = context.get(\"previousValues\") || [];\nvar threshold = 4; // Temperaturschwelle in Grad Celsius\nvar timeWindow = 60; // Zeitfenster in Sekunden\n\nvar ret;\n\n// Add debug statements here\nconsole.log(\"Incoming Message Payload:\", msg.payload);\n\nvar currentTimestamp = Date.now();\nvar currentValue = parseFloat(msg.payload); // Umwandlung in Gleitkommazahl\n\nif (isNaN(currentValue)) {\n    console.log(\"Ungültige Payload:\", msg.payload);\n} else {\n    // Holen Sie die vorherigen Werte aus dem Kontext\n    if (previousValues.length > 0) {\n        var previousValueBeforeMinute = previousValues[0].value;\n        var previousTimestampBeforeMinute = previousValues[0].timestamp;\n        var deltaTime = (currentTimestamp - previousTimestampBeforeMinute) / 1000;\n        var deltaValue = currentValue - previousValueBeforeMinute;\n\n        if (deltaTime <= timeWindow && deltaValue >= threshold) {\n            ret = \"Temperatur erhöht sich um \" + deltaValue + \" Grad Celsius innerhalb von \" + deltaTime + \" Sekunden.\"\n        }\n    }\n\n    // Aktualisieren Sie die Liste der vorherigen Werte\n    previousValues.unshift({ value: currentValue, timestamp: currentTimestamp });\n\n    // Entfernen Sie alte Werte, die älter als eine Minute sind\n    var cutoffTimestamp = currentTimestamp - (timeWindow * 1000);\n    previousValues = previousValues.filter(function (item) {\n        return item.timestamp >= cutoffTimestamp;\n    });\n\n    // Schreiben Sie die aktualisierte Liste in den Kontext\n    context.set(\"previousValues\", previousValues);\n}\n\nif (ret) return { payload: ret };\nreturn;\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":440,"y":1680,"wires":[["bd638a3a8d3097ce"]]},{"id":"bd638a3a8d3097ce","type":"debug","z":"65879b662c0554cb","name":"debug 17","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":740,"y":1680,"wires":[]},{"id":"573256e42d0bd41b","type":"inject","z":"65879b662c0554cb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"24","payloadType":"str","x":110,"y":1680,"wires":[["df8c984ddabd30e4"]]},{"id":"93262112d7fb3756","type":"inject","z":"65879b662c0554cb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"34","payloadType":"str","x":110,"y":1720,"wires":[["df8c984ddabd30e4"]]},{"id":"96c056c3a0a84ee4","type":"inject","z":"65879b662c0554cb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"20","payloadType":"str","x":110,"y":1520,"wires":[["df8c984ddabd30e4"]]},{"id":"041aefd845cc3632","type":"inject","z":"65879b662c0554cb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"21","payloadType":"str","x":110,"y":1560,"wires":[["df8c984ddabd30e4"]]},{"id":"2340c5a79093b489","type":"inject","z":"65879b662c0554cb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"22","payloadType":"str","x":110,"y":1600,"wires":[["df8c984ddabd30e4"]]},{"id":"05bc41703f604c86","type":"inject","z":"65879b662c0554cb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"23.3","payloadType":"str","x":110,"y":1640,"wires":[["df8c984ddabd30e4"]]}]

This is wrong. unshift places the new element at the beginning of the array. Use push & the documentation available for javascript.

Additionally, the result may not be as accurate as you expect it to be. I proposed to "run over the array", which is different from comparing current data with the data at index 0.

1 Like

I tried to update the code and the script should store all values within the given timespan (1 Minute) and compare the latest value always with the earliest, and if the difference is higher than the threshold variable, an output should be generated. Do you think that idea could work? The code btw. doesn't :slight_smile:

// Get the previous values from the context store
var previousValues = context.get("previousValues") || [];
var threshold = 4; // Temperaturschwelle in Grad Celsius
var timeWindow = 60; // Zeitfenster in Sekunden

var ret;

// Add debug statements here
console.log("Incoming Message Payload:", msg.payload);

var currentTimestamp = Date.now();
var currentValue = parseFloat(msg.payload); // Umwandlung in Gleitkommazahl

if (isNaN(currentValue)) {
    console.log("UngĂĽltige Payload:", msg.payload);
} else {
    // Suchen Sie den am weitesten entfernten Wert innerhalb einer Minute
    var furthestValue = null;

    for (var i = 0; i < previousValues.length; i++) {
        if (currentTimestamp - previousValues[i].timestamp <= timeWindow * 1000) {
            furthestValue = previousValues[i].value;
        } else {
            // Wenn der Wert älter als eine Minute ist, brechen Sie die Schleife ab
            break;
        }
    }

    // Wenn ein Wert gefunden wurde, vergleichen Sie ihn mit dem aktuellen Wert
    if (furthestValue !== null) {
        var deltaValue = currentValue - furthestValue;

        if (deltaValue >= threshold) {
            ret = "Temperatur erhöht sich um " + deltaValue + " Grad Celsius innerhalb einer Minute.";
        }
    }

    // Aktualisieren Sie die Liste der vorherigen Werte
    previousValues.push({ value: currentValue, timestamp: currentTimestamp });

    // Entfernen Sie alte Werte, die älter als eine Minute sind
    var cutoffTimestamp = currentTimestamp - (timeWindow * 1000);
    previousValues = previousValues.filter(function (item) {
        return item.timestamp >= cutoffTimestamp;
    });

    // Schreiben Sie die aktualisierte Liste in den Kontext
    context.set("previousValues", previousValues);
}

if (ret) return { payload: ret };
return;
[{"id":"df8c984ddabd30e4","type":"function","z":"65879b662c0554cb","name":"Temperatur stieg um 5° innerhab 1 Minute","func":"// Get the previous values from the context store\nvar previousValues = context.get(\"previousValues\") || [];\nvar threshold = 4; // Temperaturschwelle in Grad Celsius\nvar timeWindow = 60; // Zeitfenster in Sekunden\n\nvar ret;\n\n// Add debug statements here\nconsole.log(\"Incoming Message Payload:\", msg.payload);\n\nvar currentTimestamp = Date.now();\nvar currentValue = parseFloat(msg.payload); // Umwandlung in Gleitkommazahl\n\nif (isNaN(currentValue)) {\n    console.log(\"Ungültige Payload:\", msg.payload);\n} else {\n    // Suchen Sie den am weitesten entfernten Wert innerhalb einer Minute\n    var furthestValue = null;\n\n    for (var i = 0; i < previousValues.length; i++) {\n        if (currentTimestamp - previousValues[i].timestamp <= timeWindow * 1000) {\n            furthestValue = previousValues[i].value;\n        } else {\n            // Wenn der Wert älter als eine Minute ist, brechen Sie die Schleife ab\n            break;\n        }\n    }\n\n    // Wenn ein Wert gefunden wurde, vergleichen Sie ihn mit dem aktuellen Wert\n    if (furthestValue !== null) {\n        var deltaValue = currentValue - furthestValue;\n\n        if (deltaValue >= threshold) {\n            ret = \"Temperatur erhöht sich um \" + deltaValue + \" Grad Celsius innerhalb einer Minute.\";\n        }\n    }\n\n    // Aktualisieren Sie die Liste der vorherigen Werte\n    previousValues.push({ value: currentValue, timestamp: currentTimestamp });\n\n    // Entfernen Sie alte Werte, die älter als eine Minute sind\n    var cutoffTimestamp = currentTimestamp - (timeWindow * 1000);\n    previousValues = previousValues.filter(function (item) {\n        return item.timestamp >= cutoffTimestamp;\n    });\n\n    // Schreiben Sie die aktualisierte Liste in den Kontext\n    context.set(\"previousValues\", previousValues);\n}\n\nif (ret) return { payload: ret };\nreturn;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":440,"y":1680,"wires":[["bd638a3a8d3097ce"]]},{"id":"bd638a3a8d3097ce","type":"debug","z":"65879b662c0554cb","name":"debug 17","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":740,"y":1680,"wires":[]},{"id":"573256e42d0bd41b","type":"inject","z":"65879b662c0554cb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"24","payloadType":"str","x":110,"y":1680,"wires":[["df8c984ddabd30e4"]]},{"id":"93262112d7fb3756","type":"inject","z":"65879b662c0554cb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"34","payloadType":"str","x":110,"y":1720,"wires":[["df8c984ddabd30e4"]]},{"id":"96c056c3a0a84ee4","type":"inject","z":"65879b662c0554cb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"20","payloadType":"str","x":110,"y":1520,"wires":[["df8c984ddabd30e4"]]},{"id":"041aefd845cc3632","type":"inject","z":"65879b662c0554cb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"21","payloadType":"str","x":110,"y":1560,"wires":[["df8c984ddabd30e4"]]},{"id":"2340c5a79093b489","type":"inject","z":"65879b662c0554cb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"22","payloadType":"str","x":110,"y":1600,"wires":[["df8c984ddabd30e4"]]},{"id":"05bc41703f604c86","type":"inject","z":"65879b662c0554cb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"23.3","payloadType":"str","x":110,"y":1640,"wires":[["df8c984ddabd30e4"]]}]

You seem to be saying you get a temperature reading when the temperature change is 0.2° but your example shows readings arriving at 1 second intervals.

Which is it?

Can you explain why you are setting a threshold of 4° in a minute?
The sample data above shows a rate of increase varying from 42 to 126°/min

I looked at it again and the thermo sensor sends temp. updates every 0.1K. So if the rise curve is very steep, it might be very often.

I analyzed the temperature graph of a normal shower. But i can change the variables if it won't work as expected. But until that it has to work at all.

What i want is the quickest detection of: "shower is on" but no misinterpretations. So in my thought this means: Tell me when the temperature rises steep in a given time period which has to be appropriate. I thought 1 minute will do.

The sample data above shows a rate of increase varying from 42 to 126°/min

I don't get that. Lowest Temp is 24°C and highest is 28.3°C?

At 20:00:01 the temperature has risen by 0.7° (in 1 second)
The rate of increase is 0.7°/s or 0.7 * 60 = 42°/min

OK so the data for the first 2 seconds might look like this
|20:00:00.00|24|
|20:00:00.14|24.1|
|20:00:00.28|24.2|
|20:00:00.42|24.3|
|20:00:00.56|24.4|
|20:00:00.70|24.5|
|20:00:00.84|24.6|
|20:00:00.98|24.7|
|20:00:01.12|24.8|
|20:00:01.26|24.9|
|20:00:01.40|25|
|20:00:01.54|25.1|
|20:00:01.68|25.2|
|20:00:01.82|25.3|
|20:00:01.96|25.4|

You might consider the shower to be on if the temperature is rising for 3 (or 4, 5) successive measurements within 1 (or 2) seconds.
The requirement for successive rises hopefully eliminates random noise, a short time period eliminates changes due to ambient temperature.

Similarly if successive temperature readings are falling, the shower may be off. You probably need to check over a longer period in this case though, to eliminate misinterpreting turning the shower temp down.

1 Like

But the temperature rises over the day if surrounding temperature rises or falls. So i guess just to state: temperature is constantly rising or falling is not enough?

Yes that's true.

If the temperature is stable, how much noise is there on it?

24h


6h

That image does not tell me the noise level. I meant if you watch the values for a while when it is stable does it go up and down 0.1 degrees, 0.2, or what?