Temperature Control Flow Through Heater

It seems the Omron G3NA 220B would do the job. @TomGeorge not sure if I follow what you are trying to say in your previous post. @EmilyJane these are probably a similar price to the Optp22 that you have been using. Not cheap at all, especially when you need to add the head sink...

Yes, Omron is a good brand as well. And yes, you’ll need a heat sink for the power you will be controlling.

As to an SSR? Yes, the good names like Gordos, Omeron, Omega and Crydom can be costly. However, beware of the cheap off the boat flavors of SSRs and relays as well. Most of the cheap stuff will seldom meet rated specifications.

Rather than thermocouples and thermocouple conditioners have you given any thought to just using two for example Dallas DS18B20 sensors? The linked ones are 1/4" OD so you just need some 1/4 inch compression fittings.

Ron

Brief update from my side. I have worked a bit on the circuit diagram and on the components that I will need.

To be able to control the speed of the pump I integrated a dimmer module that I can control with the Arduino. Ideally I can remove that later on an run the pump on a constant speed, this is just for testing purposes.

The Arduino itself will be powered by a 12V PSU.

Now to the tricky part, the control of the FTH. I selected a SSR Fotek-40DA 40A 480VAC / 32VDC which should be sufficient for the FTH that I mentioned in my first post.

I am looking into PWM and the coding of such. Does anyone have a good example of switch at zero crossing PWM?

I really appreciate any feedback regarding my circuit as I don't have the electrical background. Any missing components that one might spot?

Hi, @schlappo22

Be very careful using dimmers to control motors, you are controlling an inductive load and a lamp dimmer may not work for you.
In fact your pump from the link in post #1 is a vibration pump, not a motor type, that is it runs at the mains frequency, it is also an inductive load so the lamp dimmer will not function in this case.

As the FTH is resistive load your Lamp Dimmer would be more suitable if it is rated for the current, you don't need zero crossing detection as that would be done in the actual Dimmer.

Tom... :smiley: :+1: :coffee: :australia:

Thanks for your reply @TomGeorge.

I see. How would you recommend to control the speed of a vibration pump instead?

Regarding the FTH, that is how the supplier recommends to do the control. "not controlled by phase cutting, the control only switches at zero-cross". I am in contact with them so hope to get a bit more information first hand.

Hi,

Zero crossing switching will mean turning on for so many cycles and off for some many. The ratio of cycles being the duty cycle.
Each time ON at a zero cross and OFF at a zero cross, this reduces electrical noise.

It will be interesting what the FTH supplier suggests.

You will need to find a motor phase controller for the pump, the amplitude that the cycle gets turned ON at is reflected in the throw of the vibrator arm, an so the amount of liquid.
Again, this pump may not respond to such control.

That is about all I can say, not being particularly familiar with this hardware.

Tom.. :smiley: :+1: :coffee: :australia:

It means it only switches at the zeros. If you switch the input at places other than the zeros, the output won't switch. You get whole positive or negative phase-chunks, and the control switches them on or off at twice the line frequency, (or 120Hz for a 60Hz line).

ETA: Per the Arduino PID V1 library, consider the relay control example:

... and choose a control time /WindowSize the length of a number of half-cycles. Maybe on the order of WindowSize = 1000L*100/120; or WindowSize = 1000L*255/120;

1 Like

Thanks for your replies!

I understand the zero crossing principle and what it does and why it in some applications should be preferred. What I am struggling with is how I implement that in my code. Or if I even need to do that or if the SSR will take care of it as it has a zero cross detection.

Adding to the vibration pump discussion we started earlier @TomGeorge . The reason why I suggested the module above was based on the coffee machine hacks that some people have implemented. Majority of coffee machines use Ulka Vibration pumps, so does this one here: $6.35 Dimmer Mod | Breville Barista Express | Step by Step - YouTube and the user implements a similar dimmer, just without the Arduino control. Not sure why this should work and my application wouldn't?

I am shocked to not see any mention of controlling the growth of dangerous bacteria in your hot water systems.

Alternate food supply.

2 Likes

@Paul_KD7HB what would you suggest to avoid bacteria build up in the system? Wondering how this system is different from any coffee machine? To keep my coffee machine as good in shape as possible I descale it regularly and that's it. Do you do something different?

I am looking into the PWM control at the moment and it doesn't seem to be too trivial. I think from a hardware perspective I am there with the SRR and the Zero Cross switching. However I struggle to find the right way forward from the software side of things.

