Vraag


Acties:
  • 0 Henk 'm!

  • Niels Niels
  • Registratie: Juli 2016
  • Laatst online: 07-07 21:28
Dag allen,

Wij hebben een Warmteterugwinsysteem van J.E. Stork Air / Zehnder WHR90 / WHR91. Hier zit een RS485 poort op. Ik heb het hele internet afgespeurd en ben meerdere topics tegen gekomen van mensen die beweren de unit te kunnen bedienen via de RS485 poort, op o.a. Tweakers, GitHub en domoticz .

Nu ben ik zelf zo ver dat ik de temperatuur en fan statussen via de RS485 poort kan uitlezen, helemaal top! Echter weet ik de commando's niet voor het bedienen/instellen van de fan statussen.

Er zijn meerdere volledige protocol beschrijvingen te vinden van andere Zehnder units (zoals de WHR950), maar die commando's werken niet op deze unit.

Ter referentie heb ik uit dit online gevonden document het read temperatuur commando gehaald:

Afbeeldingslocatie: https://tweakers.net/i/HjCTTb87QWB49cVTdiOC3UAgzBA=/x800/filters:strip_exif()/f/image/prn6saNREiwA4Ksj1ncGB7WP.png?f=fotoalbum_large

Uit een ander Zehnder document de packet opbouw (deze is wel hetzelfde voor deze unit ook): Afbeeldingslocatie: https://tweakers.net/i/1eNzxX9_nBldwMit7bfxNdNZU4o=/fit-in/4000x4000/filters:no_upscale():strip_exif()/f/image/cAr6dAQLZ8htwo5fo6MyoivK.png?f=user_large

Read temperature: 0x07, 0xF0, 0x00, 0x85, 0x00, 0x32, 0x07, 0x0F
Read fan status: 0x07, 0xF0, 0x00, 0x87, 0x00, 0x34, 0x07, 0x0F

Het set fan commando uit dit document werkt echter niet, hier bijvoorbeeld de packet die ik zend om beide fans in stand 2 te zetten (ik heb 0-3 geprobeerd, uiteraard bij elke packet de checksum opnieuw berekend).
Set fan commando, die niet werkt: 07 F0 00 A0 02 0C 4B 07 0F

Kan iemand mij hierbij helpen hoe ik de fans kan aansturen? Zou helemaal top zijn in een stukje home domotica! Ook zou ik graag het commando om de luchttoevoer aan/uit te kunnen zetten. Zou helemaal mooi zijn als iemand anders dit al werkende heeft en mij deze informatie kan delen! :)


Hier bijvoorbeeld een GitHub project van een andere Zehnder unit met een RS232 poort, deze commando's staan uitgebreid beschreven, maar werken dus allemaal NIET op mijn unti: https://github.com/adorobis/hacomfoairmqtt

Iemand in dit topic: https://forum.domoticz.com/viewtopic.php?t=9594&start=60 heeft alle commando's van de WHR90 unit gesniffed, echter weet hij ook niet meer over de aansturing commando's, enkel de eenvoudige read commando's.

[ Voor 5% gewijzigd door Niels Niels op 23-06-2025 21:23 . Reden: Foto toegevoegd van packet opbouw ]

Alle reacties


Acties:
  • +1 Henk 'm!

  • Accretion
  • Registratie: April 2014
  • Laatst online: 23:18

Accretion

⭐⭐⭐⭐⭐ (5/5)

Geen idee m.b.t dit specifieke systeem, maar heb je bericht gelezen.
In jouw set fan speed command mist een byte i.v.m de protocol beschrijving.

Uit jouw voorbeeld:
07 F0 00 A0 02 0C 4B 07 0F

Start: 07 F0 (fixed)
Command: 00 A0 (set fan)
Number: .... (Number of data bytes)
Fanspeed: 02
Fanmode: 0C
crc: 4B
End: 07 0F (fixed)

Je geeft bij het bericht dus niet mee hoeveel bytes aan data je gaat versturen, wat vaak nodig is voor framing in zo'n protocol.

Praktisch zou ik proberen:
07 F0 00 A0 02 02 0C 4D 07 0F

Al durf ik niet te zeggen of ik de checksum zo goed berekend heb, zit op telefoon, maar d'r zijn genoeg websites met checksum generators.

De meest lastige zetten heb je al gedaan, zo'n read command is in principe niet heel anders dan een write command.

[ Voor 16% gewijzigd door Accretion op 23-06-2025 22:49 ]


Acties:
  • 0 Henk 'm!

  • Niels Niels
  • Registratie: Juli 2016
  • Laatst online: 07-07 21:28
@Accretion scherp, en je hebt helemaal gelijk! Ik was dit zelf ook al tegengekomen tijdens wat debuggen, maar zie dat ik dit nog niet had geüpdatet in mijn document... (en nu dus foutief heb over gecopy-paste).

Hoe dan ook werkte het aangepaste commando helaas ook niet. Maar erg scherp gezien!

Acties:
  • 0 Henk 'm!

  • Accretion
  • Registratie: April 2014
  • Laatst online: 23:18

Accretion

⭐⭐⭐⭐⭐ (5/5)

Niels Niels schreef op maandag 23 juni 2025 @ 22:48:
@Accretion scherp, en je hebt helemaal gelijk! Ik was dit zelf ook al tegengekomen tijdens wat debuggen, maar zie dat ik dit nog niet had geüpdatet in mijn document... (en nu dus foutief heb over gecopy-paste).

Hoe dan ook werkte het aangepaste commando helaas ook niet. Maar erg scherp gezien!
Als je de wel werkende commandos door een CRC calculator haalt (waarschijnlijk alleen de bytes voor het CRC gedeelte), kom je dan op dezelfde CRC uit als in het werkende commando?

Kun je dan hetzelfde doen voor het niet werkende commando?

Gezien een read commando met response wel werkt, lijkt de aansluiting/transceiver wel goed te werken, dus dan lijkt het een protocol dingetje.


Overigens, de manier van schrijven van beide commandos is anders?
Neem aan dat de bytes wel op dezelfde manier verstuurd worden?

Dus
07 F0 00 A0 02 02 0C 4D 07 0F
Is qua definitie hetzelfde als:
0x07, 0xF0, 0x00, 0xA0, 0x02, 0x02, 0x0C, 0x4D, 0x07, 0x0F

Maar afhankelijk van het programma waarmee je het verstuurd kan het anders opgepikt worden.


----

Ik lees ook dat mensen een applicatie hebben om 'm aan te sturen:
"StorAir maintenance tool" heb jij die ook/werkend voor jouw setup?
Om te valideren dat control überhaupt werkt en als het werkt kun je kijken of je de data over die com poort kunt loggen.

[ Voor 33% gewijzigd door Accretion op 23-06-2025 23:02 ]


Acties:
  • 0 Henk 'm!

  • Niels Niels
  • Registratie: Juli 2016
  • Laatst online: 07-07 21:28
@Accretion bedankt voor je reactie. Als ik jouw commando door mijn checksum calculator haal kom ik uit op 0x5D voor de checksum en niet 0x4D. Hoe dan ook werken beide checksum waardes helaas niet. De berekende checksum waardes met deze tool werken voor het read temperature en fan status commando wel.

