Aurea 5 hybrid: interfaces met de buitenunit en thermostaat

Pagina: 1 2 Laatste
Acties:

Acties:
  • 0 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 23:41
Je maakt mooie stappen!

Excuus voor de bugs.
De eerst (- not ok wordt gelogd als de data wel ok is) zit waarschijnlijk ook in mijn code. Maar ik heb er verder geen last van.
Die tweede (de compute aanroep voor quickpid zit niet in loop(), speedstate blijft dan altijd 0) is wellicht ontstaan toen ik de code wat opschoonde om hier neer te zetten. Bij mij lijkt de QuickPID tenminste volgens verwachting te werken.

Het algoritme moet inderdaad niet te traag worden. Bij mij bleek vooral het schrijven naar het scherm veel tijd te kosten. Vandaar dat dat zo is opgesplitst (iedere cyclus weer een ander deel updaten). Ik heb nu een cyclustijd van 30~40 ms

De grote van de weerstand maakt niet veel uit. Is alleen om de stroom te begrenzen. Ik heb dezelfde weerstand naar mijn AtMega2560 board opgenomen. Dat board had ik nog liggen en kwam goed uit vanwege de 4 hardware UARTs.

Software serial zou moeten werken, want dat moet de huidige MCU in de controlbox (ATmega8) ook doen om zowel en 1200 baud verbinding naar de andere MCU, als naar WP (666 baud) of Opentherm te onderhouden. Ik heb het ook wel op een Diecimila gebruikt toen ik aan het testen was.

Acties:
  • 0 Henk 'm!

  • _JGC_
  • Registratie: Juli 2000
  • Laatst online: 00:48
Ik zit nu op 25ms met een 2560, maar dat is vooral omdat ik een statisch array bewerk op moment dat iets naar de OT MCU gestuurd moet worden. Die bewerk cyclus is trouwens best langzaam, die had ik eerst in de reguliere loop zitten, cyclustijd was toen 110ms.

150 liter RVS tapwater vat heb ik ook al, nu nog een 3wegklep, 2KW heater en temperatuursensor en iets om die boel aan te sturen. Moet 'm alleen nog wel even wrappen of spuiten want sprsun-goud is niet goed voor de WAF.

Acties:
  • 0 Henk 'm!

  • _JGC_
  • Registratie: Juli 2000
  • Laatst online: 00:48
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.
Heb vanmorgen op de laatste 0,3 graden verhoging mogen toekijken hoe elke paar minuten de warmtepomp aan en uit ging.

Fix is simpel: als er warmtevraag is vanuit thermostaat is, dan speedsetpoint minimaal 1.

Ketel loste dat op met code1 en dom rondpompen zonder te stoken.

Edit:
Aangepast in de post met de code van gisteren.
Zojuist even getest, stroomverbruik op stand 1 is marginaal, zo'n 200W. De helft van dat verbruik ging voorheen al op aan de ouderwetse AC pomp in de CV ketel.

[ Voor 19% gewijzigd door _JGC_ op 23-09-2025 16:09 ]


  • WackoH
  • Registratie: November 2012
  • Laatst online: 23:41
_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.
Bij mij speelt dit niet. Heb het even nagekeken:
De thermostaat staat op 20.5oC. De afgelopen 72 uur is de temperatuur binnen 0.4oC stabiel gebleven. De WP schakelt aan als de temperatuur naar 20.4oC gaat. Soms loopt de temperatuur op tot 20.7 en een enkele keer tot 20.8. Maar gemiddeld zal het iets van 20.55oC zijn.
Heb vanmorgen op de laatste 0,3 graden verhoging mogen toekijken hoe elke paar minuten de warmtepomp aan en uit ging.
Dit herken ik helemaal niet.
De verschillen zullen wel komen doordat mijn CV systeem anders in elkaar steekt (combinatie radiatoren en vloerverwarming) waardoor temperatuur tijdens het nadraaien van de pomp in buitenunit (~30s) al genoeg daalt.

Maar je oplossing om het setpoint op 1 te zetten als er warmtevraag is klinkt prima en maakt het geheel robuuster, want alleen de pomp aanzetten gaat niet voor zover ik weet.

  • _JGC_
  • Registratie: Juli 2000
  • Laatst online: 00:48