So I want to control the FTH by only switching at zero crossings. And as there are different power-lines inside the heater, I will be able to generate different average powers over 10 phases.

For example:
If I have a 400W heater and trigger that at different phases, it could look something like this:

Example 1:
Phase 1: 400W
Phase 2: 0W
Phase 3: 400W
Phase 4: 0W
Phase 5: 400W
Phase 6: 0W
Phase 7: 400W
Phase 8: 0W
Phase 9: 400W
Phase 10: 0W

That results in an average of 200W over 10 Phases.

If I want to introduce more heat another example:

Example 1:
Phase 1: 400W
Phase 2: 400W
Phase 3: 400W
Phase 4: 400W
Phase 5: 400W
Phase 6: 400W
Phase 7: 400W
Phase 8: 400W
Phase 9: 400W
Phase 10: 400W

That averages to 400W.

But I am a bit clueless how to achieve this sort of control. I read through the ATmega640/1280/1281/2560/2561 datasheet and there are different Modes within the PWM that can be used, Normal Mode, Clear Timer on Compare Match (CTC) Mode, Fast PWM Mode, Phase Correct PWM Mode. I believe the Fast PWM Mode would be the way forward, but that is where I am stuck now.

Is anyone here who can point into the right direction and maybe provide an example of something similar to the above?

If you were using water piped from a municipal system, I would not worry about it. But you are using an open water tank. Open because air must go into the tank and out again as the water level changes. This means any type of air born contamination can enter your system.
How do you keep the system clean now will keep bacteria from growing in the system. Please explain your current procedure.

1 Like

What you are explaining is the same as a coffee machine? I guess you don't drink your coffee at 100degC so you will most likely also have some bacteria in there I assume. I have not worked on a solution nor have I investigated how severe this actually would be, as I don't actually have a physical prototype yet. I will however put it on the to-do list. Thank you very much!

MY coffee machine eventually cooks itself dry each day, Your system looks to have water in it full time.

1 Like

The SSR likely handles switching at the zero crossings by itself--if you have the input on or off when the zero crossing happens, it will switch accordingly.

As for how to control it with a PID, consider the Arduino PID example for relay control in Temperature Control Flow Through Heater - #49 by DaveX

// https://github.com/br3ttb/Arduino-PID-Library/blob/master/examples/PID_RelayOutput/PID_RelayOutput.ino
/********************************************************
 * PID RelayOutput Example
 * Same as basic example, except that this time, the output
 * is going to a digital pin which (we presume) is controlling
 * a relay.  the pid is designed to Output an analog value,
 * but the relay can only be On/Off.
 *
 *   to connect them together we use "time proportioning
 * control"  it's essentially a really slow version of PWM.
 * first we decide on a window size (5000mS say.) we then
 * set the pid to adjust its output between 0 and that window
 * size.  lastly, we add some logic that translates the PID
 * output into "Relay On Time" with the remainder of the
 * window being "Relay Off Time"
 ********************************************************/

#include <PID_v1.h>

#define PIN_INPUT 0
#define RELAY_PIN 6

//Define Variables we'll be connecting to
double Setpoint, Input, Output;

//Specify the links and initial tuning parameters
double Kp=2, Ki=5, Kd=1;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);

int WindowSize = 5000;
unsigned long windowStartTime;

void setup()
{
  windowStartTime = millis();

  //initialize the variables we're linked to
  Setpoint = 100;

  //tell the PID to range between 0 and the full window size
  myPID.SetOutputLimits(0, WindowSize);

  //turn the PID on
  myPID.SetMode(AUTOMATIC);
}

void loop()
{
  Input = analogRead(PIN_INPUT);
  myPID.Compute();

  /************************************************
   * turn the output pin on/off based on pid output
   ************************************************/
  if (millis() - windowStartTime > WindowSize)
  { //time to shift the Relay Window
    windowStartTime += WindowSize;
  }
  if (Output < millis() - windowStartTime) digitalWrite(RELAY_PIN, HIGH);
  else digitalWrite(RELAY_PIN, LOW);

}


This code doesn't use the hardware PWM, it uses a 5sec/0.2Hz frequency software PWM with a period of 1200 zero-crossings. You could choose 256 zero crossings with a period of 2133ms and get the same resolution as analogWrite(), or 833ms for 0-100% at 1% resolution, or even 100ms to match the Arduino PID's default sampling/adjustment time for resolution of 12 zero crossings.

