:no_upscale():strip_icc():strip_exif()/f/image/29H5xCUM4cINXqja3BICrkBu.jpg?f=user_large)
Zie nu het plaatje nu in deellast, je kunt de temperaturen erbij zien, ook de retour van de installatie, ook de Dt, vermogen in Amp en Watt meterstand is voor je niet interessant die gegevens komen van een goedgekeurde E-tussenmeter .
:no_upscale():strip_icc():strip_exif()/f/image/29H5xCUM4cINXqja3BICrkBu.jpg?f=user_large)
:strip_exif()/f/image/L6HeYDI4x6RfrQO0C7b3pHUz.jpg?f=fotoalbum_large)
:strip_exif()/f/image/Lq0gpibo9A5bnjNZNS6hj17b.jpg?f=fotoalbum_large)
:strip_exif()/f/image/BvD1xgame5eiuxTR6SZNKVFa.png?f=user_large)
:strip_exif()/f/image/IYZBYPSPQmIZWluRUEloCVkw.png?f=user_large)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
| /* Simple way to control the power setting of a Chofu AEYC-0643XU-CH heatpump with an Arduino The power is set with a rotary switch and send via one-way serial communication at 666 bps to the heatpump, using the hardware of Atantic Aurea controlbox. Lift the side of resistor R14 that is connected to the base of T2, and connect the output pin (default is pin 2) of the Arduino via a 1 kOhm resistor to the base of T2. Version 1.0, JHN 02-03-2025 */ #include "SoftwareSerialTX.h" #include <string.h> // Use a SoftwareSerialTX on Pin 2 SoftwareSerialTX txOnly(2); //Define variables and constants int PowerSetpoint; // Setpoint to heat pump warmtepomp wordt gestuurd int PowerStages = 9; // Number of useful steps in heatpump setpoint // For rotary decoder int pinCLK = 10; // CLK of rotary encoder int pinDT = 9; // DT of rotary encoder int pinSwitch = 8; // Push button of rotary encoder to confirm setting int encoderPosCount = 0; // Count position rotary encoder int pinCLKLast; // for rotary encoder int aVal; // for rotary encoder bool bCW; // Boolean Clockwise for rotary encoder bool btnPressed = false; // Button of rotary switch pressed? bool currentState = LOW; const int LEDPin = 13; // Pin to activate LED int TelegramCount = 0; // Follow-up number telegrams volatile unsigned long Interval = 0; // Time since last telegram // Telegram timing unsigned long currentMicros = 0; // Storage current micro seconds unsigned long previousMicros = 0; // Tracks the last pulse start time const unsigned long SendInterval = 600000; // Interval between pulses in microseconds byte data0[] = { 0x19, 0x0, 0x8, 0x0, 0x0, 0x0, 0xd9, 0xb5 }; // Array of hexadecimal bytes for 1st message (last two butes are checksum) byte data1[] = { 0x19, 0x1, 0x0c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xaa, 0x35 }; // Array of hexadecimal bytes for 2nd message (last two butes are checksum) byte data2[] = { 0x19, 0x2, 0x8, 0x1, 0x1, 0x0, 0x99, 0x37 }; // Array of hexadecimal bytes for 3rd message. This is the message to change bytes 4 (on/off) and 5 (power setpoint), and bytes 7 and 8 (checksum) byte data3[] = { 0x19, 0x3, 0x8, 0xb2, 0x2, 0x0, 0xc1, 0x9a }; // Array of hexadecimal bytes for 4th message (last two butes are checksum) byte checksum1[] = { 0x9d, 0x99, 0xcc, 0xff, 0x66, 0x55, 0x00, 0x33, 0x23, 0x10, 0x45 }; //Array of 1st byte of checksum of 3rd message. First byte of the array is for 'off' and output '0', 2nd byte is for 'on' and putput '1' etc. byte checksum2[] = { 0x36, 0x37, 0x64, 0x55, 0xc2, 0xf3, 0xa0, 0x91, 0xaf, 0x9e, 0xcd }; //Array of 2nd byte of presumed checksum. First byte of the array is for 'off' and output '0', 2nd byte is for 'on' and putput '1' etc. void setup() { pinMode(LEDPin, OUTPUT); // Set the pulse pin as an output // Rotary Encoder Setup pinMode(pinCLK, INPUT); pinMode(pinDT, INPUT); pinMode(pinSwitch, INPUT_PULLUP); pinCLKLast = digitalRead(pinCLK); // Read Pin CLK, current pin state corresponds to latest position // Serial Setup Serial.begin(115200); // Start the Serial Monitor for debugging (Serial port 0) txOnly.begin(666); // Set-up software serial baurdrate for sending info to heat pump at 666 baud (pin 2) Serial.println("Setup complete..."); } void loop() { // Send telegram currentMicros = micros(); if (currentMicros - previousMicros >= SendInterval) { // Start a new pulse if 500,000 µs have elapsed previousMicros = currentMicros; digitalWrite(LEDPin, HIGH); switch (TelegramCount) { case 0: // Send message 0 (no changes) sendTelegram(data0, sizeof(data0), "Telegram 0"); break; case 1: // Send message 1 (no changes) sendTelegram(data1, sizeof(data1), "Telegram 1"); break; case 2: // Send and modify message 2 after modifiyng bytes 4, 5,7 and 8 according to the selected power setting if (PowerSetpoint > 0) { data2[3] = 0x1; } else { data2[3] = 0x0; } data2[4] = PowerSetpoint; data2[6] = checksum1[PowerSetpoint]; data2[7] = checksum2[PowerSetpoint]; sendTelegram(data2, sizeof(data2), "Telegram 2"); break; case 3: // Send message 3 (no changes) sendTelegram(data3, sizeof(data3), "Telegram 3"); break; default: Serial.println("Invalid TelegramCount value"); break; } TelegramCount = (TelegramCount + 1) % 4; // Cycle through 0-3 digitalWrite(LEDPin, LOW); } // Check rotary encoder processRotaryEncoder(); } ////// End of main loop ////// // Below are the subroutines void sendTelegram(byte *data, size_t size, const char *name) { for (size_t i = 0; i < size; i++) { txOnly.write(data[i]); if (i < size - 1) { } } } void processRotaryEncoder() { // Read rotary decoder and write to screen int aVal = digitalRead(pinCLK); if (aVal != pinCLKLast) { // Knog is being turned // Determine direction of rotation by reading pin B bCW = (digitalRead(pinDT) != aVal); // This means that Pin A changed first --> clockwise rotation encoderPosCount += bCW ? 1 : -1; // Otherwise Pin Bchanged first so counterclockwise rotation encoderPosCount = constrain(encoderPosCount, 0, PowerStages); } pinCLKLast = aVal; if (digitalRead(pinSwitch) == LOW && !btnPressed) { // Button is pressed btnPressed = true; } else if (digitalRead(pinSwitch) == HIGH && btnPressed) { // Button is released btnPressed = false; PowerSetpoint = encoderPosCount; } } |
Verder heb ik een eenvoudige quick & dirty control box simulator van slechts 30 [edit: 55 regels. Ik heb simulate van MCU communicatie ook toegevoegd (met iets meer onderling verschil dan in het echt, nl. 480 vs. 200 ms] om het programma offline op de hardware te kunnen testen:code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505/* Alternative control for the Atlantic Aurea heat pump, which based on the Chofu AEYC-0643XU-AT unit. This is a sketch to control the power setting of a heat pump with an Arduino ATMega2560 and to modify communication inside the controlbox using 3 of the 4 hardware serial ports of this board. The original code shows various useful information like temperatures etc. on a 480*320 TFT LCD. This has been removed here to keep the code more compact. Version 0.1Beta, JHN 19-03-2025 */ #include <QuickPID.h> // Library for PID controller, see for documentation: https://github.com/Dlloydev/QuickPID //Define variables and constants // General String MessageString = ""; // For heat pump const uint8_t SpeedStages = 7; // Number of speed stages to be used (7 --> ~1400 W max) uint8_t SpeedSetpoint = 0; // Speed setpoint sent to heatpump // const uint8_t MinTempSetpoint = 25; uint8_t TargetTemperatureThermostat = 20; // Target temperature as requested by the thermostat const float MinTemp4HP = -2; // Outside temperatute below wich heat pump should stop working and CV should start uint16_t TelegramCount = 0; // Follow-up number of telegrams // Variables to track timings uint32_t Interval = 0; // Time since last telegram uint32_t currentMillis = 0; // Check timing for sending messages uint32_t LastSpeedSetpointChange = 0; // Keep track when last time speedsetpint has been changed in milliseconds uint32_t SpeedSetpointHysteresis = 420000; // Allow a change in speedsetpoint only once per 7 minutes (when using slow PID parameter set) // Cycle time check uint32_t previousCycleMicros = 0; // To store the time of the last cycle start uint32_t currentCycleMicros = 0; // To store the time of the current cycle start uint32_t cycleTime = 0; // To store the calculated cycle time uint16_t cycleCounter = 0; // Counter to track cycles const uint16_t displayInterval = 10000; // Display cycle time every 10000 cycles uint8_t lcdUpdateStep = 0; // Variable to spread writing to screen over multiple steps // Receive and send inter MCU data const uint32_t McuMessageWaitTime = 200; // Waiting time between receiving message on Pad 1 (Serial 1) and sending on pad 2 (serial 2) in ms uint32_t McuMessageOneReceived = 0; // Tie last message send in ms bool dataMcu2Send = false; uint32_t PreviousMessageSerial2Sent = 0; uint32_t MessageSerial2Interval = 10000; // Maximum interval for sending message on serial 2 (to avoid that communication stops) const uint8_t DataMcuLength = 12; // Length of each transmission between MCU's uint8_t dataArrayOT_IC1_IC2[DataMcuLength] = {0}; // Array to store the incoming 12 bytes for OT IC1 to IC2. uint8_t dataArrayOT_IC2_IC1[DataMcuLength] = {0}; // Array to store the incoming 12 bytes for OT IC2 to IC1. uint8_t dataArrayOT_IC1_IC2_Old[DataMcuLength] = {0}; // Array to store the previous incoming 12 bytes for OT IC1 to IC2. uint8_t dataArrayOT_IC2_IC1_Old[DataMcuLength] = {0}; // Array to store the previous incoming 12 bytes for OT IC2 to IC1. uint16_t ChecksumMCU = 0; // Checksum for communication between MCU's // Variables for communication with heat pump uint8_t CB_HPlength = 0; // Length of communcation CB to HP uint16_t ChecksumCBHP = 0; // Checksum for communication of heat pump with control box uint8_t ChecksumCBHPfalseCount = 0; // Counter for number of times that the checksum did not match. After certain number of mismatches, message will be send to the serial port uint8_t data0[] = { 0x19, 0x0, 0x8, 0x0, 0x0, 0x0, 0xd9, 0xb5 }; // Array of hexadecimal bytes uint8_t data1[] = { 0x19, 0x1, 0x0c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xaa, 0x35 }; // Array of hexadecimal bytes uint8_t data2[] = { 0x19, 0x2, 0x8, 0x1, 0x1, 0x0, 0x99, 0x37 }; // Array of hexadecimal bytes. This is the one to change bytes 4 (on/off) and 5 (setpoint), and 7 and 8 (high and low bytes of the CRC CCITT checksum) uint8_t data3[] = { 0x19, 0x3, 0x8, 0xb2, 0x2, 0x0, 0xc1, 0x9a }; // Array of hexadecimal bytes // Variables for sending messages to heat pump bool IncomingMessageEnded = 0; // Last message start time in milliseconds volatile uint32_t PreviousMessageSent = 0; // Time when prveious message was sent in milliseconds volatile bool IsReceiving = false; // Tracks if message is being received from heat pump to block sending next message to heat pump const uint32_t SendTimeout = 2000; // Time in milliseconds to wait before considering the communication ended const uint32_t SendDelay = 99; // Delay in milliseconds before sending information to heatpump const uint32_t MinMessageSendInterval = 300; // Minimum interval in millis between sending messages when no reply is received volatile uint32_t previousMessageInEnded = 0; // Definitions for reading and checking heat pump data // Prepare array for storing data from heatpump const uint8_t StartByte = 0x91; // First byte must be 145 (0x91) const uint8_t Rows = 4; // Number of rows in heat pump data array const uint8_t Cols = 80; // Number of columns in heat pump data array uint8_t numChars = 0; // Number of characters to be written to screen uint8_t dataHeatPumpTemp[Cols][Rows] = {0}; // Array to store of four messages from heatpump before checksum check uint8_t dataHeatPump[Cols][Rows] = {0}; // Array to store of four messages from heatpump after checksum is correct uint8_t dataHeatPumpOld[Cols][Rows] = {0}; // Array to store previous data of four messages so that screen is only updayted when needed uint8_t extractedRow[Cols]; // 1D array to store the row const uint8_t ValidLengths[] = {13, 14, 19, 21}; // Lengths 12, 13, 18 & 20 (12+1), (13+1), (18+1), (20+1) uint16_t value; // Temporarily storage of datapoint from array float valuefloat; uint8_t Length = 0; float T_supply =0; // Supply water temperature by heatpump float T_return =0; // Return water temperature to heatpump float T_outside =0; // Outside temperature measured on heatpump // State variables for non-blocking serial reading enum SerialState { Wait_Start, // Waiting for the start of a serial message Read_Header, // Reading the header of the message Read_Payload, // Reading the main data (payload) Read_End // Reading the end of the message }; // Enumeration type, which helps in defining different states of serial communication SerialState state = Wait_Start; // Make waiting to start the default uint8_t ID = 0; uint8_t msgLength = 0; uint8_t DataLength = 0; uint8_t buffer[Cols] = {0}; // Temporary buffer for incoming data uint8_t index = 0; int8_t NegT = 0; // Store if temperature is negative (value of 255 in MSB) // For PID controller float TemperaturePIDsetpoint = 20; // Desired supply temperature const float TemperaturePIDsetpointMax = 37; // Maximum temperature setpoint float TemperaturePIDinput = 0; // Current suppy temperature (feedback from the sensor in the heatpump) float TemperaturePIDoutput = 0; // Heat pump control signal float TemperaturePIDoutputRounded = 0; // Heater control signal, rounded off // PID tuning parameter. Default values were Kp = 2.0, Ki = 5.0, Kd = 1.0 uint8_t PIDParameterSet = 0; // Keep track of whcih PID parameters set is being used so that new parameter or only written when required float Sensitivity = 3; // Sensitivity (ratio) between the slow and fast PID settings. Optionally make this chnageable via rotary encoder float PIDParameterThreshold = 1; // Threshold for switch between fast (agressive) and slow (conservative) PID parameters float T_GapPIDParameterSwitch = 0; // Gap between the setpoint and actual temperature, used to determine which set PID parameters to use const float Kp0 = 0.02, Ki0 = 0.008, Kd0 = 0.01;// float Kp = Kp0, Ki = Ki0, Kd = Kd0;// // Create PID object QuickPID myPID(&TemperaturePIDinput, &TemperaturePIDoutput, &TemperaturePIDsetpoint, Kp, Ki, Kd, /* OPTIONS */ myPID.pMode::pOnError, /* pOnError, pOnMeas, pOnErrorMeas */ myPID.dMode::dOnMeas, /* dOnError, dOnMeas */ myPID.iAwMode::iAwCondition, /* iAwCondition, iAwClamp, iAwOff */ myPID.Action::direct); /* direct, reverse */ void setup() { // Serial Setup Serial.begin(115200); // Start the Serial Monitor for debugging on terminal (Serial port 0) Serial1.begin(9600); // Start serial port 1 for receiving messages from OT MCU to HP MCU (RX1, 9600 bps) Serial2.begin(9600); // Start serial port 2 to receive messages from HP MCU and send modified message to OT MCU (RX2 pin & TX2 9600 bps) Serial3.begin(666); // Start serial port 3 for sending an receiving info to heat pump at 666 baud (RX3 and TX3) Serial1.setTimeout(100); // Changes the timeout for readBytes() and other blocking serial read functions (readString(), readBytesUntil(), etc.). Serial2.setTimeout(100); // For QuickPID myPID.SetOutputLimits(0, 99); // Set to max 99% to have max. 4 digits ('99.9') myPID.SetSampleTimeUs(5000000); // Sample time of PID controller in microseconds. Made slower than default because of relatively slow temperature changes and heat pump response to save processing time. myPID.SetTunings(Kp, Ki, Kd); myPID.SetMode(myPID.Control::automatic); // Set PID controller in automatic, i.e. input (PV) is perdiodically compared with setpoint (SP) and control variable (CV) is adjusted Serial.println("Setup complete."); } //////////////// Main loop //////////// void loop() { currentMillis = millis(); // get curent millis (ms) for sending message to heat pump currentCycleMicros = micros(); // Capture the current time (in us) to calculate the cycle time of the loop cycleCounter++; // Increment the cycle counter // Check for incoming data on Serial 1 (Pad 1 from MCU 2), 2 (Pad 2 from MCU 1) and Serial 3 (data from heat pump) readSerialMessageHP(); // Call this function continuously to check for data on the incoming serial port from the heat pump if (Serial1.available() >= DataMcuLength) { // Check whether the serial buffer connected to Pad 1 contains DataMcuLength (12) bytes or more Serial.print("Serial1.available()"); Serial.println(Serial1.available()); readSerialMessageMCU(1); // When so, jump to subroutine to read the bytes or clear the buffer McuMessageOneReceived = currentMillis; // Set timestamp after receiving message on pad 1 (from MCU 2 to 1) dataMcu2Send = true; // Set flag to send message on serial port 2 TemperaturePIDsetpoint = 1 + 0.85 * dataArrayOT_IC2_IC1[0];// Scale the setpoint from the thermostat (0, and 30~40oC) to 25~37oC if (TemperaturePIDsetpoint > TemperaturePIDsetpointMax){ TemperaturePIDsetpoint = TemperaturePIDsetpointMax; } } if (Serial2.available() >= DataMcuLength) { // Check whether the serial buffer connected to Pad 2 contains DataMcuLength (12) bytes or more Serial.print("Serial2.available()"); Serial.println(Serial2.available()); readSerialMessageMCU(2); // When so, jump to subrotuine to read the bytes or clear the buffer } // Write modified message from MCU 1 to 2 on serial 2, McuMessageWaitTime (default 200 ms) after receiving message on serial 1 if ((((currentMillis - McuMessageOneReceived) >= McuMessageWaitTime) && dataMcu2Send == true) || ((currentMillis-PreviousMessageSerial2Sent) > MessageSerial2Interval)) { Serial2.write(dataArrayOT_IC1_IC2, sizeof(dataArrayOT_IC1_IC2)); // Write array to serial port 2 PreviousMessageSerial2Sent = currentMillis; dataMcu2Send = false; } // Send the next message to the heat pump 100 ms after the previous incoming message has ended, or after 1000 ms without sending anything. Use the periodic sending also to update the screen currentMillis = millis(); // get curent millis (ms) for sending message to heat pump if ((( (currentMillis - previousMessageInEnded >= SendDelay) && (!IsReceiving)) || ( currentMillis - previousMessageInEnded >= SendTimeout )) && (currentMillis - PreviousMessageSent >= ( MinMessageSendInterval ))) { switch (TelegramCount) { case 0: Serial3.write(data0, sizeof(data0)); break; case 1: Serial3.write(data1, sizeof(data1)); break; case 2: SpeedSetpoint = (int)round( TemperaturePIDoutput / (100 / SpeedStages )) ; // Scale the PID controller output from 0~100% to 0~8 speed setpoints, and convert from double to rounded integer if (SpeedSetpoint == 0) { data2[3] = 0x0; data2[4] = 0x0; } else { if ((PIDParameterSet == 0) && (currentMillis - LastSpeedSetpointChange > SpeedSetpointHysteresis)) { // Add hysteresis when slow PID parameter set is active data2[3] = 0x1; data2[4] = SpeedSetpoint; // Write calculate speed setpoint in array LastSpeedSetpointChange = currentMillis; } else { if ((PIDParameterSet > 0) && (currentMillis - LastSpeedSetpointChange > SpeedSetpointHysteresis/5)) { // Hysteresis 1/5 of default when fast PID parameter set is active to allow for fast respons data2[3] = 0x1; data2[4] = SpeedSetpoint; // Write calculate speed setpoint in array } } } CB_HPlength = sizeof(data2); calculate_CRC_CCITT_Checksum(data2, CB_HPlength-2, &ChecksumCBHP); data2[6] = (ChecksumCBHP >> 8) & 0xFF; // Replace byte 6 with checksum High Byte data2[7] = ChecksumCBHP & 0xFF; // Replace byte 7 with checksum Low Byte Serial3.write(data2, sizeof(data2)); break; case 3: Serial3.write(data3, sizeof(data3)); break; default: Serial.print("Invalid TelegramCount value: ");Serial.println(TelegramCount); break; } // Serial.print("Message ");Serial.print(TelegramCount);Serial.println(" sent!"); // Confirm that message has bene sent TelegramCount = (TelegramCount + 1) % 4; // Cycle through 0-3 PreviousMessageSent = millis(); } if (cycleCounter >= displayInterval) { if (previousCycleMicros != 0) { cycleTime = ( currentCycleMicros - previousCycleMicros ) / displayInterval; // Time difference between cycles previousCycleMicros = currentCycleMicros; // Update the previous cycle time cycleCounter = 0; // Reset the cycle counter } else { previousCycleMicros = currentCycleMicros; } switch (lcdUpdateStep) { case 0: // Print the cycle time etc. The timing for sneding message to the heatpump wil be oof when the cycle time is too long (> ~50 ms) Serial.println("Cycle time "+String(cycleTime)+" ms"); break; case 1: // Print PID settings break; case 2: // Compresor speed 91-3,9 if (dataHeatPump[9][3] != dataHeatPumpOld[9][3]) { // Data has changed? Serial.print("Speed setpoint: "); Serial.println(dataHeatPump[9][3]); } break; case 3: // Compresor power 91-3,10 if (dataHeatPump[10][3] != dataHeatPumpOld[10][3]) { MessageString = String((25.6 * dataHeatPump[10][3]),0); Serial.print("Compressor power: "); Serial.println(MessageString); dataHeatPumpOld[10][3] = dataHeatPump[10][3]; } // Compressor mode 91-1,3 if (dataHeatPump[3][1] != dataHeatPumpOld[3][1]) { Serial.print("Compressor mode: "); Serial.println(dataHeatPump[3][1]); dataHeatPumpOld[3][1] = dataHeatPump[3][1]; } // Defrost? 91-1,4 if (dataHeatPump[4][1] != dataHeatPumpOld[4][1]) { Serial.print("Defrost ongoing? "); Serial.println(dataHeatPump[4][1]); dataHeatPumpOld[4][1] = dataHeatPump[4][1]; } break; case 4: // ?Data? 91-3,5 if (dataHeatPump[5][3] != dataHeatPumpOld[5][3]) { Serial.print("Generic info "); Serial.println(dataHeatPump[5][3]); dataHeatPumpOld[5][3] = dataHeatPump[5][3]; } break; case 5: // Temperatures // Supply temperature 91-2,3~4 if (dataHeatPump[3][2] != dataHeatPumpOld[3][2]) { value = dataHeatPump[4][2]; // MSB of temperature; previous byte is the MSB T_supply = (((value*256+dataHeatPump[3][2])-(65536*((value == 255) ? 1 : 0)))/10.0); // Do the conversion to temperature in such a way that also negative temperature can be shown TemperaturePIDinput = T_supply; // Make the input the PID controller equal to the current supply water temperature MessageString = String(T_supply,1); Serial.print("Supply temperature "); Serial.println(MessageString); dataHeatPumpOld[3][2] = dataHeatPump[3][2]; } // Return temperature 91-2,5~6 if (dataHeatPump[5][2] != dataHeatPumpOld[5][2]) { value = dataHeatPump[6][2]; // MSB of temperature; previous byte is the MSB T_return = (((value*256+dataHeatPump[5][2])-(65536*((value == 255) ? 1 : 0)))/10.0); // Do the conversion to temperature in such a way that also negative temperature can be shown MessageString = String(T_return,1); Serial.print("Return temperature "); Serial.println(MessageString); dataHeatPumpOld[5][2] = dataHeatPump[5][2]; } break; case 6: // Outside temperature 282 91-2,7~8 if (dataHeatPump[7][2] != dataHeatPumpOld[7][2]) { value = dataHeatPump[8][2]; // MSB of temperature; previous byte is the MSB T_outside = (((value*256+dataHeatPump[7][2])-(65536*((value == 255) ? 1 : 0)))/10.0); // Do the conversion to temperature in such a way that also negative temperature can be shown MessageString = String(T_outside,1); Serial.print("Outside temperature "); Serial.println(MessageString); } break; case 7: // Do PID calculation // Evaluate PID calculation T_GapPIDParameterSwitch = abs(TemperaturePIDsetpoint - TemperaturePIDinput); // Check the gap between the temperature setpoint adn the actual temperature if ((T_GapPIDParameterSwitch < PIDParameterThreshold) && (PIDParameterSet = 1)) { // If the gap is smaller than the threshold and the PID controller is currently using the fast set, then Kp = Kp0; Ki = Ki0; Kd = Kd0; // Choose slow (conservative) settings myPID.SetTunings(Kp, Ki, Kd); // Write a new set of control parameter PIDParameterSet = 0; // Set parameter to default default (low) settings } else { if (PIDParameterSet = 1) { // Gap larger than threshold but slow parameters are used, then Kp = Sensitivity*Kp0; // Choose fast or aggressive settings by larger gap Ki = Sensitivity*Ki0; Kd = Sensitivity*Kd0; myPID.SetTunings(Kp, Ki, Kd); // Write a new set of control parameter PIDParameterSet = 1; // Set parameter to default default (low) settings } } break; case 8: // Print inter MCU communication break; case 9: TemperaturePIDoutputRounded = round(TemperaturePIDoutput*10.0)/10.0; } lcdUpdateStep = (lcdUpdateStep + 1) % 10; // Cycle through screen updates } }////////////// End of main loop ////////////////////////// //////// Below are the subroutines ////////////////////// void readSerialMessageMCU(uint8_t MCU) { uint8_t TempMcuData[DataMcuLength] = {0}; uint8_t TemperatureCategory; // Variable to categorize temperature ////// Inter MCU communication switch (MCU) { case 1: // 1. Communication IC2 (OT) to IC1 (HP): This communication is via PAD 1 and therefore used at serial 1. Check if enough data has arrived in the serial buffer and read it // for (int i = 0; i < DataMcuLength; i++) { // TempMcuData[i] = Serial1.read(); // Read the bytes into the array // } Serial1.readBytes(TempMcuData, DataMcuLength); // Clean way of reading fixed number of bytes into array. Beware: Blocks execution until all bytes arrive or timeout (default timeout is 1000ms). calculateMCUChecksum(TempMcuData, DataMcuLength, ChecksumMCU);// Calculate and check the checksum by determining the sum of the first 11 bytes and taken modules 256 if (ChecksumMCU == TempMcuData[DataMcuLength-1]) { // Check if the checksum matches // for (int i = 0; i < DataMcuLength; i++) { // dataArrayOT_IC2_IC1[i] = TempMcuData[i]; // } memcpy(dataArrayOT_IC2_IC1, TempMcuData, DataMcuLength); // Checksum OK so copy temp array to permanent array, cleaner and faster code than a loop ChecksumMCU = false; // Make checksum false as start value for next time } else { // Checksum incorrect because communication possibly halfway received. clearSerialBuffer(Serial1); // Clears the read buffer of serial 1 if the checksu was wrong to make a fresh start Serial.println("Serial buffer 1 cleared"); } break; case 2: // 2. Communication IC1 (HP) to IC2 (OT): This communication is via PAD 2 and therefore used at serial 2. Check if enough data has arrived in the serial buffer, read it, modify when required and resend Serial2.readBytes(TempMcuData, DataMcuLength); // Clean way of reading fixed number of bytes into array. Beware: Blocks execution until all bytes arrive or timeout (default timeout is 1000ms). calculateMCUChecksum(TempMcuData, DataMcuLength, ChecksumMCU);// Calculate and check the checksum by determining the sum of the first 11 bytes and taken modules 256 if (ChecksumMCU == TempMcuData[DataMcuLength-1]) { // Check if the checksum matches memcpy(dataArrayOT_IC1_IC2, TempMcuData, DataMcuLength); // Cleaner and faster code than a loop ChecksumMCU = false; // Make checksum false as start value for next time if (T_outside < MinTemp4HP) { TemperatureCategory = 0; // Too cold for heat pump, switch on cental heating } else if (T_outside < 4) { TemperatureCategory = 1; // Below 4°C // Prevent that controlbox starts CV } else if (SpeedSetpoint == 0) { TemperatureCategory = 2; // SpeedSetpoint is zero } else { TemperatureCategory = 3; // In all other cases use the temperature setpoint for water as read from Plugwise Anna via MCU2 } switch (TemperatureCategory) { case 0: // Heat pump is not running because it is too cold so central heating should be started dataArrayOT_IC1_IC2[0] = 0xf0; break; case 1: // Below 4°C but heat pump can run, adjust values to prevent central heating activation dataArrayOT_IC1_IC2[0] = dataArrayOT_IC2_IC1[0]; dataArrayOT_IC1_IC2[9] = 0x04; dataArrayOT_IC1_IC2[4] = 0; // Write 0's to force MCU2 to transfer HP temperature to OpenTherm dataArrayOT_IC1_IC2[5] = 0; dataArrayOT_IC1_IC2[6] = 0; dataArrayOT_IC1_IC2[10] = 0; break; case 2: // Heat pump is not running (Speedsetpoint is zero), then let central heating boiler temperatures go to OpenTherm break; case 3: // In all other cases use the temperature setpoint for water as read from Plugwise Anna via MCU2 dataArrayOT_IC1_IC2[0] = dataArrayOT_IC2_IC1[0]; dataArrayOT_IC1_IC2[4] = 0; // Write 0's to force MCU2 to transfer HP temperature to OpenTherm dataArrayOT_IC1_IC2[5] = 0; dataArrayOT_IC1_IC2[6] = 0; dataArrayOT_IC1_IC2[10] = 0; break; } dataArrayOT_IC1_IC2[7] = round(T_supply); // Write the actual outside temperature in the array. Will not work for temperatures below zero because array is 8 bits unsigned! dataArrayOT_IC1_IC2[8] = round(T_return); // dataArrayOT_IC1_IC2[9] = round(T_outside); // Write the actual outside temperature in the array. Will not wok for temperatures below zero because array is 8 bits unsigned! // Serial.print("T_supply: ");Serial.print(T_supply) // Serial.print("T_return: ");Serial.print(T_return) // Serial.print("T_outside: ");Serial.print(T_outside) calculateMCUChecksum(dataArrayOT_IC1_IC2, DataMcuLength, ChecksumMCU);// Recalculate the checksum dataArrayOT_IC1_IC2[11] = round(ChecksumMCU); // Replace old checksum value with new calculated one } else { // Checksum incorrect because communication possibly halfway received. clearSerialBuffer(Serial2); // Clears the read buffer of serial 2 if the checksu was wrong to make a fresh start Serial.println("Serial buffer 2 cleared"); } break; } } void clearSerialBuffer(HardwareSerial &serialPort) { while (serialPort.available() > 0) { serialPort.read(); // Read and discard the data in the buffer } } void calculateMCUChecksum(uint8_t dataArray[], int Length, unsigned int &checksum) { // Calculate the checksum by determining the sum of the first 11 bytes and then take modules 256 checksum = 0; // Initialize the checksum if (Length <= 1) { // Ensure length is valid checksum = 0; return; } // Calculate the checksum for the first (Length-1) bytes in the specified row for (int i = 0; i < Length - 1; i++) { checksum += dataArray[i]; } checksum %= 256; // Modulus 256 to keep it in a single byte } // Function to calculate the CRC-CCITT (0xFFFF) checksum for the communication to and from the heat pump void calculate_CRC_CCITT_Checksum(uint8_t *data, uint8_t Length, uint16_t *checksum) { uint16_t crc = 0xFFFF; // Initial value for (uint8_t i = 0; i < Length; i++) { crc ^= (uint16_t)data[i] << 8; for (uint8_t j = 0; j < 8; j++) { if (crc & 0x8000) { crc = (crc << 1) ^ 0x1021; } else { crc <<= 1; } } } *checksum = crc; } // Function that reads the serial data coming from the heat pump void readSerialMessageHP() { while (Serial3.available()) { // Check if data is available on Serial3 IsReceiving = true; uint8_t byteReceived = Serial3.read(); // Serial.print(String(byteReceived,DEC)+" "); IsReceiving = true; switch (state) { case Wait_Start: // Code to wait for the start character if (byteReceived == StartByte) { state = Read_Header; index = 0; IncomingMessageEnded = false; // Reset if any other byte is received to indicate } break; case Read_Header: // Code to process header bytes if (index == 0) { ID = byteReceived; if (ID > 3) { // ID must be 0-3 Serial.println("Invalid ID, resetting..."); state = Wait_Start; return; } } else if (index == 1) { msgLength = byteReceived+1; if (!IsValidLength(msgLength)) { Serial.println("Invalid Length! Resetting..."); state = Wait_Start; // Reset return; } DataLength = msgLength - 3; // Exclude first 3 bytes and last byte index = 0; state = Read_Payload; } index++; break; case Read_Payload: // Code to process data payload buffer[index++] = byteReceived; if (index >= DataLength) { state = Read_End; } break; case Read_End: // Code to check that last byte of message corresponds to '0' if (byteReceived == 0) { // Ensure last byte is 0 StoreMessage(ID, DataLength, buffer); IncomingMessageEnded = true; // If the end of the incoming message is reached, set the infication boolean previousMessageInEnded = millis(); // Set timestamp after receiving end of incoming message } else { Serial.println("Warning: Last byte is not 0! Resetting..."); } IsReceiving = false; state = Wait_Start; // Reset for next message break; default: Serial.println("Error in readSerialMessageHP state"); break; } } } // Function to check if the Length is valid bool IsValidLength(uint8_t Length) { for (uint8_t i = 0; i < sizeof(ValidLengths) / sizeof(ValidLengths[0]); i++) { if (Length == ValidLengths[i]) return true; } return false; } // Function to store messages in separate arrays based on ID void StoreMessage(uint8_t ID, uint8_t DataLength, uint8_t *message) { for (uint8_t i = 0; i < (DataLength); i++) { dataHeatPumpTemp[i+2][ID] = message[i]; } dataHeatPumpTemp[0][ID] = StartByte; dataHeatPumpTemp[1][ID] = ID; dataHeatPumpTemp[2][ID] = DataLength+2; // 12, 13, 18, 20 for (int j = 0; j < DataLength+2; j++) { // Copy row extractedRow[j] = dataHeatPumpTemp[j][ID]; } calculate_CRC_CCITT_Checksum(extractedRow, DataLength+2, &ChecksumCBHP); // Calculate checksum, including the two checksum bytes --> result will be zero if mesage has been sent error free if (ChecksumCBHP == 0) { // Check that result is indeed zero memcpy(dataHeatPump[ID], dataHeatPumpTemp[ID], Cols * sizeof(uint8_t)); // Transfer the temporarily array to the permanent array ChecksumCBHPfalseCount = 0; Serial.println("Checksum heat pump data received not OK"); // Print a message if there is an error } else { ChecksumCBHPfalseCount++; // Increase counter for false checksums. Nog actie bedebken als threshold wordt bereikt Serial.print("Checksum error! "); // Print a message if there is an error Serial.print("Now ");Serial.print(ChecksumCBHPfalseCount);Serial.print(" Checksum errors (of max 10) counted!"); } }
Aan de hardware kant zij verder ook nog verbeteringen denkbaar. De belangrijkste die medetweakers ook noemden zijn:code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55/* Test program for serial communication related to Atlantic Aurea Hardware connections: Serial connections: Serial port 0: Monitoring and troubleshooting to USB port Serial port 1 (Orange): Connect Pin 19 (Rx1) to Pad 1: Receiving messages from OT MCU to HP MCU (9600 bps). This to extract thermostat setpoint Serial port 2 (Brown/white): Connect Pin 16 & 17 (Tx2 (brown)& Rx2(white)) to Pad 2. Receive messages from HP MCU and send modified message to OT MCU (9600 bps) Serial port 3 (Connect: Pin 14 & optionally 15 (Tx3 & Rx3) (Green): Send messages with requested power to heatpump and optionally receive messages from HP for monitoring purposes and (666 bps) */ //Define variables and constants // Make pulse const uint32_t MessageSerialMCUInterval = 4200; // Interval for mesages send in between MCU (add delay for sending othr data) uint32_t currentMillis = 0; // Check timing for sending messages uint32_t PreviousMessageSerial1Sent = 0; bool SendSerial2 = false; // Variables to receive inter MCU data byte dataArrayOT_IC2_IC1[] = {44,20,154,0,0,33,14,32,29,8,0,78}; // Array for testing communication picked up at Pad 1 on PCB byte dataArrayOT_IC1_IC2[] = {37,20,154,0,0,33,14,32,29,2,0,65}; // Array for testing communication picked up at Pad 2 on PCB // Example of data send from heatpump to control box byte dataHeatPump0[] = {25,0,8,0,0,0,217,181, 145,0,12,13,7,4,47,90,206,225,230,223,0}; // Length 12 byte dataHeatPump1[] = {25,1,12,0,0,0,0,0,0,0,170,53, 145,1,13,80,0,0,0,0,0,138,82,164,39,0}; // Length 13 byte dataHeatPump2[] = {25,2,8,1,5,0,85,243, 145,2,18,18,1,253,0,49,0,1,0,2,0,0,38,216,23,240,0}; // Length 18 byte dataHeatPump3[] = {25,3,8,178,2,0,193,154, 145,3,20,120,81,168,151,21,0,83,53,2,0,24,0,0,0,0,233,241,0}; // Length 20 void setup() { // Serial Setup Serial1.begin(9600); // Start serial port 1 for receiving messages from OT MCU to HP MCU (9600 bps) Serial2.begin(9600); // Start serial port 2 to receive messages from HP MCU and send modified message to OT MCU (9600 bps) Serial3.begin(666); // Start serial port 3 for sending an receiving info to heat pump at 666 baud (pin 16) Serial.println("Setup complete."); } void loop() { currentMillis = millis(); // get curent millis (ms) for sending message in between MCU's if (currentMillis-PreviousMessageSerial1Sent > MessageSerialMCUInterval) { Serial1.write(dataArrayOT_IC1_IC2, sizeof(dataArrayOT_IC1_IC2)); // Write array to serial port 1 PreviousMessageSerial1Sent = currentMillis; SendSerial2 = true; } Serial3.write(dataHeatPump0,sizeof(dataHeatPump0)); delay(412); if (SendSerial2) { Serial2.write(dataArrayOT_IC1_IC2, sizeof(dataArrayOT_IC1_IC2)); // Write array to serial port 2 SendSerial2 = false; } Serial3.write(dataHeatPump1, sizeof(dataHeatPump1)); delay(487); Serial3.write(dataHeatPump2,sizeof(dataHeatPump2)); delay(502); Serial3.write(dataHeatPump3,sizeof(dataHeatPump3)); delay(531); }
:strip_exif()/f/image/tvweTsHtszUAzBnsC4jTvDuO.png?f=user_large)
/f/image/MWWyT4eD3krjkFZABO6jWFGs.png?f=fotoalbum_large)
/f/image/o33kg9Nbk2hzM9e3ygilorbt.png?f=fotoalbum_large)
/f/image/e4fXSxCSp9M4QovmMbljWpZy.png?f=fotoalbum_large)
/f/image/ern13HxnZb9lQfXIyhbekOPI.png?f=fotoalbum_large)
/f/image/XTcfu14f6bBibsNbBkxfxJRF.png?f=fotoalbum_large)
/f/image/lZD2qqK3exCX3Qjnrw5LlvSI.png?f=fotoalbum_large)
[ Voor 102% gewijzigd door WackoH op 21-03-2025 23:19 ]
[ Voor 11% gewijzigd door Grannetia op 23-03-2025 16:14 . Reden: aangepaste veronderstelling ]
![]() | ![]() |
[ Voor 4% gewijzigd door WackoH op 25-03-2025 00:10 ]
:no_upscale():strip_icc():strip_exif()/f/image/VvZdwj3qKEjUBnPAJT9IPne7.jpg?f=user_large)
Haha, herkenbaar
:strip_exif()/f/image/MG4dQ9RWLkMt6P9n2OGSPuKr.png?f=user_large)
[ Voor 3% gewijzigd door WackoH op 09-08-2025 20:38 ]
[ Voor 5% gewijzigd door _JGC_ op 11-08-2025 18:47 ]
Het is niet alleen bij die verkoper of dat hij water zag branden._JGC_ schreef op maandag 11 augustus 2025 @ 18:33:
@WackoH Het ding is verkocht als universele aanvulling op je ketel. Meneer had een pompverdeler, die doet niks met 35 graden, dus schakelt continu je ketel in want je huis wordt koud.
Hij draait nu full electric en heeft nog steeds de pompverdeler... iets met dwarse installateurs die doen wat ze altijd doen. Heb hem verteld dat ie die pompverdeler zsm moest lozen. Hij gaf de schuld aan de bypass.
Wat ik voor komende winter ga doen is een Arduino Uno kopen met een Ethernet shield. Die hang ik met CAT5 in mijn netwerk. De ketel stel ik in als HR Solo Tap en de thermostaat laat ik hangen. Thermostaat vraagt een setpoint aan de ketel en de ketel negeert 'm volkomen, maar ondertussen vis ik via MQTT de aanvoer uit de thermostaat en stuur ik die door naar de controller. Als thermostaat afschakelt omdat temperatuur bereikt is laat ik de warmtepomp op laagste vermogen doordraaien.
Ik heb nog gekeken naar OpenTherm Slave, maar de meeste sturingen gaan over master mode (thermostaat), ik weet niet wat ik allemaal moet implementeren om een werkende aansturing met OpenTherm te krijgen.
Kan evt altijd nog de opentherm pinnen uit de tweede socket aan de Uno hangen en OpenTherm implementeren. Dat wordt project voor volgend jaar als er een boilervat en driewegklep komt en de ketel verdwijnt.
Edit: misschien toch maar een 3/5V logic shifter aanschaffen en een Wemos d1 kloon toepassen die ik nog heb. Arduino Uno is wel heel basic zie ik.
Zijn vijftig was nog schoon. Drinkwaterkaart.nl Drinkwaterspots
Mijn zwager is ook installateur die vanuit de CV ketels de wereld van de warmtepompen is ingerold. Die heeft dus ook dit ding en was altijd van mening dat warmtepompen waardeloos waren en we na 2050 nog gas stoken, want "onder de 5 graden doen ze niks". Heeft ook al vanalles aan zijn installatie verbouwd om het rendement beter te krijgen, maar zet geen zoden aan de dijk.mhr-zip schreef op maandag 11 augustus 2025 @ 19:22:
[...]
Het is niet alleen bij die verkoper of dat hij water zag branden.
De pompverdeler hoeft geen probleem te zijn als die voldoende aanvoer kan krijgen en de pomp niet onnodig hard draait._JGC_ schreef op maandag 11 augustus 2025 @ 18:33:
@WackoH Het ding is verkocht als universele aanvulling op je ketel. Meneer had een pompverdeler, die doet niks met 35 graden, dus schakelt continu je ketel in want je huis wordt koud.
Hij draait nu full electric en heeft nog steeds de pompverdeler... iets met dwarse installateurs die doen wat ze altijd doen. Heb hem verteld dat ie die pompverdeler zsm moest lozen. Hij gaf de schuld aan de bypass.
Denk ook aan de PAF (Partner Acceptance Factor). Het moet ook werken als je servertje is gecrashed.Wat ik voor komende winter ga doen is een Arduino Uno kopen met een Ethernet shield. Die hang ik met CAT5 in mijn netwerk. De ketel stel ik in als HR Solo Tap en de thermostaat laat ik hangen. Thermostaat vraagt een setpoint aan de ketel en de ketel negeert 'm volkomen, maar ondertussen vis ik via MQTT de aanvoer uit de thermostaat en stuur ik die door naar de controller. Als thermostaat afschakelt omdat temperatuur bereikt is laat ik de warmtepomp op laagste vermogen doordraaien.
Er zijn Opentherm libraries in de Arduino IDE. Bv. deze https://github.com/jpraus...erm/blob/master/README.md bevat volgens de beschrijving master, slave en gateway modes.Ik heb nog gekeken naar OpenTherm Slave, maar de meeste sturingen gaan over master mode (thermostaat), ik weet niet wat ik allemaal moet implementeren om een werkende aansturing met OpenTherm te krijgen.
Ik heb zelf wel een zwak voor de Arduino hardware vanwege de Italiaanse roots. Een Uno is een brede serie (vanaf vrijwel zelfde MCU als in Controlbox tot aan iets met cryptoprocessor). De eenvoudigste kan de buitenunit probleemloos aansturen. Maar Ethernet erbij wordt wel tricky want de timing met de buitenunit moet wel kloppen. Een Mega is dan nodig. Heeft ook voldoende seriele poorten. Maar met een WeMOs i.c.m. Arduino IDE heb je een goedkope oplossing,Kan evt altijd nog de opentherm pinnen uit de tweede socket aan de Uno hangen en OpenTherm implementeren. Dat wordt project voor volgend jaar als er een boilervat en driewegklep komt en de ketel verdwijnt.
Edit: misschien toch maar een 3/5V logic shifter aanschaffen en een Wemos d1 kloon toepassen die ik nog heb. Arduino Uno is wel heel basic zie ik.
:strip_exif()/f/image/33rirPW9TYunwNKkgWg1COa0.jpg?f=fotoalbum_large)
MP II 5000 | 25kWh LFP | Solar 5.4kWp Oost / 1.2kWp Zuid / 1.7kWp West / 0.6kWp Noord | L/L & Cascading L/W WP | Tubbergen
:strip_exif()/f/image/1tx2ylCLT0c4smCC3QUpzKGm.jpg?f=fotoalbum_large)
code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284/* * Arduino Mega 2560 - Complete Atlantic Aurea Heat Pump Interface * + Communication with R4 WiFi for web interface * * Hardware connections: * Heat Pump Interface: * Serial1 (Pin 19): OT MCU communication (9600 bps) - Pad 1 * Serial2 (Pin 17/16): HP MCU bidirectional (9600 bps) - Pad 2 * Serial3 (Pin 15/14): Heat Pump direct (666 bps) * * R4 WiFi Communication: * Pin 18 (TX1) → R4 Pin 2 (RX) * Pin 19 (RX1) ← R4 Pin 3 (TX) * GND ↔ GND */ // Heat pump timing constants const uint32_t MESSAGE_SERIAL_MCU_INTERVAL = 4200; // 4.2 seconds between MCU messages const uint16_t HP_MSG_DELAYS[] = {412, 487, 502, 531}; // Heat pump message delays in ms const uint8_t HP_MSG_COUNT = 4; const uint32_t R4_UPDATE_INTERVAL = 2000; // Send data to R4 every 2 seconds // Timing variables uint32_t currentMillis = 0; uint32_t previousMessageSerial1Sent = 0; uint32_t previousR4Update = 0; uint8_t currentHPMessageIndex = 0; bool sendSerial2 = false; bool r4Connected = false; // Message counters for monitoring uint32_t messageCounters[4] = {0, 0, 0, 0}; // serial1, serial2, serial3, received // Communication buffers const uint8_t MAX_BUFFER_SIZE = 64; uint8_t serial1Buffer[MAX_BUFFER_SIZE]; uint8_t serial2Buffer[MAX_BUFFER_SIZE]; uint8_t serial3Buffer[MAX_BUFFER_SIZE]; uint8_t bufferIndex1 = 0, bufferIndex2 = 0, bufferIndex3 = 0; // Atlantic Aurea test data arrays const byte dataArrayOT_IC2_IC1[] = {44,20,154,0,0,33,14,32,29,8,0,78}; const byte dataArrayOT_IC1_IC2[] = {37,20,154,0,0,33,14,32,29,2,0,65}; // Heat pump message structure struct HeatPumpMessage { const byte* data; uint8_t length; }; const byte dataHeatPump0[] = {25,0,8,0,0,0,217,181,145,0,12,13,7,4,47,90,206,225,230,223,0}; const byte dataHeatPump1[] = {25,1,12,0,0,0,0,0,0,0,170,53,145,1,13,80,0,0,0,0,0,138,82,164,39,0}; const byte dataHeatPump2[] = {25,2,8,1,5,0,85,243,145,2,18,18,1,253,0,49,0,1,0,2,0,0,38,216,23,240,0}; const byte dataHeatPump3[] = {25,3,8,178,2,0,193,154,145,3,20,120,81,168,151,21,0,83,53,2,0,24,0,0,0,0,233,241,0}; const HeatPumpMessage heatPumpMessages[] = { {dataHeatPump0, sizeof(dataHeatPump0)}, {dataHeatPump1, sizeof(dataHeatPump1)}, {dataHeatPump2, sizeof(dataHeatPump2)}, {dataHeatPump3, sizeof(dataHeatPump3)} }; void setup() { // Serial initialization Serial.begin(115200); // Debug output Serial1.begin(9600); // OT MCU communication (was heat pump, now using Serial2/3) Serial2.begin(9600); // HP MCU communication Serial3.begin(666); // Heat pump direct communication (666 baud - special) Serial.println("=== Arduino Mega 2560 - Atlantic Aurea Complete System ==="); Serial.println("Heat Pump Interface:"); Serial.println(" Serial1: OT MCU (9600 bps)"); Serial.println(" Serial2: HP MCU (9600 bps)"); Serial.println(" Serial3: Heat Pump Direct (666 baud)"); Serial.println("R4 WiFi Communication:"); Serial.println(" Pin 18 → R4 Pin 2, Pin 19 ← R4 Pin 3"); Serial.println(""); delay(2000); // Give R4 time to start initR4Communication(); Serial.println("Starting Atlantic Aurea heat pump monitoring..."); } void loop() { currentMillis = millis(); // Handle all heat pump communications handleSerial1Communication(); // OT MCU handleSerial2Communication(); // HP MCU handleSerial3Communication(); // Heat pump direct // Process received data from heat pump processReceivedData(); // Send data to R4 WiFi for web interface if (r4Connected && (currentMillis - previousR4Update >= R4_UPDATE_INTERVAL)) { sendDataToR4(); previousR4Update = currentMillis; } // Check R4 responses checkR4Response(); delay(50); // Small delay for system stability } void initR4Communication() { Serial.println("Initializing R4 WiFi communication..."); // Send init command (using Serial1 for R4 comm via pins 18/19) Serial1.println("MEGA_INIT"); delay(500); // Wait for R4 response unsigned long timeout = millis() + 5000; String response = ""; while (millis() < timeout) { if (Serial1.available()) { response = Serial1.readStringUntil('\n'); response.trim(); if (response.length() > 0) break; } delay(100); } if (response.indexOf("R4_READY") != -1 || response.indexOf("R4:") != -1) { r4Connected = true; Serial.println("✅ R4 WiFi connected!"); Serial.println("R4 Response: " + response); } else { Serial.println("⚠️ R4 WiFi not responding properly"); Serial.println("Response: '" + response + "'"); Serial.println("Continuing without R4..."); } } void handleSerial1Communication() { // NOTE: Serial1 is now used for R4 communication // Original OT MCU communication moved to Serial2 for simplicity // Send periodic message on Serial2 (simulating OT MCU) if (currentMillis - previousMessageSerial1Sent >= MESSAGE_SERIAL_MCU_INTERVAL) { Serial2.write(dataArrayOT_IC1_IC2, sizeof(dataArrayOT_IC1_IC2)); previousMessageSerial1Sent = currentMillis; sendSerial2 = true; // Trigger second message messageCounters[0]++; // Count as Serial1 equivalent Serial.print("Sent OT MCU message: "); printByteArray(dataArrayOT_IC1_IC2, sizeof(dataArrayOT_IC1_IC2)); } } void handleSerial2Communication() { // Send follow-up message when triggered if (sendSerial2) { delay(100); // Small delay between messages Serial2.write(dataArrayOT_IC2_IC1, sizeof(dataArrayOT_IC2_IC1)); sendSerial2 = false; messageCounters[1]++; Serial.print("Sent HP MCU message: "); printByteArray(dataArrayOT_IC2_IC1, sizeof(dataArrayOT_IC2_IC1)); } } void handleSerial3Communication() { // Heat pump direct communication (666 baud) static uint32_t lastHPMessageTime = 0; static bool waitingForDelay = false; if (!waitingForDelay) { const HeatPumpMessage& currentMsg = heatPumpMessages[currentHPMessageIndex]; Serial3.write(currentMsg.data, currentMsg.length); messageCounters[2]++; Serial.print("Heat Pump Msg " + String(currentHPMessageIndex) + ": "); printByteArray(currentMsg.data, currentMsg.length); lastHPMessageTime = currentMillis; waitingForDelay = true; } if (waitingForDelay && (currentMillis - lastHPMessageTime >= HP_MSG_DELAYS[currentHPMessageIndex])) { waitingForDelay = false; currentHPMessageIndex = (currentHPMessageIndex + 1) % HP_MSG_COUNT; } } void processReceivedData() { // Check Serial2 for heat pump responses while (Serial2.available() && bufferIndex2 < MAX_BUFFER_SIZE - 1) { serial2Buffer[bufferIndex2++] = Serial2.read(); } // Check Serial3 for heat pump responses while (Serial3.available() && bufferIndex3 < MAX_BUFFER_SIZE - 1) { serial3Buffer[bufferIndex3++] = Serial3.read(); } // Process received messages if (bufferIndex2 > 0) { Serial.print("Received from HP MCU: "); printByteArray(serial2Buffer, bufferIndex2); messageCounters[3]++; bufferIndex2 = 0; } if (bufferIndex3 > 0) { Serial.print("Received from Heat Pump: "); printByteArray(serial3Buffer, bufferIndex3); messageCounters[3]++; bufferIndex3 = 0; } } void sendDataToR4() { if (!r4Connected) return; // Create JSON data for web interface String jsonData = "{"; jsonData += "\"timestamp\":" + String(currentMillis); jsonData += ",\"uptime\":" + String(currentMillis / 1000); jsonData += ",\"counters\":{"; jsonData += "\"ot_mcu\":" + String(messageCounters[0]); jsonData += ",\"hp_mcu\":" + String(messageCounters[1]); jsonData += ",\"heat_pump\":" + String(messageCounters[2]); jsonData += ",\"received\":" + String(messageCounters[3]); jsonData += "},\"status\":{"; jsonData += "\"current_hp_msg\":" + String(currentHPMessageIndex); jsonData += ",\"system\":\"Atlantic Aurea\""; jsonData += ",\"free_memory\":" + String(freeMemory()); // Calculate message rate (messages per minute) uint32_t totalMessages = messageCounters[0] + messageCounters[1] + messageCounters[2]; float messageRate = (float)totalMessages / ((float)currentMillis / 60000.0); if (currentMillis < 60000) messageRate = 0; // Avoid crazy numbers at startup jsonData += ",\"message_rate\":" + String(messageRate, 1); jsonData += "}}"; // Send to R4 WiFi Serial1.println("DATA:" + jsonData); Serial.println("📤 Data sent to R4 WiFi"); } void checkR4Response() { if (Serial1.available()) { String response = Serial1.readStringUntil('\n'); response.trim(); if (response.length() > 0) { Serial.println("📨 R4 WiFi: " + response); if (response.indexOf("WIFI_CONNECTED") != -1) { int colonPos = response.indexOf(":"); if (colonPos != -1) { String ip = response.substring(colonPos + 1); Serial.println(""); Serial.println("🌐 SUCCESS! Atlantic Aurea Web Monitor Available:"); Serial.println("🌐 http://" + ip); Serial.println(""); } } } } } void printByteArray(const byte* data, uint8_t length) { for (uint8_t i = 0; i < length; i++) { if (data[i] < 16) Serial.print("0"); Serial.print(data[i], HEX); if (i < length - 1) Serial.print(" "); } Serial.println(); } int freeMemory() { char top; extern char *__brkval; extern char __bss_end; return __brkval ? &top - __brkval : &top - &__bss_end; }
[ Voor 3% gewijzigd door Maartenkromhout op 05-09-2025 21:38 ]
code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266/* * Arduino Uno R4 WiFi - Atlantic Aurea with Home Assistant MQTT Discovery * Full heat pump monitoring and control with auto-discovery * Made by Maarten K * * Hardware connections: * Pin 2 (RX) ← Arduino Mega Pin 18 (TX) * Pin 3 (TX) → Arduino Mega Pin 19 (RX) * GND ↔ GND */ #include <WiFiS3.h> #include <SoftwareSerial.h> #include <ArduinoMqttClient.h> #include <ArduinoJson.h> // WiFi credentials const char* ssid = ""; const char* password = ""; // Anna Thermostat settings const char* annaIP = "192.168.1."; const int annaPort = 80; const char* annaUsername = "smile"; const char* annaPassword = ""; // MQTT Settings const char* mqttBroker = "192.168.1."; const int mqttPort = 1883; const char* mqttUser = ""; const char* mqttPass = ""; const char* mqttClientId = "ArduinoR4_HeatPump"; // MQTT Topics const char* mqttTopicPrefix = "atlantic_aurea/"; const char* mqttAnnaPrefix = "anna/"; const char* discoveryPrefix = "homeassistant/"; // Communication with Mega SoftwareSerial megaComm(2, 3); // RX, TX // Web server and MQTT WiFiServer server(80); WiFiClient wifiClient; MqttClient mqttClient(wifiClient); // Data from Arduino Mega String lastMegaData = "{}"; unsigned long lastDataReceived = 0; bool megaConnected = false; bool wifiConnected = false; bool mqttConnected = false; bool discoveryPublished = false; // Heat Pump Data Structure struct HeatPumpData { float waterTemp = 0; float outsideTemp = 0; float returnTemp = 0; float evaporatorTemp = 0; float compressorTemp = 0; float targetTemp = 0; float currentPower = 0; bool compressorActive = false; bool defrostActive = false; bool heatingActive = false; bool coolingActive = false; bool dhwActive = false; // Domestic Hot Water String operationMode = "standby"; String errorCode = "none"; int fanSpeed = 0; float cop = 0; // Coefficient of Performance } hpData; // Anna thermostat data float annaCurrentTemp = 0.0; float annaSetpoint = 0.0; String annaPresetMode = "home"; bool annaHeating = false; unsigned long lastAnnaUpdate = 0; const unsigned long annaUpdateInterval = 30000; // MQTT timing unsigned long lastMqttPublish = 0; const unsigned long mqttPublishInterval = 30000; unsigned long lastMqttReconnect = 0; unsigned long lastDiscoveryPublish = 0; // System info unsigned long startTime = 0; String systemStatus = "Starting..."; int refreshInterval = 30; // Device info for Home Assistant String deviceName = "Atlantic Aurea Heat Pump"; String deviceId = "atlantic_aurea_001"; String softwareVersion = "2.0.0"; void setup() { Serial.begin(115200); megaComm.begin(9600); startTime = millis(); pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, LOW); Serial.println("=== Arduino R4 WiFi - Atlantic Aurea HA Integration ==="); Serial.println("Version: " + String(softwareVersion)); // Startup LED sequence for (int i = 0; i < 3; i++) { digitalWrite(LED_BUILTIN, HIGH); delay(300); digitalWrite(LED_BUILTIN, LOW); delay(300); } initWiFi(); if (wifiConnected) { connectMQTT(); } megaComm.println("R4_READY"); Serial.println("Sent R4_READY to Mega"); if (wifiConnected) { updateAnnaData(); } } void loop() { handleMegaCommunication(); if (wifiConnected) { handleWebServer(); if (millis() - lastAnnaUpdate > annaUpdateInterval) { updateAnnaData(); } // MQTT operations if (!mqttClient.connected() && millis() - lastMqttReconnect > 5000) { connectMQTT(); lastMqttReconnect = millis(); } if (mqttClient.connected()) { mqttClient.poll(); // Publish discovery if needed if (!discoveryPublished || millis() - lastDiscoveryPublish > 3600000) { // Re-publish every hour publishHomeAssistantDiscovery(); lastDiscoveryPublish = millis(); } // Publish data periodically if (millis() - lastMqttPublish > mqttPublishInterval) { publishMQTTData(); lastMqttPublish = millis(); } } } updateStatusLED(); delay(50); } void initWiFi() { systemStatus = "Connecting to WiFi..."; Serial.println("Connecting to WiFi: " + String(ssid)); if (WiFi.status() == WL_NO_MODULE) { systemStatus = "WiFi module not found!"; Serial.println("ERROR: WiFi module not found!"); return; } WiFi.begin(ssid, password); int attempts = 0; while (WiFi.status() != WL_CONNECTED && attempts < 30) { delay(500); Serial.print("."); attempts++; digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); } if (WiFi.status() == WL_CONNECTED) { wifiConnected = true; systemStatus = "WiFi Connected"; Serial.println("\nWiFi connected successfully!"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); Serial.print("Signal strength: "); Serial.print(WiFi.RSSI()); Serial.println(" dBm"); server.begin(); Serial.println("Web server started on port 80"); Serial.println("==================================="); Serial.println("Atlantic Aurea Monitor available at:"); Serial.println("http://" + WiFi.localIP().toString()); Serial.println("==================================="); megaComm.println("WIFI_CONNECTED:" + WiFi.localIP().toString()); for (int i = 0; i < 5; i++) { digitalWrite(LED_BUILTIN, HIGH); delay(100); digitalWrite(LED_BUILTIN, LOW); delay(100); } } else { systemStatus = "WiFi connection failed"; Serial.println("\nWiFi connection failed!"); } } void connectMQTT() { if (strlen(mqttBroker) == 0) return; Serial.print("Connecting to MQTT broker: "); Serial.println(mqttBroker); mqttClient.setId(mqttClientId); if (strlen(mqttUser) > 0) { mqttClient.setUsernamePassword(mqttUser, mqttPass); } // Set will message for availability String availTopic = String(mqttTopicPrefix) + "availability"; mqttClient.beginWill(availTopic, false, true); mqttClient.print("offline"); mqttClient.endWill(); if (mqttClient.connect(mqttBroker, mqttPort)) { mqttConnected = true; discoveryPublished = false; // Force re-publish discovery Serial.println("MQTT connected successfully!"); // Publish online status mqttClient.beginMessage(availTopic, true); mqttClient.print("online"); mqttClient.endMessage(); // Subscribe to control topics subscribeToControlTopics(); // Set message callback mqttClient.onMessage(onMqttMessage); Serial.println("MQTT setup complete"); } else { mqttConnected = false; Serial.print("MQTT connection failed! Error: "); Serial.println(mqttClient.connectError()); } } void subscribeToControlTopics() { // Anna control topics mqttClient.subscribe(String(mqttAnnaPrefix) + "setpoint/set"); mqttClient.subscribe(String(mqttAnnaPrefix) + "preset/set"); // Heat pump control topics mqttClient.subscribe(String(mqttTopicPrefix) + "target_temp/set"); mqttClient.subscribe(String(mqttTopicPrefix) + "mode/set"); mqttClient.subscribe(String(mqttTopicPrefix) + "power/set"); mqttClient.subscribe(String(mqttTopicPrefix) + "dhw/set"); Serial.println("Subscribed to all control topics"); } void publishHomeAssistantDiscovery() { Serial.println("Publishing Home Assistant Discovery messages..."); // Device info for all entities String deviceInfo = "\"device\":{"; deviceInfo += "\"identifiers\":[\"" + deviceId + "\"],"; deviceInfo += "\"name\":\"" + deviceName + "\","; deviceInfo += "\"model\":\"Atlantic Aurea\","; deviceInfo += "\"manufacturer\":\"Atlantic\","; deviceInfo += "\"sw_version\":\"" + softwareVersion + "\""; deviceInfo += "}"; // Anna Thermostat as Climate entity publishClimateDiscovery("anna", "Anna Thermostat", deviceInfo); // Heat Pump sensors publishSensorDiscovery("water_temp", "Water Temperature", "°C", "temperature", deviceInfo); publishSensorDiscovery("outside_temp", "Outside Temperature", "°C", "temperature", deviceInfo); publishSensorDiscovery("return_temp", "Return Temperature", "°C", "temperature", deviceInfo); publishSensorDiscovery("evaporator_temp", "Evaporator Temperature", "°C", "temperature", deviceInfo); publishSensorDiscovery("compressor_temp", "Compressor Temperature", "°C", "temperature", deviceInfo); publishSensorDiscovery("target_temp", "Target Temperature", "°C", "temperature", deviceInfo); publishSensorDiscovery("current_power", "Current Power", "kW", "power", deviceInfo); publishSensorDiscovery("cop", "COP", "", "power_factor", deviceInfo); publishSensorDiscovery("fan_speed", "Fan Speed", "rpm", "", deviceInfo); // Binary sensors publishBinarySensorDiscovery("compressor_active", "Compressor Active", "running", deviceInfo); publishBinarySensorDiscovery("defrost_active", "Defrost Active", "cold", deviceInfo); publishBinarySensorDiscovery("heating_active", "Heating Active", "heat", deviceInfo); publishBinarySensorDiscovery("cooling_active", "Cooling Active", "cold", deviceInfo); publishBinarySensorDiscovery("dhw_active", "DHW Active", "moisture", deviceInfo); // Selects and controls publishSelectDiscovery("mode", "Operation Mode", "auto,heat,cool,dhw,standby", deviceInfo); publishNumberDiscovery("target_temp_control", "Target Temperature Control", 15, 55, 0.5, deviceInfo); publishSwitchDiscovery("power", "Power", deviceInfo); publishSwitchDiscovery("dhw_boost", "DHW Boost", deviceInfo); // System sensors publishSensorDiscovery("uptime", "System Uptime", "s", "duration", deviceInfo); publishSensorDiscovery("wifi_rssi", "WiFi Signal", "dBm", "signal_strength", deviceInfo); publishSensorDiscovery("message_rate", "Message Rate", "msg/min", "", deviceInfo); publishSensorDiscovery("error_code", "Error Code", "", "problem", deviceInfo); discoveryPublished = true; Serial.println("Discovery messages published!"); } void publishClimateDiscovery(String id, String name, String deviceInfo) { String topic = String(discoveryPrefix) + "climate/" + deviceId + "/" + id + "/config"; String payload = "{"; payload += "\"name\":\"" + name + "\","; payload += "\"unique_id\":\"" + deviceId + "_" + id + "\","; payload += "\"current_temperature_topic\":\"" + String(mqttAnnaPrefix) + "current_temp\","; payload += "\"temperature_state_topic\":\"" + String(mqttAnnaPrefix) + "setpoint\","; payload += "\"temperature_command_topic\":\"" + String(mqttAnnaPrefix) + "setpoint/set\","; payload += "\"mode_state_topic\":\"" + String(mqttAnnaPrefix) + "preset\","; payload += "\"mode_command_topic\":\"" + String(mqttAnnaPrefix) + "preset/set\","; payload += "\"modes\":[\"home\",\"away\",\"sleep\",\"vacation\"],"; payload += "\"min_temp\":5,"; payload += "\"max_temp\":30,"; payload += "\"temp_step\":0.5,"; payload += "\"availability_topic\":\"" + String(mqttTopicPrefix) + "availability\","; payload += deviceInfo; payload += "}"; mqttClient.beginMessage(topic, true); mqttClient.print(payload); mqttClient.endMessage(); } void publishSensorDiscovery(String id, String name, String unit, String deviceClass, String deviceInfo) { String topic = String(discoveryPrefix) + "sensor/" + deviceId + "/" + id + "/config"; String payload = "{"; payload += "\"name\":\"" + name + "\","; payload += "\"unique_id\":\"" + deviceId + "_" + id + "\","; payload += "\"state_topic\":\"" + String(mqttTopicPrefix) + id + "\","; if (unit.length() > 0) { payload += "\"unit_of_measurement\":\"" + unit + "\","; } if (deviceClass.length() > 0) { payload += "\"device_class\":\"" + deviceClass + "\","; } payload += "\"availability_topic\":\"" + String(mqttTopicPrefix) + "availability\","; payload += deviceInfo; payload += "}"; mqttClient.beginMessage(topic, true); mqttClient.print(payload); mqttClient.endMessage(); } void publishBinarySensorDiscovery(String id, String name, String deviceClass, String deviceInfo) { String topic = String(discoveryPrefix) + "binary_sensor/" + deviceId + "/" + id + "/config"; String payload = "{"; payload += "\"name\":\"" + name + "\","; payload += "\"unique_id\":\"" + deviceId + "_" + id + "\","; payload += "\"state_topic\":\"" + String(mqttTopicPrefix) + id + "\","; payload += "\"payload_on\":\"ON\","; payload += "\"payload_off\":\"OFF\","; if (deviceClass.length() > 0) { payload += "\"device_class\":\"" + deviceClass + "\","; } payload += "\"availability_topic\":\"" + String(mqttTopicPrefix) + "availability\","; payload += deviceInfo; payload += "}"; mqttClient.beginMessage(topic, true); mqttClient.print(payload); mqttClient.endMessage(); } void publishSelectDiscovery(String id, String name, String options, String deviceInfo) { String topic = String(discoveryPrefix) + "select/" + deviceId + "/" + id + "/config"; String payload = "{"; payload += "\"name\":\"" + name + "\","; payload += "\"unique_id\":\"" + deviceId + "_" + id + "\","; payload += "\"state_topic\":\"" + String(mqttTopicPrefix) + id + "\","; payload += "\"command_topic\":\"" + String(mqttTopicPrefix) + id + "/set\","; payload += "\"options\":["; // Parse options int start = 0; bool first = true; while (start < options.length()) { int end = options.indexOf(',', start); if (end == -1) end = options.length(); if (!first) payload += ","; payload += "\"" + options.substring(start, end) + "\""; first = false; start = end + 1; } payload += "],"; payload += "\"availability_topic\":\"" + String(mqttTopicPrefix) + "availability\","; payload += deviceInfo; payload += "}"; mqttClient.beginMessage(topic, true); mqttClient.print(payload); mqttClient.endMessage(); } void publishNumberDiscovery(String id, String name, float min, float max, float step, String deviceInfo) { String topic = String(discoveryPrefix) + "number/" + deviceId + "/" + id + "/config"; String payload = "{"; payload += "\"name\":\"" + name + "\","; payload += "\"unique_id\":\"" + deviceId + "_" + id + "\","; payload += "\"state_topic\":\"" + String(mqttTopicPrefix) + "target_temp\","; payload += "\"command_topic\":\"" + String(mqttTopicPrefix) + "target_temp/set\","; payload += "\"min\":" + String(min) + ","; payload += "\"max\":" + String(max) + ","; payload += "\"step\":" + String(step) + ","; payload += "\"unit_of_measurement\":\"°C\","; payload += "\"availability_topic\":\"" + String(mqttTopicPrefix) + "availability\","; payload += deviceInfo; payload += "}"; mqttClient.beginMessage(topic, true); mqttClient.print(payload); mqttClient.endMessage(); } void publishSwitchDiscovery(String id, String name, String deviceInfo) { String topic = String(discoveryPrefix) + "switch/" + deviceId + "/" + id + "/config"; String payload = "{"; payload += "\"name\":\"" + name + "\","; payload += "\"unique_id\":\"" + deviceId + "_" + id + "\","; payload += "\"state_topic\":\"" + String(mqttTopicPrefix) + id + "\","; payload += "\"command_topic\":\"" + String(mqttTopicPrefix) + id + "/set\","; payload += "\"payload_on\":\"ON\","; payload += "\"payload_off\":\"OFF\","; payload += "\"availability_topic\":\"" + String(mqttTopicPrefix) + "availability\","; payload += deviceInfo; payload += "}"; mqttClient.beginMessage(topic, true); mqttClient.print(payload); mqttClient.endMessage(); } void onMqttMessage(int messageSize) { String topic = mqttClient.messageTopic(); String payload = ""; while (mqttClient.available()) { payload += (char)mqttClient.read(); } Serial.print("MQTT message - Topic: "); Serial.print(topic); Serial.print(" | Payload: "); Serial.println(payload); // Handle control commands if (topic.endsWith("setpoint/set")) { float newSetpoint = payload.toFloat(); if (newSetpoint >= 5.0 && newSetpoint <= 30.0) { setAnnaTemperature(newSetpoint); } } else if (topic.endsWith("preset/set")) { setAnnaPreset(payload); } else if (topic.endsWith("target_temp/set")) { float targetTemp = payload.toFloat(); sendHeatPumpCommand("SET_TARGET", String(targetTemp)); } else if (topic.endsWith("mode/set")) { sendHeatPumpCommand("SET_MODE", payload); } else if (topic.endsWith("power/set")) { sendHeatPumpCommand("POWER", payload); } else if (topic.endsWith("dhw/set")) { sendHeatPumpCommand("DHW", payload); } } void sendHeatPumpCommand(String command, String value) { // Send command to Mega String cmd = "HP_CMD:" + command + ":" + value; megaComm.println(cmd); Serial.println("Sent to Mega: " + cmd); } void publishMQTTData() { if (!mqttClient.connected()) return; Serial.println("Publishing MQTT data..."); // Parse heat pump data from Mega parseHeatPumpData(); // Publish Heat Pump data publishMQTTValue(String(mqttTopicPrefix) + "water_temp", String(hpData.waterTemp)); publishMQTTValue(String(mqttTopicPrefix) + "outside_temp", String(hpData.outsideTemp)); publishMQTTValue(String(mqttTopicPrefix) + "return_temp", String(hpData.returnTemp)); publishMQTTValue(String(mqttTopicPrefix) + "evaporator_temp", String(hpData.evaporatorTemp)); publishMQTTValue(String(mqttTopicPrefix) + "compressor_temp", String(hpData.compressorTemp)); publishMQTTValue(String(mqttTopicPrefix) + "target_temp", String(hpData.targetTemp)); publishMQTTValue(String(mqttTopicPrefix) + "current_power", String(hpData.currentPower)); publishMQTTValue(String(mqttTopicPrefix) + "cop", String(hpData.cop)); publishMQTTValue(String(mqttTopicPrefix) + "fan_speed", String(hpData.fanSpeed)); publishMQTTValue(String(mqttTopicPrefix) + "mode", hpData.operationMode); publishMQTTValue(String(mqttTopicPrefix) + "error_code", hpData.errorCode); // Binary states publishMQTTValue(String(mqttTopicPrefix) + "compressor_active", hpData.compressorActive ? "ON" : "OFF"); publishMQTTValue(String(mqttTopicPrefix) + "defrost_active", hpData.defrostActive ? "ON" : "OFF"); publishMQTTValue(String(mqttTopicPrefix) + "heating_active", hpData.heatingActive ? "ON" : "OFF"); publishMQTTValue(String(mqttTopicPrefix) + "cooling_active", hpData.coolingActive ? "ON" : "OFF"); publishMQTTValue(String(mqttTopicPrefix) + "dhw_active", hpData.dhwActive ? "ON" : "OFF"); // Anna data publishMQTTValue(String(mqttAnnaPrefix) + "current_temp", String(annaCurrentTemp)); publishMQTTValue(String(mqttAnnaPrefix) + "setpoint", String(annaSetpoint)); publishMQTTValue(String(mqttAnnaPrefix) + "preset", annaPresetMode); publishMQTTValue(String(mqttAnnaPrefix) + "heating", annaHeating ? "ON" : "OFF"); // System data String uptime = extractJSONValue(lastMegaData, "uptime"); String msgRate = extractJSONValue(lastMegaData, "message_rate"); publishMQTTValue(String(mqttTopicPrefix) + "uptime", uptime); publishMQTTValue(String(mqttTopicPrefix) + "wifi_rssi", String(WiFi.RSSI())); publishMQTTValue(String(mqttTopicPrefix) + "message_rate", msgRate); Serial.println("MQTT data published"); } void publishMQTTValue(String topic, String value) { mqttClient.beginMessage(topic); mqttClient.print(value); mqttClient.endMessage(); } void parseHeatPumpData() { // Extract heat pump values from JSON // This assumes the Mega sends detailed data hpData.waterTemp = extractJSONValue(lastMegaData, "water_temp").toFloat(); hpData.outsideTemp = extractJSONValue(lastMegaData, "outside_temp").toFloat(); hpData.returnTemp = extractJSONValue(lastMegaData, "return_temp").toFloat(); hpData.evaporatorTemp = extractJSONValue(lastMegaData, "evaporator_temp").toFloat(); hpData.compressorTemp = extractJSONValue(lastMegaData, "compressor_temp").toFloat(); hpData.targetTemp = extractJSONValue(lastMegaData, "target_temp").toFloat(); hpData.currentPower = extractJSONValue(lastMegaData, "power").toFloat(); hpData.cop = extractJSONValue(lastMegaData, "cop").toFloat(); hpData.fanSpeed = extractJSONValue(lastMegaData, "fan_speed").toInt(); // States hpData.compressorActive = extractJSONValue(lastMegaData, "compressor") == "1"; hpData.defrostActive = extractJSONValue(lastMegaData, "defrost") == "1"; hpData.heatingActive = extractJSONValue(lastMegaData, "heating") == "1"; hpData.coolingActive = extractJSONValue(lastMegaData, "cooling") == "1"; hpData.dhwActive = extractJSONValue(lastMegaData, "dhw") == "1"; hpData.operationMode = extractJSONValue(lastMegaData, "mode"); hpData.errorCode = extractJSONValue(lastMegaData, "error"); // Calculate COP if not provided if (hpData.cop == 0 && hpData.currentPower > 0) { // Estimate based on temperatures float deltaT = abs(hpData.waterTemp - hpData.outsideTemp); if (deltaT > 0) { hpData.cop = 5.0 - (deltaT * 0.1); // Simple estimation if (hpData.cop < 1.0) hpData.cop = 1.0; if (hpData.cop > 6.0) hpData.cop = 6.0; } } } void updateAnnaData() { WiFiClient annaClient; Serial.println("Updating Anna thermostat data..."); if (annaClient.connect(annaIP, annaPort)) { String request = "GET /core/appliances HTTP/1.1\r\n"; request += "Host: " + String(annaIP) + "\r\n"; request += "Authorization: Basic " + base64Encode(String(annaUsername) + ":" + String(annaPassword)) + "\r\n"; request += "Connection: close\r\n\r\n"; annaClient.print(request); unsigned long timeout = millis(); while (!annaClient.available() && millis() - timeout < 5000) { delay(10); } String response = ""; while (annaClient.available()) { response += annaClient.readString(); } parseAnnaXML(response); annaClient.stop(); lastAnnaUpdate = millis(); Serial.print("Anna: "); Serial.print(annaCurrentTemp); Serial.print("°C / "); Serial.print(annaSetpoint); Serial.println("°C"); } else { Serial.println("Failed to connect to Anna"); } } void setAnnaTemperature(float temperature) { WiFiClient annaClient; Serial.print("Setting Anna temperature to: "); Serial.println(temperature); if (annaClient.connect(annaIP, annaPort)) { String xmlPayload = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; xmlPayload += "<thermostat><setpoint>" + String(temperature) + "</setpoint></thermostat>"; String request = "PUT /core/appliances/thermostat HTTP/1.1\r\n"; request += "Host: " + String(annaIP) + "\r\n"; request += "Authorization: Basic " + base64Encode(String(annaUsername) + ":" + String(annaPassword)) + "\r\n"; request += "Content-Type: text/xml\r\n"; request += "Content-Length: " + String(xmlPayload.length()) + "\r\n"; request += "Connection: close\r\n\r\n"; request += xmlPayload; annaClient.print(request); delay(500); while (annaClient.available()) { annaClient.read(); } annaClient.stop(); Serial.println("Temperature command sent"); delay(1000); updateAnnaData(); if (mqttClient.connected()) { publishMQTTValue(String(mqttAnnaPrefix) + "setpoint", String(temperature)); } } } void setAnnaPreset(String preset) { WiFiClient annaClient; Serial.print("Setting Anna preset to: "); Serial.println(preset); if (annaClient.connect(annaIP, annaPort)) { String xmlPayload = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; xmlPayload += "<preset_mode>" + preset + "</preset_mode>"; String request = "PUT /core/appliances/thermostat/preset HTTP/1.1\r\n"; request += "Host: " + String(annaIP) + "\r\n"; request += "Authorization: Basic " + base64Encode(String(annaUsername) + ":" + String(annaPassword)) + "\r\n"; request += "Content-Type: text/xml\r\n"; request += "Content-Length: " + String(xmlPayload.length()) + "\r\n"; request += "Connection: close\r\n\r\n"; request += xmlPayload; annaClient.print(request); delay(500); while (annaClient.available()) { annaClient.read(); } annaClient.stop(); Serial.println("Preset command sent"); delay(1000); updateAnnaData(); } } void parseAnnaXML(String xml) { int tempPos = xml.indexOf("<temperature>"); if (tempPos != -1) { int endPos = xml.indexOf("</temperature>", tempPos); String tempStr = xml.substring(tempPos + 13, endPos); annaCurrentTemp = tempStr.toFloat(); } int setpointPos = xml.indexOf("<setpoint>"); if (setpointPos != -1) { int endPos = xml.indexOf("</setpoint>", setpointPos); String setpointStr = xml.substring(setpointPos + 10, endPos); annaSetpoint = setpointStr.toFloat(); } annaHeating = xml.indexOf("<heating>true</heating>") != -1; int presetPos = xml.indexOf("<active_preset>"); if (presetPos != -1) { int endPos = xml.indexOf("</active_preset>", presetPos); annaPresetMode = xml.substring(presetPos + 15, endPos); } } String base64Encode(String input) { const char* b64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; String output = ""; int i = 0; while (i < input.length()) { int a = i < input.length() ? input[i++] : 0; int b = i < input.length() ? input[i++] : 0; int c = i < input.length() ? input[i++] : 0; output += b64chars[a >> 2]; output += b64chars[((a & 3) << 4) | (b >> 4)]; output += b64chars[((b & 15) << 2) | (c >> 6)]; output += b64chars[c & 63]; } return output; } void handleMegaCommunication() { if (megaComm.available()) { String command = megaComm.readStringUntil('\n'); command.trim(); if (command.length() > 0 && command.length() < 1000) { Serial.println("From Mega: " + command); if (command == "MEGA_INIT") { megaComm.println("R4_READY"); megaConnected = true; Serial.println("Sent R4_READY response"); } else if (command.startsWith("DATA:")) { lastMegaData = command.substring(5); lastDataReceived = millis(); megaConnected = true; megaComm.println("R4:DATA_OK"); Serial.println("Received JSON data (" + String(lastMegaData.length()) + " chars)"); // Immediately publish if significant changes detected if (mqttClient.connected()) { checkForSignificantChanges(); } } else { megaComm.println("R4:OK"); } } } if (millis() - lastDataReceived > 15000 && lastDataReceived > 0) { megaConnected = false; Serial.println("Mega communication timeout"); } } void checkForSignificantChanges() { static float lastWaterTemp = 0; static bool lastCompressor = false; float currentWaterTemp = extractJSONValue(lastMegaData, "water_temp").toFloat(); bool currentCompressor = extractJSONValue(lastMegaData, "compressor") == "1"; // Publish immediately if significant change if (abs(currentWaterTemp - lastWaterTemp) > 1.0 || currentCompressor != lastCompressor) { publishMQTTData(); lastMqttPublish = millis(); } lastWaterTemp = currentWaterTemp; lastCompressor = currentCompressor; } String extractJSONValue(String json, String key) { int startPos = json.indexOf("\"" + key + "\":"); if (startPos == -1) return "0"; startPos += key.length() + 3; int endPos = json.indexOf(",", startPos); if (endPos == -1) endPos = json.indexOf("}", startPos); String value = json.substring(startPos, endPos); value.replace("\"", ""); value.trim(); return value; } String formatUptime(int seconds) { int hours = seconds / 3600; int minutes = (seconds % 3600) / 60; int secs = seconds % 60; String result = ""; if (hours > 0) result += String(hours) + "h "; if (minutes > 0) result += String(minutes) + "m "; result += String(secs) + "s"; return result; } void updateStatusLED() { static unsigned long lastBlink = 0; static bool ledState = false; unsigned long interval; if (wifiConnected && megaConnected && mqttConnected) { interval = 3000; // Slow blink = all systems go } else if (wifiConnected && megaConnected) { interval = 1500; // Medium = no MQTT } else if (wifiConnected) { interval = 500; // Fast = no Mega } else { interval = 100; // Very fast = no WiFi } if (millis() - lastBlink > interval) { lastBlink = millis(); ledState = !ledState; digitalWrite(LED_BUILTIN, ledState); } } void handleWebServer() { WiFiClient client = server.available(); if (client) { Serial.println("New web client connected"); String request = ""; String requestBody = ""; bool isPost = false; int contentLength = 0; while (client.connected() && client.available()) { String line = client.readStringUntil('\n'); if (request.length() == 0) { request = line; if (line.indexOf("POST") != -1) { isPost = true; } } if (line.indexOf("Content-Length: ") != -1) { contentLength = line.substring(16).toInt(); } if (line == "\r") { if (isPost && contentLength > 0) { delay(10); requestBody = client.readString(); } break; } } // Handle requests if (request.indexOf("POST /setrefresh") != -1) { int valuePos = requestBody.indexOf("interval="); if (valuePos != -1) { refreshInterval = requestBody.substring(valuePos + 9).toInt(); if (refreshInterval < 5) refreshInterval = 5; if (refreshInterval > 300) refreshInterval = 300; } client.println("HTTP/1.1 303 See Other"); client.println("Location: /"); client.println("Connection: close"); client.println(); } else if (request.indexOf("POST /anna/settemp") != -1) { int valuePos = requestBody.indexOf("temperature="); if (valuePos != -1) { float newTemp = requestBody.substring(valuePos + 12).toFloat(); setAnnaTemperature(newTemp); } client.println("HTTP/1.1 303 See Other"); client.println("Location: /"); client.println("Connection: close"); client.println(); } else if (request.indexOf("POST /anna/setpreset") != -1) { int valuePos = requestBody.indexOf("preset="); if (valuePos != -1) { String preset = requestBody.substring(valuePos + 7); preset.trim(); setAnnaPreset(preset); } client.println("HTTP/1.1 303 See Other"); client.println("Location: /"); client.println("Connection: close"); client.println(); } else if (request.indexOf("POST /hp/command") != -1) { // Handle heat pump commands from web int cmdPos = requestBody.indexOf("cmd="); int valPos = requestBody.indexOf("value="); if (cmdPos != -1 && valPos != -1) { String cmd = requestBody.substring(cmdPos + 4, requestBody.indexOf("&")); String val = requestBody.substring(valPos + 6); sendHeatPumpCommand(cmd, val); } client.println("HTTP/1.1 303 See Other"); client.println("Location: /"); client.println("Connection: close"); client.println(); } else if (request.indexOf("GET /api/data") != -1) { sendJSONResponse(client); } else { sendHTMLResponse(client); } client.stop(); Serial.println("Client disconnected"); } } void sendJSONResponse(WiFiClient &client) { // Parse current data parseHeatPumpData(); client.println("HTTP/1.1 200 OK"); client.println("Content-Type: application/json"); client.println("Access-Control-Allow-Origin: *"); client.println("Connection: close"); client.println(); client.print("{"); // System status client.print("\"status\":{"); client.print("\"wifi\":" + String(wifiConnected ? "true" : "false") + ","); client.print("\"mega\":" + String(megaConnected ? "true" : "false") + ","); client.print("\"mqtt\":" + String(mqttConnected ? "true" : "false") + ","); client.print("\"rssi\":" + String(WiFi.RSSI()) + ","); client.print("\"uptime\":" + String((millis() - startTime) / 1000)); client.print("},"); // Heat pump data client.print("\"heatpump\":{"); client.print("\"water_temp\":" + String(hpData.waterTemp) + ","); client.print("\"outside_temp\":" + String(hpData.outsideTemp) + ","); client.print("\"return_temp\":" + String(hpData.returnTemp) + ","); client.print("\"evaporator_temp\":" + String(hpData.evaporatorTemp) + ","); client.print("\"compressor_temp\":" + String(hpData.compressorTemp) + ","); client.print("\"target_temp\":" + String(hpData.targetTemp) + ","); client.print("\"power\":" + String(hpData.currentPower) + ","); client.print("\"cop\":" + String(hpData.cop) + ","); client.print("\"fan_speed\":" + String(hpData.fanSpeed) + ","); client.print("\"mode\":\"" + hpData.operationMode + "\","); client.print("\"compressor\":" + String(hpData.compressorActive ? "true" : "false") + ","); client.print("\"defrost\":" + String(hpData.defrostActive ? "true" : "false") + ","); client.print("\"heating\":" + String(hpData.heatingActive ? "true" : "false") + ","); client.print("\"cooling\":" + String(hpData.coolingActive ? "true" : "false") + ","); client.print("\"dhw\":" + String(hpData.dhwActive ? "true" : "false") + ","); client.print("\"error\":\"" + hpData.errorCode + "\""); client.print("},"); // Anna data client.print("\"anna\":{"); client.print("\"current_temp\":" + String(annaCurrentTemp) + ","); client.print("\"setpoint\":" + String(annaSetpoint) + ","); client.print("\"mode\":\"" + annaPresetMode + "\","); client.print("\"heating\":" + String(annaHeating ? "true" : "false")); client.print("},"); // Raw data client.print("\"raw_data\":" + lastMegaData); client.println("}"); } void sendHTMLResponse(WiFiClient &client) { client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html; charset=UTF-8"); client.println("Connection: close"); client.println(); // Parse current data parseHeatPumpData(); client.println("<!DOCTYPE html>"); client.println("<html>"); client.println("<head>"); client.println("<meta charset='UTF-8'>"); client.println("<meta name='viewport' content='width=device-width, initial-scale=1.0'>"); client.print("<meta http-equiv='refresh' content='"); client.print(refreshInterval); client.println("'>"); client.println("<title>Atlantic Aurea Control Center</title>"); client.println("<style>"); // Enhanced CSS client.println("body { font-family: 'Segoe UI', Arial, sans-serif; margin: 0; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; }"); client.println(".container { max-width: 1400px; margin: 0 auto; padding: 20px; }"); client.println(".header { background: rgba(255,255,255,0.95); border-radius: 15px; padding: 20px; margin-bottom: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); text-align: center; }"); client.println(".grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin-bottom: 20px; }"); client.println(".card { background: white; border-radius: 10px; padding: 20px; box-shadow: 0 5px 15px rgba(0,0,0,0.1); }"); client.println(".card h2 { margin-top: 0; color: #333; border-bottom: 2px solid #667eea; padding-bottom: 10px; }"); client.println(".status-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; }"); client.println(".status-item { background: linear-gradient(45deg, #667eea, #764ba2); color: white; padding: 10px; border-radius: 8px; text-align: center; }"); client.println(".status-item.temp { background: linear-gradient(45deg, #f093fb, #f5576c); }"); client.println(".status-item.active { background: linear-gradient(45deg, #4facfe, #00f2fe); }"); client.println(".status-item.warning { background: linear-gradient(45deg, #fa709a, #fee140); }"); client.println(".status-item.success { background: linear-gradient(45deg, #30cfd0, #330867); }"); client.println(".label { font-size: 0.8em; opacity: 0.9; margin-bottom: 5px; }"); client.println(".value { font-size: 1.5em; font-weight: bold; }"); client.println(".control-panel { background: #f8f9fa; padding: 15px; border-radius: 8px; margin-top: 15px; }"); client.println(".control-row { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; flex-wrap: wrap; }"); client.println(".btn { background: #667eea; color: white; border: none; padding: 8px 16px; border-radius: 5px; cursor: pointer; }"); client.println(".btn:hover { background: #5a67d8; }"); client.println(".btn-danger { background: #dc3545; }"); client.println(".btn-danger:hover { background: #c82333; }"); client.println(".btn-success { background: #28a745; }"); client.println(".btn-success:hover { background: #218838; }"); client.println("input, select { padding: 8px; border: 1px solid #ddd; border-radius: 5px; }"); client.println(".indicator { display: inline-block; width: 12px; height: 12px; border-radius: 50%; margin-right: 5px; }"); client.println(".indicator.on { background: #28a745; animation: pulse 2s infinite; }"); client.println(".indicator.off { background: #6c757d; }"); client.println("@keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; } }"); client.println("</style>"); client.println("</head>"); client.println("<body>"); client.println("<div class='container'>"); // Header client.println("<div class='header'>"); client.println("<h1>🔥 Atlantic Aurea Heat Pump Control Center</h1>"); client.print("<p>System Status: "); if (megaConnected && wifiConnected && mqttConnected) { client.print("<span class='indicator on'></span>All Systems Online"); } else { client.print("<span class='indicator off'></span>Limited Connectivity"); } client.print(" | Home Assistant: "); client.print(discoveryPublished ? "Discovered ✓" : "Not Published"); client.println("</p>"); client.println("</div>"); // Main grid client.println("<div class='grid'>"); // Heat Pump Temperatures Card client.println("<div class='card'>"); client.println("<h2>🌡️ Temperatures</h2>"); client.println("<div class='status-grid'>"); client.println("<div class='status-item temp'>"); client.println("<div class='label'>Water</div>"); client.print("<div class='value'>"); client.print(hpData.waterTemp, 1); client.println("°C</div></div>"); client.println("<div class='status-item temp'>"); client.println("<div class='label'>Outside</div>"); client.print("<div class='value'>"); client.print(hpData.outsideTemp, 1); client.println("°C</div></div>"); client.println("<div class='status-item temp'>"); client.println("<div class='label'>Return</div>"); client.print("<div class='value'>"); client.print(hpData.returnTemp, 1); client.println("°C</div></div>"); client.println("<div class='status-item temp'>"); client.println("<div class='label'>Target</div>"); client.print("<div class='value'>"); client.print(hpData.targetTemp, 1); client.println("°C</div></div>"); client.println("<div class='status-item temp'>"); client.println("<div class='label'>Evaporator</div>"); client.print("<div class='value'>"); client.print(hpData.evaporatorTemp, 1); client.println("°C</div></div>"); client.println("<div class='status-item temp'>"); client.println("<div class='label'>Compressor</div>"); client.print("<div class='value'>"); client.print(hpData.compressorTemp, 1); client.println("°C</div></div>"); client.println("</div>"); client.println("</div>"); // Heat Pump Status Card client.println("<div class='card'>"); client.println("<h2>⚡ Heat Pump Status</h2>"); client.println("<div class='status-grid'>"); client.println("<div class='status-item " + String(hpData.compressorActive ? "active" : "") + "'>"); client.println("<div class='label'>Compressor</div>"); client.print("<div class='value'>"); client.print(hpData.compressorActive ? "ON" : "OFF"); client.println("</div></div>"); client.println("<div class='status-item " + String(hpData.heatingActive ? "active" : "") + "'>"); client.println("<div class='label'>Heating</div>"); client.print("<div class='value'>"); client.print(hpData.heatingActive ? "ON" : "OFF"); client.println("</div></div>"); client.println("<div class='status-item " + String(hpData.dhwActive ? "active" : "") + "'>"); client.println("<div class='label'>DHW</div>"); client.print("<div class='value'>"); client.print(hpData.dhwActive ? "ON" : "OFF"); client.println("</div></div>"); client.println("<div class='status-item " + String(hpData.defrostActive ? "warning" : "") + "'>"); client.println("<div class='label'>Defrost</div>"); client.print("<div class='value'>"); client.print(hpData.defrostActive ? "ON" : "OFF"); client.println("</div></div>"); client.println("<div class='status-item'>"); client.println("<div class='label'>Power</div>"); client.print("<div class='value'>"); client.print(hpData.currentPower, 1); client.println(" kW</div></div>"); client.println("<div class='status-item success'>"); client.println("<div class='label'>COP</div>"); client.print("<div class='value'>"); client.print(hpData.cop, 1); client.println("</div></div>"); client.println("</div>"); // Heat Pump Control client.println("<div class='control-panel'>"); client.println("<h3>Heat Pump Control</h3>"); client.println("<form method='POST' action='/hp/command' style='display: flex; gap: 10px; flex-wrap: wrap;'>"); client.println("<select name='cmd'>"); client.println("<option value='SET_MODE'>Set Mode</option>"); client.println("<option value='SET_TARGET'>Set Target Temp</option>"); client.println("<option value='POWER'>Power On/Off</option>"); client.println("<option value='DHW'>DHW On/Off</option>"); client.println("</select>"); client.println("<input type='text' name='value' placeholder='Value' style='width: 100px;'>"); client.println("<button type='submit' class='btn'>Send Command</button>"); client.println("</form>"); client.println("</div>"); client.println("</div>"); // Anna Thermostat Card client.println("<div class='card'>"); client.println("<h2>🏠 Anna Thermostat</h2>"); client.println("<div class='status-grid'>"); client.println("<div class='status-item temp'>"); client.println("<div class='label'>Current</div>"); client.print("<div class='value'>"); client.print(annaCurrentTemp, 1); client.println("°C</div></div>"); client.println("<div class='status-item temp'>"); client.println("<div class='label'>Setpoint</div>"); client.print("<div class='value'>"); client.print(annaSetpoint, 1); client.println("°C</div></div>"); client.println("<div class='status-item'>"); client.println("<div class='label'>Mode</div>"); client.print("<div class='value'>"); client.print(annaPresetMode); client.println("</div></div>"); client.println("<div class='status-item " + String(annaHeating ? "active" : "") + "'>"); client.println("<div class='label'>Heating</div>"); client.print("<div class='value'>"); client.print(annaHeating ? "ON" : "OFF"); client.println("</div></div>"); client.println("</div>"); // Anna Control client.println("<div class='control-panel'>"); client.println("<h3>Thermostat Control</h3>"); client.println("<div class='control-row'>"); client.println("<form method='POST' action='/anna/settemp' style='display: flex; gap: 10px;'>"); client.print("<input type='number' name='temperature' min='5' max='30' step='0.5' value='"); client.print(annaSetpoint, 1); client.println("' style='width: 80px;'>"); client.println("<button type='submit' class='btn'>Set Temp</button>"); client.println("</form>"); client.println("<form method='POST' action='/anna/setpreset' style='display: flex; gap: 10px;'>"); client.println("<select name='preset'>"); client.print("<option value='home'"); if (annaPresetMode == "home") client.print(" selected"); client.println(">Home</option>"); client.print("<option value='away'"); if (annaPresetMode == "away") client.print(" selected"); client.println(">Away</option>"); client.print("<option value='sleep'"); if (annaPresetMode == "sleep") client.print(" selected"); client.println(">Sleep</option>"); client.print("<option value='vacation'"); if (annaPresetMode == "vacation") client.print(" selected"); client.println(">Vacation</option>"); client.println("</select>"); client.println("<button type='submit' class='btn'>Set Mode</button>"); client.println("</form>"); client.println("</div>"); client.println("</div>"); client.println("</div>"); client.println("</div>"); // End grid // Refresh control client.println("<div class='card'>"); client.println("<form method='POST' action='/setrefresh' style='display: flex; align-items: center; gap: 10px;'>"); client.println("<label>Auto-refresh interval:</label>"); client.print("<input type='number' name='interval' min='5' max='300' value='"); client.print(refreshInterval); client.println("' style='width: 80px;'>"); client.println("<span>seconds</span>"); client.println("<button type='submit' class='btn'>Update</button>"); client.print("<span style='margin-left: auto;'>Next refresh in <span id='countdown'>"); client.print(refreshInterval); client.println("</span> seconds</span>"); client.println("</form>"); client.println("</div>"); client.println("</div>"); // End container // JavaScript client.println("<script>"); client.println("var seconds = " + String(refreshInterval) + ";"); client.println("setInterval(function() {"); client.println(" seconds--;"); client.println(" if (seconds >= 0) document.getElementById('countdown').innerText = seconds;"); client.println("}, 1000);"); client.println("</script>"); client.println("</body>"); client.println("</html>"); }
[ Voor 57% gewijzigd door WackoH op 05-09-2025 17:17 ]
.Ketel op stand 2 krijgt het water niet voldoende naar de woonkamer. Gevolg is dat aanvoertemperatuur heel snel oploopt en korte runs.WackoH schreef op zondag 14 september 2025 @ 21:04:
Ik vind het geluid eerlijk wel meevallen.
De fan heeft voor zover ik weet en vaste snelheid. De compressor heeft een groot bereik (tussen ~150 en 1500 W), hangt van temperaturen af). De waterpomp kan op drie standen.
Volgens mij heeft het niet zo veel zin om de pomp van WP harder te zetten, net zo goed als dat het weinig zin heeft om de pomp van je CV op 3 (hoogste?) te zetten. De delta_T zijn toch maar relatief klein en hard pompen geeft alleen maar extra geluid en e-verbruik.
Een ATMega 168 en 328 zijn volgens mij pin compatibel (maar check dat nog even).
Het water moet eerst 2 verdiepingen opgevoerd worden met een 28mm buis en splitst daar naar 2x22mm. Ik heb 2 jaar terug heel wat leidingwerk opnieuw gelegd en knietjes verwijderd, moet op het zuid stuk nog een paar vervangen door gebogen bochten.WackoH schreef op maandag 15 september 2025 @ 21:10:
Mmm, klinkt alsof je huis via een rietje met CV en warmtepomp is verbonden![]()
Geen ideale uitgangssituatie voor efficient gebruik van een warmtepomp omdat er meer water rond moet om de warmte over te dragen: Van een typische delta_T van 20oC (bv. 60/40) bij CV naar 5oC (35/30). Dus 4x water moet er rond om dezelfde kW's over te dragen.
Staat er misschein ergens iets geknepen?
[ Voor 3% gewijzigd door _JGC_ op 15-09-2025 22:01 ]
[ Voor 14% gewijzigd door _JGC_ op 16-09-2025 17:04 ]
/f/image/DOAPVBtoT2fP2VM4ITPRR1GQ.png?f=fotoalbum_large)
Wat nog niet werkt:code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488/* Alternative control for the Atlantic Aurea heat pump, which based on the Chofu AEYC-0643XU-AT unit. This is a sketch to control the power setting of a heat pump with an Arduino ATMega2560 and to modify communication inside the controlbox using 3 of the 4 hardware serial ports of this board. The original code shows various useful information like temperatures etc. on a 480*320 TFT LCD. This has been removed here to keep the code more compact. Version 0.1Beta, JHN 19-03-2025 */ #include <QuickPID.h> // Library for PID controller, see for documentation: https://github.com/Dlloydev/QuickPID //Define variables and constants // General String MessageString = ""; // For heat pump const uint8_t SpeedStages = 7; // Number of speed stages to be used (7 --> ~1400 W max) uint8_t SpeedSetpoint = 0; // Speed setpoint sent to heatpump // const uint8_t MinTempSetpoint = 25; uint8_t TargetTemperatureThermostat = 20; // Target temperature as requested by the thermostat const float MinTemp4HP = -15; // Outside temperatute below wich heat pump should stop working and CV should start uint16_t TelegramCount = 0; // Follow-up number of telegrams // Variables to track timings uint32_t Interval = 0; // Time since last telegram uint32_t currentMillis = 0; // Check timing for sending messages uint32_t LastSpeedSetpointChange = 0; // Keep track when last time speedsetpint has been changed in milliseconds uint32_t SpeedSetpointHysteresis = 420000; // Allow a change in speedsetpoint only once per 7 minutes (when using slow PID parameter set) // Cycle time check uint32_t previousCycleMicros = 0; // To store the time of the last cycle start uint32_t currentCycleMicros = 0; // To store the time of the current cycle start uint32_t cycleTime = 0; // To store the calculated cycle time uint16_t cycleCounter = 0; // Counter to track cycles const uint16_t displayInterval = 10000; // Display cycle time every 10000 cycles uint8_t lcdUpdateStep = 0; // Variable to spread writing to screen over multiple steps // Receive and send inter MCU data const uint32_t McuMessageWaitTime = 200; // Waiting time between receiving message on Pad 1 (Serial 1) and sending on pad 2 (serial 2) in ms uint32_t McuMessageOneReceived = 0; // Tie last message send in ms bool dataMcu2Send = false; uint32_t PreviousMessageSerial1Sent = 0; uint32_t MessageSerial1Interval = 4200; // Maximum interval for sending message on serial 2 (to avoid that communication stops) const uint8_t DataMcuLength = 12; // Length of each transmission between MCU's uint8_t dataArrayOT_IC1_IC2[DataMcuLength] = {32,20,154,0,0,0,0,32,29,7,0,18}; // Array to store the incoming 12 bytes for OT IC1 to IC2. uint8_t dataArrayOT_IC2_IC1[DataMcuLength] = {0}; // Array to store the incoming 12 bytes for OT IC2 to IC1. uint16_t ChecksumMCU = 0; // Checksum for communication between MCU's // Variables for communication with heat pump uint8_t CB_HPlength = 0; // Length of communcation CB to HP uint16_t ChecksumCBHP = 0; // Checksum for communication of heat pump with control box uint8_t ChecksumCBHPfalseCount = 0; // Counter for number of times that the checksum did not match. After certain number of mismatches, message will be send to the serial port uint8_t data0[] = { 0x19, 0x0, 0x8, 0x0, 0x0, 0x0, 0xd9, 0xb5 }; // Array of hexadecimal bytes uint8_t data1[] = { 0x19, 0x1, 0x0c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xaa, 0x35 }; // Array of hexadecimal bytes uint8_t data2[] = { 0x19, 0x2, 0x8, 0x1, 0x1, 0x0, 0x99, 0x37 }; // Array of hexadecimal bytes. This is the one to change bytes 4 (on/off) and 5 (setpoint), and 7 and 8 (high and low bytes of the CRC CCITT checksum) uint8_t data3[] = { 0x19, 0x3, 0x8, 0xb2, 0x2, 0x0, 0xc1, 0x9a }; // Array of hexadecimal bytes // Variables for sending messages to heat pump bool IncomingMessageEnded = false; // Last message start time in milliseconds volatile uint32_t PreviousMessageSent = 0; // Time when prveious message was sent in milliseconds volatile bool IsReceiving = false; // Tracks if message is being received from heat pump to block sending next message to heat pump const uint32_t SendTimeout = 2000; // Time in milliseconds to wait before considering the communication ended const uint32_t SendDelay = 99; // Delay in milliseconds before sending information to heatpump const uint32_t MinMessageSendInterval = 300; // Minimum interval in millis between sending messages when no reply is received volatile uint32_t previousMessageInEnded = 0; // Definitions for reading and checking heat pump data // Prepare array for storing data from heatpump const uint8_t StartByte = 0x91; // First byte must be 145 (0x91) const uint8_t Rows = 4; // Number of rows in heat pump data array const uint8_t Cols = 80; // Number of columns in heat pump data array uint8_t numChars = 0; // Number of characters to be written to screen uint8_t dataHeatPumpTemp[Cols][Rows] = {0}; // Array to store of four messages from heatpump before checksum check uint8_t dataHeatPump[Cols][Rows] = {0}; // Array to store of four messages from heatpump after checksum is correct uint8_t dataHeatPumpOld[Cols][Rows] = {0}; // Array to store previous data of four messages so that screen is only updayted when needed uint8_t extractedRow[Cols]; // 1D array to store the row const uint8_t ValidLengths[] = {13, 14, 19, 21}; // Lengths 12, 13, 18 & 20 (12+1), (13+1), (18+1), (20+1) uint16_t value; // Temporarily storage of datapoint from array float valuefloat; uint8_t Length = 0; float T_supply =0; // Supply water temperature by heatpump float T_return =0; // Return water temperature to heatpump float T_outside =0; // Outside temperature measured on heatpump // State variables for non-blocking serial reading enum SerialState { Wait_Start, // Waiting for the start of a serial message Read_Header, // Reading the header of the message Read_Payload, // Reading the main data (payload) Read_End // Reading the end of the message }; // Enumeration type, which helps in defining different states of serial communication SerialState state = Wait_Start; // Make waiting to start the default uint8_t ID = 0; uint8_t msgLength = 0; uint8_t DataLength = 0; uint8_t buffer[Cols] = {0}; // Temporary buffer for incoming data uint8_t index = 0; int8_t NegT = 0; // Store if temperature is negative (value of 255 in MSB) // For PID controller float TemperaturePIDsetpoint = 10; // Desired supply temperature const float TemperaturePIDsetpointMax = 37; // Maximum temperature setpoint const float TemperaturePIDsetpointMin = 25; // Maximum temperature setpoint const float TemperaturePIDsetpointOff = 10; float TemperaturePIDinput = 0; // Current suppy temperature (feedback from the sensor in the heatpump) float TemperaturePIDoutput = 0; // Heat pump control signal float TemperaturePIDoutputRounded = 0; // Heater control signal, rounded off // PID tuning parameter. Default values were Kp = 2.0, Ki = 5.0, Kd = 1.0 uint8_t PIDParameterSet = 0; // Keep track of whcih PID parameters set is being used so that new parameter or only written when required float Sensitivity = 3; // Sensitivity (ratio) between the slow and fast PID settings. Optionally make this chnageable via rotary encoder float PIDParameterThreshold = 1; // Threshold for switch between fast (agressive) and slow (conservative) PID parameters float T_GapPIDParameterSwitch = 0; // Gap between the setpoint and actual temperature, used to determine which set PID parameters to use const float Kp0 = 0.02, Ki0 = 0.008, Kd0 = 0.01;// float Kp = Kp0, Ki = Ki0, Kd = Kd0;// // Create PID object QuickPID myPID(&TemperaturePIDinput, &TemperaturePIDoutput, &TemperaturePIDsetpoint, Kp, Ki, Kd, /* OPTIONS */ myPID.pMode::pOnError, /* pOnError, pOnMeas, pOnErrorMeas */ myPID.dMode::dOnMeas, /* dOnError, dOnMeas */ myPID.iAwMode::iAwCondition, /* iAwCondition, iAwClamp, iAwOff */ myPID.Action::direct); /* direct, reverse */ void setup() { // Serial Setup Serial.begin(115200); // Start the Serial Monitor for debugging on terminal (Serial port 0) Serial2.begin(9600); // Start serial port 1 for receiving messages from OT MCU to HP MCU (RX1, 9600 bps) Serial3.begin(666); // Start serial port 3 for sending an receiving info to heat pump at 666 baud (RX3 and TX3) Serial2.setTimeout(100); // Changes the timeout for readBytes() and other blocking serial read functions (readString(), readBytesUntil(), etc.). // For QuickPID myPID.SetOutputLimits(0, 99); // Set to max 99% to have max. 4 digits ('99.9') myPID.SetSampleTimeUs(5000000); // Sample time of PID controller in microseconds. Made slower than default because of relatively slow temperature changes and heat pump response to save processing time. myPID.SetTunings(Kp, Ki, Kd); myPID.SetMode(myPID.Control::automatic); // Set PID controller in automatic, i.e. input (PV) is perdiodically compared with setpoint (SP) and control variable (CV) is adjusted delay(5000); Serial.println("Setup complete."); } //////////////// Main loop //////////// void loop() { currentMillis = millis(); // get curent millis (ms) for sending message to heat pump currentCycleMicros = micros(); // Capture the current time (in us) to calculate the cycle time of the loop cycleCounter++; // Increment the cycle counter // Check for incoming data on Serial 1 (Pad 1 from MCU 2), 2 (Pad 2 from MCU 1) and Serial 3 (data from heat pump) readSerialMessageHP(); // Call this function continuously to check for data on the incoming serial port from the heat pump if (Serial2.available() >= DataMcuLength) { // Check whether the serial buffer connected to Pad 1 contains DataMcuLength (12) bytes or more readSerialMessageMCU(1); // When so, jump to subroutine to read the bytes or clear the buffer //TemperaturePIDsetpoint = 1 + 0.85 * dataArrayOT_IC2_IC1[0];// Scale the setpoint from the thermostat (0, and 30~40oC) to 25~37oC TemperaturePIDsetpoint = dataArrayOT_IC2_IC1[0]; if (TemperaturePIDsetpoint > TemperaturePIDsetpointMax){ TemperaturePIDsetpoint = TemperaturePIDsetpointMax; } else if (TemperaturePIDsetpoint <= TemperaturePIDsetpointOff) { TemperaturePIDsetpoint = 0; } else if (TemperaturePIDsetpoint < TemperaturePIDsetpointMin) { TemperaturePIDsetpoint = TemperaturePIDsetpointMin; } } // Write modified message from MCU 1 to 2 on serial 2 if ((currentMillis-PreviousMessageSerial1Sent) > MessageSerial1Interval) { readSerialMessageMCU(2); Serial2.write(dataArrayOT_IC1_IC2, sizeof(dataArrayOT_IC1_IC2)); // Write array to serial port 2 PreviousMessageSerial1Sent = currentMillis; } // Send the next message to the heat pump 100 ms after the previous incoming message has ended, or after 1000 ms without sending anything. Use the periodic sending also to update the screen currentMillis = millis(); // get curent millis (ms) for sending message to heat pump if ((( (currentMillis - previousMessageInEnded >= SendDelay) && (!IsReceiving)) || ( currentMillis - previousMessageInEnded >= SendTimeout )) && (currentMillis - PreviousMessageSent >= ( MinMessageSendInterval ))) { switch (TelegramCount) { case 0: Serial3.write(data0, sizeof(data0)); break; case 1: Serial3.write(data1, sizeof(data1)); break; case 2: SpeedSetpoint = (int)round( TemperaturePIDoutput / (100 / SpeedStages )) ; // Scale the PID controller output from 0~100% to 0~8 speed setpoints, and convert from double to rounded integer if (SpeedSetpoint == 0 && TemperaturePIDsetpoint > 0) { // Workaround: We still want heat, but not much. Keep WP running SpeedSetpoint = 1; } if (SpeedSetpoint == 0) { data2[3] = 0x0; data2[4] = 0x0; } else if ((PIDParameterSet == 0) && (currentMillis - LastSpeedSetpointChange > SpeedSetpointHysteresis)) { // Add hysteresis when slow PID parameter set is active data2[3] = 0x1; data2[4] = SpeedSetpoint; // Write calculate speed setpoint in array LastSpeedSetpointChange = currentMillis; } else if ((PIDParameterSet > 0) && (currentMillis - LastSpeedSetpointChange > SpeedSetpointHysteresis/5)) { // Hysteresis 1/5 of default when fast PID parameter set is active to allow for fast respons data2[3] = 0x1; data2[4] = SpeedSetpoint; // Write calculate speed setpoint in array } CB_HPlength = sizeof(data2); calculate_CRC_CCITT_Checksum(data2, CB_HPlength-2, &ChecksumCBHP); data2[6] = (ChecksumCBHP >> 8) & 0xFF; // Replace byte 6 with checksum High Byte data2[7] = ChecksumCBHP & 0xFF; // Replace byte 7 with checksum Low Byte Serial3.write(data2, sizeof(data2)); break; case 3: Serial3.write(data3, sizeof(data3)); break; default: Serial.print("Invalid TelegramCount value: ");Serial.println(TelegramCount); break; } TelegramCount = (TelegramCount + 1) % 4; // Cycle through 0-3 PreviousMessageSent = millis(); } if (cycleCounter >= displayInterval) { if (previousCycleMicros != 0) { cycleTime = ( currentCycleMicros - previousCycleMicros ) / displayInterval; // Time difference between cycles previousCycleMicros = currentCycleMicros; // Update the previous cycle time cycleCounter = 0; // Reset the cycle counter } else { previousCycleMicros = currentCycleMicros; } switch (lcdUpdateStep) { case 0: // Print the cycle time etc. The timing for sneding message to the heatpump wil be oof when the cycle time is too long (> ~50 ms) Serial.println("Cycle time "+String(cycleTime)+" ms"); break; case 1: // Print PID settings break; case 2: // Compresor speed 91-3,9 if (dataHeatPump[9][3] != dataHeatPumpOld[9][3]) { // Data has changed? Serial.print("Speed setpoint: "); Serial.println(dataHeatPump[9][3]); } break; case 3: // Compresor power 91-3,10 if (dataHeatPump[10][3] != dataHeatPumpOld[10][3]) { MessageString = String((25.6 * dataHeatPump[10][3]),0); Serial.print("Compressor power: "); Serial.println(MessageString); dataHeatPumpOld[10][3] = dataHeatPump[10][3]; } // Compressor mode 91-1,3 if (dataHeatPump[3][1] != dataHeatPumpOld[3][1]) { Serial.print("Compressor mode: "); Serial.println(dataHeatPump[3][1]); dataHeatPumpOld[3][1] = dataHeatPump[3][1]; } // Defrost? 91-1,4 if (dataHeatPump[4][1] != dataHeatPumpOld[4][1]) { Serial.print("Defrost ongoing? "); Serial.println(dataHeatPump[4][1]); dataHeatPumpOld[4][1] = dataHeatPump[4][1]; } break; case 4: // ?Data? 91-3,5 if (dataHeatPump[5][3] != dataHeatPumpOld[5][3]) { Serial.print("Generic info "); Serial.println(dataHeatPump[5][3]); dataHeatPumpOld[5][3] = dataHeatPump[5][3]; } break; case 5: // Temperatures // Supply temperature 91-2,3~4 if (dataHeatPump[3][2] != dataHeatPumpOld[3][2]) { value = dataHeatPump[4][2]; // MSB of temperature; previous byte is the MSB T_supply = (((value*256+dataHeatPump[3][2])-(65536*((value == 255) ? 1 : 0)))/10.0); // Do the conversion to temperature in such a way that also negative temperature can be shown TemperaturePIDinput = T_supply; // Make the input the PID controller equal to the current supply water temperature MessageString = String(T_supply,1); Serial.print("Supply temperature "); Serial.println(MessageString); dataHeatPumpOld[3][2] = dataHeatPump[3][2]; } // Return temperature 91-2,5~6 if (dataHeatPump[5][2] != dataHeatPumpOld[5][2]) { value = dataHeatPump[6][2]; // MSB of temperature; previous byte is the MSB T_return = (((value*256+dataHeatPump[5][2])-(65536*((value == 255) ? 1 : 0)))/10.0); // Do the conversion to temperature in such a way that also negative temperature can be shown MessageString = String(T_return,1); Serial.print("Return temperature "); Serial.println(MessageString); dataHeatPumpOld[5][2] = dataHeatPump[5][2]; } break; case 6: // Outside temperature 282 91-2,7~8 if (dataHeatPump[7][2] != dataHeatPumpOld[7][2]) { value = dataHeatPump[8][2]; // MSB of temperature; previous byte is the MSB T_outside = (((value*256+dataHeatPump[7][2])-(65536*((value == 255) ? 1 : 0)))/10.0); // Do the conversion to temperature in such a way that also negative temperature can be shown MessageString = String(T_outside,1); Serial.print("Outside temperature "); Serial.println(MessageString); } break; case 7: // Do PID calculation // Evaluate PID calculation T_GapPIDParameterSwitch = abs(TemperaturePIDsetpoint - TemperaturePIDinput); // Check the gap between the temperature setpoint adn the actual temperature if ((T_GapPIDParameterSwitch < PIDParameterThreshold) && (PIDParameterSet = 1)) { // If the gap is smaller than the threshold and the PID controller is currently using the fast set, then Kp = Kp0; Ki = Ki0; Kd = Kd0; // Choose slow (conservative) settings myPID.SetTunings(Kp, Ki, Kd); // Write a new set of control parameter PIDParameterSet = 0; // Set parameter to default default (low) settings } else if (PIDParameterSet = 1) { // Gap larger than threshold but slow parameters are used, then Kp = Sensitivity*Kp0; // Choose fast or aggressive settings by larger gap Ki = Sensitivity*Ki0; Kd = Sensitivity*Kd0; myPID.SetTunings(Kp, Ki, Kd); // Write a new set of control parameter PIDParameterSet = 1; // Set parameter to default default (low) settings } break; case 8: // Print inter MCU communication break; case 9: TemperaturePIDoutputRounded = round(TemperaturePIDoutput*10.0)/10.0; } lcdUpdateStep = (lcdUpdateStep + 1) % 10; // Cycle through screen updates } myPID.Compute(); }////////////// End of main loop ////////////////////////// //////// Below are the subroutines ////////////////////// void readSerialMessageMCU(uint8_t MCU) { uint8_t TempMcuData[DataMcuLength] = {0}; uint8_t TemperatureCategory; // Variable to categorize temperature ////// Inter MCU communication switch (MCU) { case 1: // 1. Communication IC2 (OT) to IC1 (HP): This communication is via PAD 1 and therefore used at serial 1. Check if enough data has arrived in the serial buffer and read it Serial2.readBytes(TempMcuData, DataMcuLength); // Clean way of reading fixed number of bytes into array. Beware: Blocks execution until all bytes arrive or timeout (default timeout is 1000ms). calculateMCUChecksum(TempMcuData, DataMcuLength, ChecksumMCU);// Calculate and check the checksum by determining the sum of the first 11 bytes and taken modules 256 if (ChecksumMCU == TempMcuData[DataMcuLength-1]) { // Check if the checksum matches memcpy(dataArrayOT_IC2_IC1, TempMcuData, DataMcuLength); // Checksum OK so copy temp array to permanent array, cleaner and faster code than a loop ChecksumMCU = false; // Make checksum false as start value for next time McuMessageOneReceived = currentMillis; } else { // Checksum incorrect because communication possibly halfway received. clearSerialBuffer(Serial2); // Clears the read buffer of serial 1 if the checksu was wrong to make a fresh start Serial.println("Serial buffer 1 cleared"); } break; case 2: // 2. Communication IC1 (HP) to IC2 (OT): This communication is via PAD 2 and therefore used at serial 2. Check if enough data has arrived in the serial buffer, read it, modify when required and resend //Serial1.readBytes(TempMcuData, DataMcuLength); // Clean way of reading fixed number of bytes into array. Beware: Blocks execution until all bytes arrive or timeout (default timeout is 1000ms). //calculateMCUChecksum(TempMcuData, DataMcuLength, ChecksumMCU);// Calculate and check the checksum by determining the sum of the first 11 bytes and taken modules 256 // Make checksum false as start value for next time if (T_outside < MinTemp4HP) { TemperatureCategory = 0; // Too cold for heat pump, switch on cental heating } else if (T_outside < 4) { TemperatureCategory = 1; // Below 4°C // Prevent that controlbox starts CV } else if (SpeedSetpoint == 0) { TemperatureCategory = 2; // SpeedSetpoint is zero } else { TemperatureCategory = 3; // In all other cases use the temperature setpoint for water as read from Plugwise Anna via MCU2 } switch (TemperatureCategory) { case 0: // Heat pump is not running because it is too cold so central heating should be started dataArrayOT_IC1_IC2[0] = 0xf0; break; case 1: // Below 4°C but heat pump can run, adjust values to prevent central heating activation dataArrayOT_IC1_IC2[0] = dataArrayOT_IC2_IC1[0]; dataArrayOT_IC1_IC2[9] = 0x04; dataArrayOT_IC1_IC2[4] = 0; // Write 0's to force MCU2 to transfer HP temperature to OpenTherm dataArrayOT_IC1_IC2[5] = 0; dataArrayOT_IC1_IC2[6] = 0; dataArrayOT_IC1_IC2[10] = 0; break; case 2: // Heat pump is not running (Speedsetpoint is zero), then let central heating boiler temperatures go to OpenTherm case 3: // In all other cases use the temperature setpoint for water as read from Plugwise Anna via MCU2 dataArrayOT_IC1_IC2[0] = dataArrayOT_IC2_IC1[0]; dataArrayOT_IC1_IC2[4] = 0; // Write 0's to force MCU2 to transfer HP temperature to OpenTherm dataArrayOT_IC1_IC2[5] = 0; dataArrayOT_IC1_IC2[6] = 0; dataArrayOT_IC1_IC2[10] = 0; break; } dataArrayOT_IC1_IC2[7] = round(T_supply); dataArrayOT_IC1_IC2[8] = round(T_return); if (T_outside >= 4) { dataArrayOT_IC1_IC2[9] = round(T_outside); } calculateMCUChecksum(dataArrayOT_IC1_IC2, DataMcuLength, ChecksumMCU);// Recalculate the checksum dataArrayOT_IC1_IC2[11] = round(ChecksumMCU); // Replace old checksum value with new calculated one break; } } void clearSerialBuffer(HardwareSerial &serialPort) { while (serialPort.available() > 0) { serialPort.read(); // Read and discard the data in the buffer } } void calculateMCUChecksum(uint8_t dataArray[], int Length, unsigned int &checksum) { // Calculate the checksum by determining the sum of the first 11 bytes and then take modules 256 checksum = 0; // Initialize the checksum if (Length <= 1) { // Ensure length is valid checksum = 0; return; } // Calculate the checksum for the first (Length-1) bytes in the specified row for (int i = 0; i < Length - 1; i++) { checksum += dataArray[i]; } checksum %= 256; // Modulus 256 to keep it in a single byte } // Function to calculate the CRC-CCITT (0xFFFF) checksum for the communication to and from the heat pump void calculate_CRC_CCITT_Checksum(uint8_t *data, uint8_t Length, uint16_t *checksum) { uint16_t crc = 0xFFFF; // Initial value for (uint8_t i = 0; i < Length; i++) { crc ^= (uint16_t)data[i] << 8; for (uint8_t j = 0; j < 8; j++) { if (crc & 0x8000) { crc = (crc << 1) ^ 0x1021; } else { crc <<= 1; } } } *checksum = crc; } // Function that reads the serial data coming from the heat pump void readSerialMessageHP() { while (Serial3.available()) { // Check if data is available on Serial3 IsReceiving = true; uint8_t byteReceived = Serial3.read(); IsReceiving = true; switch (state) { case Wait_Start: // Code to wait for the start character if (byteReceived == StartByte) { state = Read_Header; index = 0; IncomingMessageEnded = false; // Reset if any other byte is received to indicate } break; case Read_Header: // Code to process header bytes if (index == 0) { ID = byteReceived; if (ID > 3) { // ID must be 0-3 Serial.println("Invalid ID, resetting..."); state = Wait_Start; return; } } else if (index == 1) { msgLength = byteReceived+1; if (!IsValidLength(msgLength)) { Serial.println("Invalid Length! Resetting..."); state = Wait_Start; // Reset return; } DataLength = msgLength - 3; // Exclude first 3 bytes and last byte index = 0; state = Read_Payload; } index++; break; case Read_Payload: // Code to process data payload buffer[index++] = byteReceived; if (index >= DataLength) { state = Read_End; } break; case Read_End: // Code to check that last byte of message corresponds to '0' if (byteReceived == 0) { // Ensure last byte is 0 StoreMessage(ID, DataLength, buffer); IncomingMessageEnded = true; // If the end of the incoming message is reached, set the infication boolean previousMessageInEnded = millis(); // Set timestamp after receiving end of incoming message } else { Serial.println("Warning: Last byte is not 0! Resetting..."); } IsReceiving = false; state = Wait_Start; // Reset for next message break; default: Serial.println("Error in readSerialMessageHP state"); break; } } } // Function to check if the Length is valid bool IsValidLength(uint8_t Length) { for (uint8_t i = 0; i < sizeof(ValidLengths) / sizeof(ValidLengths[0]); i++) { if (Length == ValidLengths[i]) return true; } return false; } // Function to store messages in separate arrays based on ID void StoreMessage(uint8_t ID, uint8_t DataLength, uint8_t *message) { for (uint8_t i = 0; i < (DataLength); i++) { dataHeatPumpTemp[i+2][ID] = message[i]; } dataHeatPumpTemp[0][ID] = StartByte; dataHeatPumpTemp[1][ID] = ID; dataHeatPumpTemp[2][ID] = DataLength+2; // 12, 13, 18, 20 for (int j = 0; j < DataLength+2; j++) { // Copy row extractedRow[j] = dataHeatPumpTemp[j][ID]; } calculate_CRC_CCITT_Checksum(extractedRow, DataLength+2, &ChecksumCBHP); // Calculate checksum, including the two checksum bytes --> result will be zero if mesage has been sent error free if (ChecksumCBHP == 0) { // Check that result is indeed zero memcpy(dataHeatPump[ID], dataHeatPumpTemp[ID], Cols * sizeof(uint8_t)); // Transfer the temporarily array to the permanent array ChecksumCBHPfalseCount = 0; } else { ChecksumCBHPfalseCount++; // Increase counter for false checksums. Nog actie bedebken als threshold wordt bereikt Serial.print("Checksum error! "); // Print a message if there is an error Serial.print("Now ");Serial.print(ChecksumCBHPfalseCount);Serial.print(" Checksum errors (of max 10) counted!"); } }
[ Voor 0% gewijzigd door _JGC_ op 23-09-2025 16:02 . Reden: workaround toegevoegd ]
[ Voor 19% gewijzigd door _JGC_ op 23-09-2025 16:09 ]
Bij mij speelt dit niet. Heb het even nagekeken:_JGC_ schreef op dinsdag 23 september 2025 @ 11:13:
Zie dat er nog 1 hele nare bug in zit: als thermostaat warmte vraagt maar water heeft die temperatuur al dan blijft de warmtepomp uit. Geen circulatie, helemaal niks.
Dit herken ik helemaal niet.Heb vanmorgen op de laatste 0,3 graden verhoging mogen toekijken hoe elke paar minuten de warmtepomp aan en uit ging.
[ Voor 13% gewijzigd door _JGC_ op 24-09-2025 22:19 ]
:strip_exif()/f/image/vJduySGftSiIzZtZZhMqJwpQ.png?f=user_large)
code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480/* Alternative control for the Atlantic Aurea heat pump, which based on the Chofu AEYC-0643XU-AT unit. This is a sketch to control the power setting of a heat pump with an Arduino ATMega2560 and to modify communication inside the controlbox using 3 of the 4 hardware serial ports of this board. The original code shows various useful information like temperatures etc. on a 480*320 TFT LCD. This has been removed here to keep the code more compact. Version 0.1Beta, JHN 19-03-2025 */ #include <QuickPID.h> // Library for PID controller, see for documentation: https://github.com/Dlloydev/QuickPID //Define variables and constants // General String MessageString = ""; // For heat pump const uint8_t SpeedStages = 6; // Number of speed stages to be used (7 --> ~1400 W max) uint8_t SpeedSetpoint = 0; // Speed setpoint sent to heatpump uint8_t SpeedSetpointOld = 0; uint8_t TargetTemperatureThermostat = 20; // Target temperature as requested by the thermostat const float MinTemp4HP = -15; // Outside temperatute below wich heat pump should stop working and CV should start uint16_t TelegramCount = 0; // Follow-up number of telegrams // Variables to track timings uint32_t Interval = 0; // Time since last telegram uint32_t currentMillis = 0; // Check timing for sending messages uint32_t LastSpeedSetpointChange = 0; // Keep track when last time speedsetpint has been changed in milliseconds uint32_t SpeedSetpointHysteresis = 120000; // Allow a change in speedsetpoint only once per 2 minutes (when using slow PID parameter set) // Cycle time check uint32_t previousCycleMicros = 0; // To store the time of the last cycle start uint32_t currentCycleMicros = 0; // To store the time of the current cycle start uint32_t cycleTime = 0; // To store the calculated cycle time uint16_t cycleCounter = 0; // Counter to track cycles const uint16_t displayInterval = 10000; // Display cycle time every 10000 cycles uint8_t lcdUpdateStep = 0; // Variable to spread writing to screen over multiple steps // Receive and send inter MCU data const uint32_t McuMessageWaitTime = 200; // Waiting time between receiving message on Pad 1 (Serial 1) and sending on pad 2 (serial 2) in ms uint32_t McuMessageOneReceived = 0; // Tie last message send in ms bool dataMcu2Send = false; uint32_t PreviousMessageSerial1Sent = 0; uint32_t MessageSerial1Interval = 4200; // Maximum interval for sending message on serial 2 (to avoid that communication stops) const uint8_t DataMcuLength = 12; // Length of each transmission between MCU's uint8_t dataArrayOT_IC1_IC2[DataMcuLength] = {32,20,154,0,0,0,0,32,29,7,0,18}; // Array to store the incoming 12 bytes for OT IC1 to IC2. uint8_t dataArrayOT_IC2_IC1[DataMcuLength] = {0}; // Array to store the incoming 12 bytes for OT IC2 to IC1. uint16_t ChecksumMCU = 0; // Checksum for communication between MCU's // Variables for communication with heat pump uint8_t CB_HPlength = 0; // Length of communcation CB to HP uint16_t ChecksumCBHP = 0; // Checksum for communication of heat pump with control box uint8_t ChecksumCBHPfalseCount = 0; // Counter for number of times that the checksum did not match. After certain number of mismatches, message will be send to the serial port uint8_t data0[] = { 0x19, 0x0, 0x8, 0x0, 0x0, 0x0, 0xd9, 0xb5 }; // Array of hexadecimal bytes uint8_t data1[] = { 0x19, 0x1, 0x0c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xaa, 0x35 }; // Array of hexadecimal bytes uint8_t data2[] = { 0x19, 0x2, 0x8, 0x1, 0x1, 0x0, 0x99, 0x37 }; // Array of hexadecimal bytes. This is the one to change bytes 4 (on/off) and 5 (setpoint), and 7 and 8 (high and low bytes of the CRC CCITT checksum) uint8_t data3[] = { 0x19, 0x3, 0x8, 0xb2, 0x2, 0x0, 0xc1, 0x9a }; // Array of hexadecimal bytes // Variables for sending messages to heat pump bool IncomingMessageEnded = false; // Last message start time in milliseconds volatile uint32_t PreviousMessageSent = 0; // Time when prveious message was sent in milliseconds volatile bool IsReceiving = false; // Tracks if message is being received from heat pump to block sending next message to heat pump const uint32_t SendTimeout = 2000; // Time in milliseconds to wait before considering the communication ended const uint32_t SendDelay = 99; // Delay in milliseconds before sending information to heatpump const uint32_t MinMessageSendInterval = 300; // Minimum interval in millis between sending messages when no reply is received volatile uint32_t previousMessageInEnded = 0; // Definitions for reading and checking heat pump data // Prepare array for storing data from heatpump const uint8_t StartByte = 0x91; // First byte must be 145 (0x91) const uint8_t Rows = 4; // Number of rows in heat pump data array const uint8_t Cols = 80; // Number of columns in heat pump data array uint8_t numChars = 0; // Number of characters to be written to screen uint8_t dataHeatPumpTemp[Cols][Rows] = {0}; // Array to store of four messages from heatpump before checksum check uint8_t dataHeatPump[Cols][Rows] = {0}; // Array to store of four messages from heatpump after checksum is correct uint8_t dataHeatPumpOld[Cols][Rows] = {0}; // Array to store previous data of four messages so that screen is only updayted when needed uint8_t extractedRow[Cols]; // 1D array to store the row const uint8_t ValidLengths[] = {13, 14, 19, 21}; // Lengths 12, 13, 18 & 20 (12+1), (13+1), (18+1), (20+1) uint16_t value; // Temporarily storage of datapoint from array float valuefloat; uint8_t Length = 0; float T_supply =0; // Supply water temperature by heatpump float T_return =0; // Return water temperature to heatpump float T_outside =0; // Outside temperature measured on heatpump // State variables for non-blocking serial reading enum SerialState { Wait_Start, // Waiting for the start of a serial message Read_Header, // Reading the header of the message Read_Payload, // Reading the main data (payload) Read_End // Reading the end of the message }; // Enumeration type, which helps in defining different states of serial communication SerialState state = Wait_Start; // Make waiting to start the default uint8_t ID = 0; uint8_t msgLength = 0; uint8_t DataLength = 0; uint8_t buffer[Cols] = {0}; // Temporary buffer for incoming data uint8_t index = 0; int8_t NegT = 0; // Store if temperature is negative (value of 255 in MSB) // For PID controller float TemperaturePIDsetpoint = 0; // Desired supply temperature uint8_t TemperaturePIDsetpointOld = 0; const float TemperaturePIDsetpointMax = 37; // Maximum temperature setpoint const float TemperaturePIDsetpointMin = 25; // Maximum temperature setpoint const float TemperaturePIDsetpointOff = 15; const uint8_t TemperaturePIDsetpointMinStart = 19; float TemperaturePIDinput = 0; // Current suppy temperature (feedback from the sensor in the heatpump) float TemperaturePIDoutput = 0; // Heat pump control signal float TemperaturePIDoutputRounded = 0; // Heater control signal, rounded off // PID tuning parameter. Default values were Kp = 2.0, Ki = 5.0, Kd = 1.0 const float Kp0 = 0.02, Ki0 = 0.008, Kd0 = 0.01;// float Kp = Kp0, Ki = Ki0, Kd = Kd0;// // Create PID object QuickPID myPID(&TemperaturePIDinput, &TemperaturePIDoutput, &TemperaturePIDsetpoint, Kp, Ki, Kd, /* OPTIONS */ myPID.pMode::pOnError, /* pOnError, pOnMeas, pOnErrorMeas */ myPID.dMode::dOnMeas, /* dOnError, dOnMeas */ myPID.iAwMode::iAwCondition, /* iAwCondition, iAwClamp, iAwOff */ myPID.Action::direct); /* direct, reverse */ void setup() { // Serial Setup Serial.begin(115200); // Start the Serial Monitor for debugging on terminal (Serial port 0) Serial3.begin(9600); // Start serial port 1 for receiving messages from OT MCU to HP MCU (RX3, 9600 bps) Serial1.begin(666); // Start serial port 3 for sending an receiving info to heat pump at 666 baud (RX1 and TX1) Serial3.setTimeout(100); // Changes the timeout for readBytes() and other blocking serial read functions (readString(), readBytesUntil(), etc.). // For QuickPID myPID.SetOutputLimits(0, 99); // Set to max 99% to have max. 4 digits ('99.9') myPID.SetSampleTimeUs(5000000); // Sample time of PID controller in microseconds. Made slower than default because of relatively slow temperature changes and heat pump response to save processing time. myPID.SetTunings(Kp, Ki, Kd); myPID.SetMode(myPID.Control::automatic); // Set PID controller in automatic, i.e. input (PV) is perdiodically compared with setpoint (SP) and control variable (CV) is adjusted delay(5000); Serial.println("Setup complete."); } //////////////// Main loop //////////// void loop() { currentMillis = millis(); // get curent millis (ms) for sending message to heat pump currentCycleMicros = micros(); // Capture the current time (in us) to calculate the cycle time of the loop cycleCounter++; // Increment the cycle counter // Check for incoming data on Serial 1 (Pad 1 from MCU 2), 2 (Pad 2 from MCU 1) and Serial 3 (data from heat pump) readSerialMessageHP(); // Call this function continuously to check for data on the incoming serial port from the heat pump if (Serial3.available() >= DataMcuLength) { // Check whether the serial buffer connected to Pad 1 contains DataMcuLength (12) bytes or more readSerialMessageMCU(1); // When so, jump to subroutine to read the bytes or clear the buffer TemperaturePIDsetpoint = dataArrayOT_IC2_IC1[0]; if (TemperaturePIDsetpoint > TemperaturePIDsetpointMax){ TemperaturePIDsetpoint = TemperaturePIDsetpointMax; } else if (TemperaturePIDsetpoint <= TemperaturePIDsetpointOff || (TemperaturePIDsetpointOld == 0 && round(TemperaturePIDsetpoint) < TemperaturePIDsetpointMinStart)) { TemperaturePIDsetpoint = 0; } else if (TemperaturePIDsetpoint < TemperaturePIDsetpointMin) { TemperaturePIDsetpoint = TemperaturePIDsetpointMin; } TemperaturePIDsetpointOld = round(TemperaturePIDsetpoint); } // Write modified message from MCU 1 to 2 on serial 2 if ((currentMillis-PreviousMessageSerial1Sent) > MessageSerial1Interval) { readSerialMessageMCU(2); Serial3.write(dataArrayOT_IC1_IC2, sizeof(dataArrayOT_IC1_IC2)); // Write array to serial port 2 PreviousMessageSerial1Sent = currentMillis; } // Send the next message to the heat pump 100 ms after the previous incoming message has ended, or after 1000 ms without sending anything. Use the periodic sending also to update the screen currentMillis = millis(); // get curent millis (ms) for sending message to heat pump if ((( (currentMillis - previousMessageInEnded >= SendDelay) && (!IsReceiving)) || ( currentMillis - previousMessageInEnded >= SendTimeout )) && (currentMillis - PreviousMessageSent >= ( MinMessageSendInterval ))) { switch (TelegramCount) { case 0: Serial1.write(data0, sizeof(data0)); break; case 1: Serial1.write(data1, sizeof(data1)); break; case 2: SpeedSetpoint = (int)round( TemperaturePIDoutput / (100 / SpeedStages )) ; // Scale the PID controller output from 0~100% to 0~8 speed setpoints, and convert from double to rounded integer if (SpeedSetpoint == 0 && TemperaturePIDsetpoint > 0) { // Workaround: We still want heat, but not much. Keep WP running SpeedSetpoint = 1; } if (SpeedSetpoint == 0) { data2[3] = 0x0; data2[4] = 0x0; SpeedSetpointOld = 0; } else if (SpeedSetpointOld != SpeedSetpoint && (currentMillis - LastSpeedSetpointChange > SpeedSetpointHysteresis)) { if (SpeedSetpoint > SpeedSetpointOld) { SpeedSetpoint = SpeedSetpointOld+1; } else { SpeedSetpoint = SpeedSetpointOld-1; } data2[3] = 0x1; data2[4] = SpeedSetpoint; // Write calculate speed setpoint in array LastSpeedSetpointChange = currentMillis; SpeedSetpointOld = SpeedSetpoint; } CB_HPlength = sizeof(data2); calculate_CRC_CCITT_Checksum(data2, CB_HPlength-2, &ChecksumCBHP); data2[6] = (ChecksumCBHP >> 8) & 0xFF; // Replace byte 6 with checksum High Byte data2[7] = ChecksumCBHP & 0xFF; // Replace byte 7 with checksum Low Byte Serial1.write(data2, sizeof(data2)); break; case 3: Serial1.write(data3, sizeof(data3)); break; default: Serial.print("Invalid TelegramCount value: ");Serial.println(TelegramCount); break; } TelegramCount = (TelegramCount + 1) % 4; // Cycle through 0-3 PreviousMessageSent = millis(); } if (cycleCounter >= displayInterval) { if (previousCycleMicros != 0) { cycleTime = ( currentCycleMicros - previousCycleMicros ) / displayInterval; // Time difference between cycles previousCycleMicros = currentCycleMicros; // Update the previous cycle time cycleCounter = 0; // Reset the cycle counter } else { previousCycleMicros = currentCycleMicros; } switch (lcdUpdateStep) { case 0: // Print the cycle time etc. The timing for sneding message to the heatpump wil be oof when the cycle time is too long (> ~50 ms) Serial.println("Cycle time "+String(cycleTime)+" ms"); break; case 1: // Print PID settings break; case 2: // Compresor speed 91-3,9 if (dataHeatPump[9][3] != dataHeatPumpOld[9][3]) { // Data has changed? Serial.print("Speed setpoint: "); Serial.println(dataHeatPump[9][3]); dataHeatPumpOld[9][3] = dataHeatPump[9][3]; } break; case 3: // Compresor power 91-3,10 if (dataHeatPump[10][3] != dataHeatPumpOld[10][3]) { MessageString = String((25.6 * dataHeatPump[10][3]),0); Serial.print("Compressor power: "); Serial.println(MessageString); dataHeatPumpOld[10][3] = dataHeatPump[10][3]; } // Compressor mode 91-1,3 if (dataHeatPump[3][1] != dataHeatPumpOld[3][1]) { Serial.print("Compressor mode: "); Serial.println(dataHeatPump[3][1]); dataHeatPumpOld[3][1] = dataHeatPump[3][1]; } // Defrost? 91-1,4 if (dataHeatPump[4][1] != dataHeatPumpOld[4][1]) { Serial.print("Defrost ongoing? "); Serial.println(dataHeatPump[4][1]); dataHeatPumpOld[4][1] = dataHeatPump[4][1]; } break; case 4: // ?Data? 91-3,5 if (dataHeatPump[5][3] != dataHeatPumpOld[5][3]) { Serial.print("Generic info "); Serial.println(dataHeatPump[5][3]); dataHeatPumpOld[5][3] = dataHeatPump[5][3]; } break; case 5: // Temperatures // Supply temperature 91-2,3~4 if (dataHeatPump[3][2] != dataHeatPumpOld[3][2]) { value = dataHeatPump[4][2]; // MSB of temperature; previous byte is the MSB T_supply = (((value*256+dataHeatPump[3][2])-(65536*((value == 255) ? 1 : 0)))/10.0); // Do the conversion to temperature in such a way that also negative temperature can be shown TemperaturePIDinput = T_supply; // Make the input the PID controller equal to the current supply water temperature MessageString = String(T_supply,1); Serial.print("Supply temperature "); Serial.println(MessageString); dataHeatPumpOld[3][2] = dataHeatPump[3][2]; } // Return temperature 91-2,5~6 if (dataHeatPump[5][2] != dataHeatPumpOld[5][2]) { value = dataHeatPump[6][2]; // MSB of temperature; previous byte is the MSB T_return = (((value*256+dataHeatPump[5][2])-(65536*((value == 255) ? 1 : 0)))/10.0); // Do the conversion to temperature in such a way that also negative temperature can be shown MessageString = String(T_return,1); Serial.print("Return temperature "); Serial.println(MessageString); dataHeatPumpOld[5][2] = dataHeatPump[5][2]; } break; case 6: // Outside temperature 282 91-2,7~8 if (dataHeatPump[7][2] != dataHeatPumpOld[7][2]) { value = dataHeatPump[8][2]; // MSB of temperature; previous byte is the MSB T_outside = (((value*256+dataHeatPump[7][2])-(65536*((value == 255) ? 1 : 0)))/10.0); // Do the conversion to temperature in such a way that also negative temperature can be shown MessageString = String(T_outside,1); Serial.print("Outside temperature "); Serial.println(MessageString); dataHeatPumpOld[7][2] = dataHeatPump[7][2]; } break; case 7: // Do PID calculation break; case 8: // Print inter MCU communication break; case 9: TemperaturePIDoutputRounded = round(TemperaturePIDoutput*10.0)/10.0; } lcdUpdateStep = (lcdUpdateStep + 1) % 10; // Cycle through screen updates } myPID.Compute(); }////////////// End of main loop ////////////////////////// //////// Below are the subroutines ////////////////////// void readSerialMessageMCU(uint8_t MCU) { uint8_t TempMcuData[DataMcuLength] = {0}; uint8_t TemperatureCategory; // Variable to categorize temperature ////// Inter MCU communication switch (MCU) { case 1: // 1. Communication IC2 (OT) to IC1 (HP): This communication is via PAD 1 and therefore used at serial 1. Check if enough data has arrived in the serial buffer and read it Serial3.readBytes(TempMcuData, DataMcuLength); // Clean way of reading fixed number of bytes into array. Beware: Blocks execution until all bytes arrive or timeout (default timeout is 1000ms). calculateMCUChecksum(TempMcuData, DataMcuLength, ChecksumMCU);// Calculate and check the checksum by determining the sum of the first 11 bytes and taken modules 256 if (ChecksumMCU == TempMcuData[DataMcuLength-1]) { // Check if the checksum matches memcpy(dataArrayOT_IC2_IC1, TempMcuData, DataMcuLength); // Checksum OK so copy temp array to permanent array, cleaner and faster code than a loop ChecksumMCU = false; // Make checksum false as start value for next time McuMessageOneReceived = currentMillis; } else { // Checksum incorrect because communication possibly halfway received. clearSerialBuffer(Serial3); // Clears the read buffer of serial 1 if the checksu was wrong to make a fresh start Serial.println("Serial buffer 1 cleared"); } break; case 2: // 2. Communication IC1 (HP) to IC2 (OT): This communication is via PAD 2 and therefore used at serial 2. Check if enough data has arrived in the serial buffer, read it, modify when required and resend //Serial1.readBytes(TempMcuData, DataMcuLength); // Clean way of reading fixed number of bytes into array. Beware: Blocks execution until all bytes arrive or timeout (default timeout is 1000ms). //calculateMCUChecksum(TempMcuData, DataMcuLength, ChecksumMCU);// Calculate and check the checksum by determining the sum of the first 11 bytes and taken modules 256 // Make checksum false as start value for next time if (T_outside < MinTemp4HP) { TemperatureCategory = 0; // Too cold for heat pump, switch on cental heating } else if (T_outside < 4) { TemperatureCategory = 1; // Below 4°C // Prevent that controlbox starts CV } else if (SpeedSetpoint == 0) { TemperatureCategory = 2; // SpeedSetpoint is zero } else { TemperatureCategory = 3; // In all other cases use the temperature setpoint for water as read from Plugwise Anna via MCU2 } switch (TemperatureCategory) { case 0: // Heat pump is not running because it is too cold so central heating should be started dataArrayOT_IC1_IC2[0] = 0xf0; break; case 1: // Below 4°C but heat pump can run, adjust values to prevent central heating activation dataArrayOT_IC1_IC2[0] = dataArrayOT_IC2_IC1[0]; dataArrayOT_IC1_IC2[9] = 0x04; dataArrayOT_IC1_IC2[4] = 0; // Write 0's to force MCU2 to transfer HP temperature to OpenTherm dataArrayOT_IC1_IC2[5] = 0; dataArrayOT_IC1_IC2[6] = 0; dataArrayOT_IC1_IC2[10] = 0; break; case 2: // Heat pump is not running (Speedsetpoint is zero), then let central heating boiler temperatures go to OpenTherm case 3: // In all other cases use the temperature setpoint for water as read from Plugwise Anna via MCU2 dataArrayOT_IC1_IC2[0] = dataArrayOT_IC2_IC1[0]; dataArrayOT_IC1_IC2[4] = 0; // Write 0's to force MCU2 to transfer HP temperature to OpenTherm dataArrayOT_IC1_IC2[5] = 0; dataArrayOT_IC1_IC2[6] = 0; dataArrayOT_IC1_IC2[10] = 0; break; } dataArrayOT_IC1_IC2[7] = round(T_supply); dataArrayOT_IC1_IC2[8] = round(T_return); if (T_outside >= 4) { dataArrayOT_IC1_IC2[9] = round(T_outside); } calculateMCUChecksum(dataArrayOT_IC1_IC2, DataMcuLength, ChecksumMCU);// Recalculate the checksum dataArrayOT_IC1_IC2[11] = round(ChecksumMCU); // Replace old checksum value with new calculated one break; } } void clearSerialBuffer(HardwareSerial &serialPort) { while (serialPort.available() > 0) { serialPort.read(); // Read and discard the data in the buffer } } void calculateMCUChecksum(uint8_t dataArray[], int Length, unsigned int &checksum) { // Calculate the checksum by determining the sum of the first 11 bytes and then take modules 256 checksum = 0; // Initialize the checksum if (Length <= 1) { // Ensure length is valid checksum = 0; return; } // Calculate the checksum for the first (Length-1) bytes in the specified row for (int i = 0; i < Length - 1; i++) { checksum += dataArray[i]; } checksum %= 256; // Modulus 256 to keep it in a single byte } // Function to calculate the CRC-CCITT (0xFFFF) checksum for the communication to and from the heat pump void calculate_CRC_CCITT_Checksum(uint8_t *data, uint8_t Length, uint16_t *checksum) { uint16_t crc = 0xFFFF; // Initial value for (uint8_t i = 0; i < Length; i++) { crc ^= (uint16_t)data[i] << 8; for (uint8_t j = 0; j < 8; j++) { if (crc & 0x8000) { crc = (crc << 1) ^ 0x1021; } else { crc <<= 1; } } } *checksum = crc; } // Function that reads the serial data coming from the heat pump void readSerialMessageHP() { while (Serial1.available()) { // Check if data is available on Serial1 IsReceiving = true; uint8_t byteReceived = Serial1.read(); IsReceiving = true; switch (state) { case Wait_Start: // Code to wait for the start character if (byteReceived == StartByte) { state = Read_Header; index = 0; IncomingMessageEnded = false; // Reset if any other byte is received to indicate } break; case Read_Header: // Code to process header bytes if (index == 0) { ID = byteReceived; if (ID > 3) { // ID must be 0-3 Serial.println("Invalid ID, resetting..."); state = Wait_Start; return; } } else if (index == 1) { msgLength = byteReceived+1; if (!IsValidLength(msgLength)) { Serial.println("Invalid Length! Resetting..."); state = Wait_Start; // Reset return; } DataLength = msgLength - 3; // Exclude first 3 bytes and last byte index = 0; state = Read_Payload; } index++; break; case Read_Payload: // Code to process data payload buffer[index++] = byteReceived; if (index >= DataLength) { state = Read_End; } break; case Read_End: // Code to check that last byte of message corresponds to '0' if (byteReceived == 0) { // Ensure last byte is 0 StoreMessage(ID, DataLength, buffer); IncomingMessageEnded = true; // If the end of the incoming message is reached, set the infication boolean previousMessageInEnded = millis(); // Set timestamp after receiving end of incoming message } else { Serial.println("Warning: Last byte is not 0! Resetting..."); } IsReceiving = false; state = Wait_Start; // Reset for next message break; default: Serial.println("Error in readSerialMessageHP state"); break; } } } // Function to check if the Length is valid bool IsValidLength(uint8_t Length) { for (uint8_t i = 0; i < sizeof(ValidLengths) / sizeof(ValidLengths[0]); i++) { if (Length == ValidLengths[i]) return true; } return false; } // Function to store messages in separate arrays based on ID void StoreMessage(uint8_t ID, uint8_t DataLength, uint8_t *message) { for (uint8_t i = 0; i < (DataLength); i++) { dataHeatPumpTemp[i+2][ID] = message[i]; } dataHeatPumpTemp[0][ID] = StartByte; dataHeatPumpTemp[1][ID] = ID; dataHeatPumpTemp[2][ID] = DataLength+2; // 12, 13, 18, 20 for (int j = 0; j < DataLength+2; j++) { // Copy row extractedRow[j] = dataHeatPumpTemp[j][ID]; } calculate_CRC_CCITT_Checksum(extractedRow, DataLength+2, &ChecksumCBHP); // Calculate checksum, including the two checksum bytes --> result will be zero if mesage has been sent error free if (ChecksumCBHP == 0) { // Check that result is indeed zero memcpy(dataHeatPump[ID], dataHeatPumpTemp[ID], Cols * sizeof(uint8_t)); // Transfer the temporarily array to the permanent array ChecksumCBHPfalseCount = 0; } else { ChecksumCBHPfalseCount++; // Increase counter for false checksums. Nog actie bedebken als threshold wordt bereikt Serial.print("Checksum error! "); // Print a message if there is an error Serial.print("Now ");Serial.print(ChecksumCBHPfalseCount);Serial.print(" Checksum errors (of max 10) counted!"); } }
[ Voor 0% gewijzigd door _JGC_ op 04-10-2025 15:52 . Reden: code aanpassingen ]
[ Voor 51% gewijzigd door _JGC_ op 04-10-2025 14:07 ]
:strip_exif()/f/image/OF7RT2dEkjJ6ldb2drOQGUGm.png?f=user_large)
[ Voor 5% gewijzigd door WackoH op 04-10-2025 22:19 ]
:strip_exif()/f/image/axQNj4czDKtMrLKXU9vbYzir.jpg?f=fotoalbum_large)
:strip_exif()/f/image/foP1iqIAydLpSLYSGYowW43R.jpg?f=fotoalbum_large)
Die bypass heeft als functie om de overload aan pompvermogen weg te krijgen als de ketel en warmtepomp samen in werking zijn. In mijn geval zou dat 2500 liter per uur betekenen (ik moet de ketelpomp nog een stand naar beneden zetten, maar ketel moet handmatig aangezet worden dus die draaien nooit tegelijk).P1 modubus schreef op maandag 17 november 2025 @ 20:56:
JGC schreef
Gevolg is deltaT van 10-12 graden en een rammelende compressor en een buurman die het in huis hoort (vanmorgen sms van hem dat hij het best wel vervelend vond).
Het is belangrijk dat het hydraulisch goed in me kaar zit, welke watertemperatuur ook gevraagd wordt, de Dt moet binnen de beperken blijven ook met 100% vermogen, zorg eerst dat de bypass goed ingeregeld wordt, daarna kun je verder, kijk maar naar mijn Dt en stookgedrag van de WP, buurman en jij zullen dan ook blij zijn geen overmatig geluid meer.
Ps. weet niet hoe ik een reactie moet plaatsen, dus nu maar zo.
Succes
Ik heb de IC die met de warmtepomp communiceert eruit gehaald, die ligt gewoon werkloos bovenop de box.Ikke83 schreef op donderdag 20 november 2025 @ 21:24:
_JGC_ mag ik vragen hoe jij het nu hebt gedaan, ik heb terug gelezen maar kom er toch niet helemaal uit.
wat ik me kan bedenken is het volgende:
- Je hebt de IC (die op een arduino uno zit) geprogrammeerd met een aangepaste code van WackoH en deze dan gewisseld met IC2 in de box
- ik lees dat je solderingen heb gedaan, waar precies? (of zijn dezelfde als WackoH?)
- IC4 heb je verwijderd waardoor je niet hoefde te snijden in de printplaat.
- een DIYLESS gebruik je als vervanger voor Anna, is deze dan aangesloten op de verbindingen van de Smile? zeg maar de meest linker aansluiting.
Bij mij gaat het met de standaard instellingen op zich prima.
Op de een of andere manier kan ik de maximale aanvoertemperatuur niet wijzigen, deze staat standaard (en grijs) op 60.
Op zich is dat geen probleem, want ik gebruik de weersafhankelijke modus. Heel vaak draait ie netjes op 34 graden aanvoer temperatuur, met de heatboosters in mijn convector radiatoren gaat dit heel goed.
Het jammere is dat als de temperatuur bereikt is volgens Anna de gehele warmtepomp uit gaat. Wat natuurlijk logisch is
Het liefst zou ik zien, net als bij jou en WackoH dat de Warmtepomp op iets van 250W gaat draaien zodat er toch iets van een warme luchtstroom (of wat er van over blijft) door het huis gaat. Het gevoel van kilheid komt namelijk snel boven als de Aurea er mee stopt.
alvast bedankt
[ Voor 3% gewijzigd door _JGC_ op 21-11-2025 11:57 ]
[ Voor 3% gewijzigd door dingo35 op 26-11-2025 02:47 ]
[ Voor 63% gewijzigd door dingo35 op 26-11-2025 13:41 ]
Logic shifter (zo'n goedkoop printje met 4 SMD transistors) had ik geprobeerd, die geeft geen werkend signaal. Die chip heb ik meer vertrouwen in dan een klein printje van Amazon.dingo35 schreef op woensdag 26 november 2025 @ 13:35:
Is een 74AHCT125 niet een veel simpeler oplossing?
EDIT: voor lezen van signalen zou een 1k weerstand in serie met het signaal al moeten werken, en anders een spanningsdeler 10k/20k. SN74LVC8T245 zou ook moeten werlen...
Ik heb veel ervaring met de SmartEVSE code; daar lezen we meerdere signalen tegelijkertijd uit:_JGC_ schreef op woensdag 26 november 2025 @ 13:59:
[...]
Maargoed, waar je even mee zit: de buitenunit wil een constant signaal krijgen van de controller, anders slaat 'ie af. Als je 1 controller gebruikt voor Wifi/MQTT, buitenunit communicatie en OpenTherm, kom je met je timings in de problemen zodra je MQTT niet lekker reageert of je wifi signaal slecht is.
[ Voor 12% gewijzigd door dingo35 op 26-11-2025 14:55 ]
Ik lees mee, heh…. Iets vriendelijker en minder arrogant/neerbuigend mag wel... 😉_JGC_ schreef op woensdag 26 november 2025 @ 15:24:
Code "werkt", maar is voor verbetering vatbaar. Ik vind het vooral onleesbaar. Heb een informatica opleiding genoten maar ben geen MCU-programmeur. Die code ga ik zeker herschrijven
[ Voor 6% gewijzigd door _JGC_ op 26-11-2025 23:47 ]
/f/image/t0vTNs8miWJAVHOMbJF7yNvL.png?f=fotoalbum_large)
Je weet dat via de diff de credentials zo zijn terug te halen en dus nog steeds leesbaar in de repo zijn te vinden?Maartenkromhout schreef op dinsdag 24 februari 2026 @ 17:57:
@_JGC_ heb jij misschien die github voor ons?
Ik ben zelf ook weer bezig geweest en heb het 1 en ander op github staan. misschien leuk om te zien.
https://github.com/kromhoutmaarten-sys/Aurea-5-hybrid_uno4/
[ Voor 3% gewijzigd door ThinkPad op 24-02-2026 21:15 ]
:strip_exif()/f/image/bIXDNn6wiEcYOUWT8Xo3crSm.png?f=user_large)
[ Voor 32% gewijzigd door hyeronimo op 25-03-2026 13:09 ]