Ik heb 'm vanmorgen laten lopen. Thermostaat vraagt continu setpoint van 25 graden bij verwarmen van 20.0 naar 20.5. Setpoint wordt redelijk snel gehaald en daarna stijgt deze in 3 uur tijd langzaam door naar 30 graden.
Het halfje opstoken op stand 1 duurde 2,5 uur en thermostaat liet de warmtepomp doorstoken tot 20.7. Al die tijd was het aangenaam, het kille gevoel van vanmorgen was vrij snel weg.

Nu is dit nog geen maatstaf voor de winter, want thermostaat regelt nog heel conservatief en de heatboosters starten de fans pas op bij verschil van 4 graden tussen aanvoer en kamertemperatuur. Verder hangen er boven nog radiatoren die in de winter ook warmte vragen maar nu helemaal niet.

Ik merk wel dat ik dit veel fijner vind dan die ketel die er 2-4x per uur even een paar minuten 6KW in duwde. Ook veel fijner dan de originele regeling die direct naar 35 graden doorstookt en daar blijft hangen.

[ Voor 13% gewijzigd door _JGC_ op 24-09-2025 22:19 ]


  • _JGC_
  • Registratie: Juli 2000
  • Laatst online: 00:48
@WackoH Ik vermoed dat die dingen komen door verschillen in regeling. Ik kwam er vanmorgen achter dat de OT MCU helemaal niet luistert naar CH_ON en CH_OFF. Die geeft gewoon vrolijk het setpoint van de thermostaat door, dat is ook waarom de pomp met de iSense continu bleef draaien.

De DIYLESS bouwt bij <0,3 van de temperatuur verwijderd een setpoint op en zodra die >=20 graden wordt volgt een CH_ON. Vanmorgen heeft de warmtepomp volgens vraag van thermostaat van 6:55 tot 8:15 gestookt, om 8:35 ging deze vrolijk tot 11:00 op laagste stand draaien en thermostaat heeft nooit wat gevraagd :X. Setpoint was al die tijd 12-13 graden ipv 10.

Algoritme nog maar iets aanpassen, ik wil wel doorstoken als setpoint geen 10 (uit) is, maar als setpoint van 10 (uit) af komt niet eerder stoken dan wanneer deze 20 wordt.

  • WackoH
  • Registratie: November 2012
  • Laatst online: 23:41
Zoals je het hier beschrijft en als ik naar het gedrag van de Anna kijkt, komt het inderdaad door de regeling.
Wat de Anna thermostaat doet, is de aanvraagtemperatuur op '0' houden als er geen warmtevraag is, en die snel laten stijgen als die er wel is. Dit ziet er dan zo uit:
Afbeeldingslocatie: https://tweakers.net/i/9gPEHQQc4mviSopWSq1LKVnXfWc=/fit-in/4000x4000/filters:no_upscale():strip_exif()/f/image/vJduySGftSiIzZtZZhMqJwpQ.png?f=user_large
Dus bij geen warmtevraag wordt het setpoint van aanvoertemperatuur van de WP ook naar snel beneden getrokken waardoor dat kort achter aan en uit schakelen wat jij zag, wordt vermeden.

Verder geeft de controlbox de aanvoertemperatuur naar de ketel 1-op-1 door, maar schaalt deze temperatuur als de WP warmte moet leveren.
Bv. 30oC aanvoer wordt setpoint 25oC voor de WP, en 60 wordt 40oC.
Dit concept heb ik overgenomen om de WP aan te sturen, maar ik weet eerlijk niet of het bij de ketel goed gaat. Die heb ik sinds begin dit jaar niet meer aan gehad :P

Acties:
  • 0 Henk 'm!

  • _JGC_
  • Registratie: Juli 2000
  • Laatst online: 00:48
Inmiddels de code wat aangepast. Dingen waar ik tegenaan liep:
- afrondingsverschillen vanwege floats, thermostaat geeft 20 graden setpoint, maar 20 >= 20 is niet altijd true, dus ging de regeling pas stoken bij 21 graden setpoint.
- nogal agressieve regeling. Van laag vermogen direct naar hoogste en daar dan 7 minuten op blijven draaien (hallo buurman)

Wat ik heb aangepast:
- stappenmodulatie, elke 120 seconden mag de regeling 1 stap omhoog of 1 stap omlaag.
- Uit als setpoint onder 15 komt na een stooksessie (float afronding zal me hier een zorg zijn)
- Aan als setpoint 19 of hoger wordt (uint8_t ipv float nu)
- Schakelen tussen trage en snelle regeling eruit
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 ]


Acties:
  • 0 Henk 'm!

  • _JGC_
  • Registratie: Juli 2000
  • Laatst online: 00:48