ETA: For 10 phase resolution, use 83ms.

The burners on my electric stove use this method of switching through a self-heating bi-metallic thermal switch with about a 10s period.

So I have been playing with the Heater and the PID a bit. But here is what I did step by step.

I implemented the NTC and I am able to read the temperature fairly accurate. I compared it to a reading of one of my thermocouples. The error is within +-1.1degC between both sensors. That however is dispensing at room temperature. This is what I am using:

#include <FlowSensor.h>
#include <LcdBarGraph.h>
#include <LiquidCrystal.h>

#define ntc_pin A15  // Pin,to which the voltage divider is connected
//#define vd_power_pin 2            // 5V for the voltage divider
#define nominal_resistance 50000  //Nominal resistance at 25⁰C
#define nominal_temeprature 25    // temperature for nominal resistance (almost always 25⁰ C)
#define samplingrate 5            // Number of samples
#define beta 3970                 // The beta coefficient or the B value of the thermistor (usually 3000-4000) check the datasheet for the accurate value.
#define Rref 12000                //Value of  resistor used for the voltage divider

#include <SPI.h>
#include "Adafruit_MAX31855.h"

// Default connection is using software SPI, but comment and uncomment one of
// the two examples below to switch between software SPI and hardware SPI:

// Example creating a thermocouple instance with software SPI on any three
// digital IO pins.
#define MAXDO_1 31
#define MAXCS_1 30
#define MAXCLK_1 29

// initialize the Thermocouple
Adafruit_MAX31855 thermocouple_1(MAXCLK_1, MAXCS_1, MAXDO_1);

#define MAXDO_2 26
#define MAXCS_2 27
#define MAXCLK_2 28

// initialize the Thermocouple
Adafruit_MAX31855 thermocouple_2(MAXCLK_2, MAXCS_2, MAXDO_2);

const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
byte LCD_Columns = 20;
byte LCD_Rows = 4;

// pin -> interrupt pin
FlowSensor Sensor(YFS201, 18);
unsigned long timebefore = 0;  // same type as millis()
const int Pump = 10;

int variables[2][2] = { { 0, 1 }, { 2, 200 } };

double overall_error;
double overall;
double error;
int reading;
// Comment if use ESP8266 and ESP32
void count() {
  Sensor.count();
}

void setup() {
  Serial.begin(9600);
  Sensor.begin(count);
  thermocouple_1.begin();
  thermocouple_2.begin();
  pinMode(Pump, OUTPUT);
  lcd.begin(20, 4);
  lcd.print("Test");
  delay(1000);
  lcd.clear();
}



void loop() {
  //Pump Stuff
  LcdBarGraph lbg0(&lcd, 19, 1, 2);
  for (variables[0][0]; variables[0][0] < variables[1][1];) {
    lcd.setCursor(7, 1);
    lcd.print("PROGRESS");
    lbg0.drawValue(variables[0][0], variables[1][1]);
    delay(10);
    digitalWrite(Pump, HIGH);
    Sensor.read();
    variables[0][0] = Sensor.getVolume() * 1000;
    Serial.println(variables[0][0]);
    //Temperature Stuff
    float value;
    value = analogRead(ntc_pin);
    Serial.print("Analog Read: ");
    Serial.println(analogRead(ntc_pin));
    // Calculate NTC resistance
    value = 1023 / value - 1;
    value = Rref / value;
    Serial.print("Thermistor resistance ");
    Serial.println(value);
    float temperature;
    temperature = value / nominal_resistance;             // (R/Ro)
    temperature = log(temperature);                       // ln(R/Ro)
    temperature /= beta;                                  // 1/B * ln(R/Ro)
    temperature += 1.0 / (nominal_temeprature + 273.15);  // + (1/To)
    temperature = 1.0 / temperature;                      // Invert
    temperature -= 273.15;                                // convert absolute temp to C
    Serial.print("NTC Temperature ");
    Serial.print(temperature);
    //Thermocouple
    Serial.print(" *C --- ");
    Serial.print("Internal Temp 1 = ");
    Serial.print(thermocouple_1.readInternal());
    double c = thermocouple_1.readCelsius();
    Serial.print(" --- C_1 = ");
    Serial.print(c);
    Serial.print(" degC");
    reading++;
    error = abs(c - temperature);
    Serial.print(" --- Error = ");
    Serial.print(error);
    Serial.println(" degC");
    overall = overall + error;
  }
  digitalWrite(Pump, LOW);
  overall_error = overall / reading;
  Serial.print(" --- Overallerror = ");
  Serial.print(overall_error);
  Serial.println(" degC");
  Serial.print("Readings: ");
  Serial.print(reading);
  lcd.clear();
  lcd.setCursor(8, 1);
  lcd.print("DONE");
  delay(1000);
}