Goede over de Maintenace tool! Ik heb deze inderdaad ergens weten te downloaden, echter krijg ik geen verbinding met de unit via deze software.. ik heb een Logic Analyzer op de A/B lijnen en zie wat TX data, maar nooit RX data. Ik heb zo ook gelijk bevestigd dat ik de data netjes in HEX stuur, dus niet dat die letterlijk de '0x' bijvoorbeeld ook meestuurt.

Als ik vervolgens dit programma sluit, en software zoals PuTTy open en mijn read temperature commando verzend, krijg ik gewoon data terug met exact deze hardware aansluitingen! Dus de aansluiting en RS485 dongle zou gewoon goed moeten zijn. Echter nog altijd geen succes met het aanstuur commando ook..

Acties:
  • 0 Henk 'm!

  • Accretion
  • Registratie: April 2014
  • Laatst online: 23:18

Accretion

⭐⭐⭐⭐⭐ (5/5)

Hmm, de hardware en checksum heb je dus onder controle.
Dan is het de vraag of we wel het juiste commando hebben óf dat we eerst een ander soort commando moeten sturen om de unit in een andere modus te zetten, wellicht functioneert hij nu standalone en moet je 'm in een soort slave modus zetten.

Denk toch terug naar research (meer informatie zoeken op het internet of proberen te snoopen.

---

Wat als je een twee virtuele gekoppelde com-poorten aan maakt.

Je kunt dan aan de ene kant ene programma zoals Putty hangen en aan de andere kant de maintenance tool, om i.i.g te loggen welke commando's de tool verstuurt.

Alhoewel ik geen idee heb of je verder komt in de tool als hij geen reactie krijgt op de initiële berichten.

Acties:
  • 0 Henk 'm!

  • Accretion
  • Registratie: April 2014
  • Laatst online: 23:18

Accretion

⭐⭐⭐⭐⭐ (5/5)

Welk document gebruik jij om die commando's vandaan te halen?

https://forum.domoticz.co...78c1f83907fd453d&start=80

In artikel hierboven, de unit moet op stand 1 staan om extern aan te kunnen sturen?

In dat artikel proberen ze ook een heel ander commando:
x07 xF0 x00 x99 x01 x04 x4B x07 x0f
En iets over een paneel:
In my solution was disconect oem panel from Zehnder ( CCeasy). When I disconect the CCeasy, the values was stable and true.
Potentieel moeten eerst deze settings ingesteld worden, maar geen idee op welke waardes:
Afbeeldingslocatie: https://tweakers.net/i/6Or_g_BQFppnMEWLF8p4jfGD27M=/800x/filters:strip_icc():strip_exif()/f/image/KERUhZE7ryS2ePPGZK0vEPww.jpg?f=fotoalbum_large

Acties:
  • +1 Henk 'm!

  • FAQ
  • Registratie: April 2003
  • Niet online

FAQ

Het is voor mij alweer jaren geleden dat ik dit werkend heb gekregen op mijn WHR-91B en ik heb het helaas niet goed gedocumenteerd.

Hierbij een snippet van de code die dik draai vanaf een Rpi:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def reset_filter_timer():
    info_msg('Reset button pushed')
    # Hieronder: filter reset let op ook oa comfort temp wordt aangepast!!
    data = send_command(b'\x00\x8D', b'\x4B\x46\x4C\x36\x4C\x2C\x0F\x2A\x03\x1A', expect_reply=False) 
    
    # Hieronder: Standen wijzigen
    #0: configuratie (8=bypass)         (0x00 => 0)                        
    #1: inschakelvertraging             (0x05 => 5)
    #2: uitschakelvertraging            (0x1E => 30)
    #3: Test 13                         (0x00 => 0)
    #4: Test 14                         (0x00 => 0)
    #5: uit 1                           (0x1E => 30)
    #6: uit 2                           (0x32 => 50)
    #7: uit 3                           (0x5A => 90)
    #8: in 1                            (0x1E => 30)
    #9: in 2, x0F=15,x1E=30,x4B=75      (0x32 => 50) 
    # data = send_command(b'\x00\x8C', b'\x08\x00\x00\x00\x00\x0F\x32\x4B\x0F\x32', expect_reply=False)


Wat ik me nog herinner is dat je niet 1 ding aan kunt passen, tenzij je van de andere instellingen ook de huidige waardes meestuurt.

Acties:
  • +1 Henk 'm!

  • Niels Niels
  • Registratie: Juli 2016
  • Laatst online: 07-07 21:28
@FAQ wat fantastisch zeg!! Ik lees dit nu pas, maar dit ziet er veel belovend uit, deze commando's lijken inderdaad meer op de commando's die ik bij mijn unit zou verwachten. Helaas dat het mensen nog niet gelukt is om direct naar stand 1, 2 of 3 te schakelen, maar opzich met de percentages wijzigen per stand lukt dit ook.

Ik ga het z.s.m. in mijn eigen scriptje verwerken en testen (ik zal je dan een update geven in een nieuwe comment). Weet jij toevallig ook of dat je nog een commando had om de luchttoevoer aan/uit te kunnen schakelen? Of zou dat al mogelijk zijn door de luchttoever op 0% te schrijven (wat in de units settings zelf niet mag, die moet tenminste 15 zijn, maar misschien dat het via een commando wel werkt?).

Nogmaals dank! Mag ik trouwens ook vragen hoe je aan deze informatie komt? Ik heb het halve internet afgespeurd en kan dit werkelijk waar nergens vinden.

[ Voor 3% gewijzigd door Niels Niels op 05-07-2025 20:24 ]


Acties:
  • 0 Henk 'm!

  • Niels Niels
  • Registratie: Juli 2016
  • Laatst online: 07-07 21:28
@FAQ wat testen later, en dit werkt! Ik kan de fan waardes schrijven, helemaal top! Super bedankt!

Als ik de toevoer uit en aan zet, zie ik het 0x00 0x87 Retrieve fans commando byte 7 en 9 togglen (en gaan uiteraard de bytes 0 en 2 P37 Supply air % en Supply air speed naar 0 als die wordt uitgezet).

Met fysieke knop toevoer aan zijn bytes 7 en 9 respectievelijk: 0 en 1
Met fysieke knop toevoer uit zijn bytes 7 en 9 respectievelijk: 1 en 0

Als ik met jouw commando simpelweg de supply air speed op 0% zet, werkt dit helaas niet om de fan daadwerkelijk uit te zetten. De waarde 0% wordt wel geschreven naar de unit, maar de unit regelt het automatisch naar de minimale waarde van 15% als ik het opvraag, ook blijft de groene LED van 'luchttoevoer aan'. De enige manier om de toevoer écht naar 0% te krijgen is nog steeds door die knop 'luchttoevoer' in en uit te drukken. Ik hoop nog altijd dat ik dit ook in een commando kan vinden, want dan heb ik echt alle bedieningsopties die ik graag zou willen!


Afbeeldingslocatie: https://tweakers.net/i/VPxjACiE3XouWEL-cjgYfwUJL0Q=/fit-in/4000x4000/filters:no_upscale():strip_exif()/f/image/UitrBA6QvccuDwMUhlfbSMS5.png?f=user_large

[ Voor 3% gewijzigd door Niels Niels op 05-07-2025 22:12 ]


Acties:
  • +1 Henk 'm!

  • FAQ
  • Registratie: April 2003
  • Niet online

FAQ

Niels Niels schreef op zaterdag 5 juli 2025 @ 20:23:
@FAQ wat fantastisch zeg!! Ik lees dit nu pas, maar dit ziet er veel belovend uit, deze commando's lijken inderdaad meer op de commando's die ik bij mijn unit zou verwachten. Helaas dat het mensen nog niet gelukt is om direct naar stand 1, 2 of 3 te schakelen, maar opzich met de percentages wijzigen per stand lukt dit ook.

Ik ga het z.s.m. in mijn eigen scriptje verwerken en testen (ik zal je dan een update geven in een nieuwe comment). Weet jij toevallig ook of dat je nog een commando had om de luchttoevoer aan/uit te kunnen schakelen? Of zou dat al mogelijk zijn door de luchttoever op 0% te schrijven (wat in de units settings zelf niet mag, die moet tenminste 15 zijn, maar misschien dat het via een commando wel werkt?).

Nogmaals dank! Mag ik trouwens ook vragen hoe je aan deze informatie komt? Ik heb het halve internet afgespeurd en kan dit werkelijk waar nergens vinden.
Het is een paar jaar geleden maar volgens mij heb ik me laten inspireren door code die ik online had gevonden van de WHR 950.

Naar 0 schakelen of toevoer uitschakelen is me niet gelukt, ik lees nu enkel via deze code de toeren en standen uit en gebruik hem om de temperatuur t.b.v. de bypass in te stellen (in de winter op 21, in de zomer op 10). Filter teller resetten kan ook maar als je de filters wisselt sta je toch naast het apparaat dus kun je het net zo goed ter plaatse doen.

Ik weet niet hoe solide het geheugen is en of veelvuldig herschrijven van de instellingen naar de eeprom (?) tot fouten kan leiden.

Voor het schakelen heb ik daarom uiteindelijk een extra esp8266 met relais eraan gehangen die op basis van CO2 en luchtvochtigheid (verzameld via homeassistant) naar hoogstand schakelt en als alles weer gedaald is terugschakelt.

Volgens mij was minimum van de standen inderdaad 15% en moet stand 2 hoger zijn dan 1 en 3 weer hoger dan 2.

Succes ermee en mocht je verder komen, leuk om te lezen!

Acties:
  • 0 Henk 'm!

  • Niels Niels
  • Registratie: Juli 2016
  • Laatst online: 07-07 21:28
@FAQ Ah, nogmaals dank! Ook weer wat geleerd dat je de filter teller kan resetten haha.

Ik had helemaal geen rekening gehouden met het aantal writes naar de EEPROM... dat kan er nog wel eens voor zorgen dat alles hoe ik het nu heb bedacht helemaal geen goede optie is. Het lijkt me dan inderdaad beter om het in de hardware aan te passen met relays.

Ik ben wat beter in de software dan de hardware, en houd er niet zo van om met apparaten te knoeien die aan de 230V hangen (ook al zal het grootste gedeelte van de print zelf niet op 230V werken natuurlijk). Is het moeilijk om hier een ESP'tje tussen te hangen? Dan kan ik namelijk ook een relay hangen tussen de luchttoevoer aan/uit knop. Heb je misschien iets van een schema of foto's hoe jij dit gedaan had? Ik zag sowieso al in een ander topic wat foto's, maar van die bedrading begreep ik nog niet heel veel.

Van wat ik verder testte leek het wel mogelijk dat ik stand 1 gewoon op 90% kan schrijven, ondanks dat stand 2 en 3 lager of gelijk zijn aan deze waarde!

Nogmaals dank!

Acties:
  • 0 Henk 'm!

  • FAQ
  • Registratie: April 2003
  • Niet online

FAQ

Ik heb er een ESP8266 Wifi Dual 2/4-Kanaals Wifi Relais Module 110/220V Schakelaar Controller Board Ac/Dc ESP-12F development Board Voor Smart Home als ESP tussen gehangen. Past mooi naast de bestaande printplaat op de onderplaat van de WHR. Voeding krijgt deze ESP rechtstreeks van de laagspanningskant van de print van de WHR.

Via een van de relais van deze ESP schakel ik de hoogstand (dat is ook gewoon laagspanning)
Eventueel kun je een ander relais gebruiken om de toevoer te schakelen.

Als je zoekt op Protokoll_CA500_Avignon.pdf vind je een document wat ik heb gebruikt om de verschillende stuurcodes te achterhalen. Mogelijk is dat precies hetzelfde als wat jij ook al had gevonden maar wellicht bevat het net iets meer informatie. Als ik het me goed herinner werkte bij mij de 0x00 0xA0 code niet.

In principe denk ik dat de esp8266 krachtig genoeg zou moeten zijn om ook de Raspberry Pi te vervangen maar zover gaat mijn software kennis helaas niet dat ik die code om kan schrijven.

Ik heb de volledige code die ik gebruik opgezocht (bevat ook wat aantekeningen en wellicht wat slordig programmeerwerk):

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
#!/usr/local/share/ca350/bin/python3.8
# -*- coding: utf-8 -*-

import paho.mqtt.client as mqtt
import time
import serial
import sys
import os
import json

from queue import Queue
q=Queue()

# Service Configuration
SerialPort = '/dev/ttyUSB0'                 # Serial port RS485
refresh_interval = 60                       # Interval in seconds at which data will be polled
debug = False

MQTTServer = ''*********'               # MQTT broker - IP
MQTTPort = 1883                             # MQTT broker - Port
MQTTKeepalive = 10                          # MQTT broker - keepalive
MQTTUser = 'mqqt_user'                      # MQTT broker - user - default: 0 (disabled/no authentication)
MQTTPassword = '*********'                  # MQTT broker - password - default: 0 (disabled/no authentication)

HAAutoDiscoveryDeviceName = 'WHR91'         # Home Assistant Device Name
HAAutoDiscoveryDeviceId = 'WHR91'           # Home Assistant Unique Id
HAAutoDiscoveryDeviceManufacturer = 'StorkAir'
HAAutoDiscoveryDeviceModel = 'WHR91BL'

def send_autodiscover(name, entity_id, entity_type, state_topic = None, device_class = None, unit_of_measurement = None, icon = None, attributes = {}, command_topic = None, step = None, min = None, max = None):
    mqtt_config_topic = "homeassistant/" + entity_type + "/" + entity_id + "/config"
    sensor_unique_id = HAAutoDiscoveryDeviceId + "-" + entity_id

    discovery_message = {
        "name": HAAutoDiscoveryDeviceName + " " + name,
        "availability_topic":"WHR/status",
        "payload_available":"online",
        "payload_not_available":"offline",
        "unique_id": sensor_unique_id,
        "device": {
            "identifiers":[
                HAAutoDiscoveryDeviceId
            ],
            "name": HAAutoDiscoveryDeviceName,
            "manufacturer": HAAutoDiscoveryDeviceManufacturer,
            "model": HAAutoDiscoveryDeviceModel
        }
    }
    if state_topic:
        discovery_message["state_topic"] = state_topic

    if command_topic:
        discovery_message["command_topic"] = command_topic

    if unit_of_measurement:
        discovery_message["unit_of_measurement"] = unit_of_measurement

    if device_class:
        discovery_message["device_class"] = device_class

    if icon:
        discovery_message["icon"] = icon

    if min:
        discovery_message["min"] = min

    if step:
        discovery_message["step"] = step

    if max:
        discovery_message["max"] = max

    if len(attributes) > 0:
        for attribute_key, attribute_value in attributes.items():
            discovery_message[attribute_key] = attribute_value

    mqtt_message = json.dumps(discovery_message)
    debug_msg('Sending autodiscover for ' + mqtt_config_topic)
    publish_message(mqtt_message, mqtt_config_topic)

def on_connect(client, userdata, flags, rc):
    publish_message("online","WHR/status")
    send_autodiscover(name="Afblaas temperatuur (naar dak)", entity_id="afblaastemp", entity_type="sensor", state_topic="WHR/afblaastemp", device_class="temperature", unit_of_measurement="°C")
    send_autodiscover(name="Buiten temperatuur (vanaf dak)", entity_id="buitentemp", entity_type="sensor", state_topic="WHR/buitentemp", device_class="temperature", unit_of_measurement="°C")
    send_autodiscover(name="Retour temperatuur (huis uit)", entity_id="retourtemp", entity_type="sensor", state_topic="WHR/retourtemp", device_class="temperature", unit_of_measurement="°C")
    send_autodiscover(name="Comfort temperatuur", entity_id="comforttemp", entity_type="sensor", state_topic="WHR/comforttemp", device_class="temperature", unit_of_measurement="°C")
    send_autodiscover(name="Intake fan speed", entity_id="fan_speed_intake", entity_type="sensor", state_topic="WHR/intakefanrpm", unit_of_measurement="rpm", icon="mdi:fan")
    send_autodiscover(name="Exhaust fan speed", entity_id="fan_speed_exhaust", entity_type="sensor", state_topic="WHR/exhaustfanrpm", unit_of_measurement="rpm", icon="mdi:fan")
    send_autodiscover(name="Intake air level", entity_id="intake_air_level", entity_type="sensor", state_topic="WHR/intakefanspeed", unit_of_measurement="%", icon="mdi:fan")
    send_autodiscover(name="Exhaust air level", entity_id="exhaust_air_level", entity_type="sensor", state_topic="WHR/exhaustfanspeed", unit_of_measurement="%", icon="mdi:fan")
    send_autodiscover(name="Filter status", entity_id="filterstatus", entity_type="binary_sensor", state_topic="WHR/filterstatus", device_class="problem")
    send_autodiscover(name="Bypass", entity_id="bypass", entity_type="binary_sensor", state_topic="WHR/bypass", device_class="opening") 
    send_autodiscover(name="Bypass aanwezig", entity_id="bypasspresent", entity_type="binary_sensor", state_topic="WHR/bypasspresent", device_class="plug") 
    send_autodiscover(name="Filtertimer", entity_id="filtertimer", entity_type="sensor", state_topic="WHR/filtertimer", unit_of_measurement="weeks", icon="mdi:calendar")
    send_autodiscover(name="Inschakelvertraging", entity_id="inschakelvertraging", entity_type="sensor", state_topic="WHR/inschakelvertraging", unit_of_measurement="min", icon="mdi:clock")
    send_autodiscover(name="Uitschakelvertraging", entity_id="uitschakelvertraging", entity_type="sensor", state_topic="WHR/uitschakelvertraging", unit_of_measurement="min", icon="mdi:clock")
    send_autodiscover(name="Stand 1 Uit", entity_id="stand1uit", entity_type="sensor", state_topic="WHR/stand1uit", unit_of_measurement="%", icon="mdi:fan-speed-1")
    send_autodiscover(name="Stand 2 Uit", entity_id="stand2uit", entity_type="sensor", state_topic="WHR/stand2uit", unit_of_measurement="%", icon="mdi:fan-speed-2")
    send_autodiscover(name="Stand 3 Uit", entity_id="stand3uit", entity_type="sensor", state_topic="WHR/stand3uit", unit_of_measurement="%", icon="mdi:fan-speed-3")
    send_autodiscover(name="Stand 1 In", entity_id="stand1in", entity_type="sensor", state_topic="WHR/stand1in", unit_of_measurement="%", icon="mdi:fan-speed-1")
    send_autodiscover(name="Stand 2 In", entity_id="stand2in", entity_type="sensor", state_topic="WHR/stand2in", unit_of_measurement="%", icon="mdi:fan-speed-2")
    send_autodiscover(name="Stand 3 In", entity_id="stand3in", entity_type="sensor", state_topic="WHR/stand3in", unit_of_measurement="%", icon="mdi:fan-speed-3")
    send_autodiscover(name="Stand", entity_id="stand", entity_type="sensor", state_topic="WHR/stand", icon="mdi:av-timer")
    send_autodiscover(name="Firmware", entity_id="firmware", entity_type="sensor", state_topic="WHR/firmware", icon="mdi:chip")
    send_autodiscover(name="Standnaam", entity_id="standnaam", entity_type="sensor", state_topic="WHR/standnaam", icon="mdi:av-timer")
    send_autodiscover(name="Reset Filter", entity_id="reset_filter", entity_type="button", command_topic="WHR/reset_filter", icon="mdi:air-filter")
    send_autodiscover(name="Gewenste temperatuur (comfort)", entity_id="stuurtemp", entity_type="number", min=10, max=23, command_topic="WHR/stuurtemp/set", icon="mdi:temperature-celsius", unit_of_measurement="°C")
    send_autodiscover(name="T01", entity_id="t01", entity_type="sensor", state_topic="WHR/t01", icon="mdi:chip")
    send_autodiscover(name="T02", entity_id="t02", entity_type="sensor", state_topic="WHR/t02", icon="mdi:chip")
    send_autodiscover(name="T03", entity_id="t03", entity_type="sensor", state_topic="WHR/t03", icon="mdi:chip")
    send_autodiscover(name="T04", entity_id="t04", entity_type="sensor", state_topic="WHR/t04", icon="mdi:chip")
    send_autodiscover(name="T05", entity_id="t05", entity_type="sensor", state_topic="WHR/t05", icon="mdi:chip")
    send_autodiscover(name="T06", entity_id="t06", entity_type="sensor", state_topic="WHR/t06", icon="mdi:chip")
    send_autodiscover(name="T07", entity_id="t07", entity_type="sensor", state_topic="WHR/t07", icon="mdi:chip")
    send_autodiscover(name="T08", entity_id="t08", entity_type="sensor", state_topic="WHR/t08", icon="mdi:chip")
    send_autodiscover(name="T09", entity_id="t09", entity_type="sensor", state_topic="WHR/t09", icon="mdi:chip")
    send_autodiscover(name="T10", entity_id="t10", entity_type="sensor", state_topic="WHR/t10", icon="mdi:chip")
    send_autodiscover(name="T11", entity_id="t11", entity_type="sensor", state_topic="WHR/t11", icon="mdi:chip")
    send_autodiscover(name="T12", entity_id="t12", entity_type="sensor", state_topic="WHR/t12", icon="mdi:chip")
    send_autodiscover(name="T13", entity_id="t13", entity_type="sensor", state_topic="WHR/t13", icon="mdi:chip")
    send_autodiscover(name="T14", entity_id="t14", entity_type="sensor", state_topic="WHR/t14", icon="mdi:chip")
    send_autodiscover(name="T15", entity_id="t15", entity_type="sensor", state_topic="WHR/t15", icon="mdi:chip")
    send_autodiscover(name="T16", entity_id="t16", entity_type="sensor", state_topic="WHR/t16", icon="mdi:chip")
    send_autodiscover(name="T17", entity_id="t17", entity_type="sensor", state_topic="WHR/t17", icon="mdi:chip") 
    send_autodiscover(name="T18", entity_id="t18", entity_type="sensor", state_topic="WHR/t18", icon="mdi:chip") 
    send_autodiscover(name="T19", entity_id="t19", entity_type="sensor", state_topic="WHR/t19", icon="mdi:chip")
    send_autodiscover(name="T20", entity_id="t20", entity_type="sensor", state_topic="WHR/t20", icon="mdi:chip") 
    send_autodiscover(name="T21", entity_id="t21", entity_type="sensor", state_topic="WHR/t21", icon="mdi:chip")     
    info_msg('MQTT entities verstuurd (autodiscovery)')

print("\x1b[H\x1b[2J") 

def debug_msg(message):
    if debug is True:
        print('{0} DEBUG: {1}'.format(time.strftime("%d-%m-%Y %H:%M:%S", time.gmtime()), message))

def warning_msg(message):
    print('{0} WARNING: {1}'.format(time.strftime("%d-%m-%Y %H:%M:%S", time.gmtime()), message))

def info_msg(message):
    print('{0} INFO: {1}'.format(time.strftime("%d-%m-%Y %H:%M:%S", time.gmtime()), message))

if debug is True:
    debug_msg('WHR91 Home Assistant opgestart (in DEBUG modus)')
else:
    info_msg('WHR91 Home Assistant opgestart (niet in DEBUG modus)')

# Get the checksum from the serial data (third to last byte)
def get_returned_checksum(serial_data):
    return serial_data[-3:-2]

# Calculate the checksum for a given byte string received from the serial connection.
# The checksum is calculated by adding all bytes (excluding start and end) plus 173.
# If the value 0x07 appears twice in the data area, only one 0x07 is used for the checksum calculation.
# If the checksum is greater than one byte, the least significant byte is used.
def calculate_checksum(serial_data_slice):
    checksum = 173
    seven_encountered = False

    for byte in serial_data_slice:
        if byte == 0x07:
            if not seven_encountered:
                seven_encountered = True  # Mark that we have encountered the first 0x07
            else:
                seven_encountered = False # Next one will be counted again
                continue  # Skip the seconds 0x07

        checksum += int(byte)

    return checksum.to_bytes(((checksum.bit_length() + 8) // 8), byteorder='big')[-1:]

# Calculate the length for a given byte string received from the serial connection.
# If the value 0x07 appears twice in the data area, only one 0x07 is used for the checksum calculation.
def calculate_length(serial_data_slice):
    length = 0
    seven_encountered = False

    for byte in serial_data_slice:
        if byte == 0x07:
            if not seven_encountered:
                seven_encountered = True  # Mark that we have encountered the first 0x07
            else:
                seven_encountered = False # Next one will be counted again
                continue  # Skip the seconds 0x07

        length += 1

    return length.to_bytes(1, byteorder='big')

# Filter the data from the serial connection to find the output we're looking for.
# The serial connection is sometimes busy with input/output from other devices (e. g. ComfoSense).
# Then, validate the checksum for the output we're looking for.
# Currently, the data returned is passed as a string, so we'll need to convert it back to bytes for easier handling.
def filter_and_validate(data, result_command):
    split_data = split_result(data)

    for line in split_data:
        if not (len(line) == 2 and line[0] == b'\x07' and line[1] == b'\xf3'):  # Check if it's not an ACK
            if (
                    len(line) >= 7 and
                    line[0:2] == b'\x07\xf0' and  # correct start
                    line[-2:] == b'\x07\x0f' and  # correct end
                    line[2:4] == result_command[0:2] # is it the return we're looking for
            ):
                # Validate length of data
                line_length = calculate_length(line[5:-3])  # Strip start, command, length, checksum and end
                if line[4:5] != line_length:
                    warning_msg('Incorrect length')
                    return None

                # Validate checksum
                returned_checksum = get_returned_checksum(line)
                calculated_checksum = calculate_checksum(line[2:-3])  # Strip start, checksum and end
                if returned_checksum != calculated_checksum:
                    warning_msg('Incorrect checksum')
                    return None

                return line[5:-3]  # Only return data, no start, end, length and checksum

    warning_msg('Expected return not found')
    return None

def on_message(client, userdata, message):
   q.put(message)
   pass
   
def publish_message(msg, mqtt_path):
    try:
        mqttc.publish(mqtt_path, payload=msg, qos=0, retain=True)
    except:
        warning_msg('Publishing message '+msg+' to topic '+mqtt_path+' failed.')
        warning_msg('Exception information:')
        warning_msg(sys.exc_info())
    else:
        time.sleep(0.1)
        debug_msg('published message {0} on topic {1} at {2}'.format(msg, mqtt_path, time.asctime(time.localtime(time.time()))))

def delete_message(mqtt_path):
    try:
        mqttc.publish(mqtt_path, payload="", qos=0, retain=False)
    except:
        warning_msg('Deleting topic ' + mqtt_path + ' failed.')
        warning_msg('Exception information:')
        warning_msg(sys.exc_info())
    else:
        time.sleep(0.1)
        debug_msg('delete topic {0} at {1}'.format(mqtt_path, time.asctime(time.localtime(time.time()))))

def serial_command(cmd):
    try:
        data = b''
        ser.write(cmd)
        time.sleep(2)

        while ser.inWaiting() > 0:
            data += ser.read(1)
        if len(data) > 0:
            return data
        else:
            return None
    except:
        warning_msg('Serial command write and read exception:')
        warning_msg(sys.exc_info())
        return None

# Write serial data for the given command and data.
# Start, end as well as the length and checksum are added automatically.
def send_command(command, data, expect_reply=True):
    start = b'\x07\xF0'
    end = b'\x07\x0F'
    if data is None:
        length = b'\x00'
        command_plus_data = command + length
    else:
        length_int = len(data)
        length = length_int.to_bytes(((length_int.bit_length() + 8) // 8), byteorder='big')[-1:]
        command_plus_data = command + length + data

    checksum = calculate_checksum(command_plus_data)

    cmd = start + command_plus_data + checksum + end

    result = serial_command(cmd)

    if expect_reply:
        if result:
            result_command_int = int.from_bytes(command, byteorder='big')  - 1
            result_command = result_command_int.to_bytes(2, byteorder='big')
            filtered_result = filter_and_validate(result, result_command)
            debug_msg('------------------------------------------------------------------')
            debug_msg('command: '+str(command))
            debug_msg('resultc: '+str(result_command))
            debug_msg('results: '+str(result))
            if filtered_result:
                ser.write(b'\x07\xF3')  # Send an ACK after receiving the correct result
                return filtered_result
    else:
        return True

    return None

# Split the data at \x07\f0 (start) or \x07\xf3 (ACK)
def split_result(data):
    split_data = []
    line = b''

    for index in range(len(data)):
        byte = data[index:index+1]
        nextbyte = data[index+1:index+2]
        if index > 0 and len(data) > index+2 and (byte == b'\x07' and nextbyte == b'\xf0' or byte == b'\x07' and nextbyte == b'\xf3'):
            split_data.append(line)
            line = b''
        line += byte

    split_data.append(line)
    return split_data

def get_instellingen():
    #Check of er binnenkomende berichten klaar staan om verwerkt te worden
    while not q.empty():
      message = q.get()
      if message is None:
        continue
      msg_data = str(message.payload.decode("utf-8"))
      if message.topic == "WHR/stuurtemp/set":
       stuurtemp = int(float(msg_data))
       set_stuurtemp(stuurtemp)
      elif message.topic == "WHR/reset_filter":
       selector = msg_data
       if selector == "PRESS":
            reset_filter_timer()
      else:
        warning_msg("Message "+message.topic+" with message: "+msg_data+" ignored")
    step='stap 1 (firmware)'
    data = send_command(b'\x00\x83', None)
    if data is None:
        warning_msg('Instellingen ' + step + ' geen seriële waarde opgehaald')
    else:
        if len(data) > 2:
            Firmware = str(data[2]) +'.0' + str(data[3])
            info_msg('Instellingen ' + step + ' opgehaald')
        else:
            warning_msg('Instellingen ' + step + ' foutieve seriële waarde opgehaald')
 
    step='stap 2 (temperaturen en Bypass status)'
    data = send_command(b'\x00\x85', None)
    if data is None:
        warning_msg('Instellingen ' + step + ' geen seriële waarde opgehaald')
    else:
        if len(data) > 5:
            Test1 = data[0] # na reset 1, sprong daarna 6 minuten lang op 248 en weer terug naar 1, toen bypass dicht was stond deze op 0
            if data[0] == 0:
                Bypass = 'OFF'
            else:
                Bypass = 'ON'
            Test2 = data[1] # na reset 18, sprong daarna naar 210 (gelijk met data[0), tijdje op 0, weer naar 18 en vervolgens 182, 188, 182 en weer naar 18 # geen idee, wellicht stand bypass klep?
            Test3 = data[2] # na reset 0, sprong daarna naar kort op 182, daarna tijdje op 18 en weer naar 0
            BuitenAirTemp   = data[3] / 2.0 - 20
            RetourAirTemp   = data[4] / 2.0 - 20
            AfblaasAirTemp  = data[5] / 2.0 - 20
            Test4 = data[6] # 136? 137, 138
            Test5 = data[7] # frequent wisselende waardes
            Test6 = data[8] # frequent wisselende waardes
            Test7 = data[9] # altijd 0?
            info_msg('Instellingen ' + step + ' opgehaald')
        else:
            warning_msg('Instellingen ' + step + ' foutieve seriële waarde opgehaald')

    step='stap 3 (fans actuele stand)'
    data = send_command(b'\x00\x87', None)
    if data is None:
        warning_msg('Instellingen ' + step + ' geen seriële waarde opgehaald')
    else:
        if len(data) > 9:
            IntakeFanSpeed  = data[0]
            ExhaustFanSpeed = data[1]
            IntakeFanRPM    = data[2] * 20
            ExhaustFanRPM   = data[3] * 20
            Test8 = data[4] # na factory reset altijd 0, daarvoor wisselend tussen 4 en 8, wellicht iets te maken met bypass status/stand?
            Test9 = data[5] # na factory reset 128, daarvoor 129
            Test10 = data[6] # altijd 0?
            Test11 = data[7] # altijd 0? 
            FanLevel        = data[8] + 1
            Test12 = data[9] # altijd 1?
            info_msg('Instellingen ' + step + ' opgehaald')
        else:
            warning_msg('Instellingen ' + step + ' foutieve seriële waarde opgehaald')

    step='stap 4 (fans instellingen en schakelvertragingen)'
    data = send_command(b'\x00\x89', None)
    if data is None:
        warning_msg('Instellingen ' + step + ' geen seriële waarde opgehaald')
    else:
        if len(data) > 8:
            if data[0] == 8:
                BypassPresent = 'ON'
            else:
                BypassPresent = 'OFF'
            #data[0] is most absence / presence of EWT, Heater, Bypass & Filterguard. 8 seems to be bypass present, rest absent.
            SwitchOnDelay   = data[1]
            SwitchOffDelay  = data[2]
            Test13 = data[3]  # altijd 0?
            Test14 = data[4]  # altijd 0?
            OutLow          = data[5]
            OutMid          = data[6]
            OutHigh         = data[7]
            InLow           = data[8]
            InMid           = data[9]
            info_msg('Instellingen ' + step + ' opgehaald')
        else:
            warning_msg('Instellingen ' + step + ' foutieve seriële waarde opgehaald')

    step='stap 5 (fans instellingen en Comforttemp en filter)'
    data = send_command(b'\x00\x8B', None)
    if data is None:
        warning_msg('Instellingen ' + step + ' geen seriële waarde opgehaald')
    else:
        if len(data) > 8:
            InHigh = data[0] # 90% na factory reset
            ComfortTemp = data[1] / 2.0 - 20 # 18 na factory reset
            Test15 = data[2] # Heatertemp,  76 na factory reset => 18 graden
            Test16 = data[3] # EWTTempLow,  54 na factory reset => 7  graden
            Test17 = data[4] # EWTTempHigh, 76 na factory reset => 18 graden
            Test18 = data[5] # BypassHysteresisTemp 44 na factory reset => 2 graden
            Test19 = data[6] # BypassOutCorr, 15 na factory reset =??
            Test20 = data[7] # AntiFrostTemp, 42 na factory reset => 1 graad
            Test21 = data[8] # EWTInCorr => 3 na factory reset
            FilterTimer = data[9] # 26 na factory reset
            if data[9] == 0:
                FilterStatus = 'OFF'
            elif data[9] == 1:
                FilterStatus = 'ON'
            else:
                FilterStatus = 'OFF'
            info_msg('Instellingen ' + step + ' opgehaald')
        else:
            warning_msg('Instellingen ' + step + ' foutieve seriële waarde opgehaald')

    publish_message(msg=str(Bypass), mqtt_path='WHR/bypass')
    publish_message(msg=str(BypassPresent), mqtt_path='WHR/bypasspresent')
    publish_message(msg=str(ComfortTemp), mqtt_path='WHR/comforttemp')
    publish_message(msg=str(BuitenAirTemp), mqtt_path='WHR/buitentemp')
    publish_message(msg=str(ExhaustFanRPM), mqtt_path='WHR/exhaustfanrpm')
    publish_message(msg=str(ExhaustFanSpeed), mqtt_path='WHR/exhaustfanspeed')
    publish_message(msg=str(FanLevel), mqtt_path='WHR/stand')
    publish_message(msg=str(Firmware), mqtt_path='WHR/firmware')
    publish_message(msg=str(FilterTimer), mqtt_path='WHR/filtertimer')
    publish_message(msg=str(FilterStatus), mqtt_path='WHR/filterstatus')
    publish_message(msg=str(InLow), mqtt_path='WHR/stand1in')
    publish_message(msg=str(InMid), mqtt_path='WHR/stand2in')
    publish_message(msg=str(InHigh), mqtt_path='WHR/stand3in')
    publish_message(msg=str(IntakeFanSpeed), mqtt_path='WHR/intakefanspeed')
    publish_message(msg=str(IntakeFanRPM), mqtt_path='WHR/intakefanrpm')
    publish_message(msg=str(OutLow), mqtt_path='WHR/stand1uit')
    publish_message(msg=str(OutMid), mqtt_path='WHR/stand2uit')
    publish_message(msg=str(OutHigh), mqtt_path='WHR/stand3uit')
    publish_message(msg=str(AfblaasAirTemp), mqtt_path='WHR/afblaastemp')
    publish_message(msg=str(RetourAirTemp), mqtt_path='WHR/retourtemp')
    publish_message(msg=str(SwitchOnDelay), mqtt_path='WHR/inschakelvertraging')
    publish_message(msg=str(SwitchOffDelay), mqtt_path='WHR/uitschakelvertraging')
    publish_message(msg=str(Test1), mqtt_path='WHR/t01')
    publish_message(msg=str(Test2), mqtt_path='WHR/t02')
    publish_message(msg=str(Test3), mqtt_path='WHR/t03')
    publish_message(msg=str(Test4), mqtt_path='WHR/t04')
    publish_message(msg=str(Test5), mqtt_path='WHR/t05')
    publish_message(msg=str(Test6), mqtt_path='WHR/t06')
    publish_message(msg=str(Test7), mqtt_path='WHR/t07')
    publish_message(msg=str(Test8), mqtt_path='WHR/t08')
    publish_message(msg=str(Test9), mqtt_path='WHR/t09')
    publish_message(msg=str(Test10), mqtt_path='WHR/t10')
    publish_message(msg=str(Test11), mqtt_path='WHR/t11')
    publish_message(msg=str(Test12), mqtt_path='WHR/t12')
    publish_message(msg=str(Test13), mqtt_path='WHR/t13')
    publish_message(msg=str(Test14), mqtt_path='WHR/t14')
    publish_message(msg=str(Test15), mqtt_path='WHR/t15')
    publish_message(msg=str(Test16), mqtt_path='WHR/t16')
    publish_message(msg=str(Test17), mqtt_path='WHR/t17')
    publish_message(msg=str(Test18), mqtt_path='WHR/t18')
    publish_message(msg=str(Test19), mqtt_path='WHR/t19')
    publish_message(msg=str(Test20), mqtt_path='WHR/t20')
    publish_message(msg=str(Test21), mqtt_path='WHR/t21')
    if FanLevel == 1:
        publish_message(msg='laag', mqtt_path='WHR/standnaam')
    elif FanLevel == 2:
        publish_message(msg='midden', mqtt_path='WHR/standnaam')
    elif FanLevel == 3:
        publish_message(msg='hoog', mqtt_path='WHR/standnaanm')
    elif FanLevel == 5:
        publish_message(msg='knop op laag (maar via relais op hoogstand)', mqtt_path='WHR/standnaam')
    elif FanLevel == 6:
        publish_message(msg='knop op midden (maar via relais op hoogstand)', mqtt_path='WHR/standnaam')
    elif FanLevel == 7:
        publish_message(msg='knop op hoog (en via relais op hoogstand)', mqtt_path='WHR/standnaam')
    else:
        warning_msg('Wrong FanLevel value: {0}'.format(FanLevel))
    info_msg('MQTT berichten verstuurd') 
    
def recon():
    try:
        mqttc.reconnect()
        debug_msg('Successfull reconnected to the MQTT server')
        topic_subscribe()
    except:
        warning_msg('Could not reconnect to the MQTT server. Trying again in 10 seconds')
        time.sleep(10)
        recon()

def topic_subscribe():
    try:
        mqttc.subscribe("WHR/stuurtemp/set", 0)
        info_msg('Successfull subscribed to the WHR/stuurtemp/set topic')
        mqttc.subscribe("WHR/reset_filter", 0)
        info_msg('Successfull subscribed to the WHR/reset_filter topic')
    except:
        warning_msg('There was an error while subscribing to the MQTT topic(s), trying again in 10 seconds')
        time.sleep(10)
        topic_subscribe()
    
def on_disconnect(client, userdata, rc):
    if rc != 0:
        warning_msg('Unexpected disconnection from MQTT (returncode '+ str(rc) +') , trying to reconnect')
        recon()

def reset_filter_timer():
    info_msg('Reset button pushed')
    # Hieronder: filter reset let op ook oa comfort temp wordt aangepast!!
    data = send_command(b'\x00\x8D', b'\x4B\x46\x4C\x36\x4C\x2C\x0F\x2A\x03\x1A', expect_reply=False) 
    
    # Hieronder: Standen wijzigen
    #0: configuratie (8=bypass)         (0x00 => 0)                        
    #1: inschakelvertraging             (0x05 => 5)
    #2: uitschakelvertraging            (0x1E => 30)
    #3: Test 13                         (0x00 => 0)
    #4: Test 14                         (0x00 => 0)
    #5: uit 1                           (0x1E => 30)
    #6: uit 2                           (0x32 => 50)
    #7: uit 3                           (0x5A => 90)
    #8: in 1                            (0x1E => 30)
    #9: in 2, x0F=15,x1E=30,x4B=75      (0x32 => 50) 
    # data = send_command(b'\x00\x8C', b'\x08\x00\x00\x00\x00\x0F\x32\x4B\x0F\x32', expect_reply=False)  

def set_stuurtemp(nr):
    if 10 <= nr <= 23:
        # De bypass gaat open als comforttemperatuur < buitenlucht < retourtemperatuur 
        # factory reset waarden tussen haakjes weergegeven)
        #0: in 3, x0F=15,x1E=30,x4B=75  (0x5A => 90)
        #1: Comforttemp                 (0x4C => 76 => 18 graden)
        #2: HeaterTemp                  (0x4C => 76 => 18 graden)
        #3: EWTTempLow                  (0x36 => 54 => 7 graden)
        #4: EWTTempHigh                 (0x4C => 76 => 18 graden)
        #5: BypassHysteresisTemp        (0x2C => 44 => 2 graden) 
        #6: BypassOutcorr               (0x0F => 15)
        #7: AntiFrostTemp               (0x2A => 42 => 1 graad)
        #8: EWTInCorr                   (0x03 => 3)
        #9: Filterstatus                (0x1A => 26 weken)
        if nr == 10:
            data = send_command(b'\x00\x8D', b'\x4B\x3C\x4C\x36\x4C\x2C\x0F\x2A\x03\x1A', expect_reply=False)
        if nr == 11:
            data = send_command(b'\x00\x8D', b'\x4B\x3E\x4C\x36\x4C\x2C\x0F\x2A\x03\x1A', expect_reply=False)
        if nr == 12:
            data = send_command(b'\x00\x8D', b'\x4B\x40\x4C\x36\x4C\x2C\x0F\x2A\x03\x1A', expect_reply=False)
        if nr == 13:
            data = send_command(b'\x00\x8D', b'\x4B\x42\x4C\x36\x4C\x2C\x0F\x2A\x03\x1A', expect_reply=False)
        if nr == 14:
            data = send_command(b'\x00\x8D', b'\x4B\x44\x4C\x36\x4C\x2C\x0F\x2A\x03\x1A', expect_reply=False)
        if nr == 15:
            data = send_command(b'\x00\x8D', b'\x4B\x46\x4C\x36\x4C\x2C\x0F\x2A\x03\x1A', expect_reply=False)
        if nr == 16:
            data = send_command(b'\x00\x8D', b'\x4B\x48\x4C\x36\x4C\x2C\x0F\x2A\x03\x1A', expect_reply=False)
        if nr == 17:
            data = send_command(b'\x00\x8D', b'\x4B\x4A\x4C\x36\x4C\x2C\x0F\x2A\x03\x1A', expect_reply=False)            
        if nr == 18:
            data = send_command(b'\x00\x8D', b'\x4B\x4C\x4C\x36\x4C\x2C\x0F\x2A\x03\x1A', expect_reply=False)
        if nr == 19:
            data = send_command(b'\x00\x8D', b'\x4B\x4E\x4C\x36\x4C\x2C\x0F\x2A\x03\x1A', expect_reply=False)
        if nr == 20:
            data = send_command(b'\x00\x8D', b'\x4B\x50\x4C\x36\x4C\x2C\x0F\x2A\x03\x1A', expect_reply=False)
        if nr == 21:
            data = send_command(b'\x00\x8D', b'\x4B\x52\x4C\x36\x4C\x2C\x0F\x2A\x03\x1A', expect_reply=False)
        if nr == 22:
            data = send_command(b'\x00\x8D', b'\x4B\x54\x4C\x36\x4C\x2C\x0F\x2A\x03\x1A', expect_reply=False)
        if nr == 23:
            data = send_command(b'\x00\x8D', b'\x4B\x56\x4C\x36\x4C\x2C\x0F\x2A\x03\x1A', expect_reply=False)
    else:
        data = None
        warning_msg('Wrong temperature provided: {0}. No changes made.'.format(nr))
    if data:
        info_msg('Changed comfort temperature to {0}'.format(nr))
    else:
        warning_msg('Changing comfort temperature to {0} went wrong, did not receive an ACK after the set command'.format(nr))

# Connect to the MQTT broker
mqttc = mqtt.Client('WHR91')
if  MQTTUser != False and MQTTPassword != False :
    mqttc.username_pw_set(MQTTUser,MQTTPassword)
# Define the mqtt callbacks
mqttc.on_connect = on_connect
mqttc.on_disconnect = on_disconnect
mqttc.on_message = on_message
mqttc.will_set("WHR/status",payload="offline", qos=0, retain=True)

# Connect to the MQTT server
while True:
    try:
        mqttc.connect(MQTTServer, MQTTPort, MQTTKeepalive)
        topic_subscribe()
        break
    except:
        warning_msg('Can\'t connect to MQTT broker. Retrying in 10 seconds.')
        time.sleep(10)
        pass

# Open the serial port
try:
    ser = serial.Serial(port = SerialPort, baudrate = 9600, bytesize = serial.EIGHTBITS, parity = serial.PARITY_NONE, stopbits = serial.STOPBITS_ONE)
except:
    warning_msg('Opening serial port exception:')
    warning_msg(sys.exc_info())
else:
    mqttc.loop_start()
    while True:
        try:
            get_instellingen()
            time.sleep(refresh_interval)
            pass
        except KeyboardInterrupt:
            warning_msg('Keyboardinterrupt ontvangen, nu afsluiten')
            mqttc.loop_stop()
            ser.close()
            break
# End of program

Acties:
  • 0 Henk 'm!

  • Niels Niels
  • Registratie: Juli 2016
  • Laatst online: 07-07 21:28
@FAQ je bent echt fantastisch voor al deze uitgebreide informatie!

Het code stukje moet me wel lukken, dat vind ik niet zo spannend allemaal (gelukkig). Het hardware stukje is wat spannender voor mij.
Als je zoekt op Protokoll_CA500_Avignon.pdf vind je een document wat ik heb gebruikt om de verschillende stuurcodes te achterhalen. Mogelijk is dat precies hetzelfde als wat jij ook al had gevonden maar wellicht bevat het net iets meer informatie. Als ik het me goed herinner werkte bij mij de 0x00 0xA0 code niet.
Jup, ik heb ook exact dit document, deze aanstuurcode werkt helaas bij mij ook niet. Hoe dan ook wil ik nu jouw oplossing met de relay gaan proberen, omdat ik inderdaad ook denk dat het niet bepaald bevorderend is voor de EEPROM op de unit..
Past mooi naast de bestaande printplaat op de onderplaat van de WHR. Voeding krijgt deze ESP rechtstreeks van de laagspanningskant van de (Meanwell) voeding van de WHR.
Dat is helemaal top dan! Dit klinkt als een veel betere optie. Ik heb het schema van de WHR91B hier (pagina 19): https://cdn.webshopapp.co...nhoek-r-vent-whr90-91.pdf

Zou je kunnen markeren waar ik precies de relay tussen moet stoppen? En vanwaar jij de ESP voedt? Dan weet ik in ieder geval zeker dat ik het juiste doe, wat iemand anders al getest en bewezen heeft.

Ik zat verder nog in het schema te kijken en ik zie dat A (bedieningspaneel) meerdere lijnen naar de regelprint heeft, mogelijk dat één van deze signalen dus de toevoer aan/uit knop is, en dan kan ik daar ook gewoon een relay tussen hangen? Weet alleen nog niet echt hoe ik er een relay tussen moet hangen, tenminste ik heb wel een idee, maar of dat veilig is weet ik niet!

Acties:
  • +1 Henk 'm!

  • FAQ
  • Registratie: April 2003
  • Niet online

FAQ

Hi Niels, bij F kun je het relais aansluiten, als je in- en uitschakelvertraging op 0 zit reageert de unit direct. Ik heb volgens mij de stroom (12V) afgetapt op G

Acties:
  • 0 Henk 'm!

  • Niels Niels
  • Registratie: Juli 2016
  • Laatst online: 07-07 21:28
FAQ schreef op zondag 6 juli 2025 @ 23:11:
Hi Niels, bij F kun je het relais aansluiten, als je in- en uitschakelvertraging op 0 zit reageert de unit direct. Ik heb volgens mij de stroom (12V) afgetapt op G
Top! Dat maakt het wel heel makkelijk dan! Drie kleine vraagjes:

1. Als je de F benut overruled die denk ik je 1,2,3 standen, wat doe je als je router/WiFi/HA eruit ligt en je hebt hoogstand aangezet, hoe zet jij dit dan uit, of is het dan gewoon pech?

2. De RS485 poort (G) had ik al eens eerder doorgemeten, en het viel mij op dat er bij mij 17V op stond. Weet niet of jij dit ook had? Hoe dan ook ga ik hem denk ik hier ook op aftappen en dan gewoon met een buck converter naar de ESP32!

3. En tot slot, is hoogstand gewoon stand je eigen stand 3 (dat die daarmee nog wel instelbaar is), of is het een vaste waarde van bijv. altijd 100%.

Acties:
  • +1 Henk 'm!

  • FAQ
  • Registratie: April 2003
  • Niet online

FAQ

Niels Niels schreef op maandag 7 juli 2025 @ 17:44:
[...]


Top! Dat maakt het wel heel makkelijk dan! Drie kleine vraagjes:

1. Als je de F benut overruled die denk ik je 1,2,3 standen, wat doe je als je router/WiFi/HA eruit ligt en je hebt hoogstand aangezet, hoe zet jij dit dan uit, of is het dan gewoon pech?
Als je de contacten van F doorverbind, zet je stand 3 aan. Eventueel kun je deze ook weer rechtstreeks op de esp uitzetten (softwarematig of door even de esp op nieuw op te starten of in het ergste geval door een van de draden los te halen.
2. De RS485 poort (G) had ik al eens eerder doorgemeten, en het viel mij op dat er bij mij 17V op stond. Weet niet of jij dit ook had? Hoe dan ook ga ik hem denk ik hier ook op aftappen en dan gewoon met een buck converter naar de ESP32!
De esp waar ik naar verwijs slikt volgens mij tussen de 5v en 24v als ingangsspanning.
3. En tot slot, is hoogstand gewoon stand je eigen stand 3 (dat die daarmee nog wel instelbaar is), of is het een vaste waarde van bijv. altijd 100%.
Hoogstand is inderdaad de eigen stand 3 (maar deze overrulet inderdaad wel de standenschakelaar al deze op 1 of 2 staat).
Pagina: 1