@WackoH Ik lees hier eerder in het topic dat de warmtepomp niet op stand 1 wil draaien op kou. Wat gaat deze doen dan? Gaat de warmtepomp dan steeds uit, of krijg je stand 2?

Nieuwe regeling lijkt hier de temperatuur niet meer door te geven aan de thermostaat, dus heb nog een bug ergens. Lampje op de schakelaar brandt vanmorgen ook ineens dus lijkt erop dat de OT MCU denkt dat de ketel stookt.

Edit: inmiddels achterhaald. Lijkt erop dat ik de seriele poort van mijn Mega gesloopt heb of de bedrading slecht contact maakt, ik krijg niks meer door van de OT MCU. Gek genoeg kreeg ik dat nog wel éénmalig na het flashen van de oude code, maar inmiddels ook niet meer.

Edit2: seriele poorten omgedraaid en werkt weer. Poort dus niet kapot maar speling op de prikkabeltjes.
Ik overweeg sterk om de OT MCU gewoon dood te zwijgen en een ESP8266 te gebruiken met een OT Slave bord erop. Huidige code gooit de setpoint naar 0.0 bij een communicatiefout, dat heb ik vanmorgen kunnen zien in de aanvoertemperaturen van de heatboosters. Ik dacht aan defrosts (koud en veel regen), maar ding heeft vanmorgen vroeg een hele tijd staan knipperen tussen aan en uit.

[ Voor 51% gewijzigd door _JGC_ op 04-10-2025 14:07 ]


Acties:
  • 0 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 23:41
De buitenunit gaat gewoon aan als je stand 1 stuurt. Volgens mij bepaalt dit de snelheid van de compressor (vandaar dat ik het 'speed stage' en niet 'power stage' heb genoemd). Welk vermogen dan wordt opgenomen hangt vermoedelijk primair af van de gasdrukken in de buitenunit. En die hangen weer af van o.a. de buiten-, aan- en afvoertemperatuur (denk ik, ben geen airco specialist). Dit blijkt bv. hieruit:
Afbeeldingslocatie: https://tweakers.net/i/IpOqcsI6VogP6lo_hi8vzIQLM2A=/fit-in/4000x4000/filters:no_upscale():strip_exif()/f/image/5ULZTFL5AZgdBCExsAu9xPnw.png?f=user_large

Het beste wat ik heb kunnen doen is met een Arduino steeds een hogere stand vragen, en dan met een externe meter het opgenomen vermogen bepalen:
Afbeeldingslocatie: https://tweakers.net/i/_fWXMFifsYaMO3xcZ1XEE6JDs1w=/fit-in/4000x4000/filters:no_upscale():strip_exif()/f/image/OF7RT2dEkjJ6ldb2drOQGUGm.png?f=user_large
Mijn conclusie was dat er 12 standen (plus 'uit') zijn Maar er zijn ook condities dat de WP niet meer vermogen op gaat nemen bij de hoogste standen. Dan zit 'ie blijkbaar al eerder aan z'n max. Sowieso is het beter niet op max vermogen te draaien want dan zakt de COP in. Atlantic geeft slechts 1.1 kW als max. vermogen met een COP van 4.63 bij A7W35. In een Chofu service manual staat een COP van 4.20 bij 1.43 kW. Wellicht heeft Atlantic het vermogen begrensd om in een hogere subsidie-categorie te vallen.

Daarom heb ik het aantal standen in mijn programma tot slechts 7 beperkt. Daarmee wordt het opgenomen vermogen in de praktijk max. ~1400W. Dit voldeed om het huis gedurende de afgelopen winter zonder CV warm te houden.

Zoals ik het heb opgebouwd is relatief complex. Met de kennis van nu zou ik bv. een ESP32 (heeft twee complete hardware UARTS vrij, de ESP8266 is wat kreupel op dat vlak) nemen en die zeker de HP, en misschien zelfs de OT communicatie laten doen, en dan via WiFi met Home Assistant verbinden om data uit te lezen.

Ik heb er zelfs eentje liggen met scherm en al. Maar ja, ik heb nu de volledige Plugwise functionaliteit (incl. Home Assistant integratie), de temperatuur in de woonkamer blijft prima stabiel, en bovenal draait het probleemloos. Dus ik zou er niets op vooruit gaan.

[ Voor 5% gewijzigd door WackoH op 04-10-2025 22:19 ]

Pagina: 1 2 Laatste