Now to the PID implementation. It seems to work somehow. I didn't want to push it further as I faced the following issue. Now heating up the temperature within the FTH I can read a higher value on my Thermocouple but the NTC value does not seem to change accordingly. I am wondering if the calibration might be of? Or am I doing something else wrong?

#include <FlowSensor.h>
#include <LcdBarGraph.h>
#include <LiquidCrystal.h>

//Internal NTC Stuff
#define ntc_pin A15  // Pin,to which the voltage divider is connected
//#define vd_power_pin 2            // 5V for the voltage divider
#define nominal_resistance 50000  //Nominal resistance at 25⁰C
#define nominal_temeprature 25    // temperature for nominal resistance (almost always 25⁰ C)
#define samplingrate 5            // Number of samples
#define beta 3970                 // The beta coefficient or the B value of the thermistor (usually 3000-4000) check the datasheet for the accurate value.
#define Rref 12000                //Value of  resistor used for the voltage divider
int samples = 0;                  //array to store the samples

//Thermocouple Stuff
#include <SPI.h>
#include "Adafruit_MAX31855.h"

// Example creating a thermocouple instance with software SPI on any three
// digital IO pins.
#define MAXDO_1 31
#define MAXCS_1 30
#define MAXCLK_1 29

// initialize the Thermocouple
Adafruit_MAX31855 thermocouple_1(MAXCLK_1, MAXCS_1, MAXDO_1);

#define MAXDO_2 26
#define MAXCS_2 27
#define MAXCLK_2 28

// initialize the Thermocouple
Adafruit_MAX31855 thermocouple_2(MAXCLK_2, MAXCS_2, MAXDO_2);

//PID Stuff
#include <PID_v1.h>
#define RELAY_PIN 19

//Define Variables we'll be connecting to
double Setpoint, Input, Output;

//Specify the links and initial tuning parameters
double Kp = 2, Ki = 5, Kd = 1;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);

int WindowSize = 83;
unsigned long windowStartTime;

//LCD
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
byte LCD_Columns = 20;
byte LCD_Rows = 4;

//Flow Sensor
FlowSensor Sensor(YFS201, 18);
unsigned long timebefore = 0;  // same type as millis()
const int Pump = 10;

int variables[2][2] = { { 0, 1 }, { 2, 50 } };

void count() {
  Sensor.count();
}

void setup() {
  Serial.begin(9600);
  //PID Stuff
  windowStartTime = millis();
  //initialize the variables we're linked to
  Setpoint = 30; //Temperature is the Setpoint
  //tell the PID to range between 0 and the full window size
  myPID.SetOutputLimits(0, WindowSize);
  //turn the PID on
  myPID.SetMode(AUTOMATIC);

  //Thermocouple
  thermocouple_1.begin();
  thermocouple_2.begin();

  //Flowmeter
  Sensor.begin(count);

  //Flow Through Heater
  pinMode(RELAY_PIN, OUTPUT);

  //Pump
  pinMode(Pump, OUTPUT);

  //LCD Begin and Test
  lcd.begin(20, 4);
  lcd.print("Test");
  delay(1000);
  lcd.clear();
}

void loop() {
  // Pump and show progress on LCD until the desired volume has been reached
  LcdBarGraph lbg0(&lcd, 19, 1, 2);
  for (variables[0][0]; variables[0][0] < variables[1][1];) {
    lcd.setCursor(7, 1);
    lcd.print("PROGRESS");
    lbg0.drawValue(variables[0][0], variables[1][1]);
    delay(10);
    digitalWrite(Pump, HIGH);
    Sensor.read();
    variables[0][0] = Sensor.getVolume() * 1000;
    Serial.println(variables[0][0]);
    //Temperature Stuff
    float value;
    value = analogRead(ntc_pin);
    // Calculate NTC resistance
    value = 1023 / value - 1;
    value = Rref / value;
    //Serial.print("Thermistor resistance ");
    //Serial.println(value);
    float temperature;
    temperature = value / nominal_resistance;             // (R/Ro)
    temperature = log(temperature);                       // ln(R/Ro)
    temperature /= beta;                                  // 1/B * ln(R/Ro)
    temperature += 1.0 / (nominal_temeprature + 273.15);  // + (1/To)
    temperature = 1.0 / temperature;                      // Invert
    temperature -= 273.15;                                // convert absolute temp to C
    Serial.print("NTC Temperature ");
    Serial.print(temperature);
    Serial.print(" degC");
    double c = thermocouple_1.readCelsius();
    double d = thermocouple_2.readCelsius();
    Serial.print(" --- Outlet Control Temperature = ");
    Serial.print(c);
    Serial.print(" degC");
    Serial.print(" --- FTH Control Temperature = ");
    Serial.print(d);
    Serial.print(" degC");

    Input = temperature;
    myPID.Compute();

    /************************************************
   * turn the output pin on/off based on pid output
   ************************************************/

    if (millis() - windowStartTime > WindowSize) {  //time to shift the Relay Window
      windowStartTime += WindowSize;
    }
    if (Output < millis() - windowStartTime) digitalWrite(RELAY_PIN, HIGH);
    else digitalWrite(RELAY_PIN, LOW);
  }
  digitalWrite(RELAY_PIN, LOW);
  digitalWrite(Pump, LOW);
  lcd.clear();
  lcd.setCursor(8, 1);
  lcd.print("DONE");
  delay(1000);
}

How wrong are they and at what temperatures? What is your NTC schematic? With an NTC, it's easy to make the schematic backwards from the voltage divider calculation.

Your couple-step voltage divider calc looks like it reduces to:

Rth = Rref /(1023.0/ADC -1)

which is reasonable for a schematic with the Rref between the ADC and ground, so the measured voltage goes up as the temperature goes up on the NTC Rth. Is that what your schematic is?

The difference is small, but I'm partial to this form:
Rth = Rref/(1024.0/((ADC+0.5) -1)

Why choose Rref = 12000?

With two independent measurements, it is difficult to know which one is more true without good calibration.

Knowing the actual resistances for the thermistor circuit is a big source of error in the calculation. Can you measure Rref, nominal_temperature and nominal_resistance and value? I'd think your nominal 50K Beta=3970 should measure around 3.4K at 100C. With the actual measured values, you should be able to calibrate the thermistor precisely to your thermocouple at room temperature and at operating temperature with a beta calculator, and then the two sensors should agree very well.

Thanks for your reply. I will have to make a few measurements today so that I can provide some data. But my schematics look as followed:

image

Rref was chosen to be 12000 as that was the recommended value from the FTH supplier. It should not make a difference as long as we know the value and get the calculation correct accordingly.

So what I am reading independently of the other Thermocouples on the NTC is the following:
Thermistor resistance 50000.00
Temperature 25.00 *C
Analog Read: 826

but I got that out of this script:

#define ntc_pin A15  // Pin,to which the voltage divider is connected
//#define vd_power_pin 2            // 5V for the voltage divider
#define nominal_resistance 50000  //Nominal resistance at 25⁰C
#define nominal_temeprature 25    // temperature for nominal resistance (almost always 25⁰ C)
#define samplingrate 5            // Number of samples
#define beta 3970                 // The beta coefficient or the B value of the thermistor (usually 3000-4000) check the datasheet for the accurate value.
#define Rref 12000                //Value of  resistor used for the voltage divider

int samples = 0;  //array to store the samples

void setup(void) {
  //pinMode(vd_power_pin, OUTPUT);
  Serial.begin(9600);  //initialize serial communication at a baud rate of 9600
}

// Working with single read Temperature seems off
void loop(void) {
  float value;
  value = analogRead(ntc_pin);
  Serial.print("Analog Read: ");
  Serial.println(analogRead(ntc_pin));
  delay(10);
  // Calculate NTC resistance
  value = 1023 / value - 1;
  value = Rref / value;
  Serial.print("Thermistor resistance ");
  Serial.println(value);
  float temperature;
  temperature = value / nominal_resistance;             // (R/Ro)
  temperature = log(temperature);                       // ln(R/Ro)
  temperature /= beta;                                  // 1/B * ln(R/Ro)
  temperature += 1.0 / (nominal_temeprature + 273.15);  // + (1/To)
  temperature = 1.0 / temperature;                      // Invert
  temperature -= 273.15;                                // convert absolute temp to C
  Serial.print("Temperature ");
  Serial.print(temperature);
  Serial.println(" *C");
  delay(2000);
}

I performed a few trials now. At room temperature, all sensors read the same value (+-0.5degC). One Thermocouple I use to monitor the temperature on the FTH itself and the other one is held into the outgoing water stream (which explains why some readings don't come through, probably grounded?)

With the PID above, the temperature on the NTC raised from 25degC to 28degC. The Temperature on the FTH raised from 25degC to max. 69degC and the water temperature on the outlet side raised to 37degC. So I have a huge difference between the NTC (which is the Input value for the PID) and the actual output temperature.

Solved --> Water Inlet and Outlet were swapped, so the NTC was located on the inlet side.... rookie mistake :smiley:

However now I have played a bit with the PID. I get 70degC Water within roughly 1s. That is heating it up from 25degC. But the PID does not seem to do anything but to just open the "flood gates" meaning 100% of the time the SSR is open, regardless of the input value... I set Kd and Ki to 0 and started playing with the Kp, even at 0.1 I get 70degC even though I set 35 as a setpoint.

Even when all (Kp, Kd and Ki) are set to 0 the Relay is open the whole time. I am trying to understand the PID code at the moment where this might be coming from but unsure so far.

Further investigation has shown, that the PID seems to work. But there are a few unexpected behavior patterns. I print the Input, Calculated Error, Delta Input, Output after Ki has been added, Output after Kd has been added and the "myOutput".

--- NTC Temperature: 62.06 degC --- FTH Control Temp. = 60.00 degC --- Water Outlet Control Temp. = nan degC
Input: 62.06 --- Error: 7.94 --- Delta Input: 2.20 --- OutputSum Ki added: 1.81 --- OutputSum Kp added: 1.81 --- myOutput: 0.00
VALUES: --- Input: 62.06 --- Setpoint: 70.00 --- Output: 0.00

--- NTC Temperature: 64.52 degC --- FTH Control Temp. = 60.25 degC --- Water Outlet Control Temp. = 62.00 degC
Input: 64.52 --- Error: 5.48 --- Delta Input: 2.46 --- OutputSum Ki added: 2.36 --- OutputSum Kp added: 2.36 --- myOutput: 0.00
VALUES: --- Input: 64.52 --- Setpoint: 70.00 --- Output: 0.00

--- NTC Temperature: 62.95 degC --- FTH Control Temp. = 60.75 degC --- Water Outlet Control Temp. = 58.25 degC
Input: 62.95 --- Error: 7.05 --- Delta Input: -1.57 --- OutputSum Ki added: 3.06 --- OutputSum Kp added: 3.06 --- myOutput: 25.81
VALUES: --- Input: 62.95 --- Setpoint: 70.00 --- Output: 25.81

--- NTC Temperature: 62.84 degC --- FTH Control Temp. = 61.25 degC --- Water Outlet Control Temp. = 58.75 degC
Input: 62.84 --- Error: 7.16 --- Delta Input: -0.11 --- OutputSum Ki added: 3.78 --- OutputSum Kp added: 3.78 --- myOutput: 12.06
VALUES: --- Input: 62.84 --- Setpoint: 70.00 --- Output: 12.06

--- NTC Temperature: 64.18 degC --- FTH Control Temp. = 61.75 degC --- Water Outlet Control Temp. = nan degC
Input: 64.18 --- Error: 5.82 --- Delta Input: 1.34 --- OutputSum Ki added: 4.36 --- OutputSum Kp added: 4.36 --- myOutput: 0.00
VALUES: --- Input: 64.18 --- Setpoint: 70.00 --- Output: 0.00

--- NTC Temperature: 65.54 degC --- FTH Control Temp. = 62.25 degC --- Water Outlet Control Temp. = nan degC
Input: 65.54 --- Error: 4.46 --- Delta Input: 1.36 --- OutputSum Ki added: 4.81 --- OutputSum Kp added: 4.81 --- myOutput: 0.00
VALUES: --- Input: 65.54 --- Setpoint: 70.00 --- Output: 0.00

those are just a few readings but as you can see, the setpoint doesn't change, which at some point triggers a Output change, however the output reduces way before the goal is setpoint is reached. And the worst part the SSR is open independent of the Output in general...