Aurea 5 hybrid: interfaces met de buitenunit en thermostaat

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • Freeckje
  • Registratie: Juni 2019
  • Laatst online: 16:46
Op het forum zie je veel duscussies over de matige werking van de Aurea warmtepomp m.n. het onbekende en onverwachte regelgedrag. Aangezien ik belangstelling heb om deze WP aan te schaffen, zou ik graag met de techneuten onder elkaar, erachter zien te komen hoe het technisch ontwerp in elkaar steekt. Dus geen discussies over het regeldrag van de individuele gevallen maar puur bedoeld om kennis uit te wisselen mbt de technische implementatie. Mogelijk kan met die kennis de controlbox incl Anna en plugwise vervangen worden door een oplossing zoals bv https://diyless.com/product/esp8266-opentherm-gateway.
Vragen:
1: Hoe is de interface tussen de controlbox en de buitenunit geimplementeerd? Via een relais aan/uit aansturing of digitale powerline-interface?
2: Hoe meet de controlbox de buitentemperatuur? Rechtstreeks via de buiteninit, of misschien via het internet?
3: Hoe worden de aanvoer en retourtemperatuur gemeten, via de CV-ketel of mogelijk via de buitenunit?
4: Gesteld dat je een aan-uit CV-ketel hebt: zijn dan de aanvoer- en retourtemperatuur ook zichtbaar?
5: De foto van de controlbox geeft onvoldoende inzicht welke componenten deze bevat, kan iemand de typenummers van de IC's noteren? Is het huidige ontwerp mogelijk gebaseerd op de welbekende Opentherm-gateway (Schelte Bron) :) ?

[ Voor 126% gewijzigd door Freeckje op 02-12-2023 11:27 ]

NH, vrijstaand huis 70m2 bg vvw/40m2 boven radiatoren, Gas 2023:1500m3, 2024:200m3, Quatt 4kw, TS:DIY-Arduino, 6400 Wp panelen/SolarEdge.


Acties:
  • 0 Henk 'm!

  • mhr-zip
  • Registratie: Januari 2001
  • Laatst online: 21:57
Volgens mij praat de thermostaat opentherm met de controlbox, waarna de controlbox er iets van maakt.

Probleem in mijn ogen zit in die controlbox en niet in de thermostaat. En ja, met de thermostaat manipuleren kan je hem misschien iets meer laten doen wat jij wil ipv dat hij een eigen leven lijdt.


(Kan de retoursensor niet op je aanvoerleiding, maar dan een paar meter verder dat je wel verschil meet, maar dat hij niet steeds uit springt).

Zijn vijftig was nog schoon. Drinkwaterkaart.nl Drinkwaterspots


Acties:
  • 0 Henk 'm!

  • Freeckje
  • Registratie: Juni 2019
  • Laatst online: 16:46
Helemaal mee eens, mijn gedachte is om de gevraagde aanvoertemperatuur te laten afhangen van de actuele aanvoertemp. Dus de gevraagde aanvoer-temp zolang mogelijk binnen de 5 graden houden.
De Anna heeft zijn eigen regelstrategie die negeer je hiermee.

Wat de controlbox nog meer doet weet ik niet, maar met bovenstaande los je m.i. de belangrijkste klacht op n.l. het niet of te laat opstarten of te vroeg stoppen van de WP.

"(Kan de retoursensor niet op je aanvoerleiding, maar dan een paar meter verder dat je wel verschil meet, maar dat hij niet steeds uit springt)."
Ik heb zelf geen Aurea, dus ik snap niet precies wat je bedoelt... Worden de aanvoer en retourtemperaturen dan niet door je CV gemeten, of krijg je die van de WP zelf? Zie mijn vragenlijstje.

[ Voor 30% gewijzigd door Freeckje op 04-12-2023 16:17 ]

NH, vrijstaand huis 70m2 bg vvw/40m2 boven radiatoren, Gas 2023:1500m3, 2024:200m3, Quatt 4kw, TS:DIY-Arduino, 6400 Wp panelen/SolarEdge.


Acties:
  • 0 Henk 'm!

  • Freeckje
  • Registratie: Juni 2019
  • Laatst online: 16:46
"(Kan de retoursensor niet op je aanvoerleiding, maar dan een paar meter verder dat je wel verschil meet, maar dat hij niet steeds uit springt)."
Als deze sensoren zich in de wp bevinden, dan verstoor je daarmee mogelijk de regeling cq de vermogenstoevoer van de compressor!?

De controlbox regelt toch op het verschil tussen gevraagde (via Anna) en geleverde aanvoertemperatuur? De controlbox zet de Wp aan of uit...Dat staat toch los van de retoursensor?

NH, vrijstaand huis 70m2 bg vvw/40m2 boven radiatoren, Gas 2023:1500m3, 2024:200m3, Quatt 4kw, TS:DIY-Arduino, 6400 Wp panelen/SolarEdge.


Acties:
  • 0 Henk 'm!

  • JJvdB
  • Registratie: December 2022
  • Laatst online: 07-10-2024
dit zou m.i. ook de oplossing kunnen zijn. Het binnen 5 graden houden van het verschil tussen gevraagde temp en geleverde temp. Enige dat je dan niet kun beinvloeden is het <5gr buitentemp setpoint. Dat is wel door sommigen opgelost door manipulatie buitenvoeler. Echter, weet niet of dat de regelstrategie van de warmtepomp zelf (compressor, expansieventiel, etc) (negatief) beinvloedt.

Acties:
  • 0 Henk 'm!

  • Freeckje
  • Registratie: Juni 2019
  • Laatst online: 16:46
Ik heb geen idee wie de <5gr buitentemp-regel echt aanstuurt, de WP zelf intern , of de Controlbox.
Heb jij een idee hoe dat zit?
Zie parameters op je display die persee door de WP aangeleverd worden? Bv de retour- en aanvoertemperatuur.. of komt die data van de CV via de OT-kabel?
Is er een vorm van digitale communicatie tussen de Wp en Controlbox, en welke standaard gebruiken ze daarvoor?

NH, vrijstaand huis 70m2 bg vvw/40m2 boven radiatoren, Gas 2023:1500m3, 2024:200m3, Quatt 4kw, TS:DIY-Arduino, 6400 Wp panelen/SolarEdge.


Acties:
  • 0 Henk 'm!

  • Freeckje
  • Registratie: Juni 2019
  • Laatst online: 16:46
Ik bedacht me later dat toen ik het hydraulisch schema van de installatie bekeek, dat de aanvoertemperatuur waarschijnlijk niet door CV via OT wordt aangeleverd omdat dat circuit in WP-bedrijf door een keerklep losstaat van de WP-aanvoer.

Kan jij obv wat je op de printplaat van de controlbox ziet qua componenten en de layout, uitmaken of er een vorm van digitale communicatie is tussen de Controlbox en de WP is?

NH, vrijstaand huis 70m2 bg vvw/40m2 boven radiatoren, Gas 2023:1500m3, 2024:200m3, Quatt 4kw, TS:DIY-Arduino, 6400 Wp panelen/SolarEdge.


  • JJvdB
  • Registratie: December 2022
  • Laatst online: 07-10-2024
controlbox bepaalt keuze voor WP of Ketel op basis van een aantal parameters zoals buitentemp, verschil setpoit en aanvoer voor bepaalde tijd en geeft dus signaal aan ketel of warmtepomp. Signaal moet dus digtaal zijn om die info van de warmtepomp te verkrijgen.

  • JJvdB
  • Registratie: December 2022
  • Laatst online: 07-10-2024
kan een foto maken van controlbox maar die staat ook in manual. Misschien niet duidelijk genoeg:
https://atlanticclimate.nl/download/2946/

  • Freeckje
  • Registratie: Juni 2019
  • Laatst online: 16:46
Die foto is onduidelijk. Ik zou op die foto op basis van de componenten eventueel kunnen ontwaren of er digitale communicatie is.
Qua bekabeling tussen wp en controlbox (los van 220volt en aarde) zie je maar 1 ader lopen. Is het dan toch power line- of draadloze communicatie, geen idee.
Mijn hoop was dat die ene ader een relaisaansturing was die de wp alleen maar aan- of uitzet. Als er sprake is van digitale communicatie wordt het vervangen van de controlbox door een eigen controller een onhaalbare zaak.

Blijft over of om de Anna te vervangen door een eigenbouw thermostaat, of er een gateway tussen te zetten die de gevraagde aanvoertemperatuur overneemt van de Anna.
En tevens die buitensensor te beinvloeden. (Kan ook eventueel door een programmeerbare weerstand ;) )

Het blijft klooien in de marge ben ik bang. En als je tevreden bent hoe de wp het nu doet, gewoon laten draaien.

NH, vrijstaand huis 70m2 bg vvw/40m2 boven radiatoren, Gas 2023:1500m3, 2024:200m3, Quatt 4kw, TS:DIY-Arduino, 6400 Wp panelen/SolarEdge.


Acties:
  • 0 Henk 'm!

  • Freeckje
  • Registratie: Juni 2019
  • Laatst online: 16:46
Antwoord van Aurea:
"De warmtepomp wordt via een eigen modulerend protocol aangestuurd door de controlbox. Via de protocol wordt ook de aanvoer- en retourtemperatuur (via de sensoren van het buitendeel) uitgelezen en OT teruggekoppeld van de controlbox naar Anna zodat die in de metingen zichtbaar zijn."
Vertrouwende u hiermee voldoende te hebben geïnformeerd.
Met vriendelijke groeten – Best regards,
Simon Reijmes
Aftersales Engineer - Groupe Atlantic Nederland
T: +31 (0) 318 544 705 / +31 (0) 318 544 691"
service.nl@groupe-atlantic.com / sreijmes@groupe-atlantic.com

Helder verhaal: Daar kom je dus niet eenvoudig tussen. Blijft over het geleidelijk aansturen van de gevraagde aanvoertemperatuur van het cv-water. En beinvloeden buitentemperatuur sensor.

DRAADJE wat mij betreft gesloten.

[ Voor 6% gewijzigd door Freeckje op 15-12-2023 16:27 ]

NH, vrijstaand huis 70m2 bg vvw/40m2 boven radiatoren, Gas 2023:1500m3, 2024:200m3, Quatt 4kw, TS:DIY-Arduino, 6400 Wp panelen/SolarEdge.


Acties:
  • +3 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
Ik zou een poging willen maken dit draadje weer op te pakken.

Sinds een week heb ik een 2de hands Aurea in gebruik en laat ik voorzichtig zeggen: er is ruimte voor verbetering bij de besturing zoals velen al hebben opgemerkt.

Ik doe al jarenlang simpele elektronica dingen, waaronder Arduino en zo, maar een expert ben ik absoluut niet (om te verwachtingen wat te temperen 8) ).

De besturing in de cotrol box is op basis van twee ouderwetse maar doodnormale microcontrollers, nl. de Atmel Atmega8L uit 2003 op 8 MHz en met 8 kB flash geheugen. Dit is een zwakkere broeder van degene die in een van de eerste Arduino’s zit (Atmega 168 met 16k flash geheugen en tot max. 20 MHz). Ze lijken pin compatibel, dus bv. met de Arduino IDE een alternatief programma maken, dat in een Atmega 168 of 328 zetten en vervolgens de chip(s) in de control box omwisselen lijkt geen enkel probleem. Dan kan de rest van de control box gewoon worden gebruikt. Nog mooier is natuurlijk iets met ESPhome en Over-The-Air updates.

Ik heb de printplaat gedeeltelijk gereversed engineered. Als ik een keer tijd heb, zal ik m'n schetsje in een schema omzetten, maar vooralsnog gaat het om de communicatie. Observaties:
  • De aansluitingen gemarkeerd met Smile en Anna, en die bij ‘emergency’ zijn 1-op-1 doorverbonden zonder verbinding met de rest van de elektronica.
  • De communicatie met buitenunit verloopt via de ‘N’ en ‘S’ draden die naar twee afzonderlijke optocouplers gaan (zal wel voor veiligheid zijn)
  • De N-draad lijkt de verbinding van de buitenunit naar de control box. Deze komt op pin 27 van de linker MCU (IC 1) terecht
  • De S-draad lijkt zend-draad van de control box naar de buitenunit. Deze komt van pin 26 van de linker MCU
  • De rechter MCU (IC2) is via een dubbele optocoupler met de Opentherm in- en uitgang verbonden. Hier is pin 11 de zender en 5 de ontvanger
  • Beide MCU communiceren met elkaar via pin 2 --> 3, en 3 --> 2. Dit loopt langs de soldeerpads PAD1, PAD2, resp. PAD3 is de ‘0’ (of massa).
  • De reset knop geeft een reset signaal naar beide microcontroller (pin 1)
  • Verder zitten beide MCU’s met pin 25 aan elkaar
  • IC1 neemt is ook aangesloten op de schakelaar en bestuurt de LED en vermoedelijk ook nog 1 of meerdere relais. Dat stuk heb ik verder niet in detail uitgezocht
  • Dus IC2 lijkt primair de Opentherm communicatie te doen, en IC2 de rest
Nu de communicatielijnen bekend zijn, is de volgende stap om te gaan kijken wat voor signalen daar zijn. Ik heb m’n eenvoudige logic analyser (Labnation Smartscope) aangesloten en ben eens gaan kijken.

Afbeeldingslocatie: https://tweakers.net/i/UAER6hskZ86RP7cl4qIlrcT2wW0=/800x/filters:strip_exif()/f/image/EbRwKvycpn1GUpgMfklEc1NH.png?f=fotoalbum_large

Dat gaf meteen een mooi beeld:

Afbeeldingslocatie: https://tweakers.net/i/5uvamkA6ChLp3trXSOzcXRnfOT0=/800x/filters:strip_exif()/f/image/lbyHENxgufNIMKc4XoKUrRGx.png?f=fotoalbum_large
Kanaal 0 (IC 1 Pin 3, IC2 pin 2) en 1 (IC 1 Pin 2, IC2 pin 3: Communicatie tussen beide MCU’s
Kanaal 2: Pin 25 van beide MCU’s
Kanaal 3: IC1, pin 27: Signaal uit buitenunit
Kanaal 4: IC1, pin 26: Signaal naar buitenunit
Kanaal 5 en 6: Opentherm in en uit. Voorlopig niet interessant.
Dus de MCU wisselen iedere 5 s data uit. De buitenunit praat een stuk meer dan de control box. Er lijkt geen communicatie op de pinnen 25.

Hier een detail van de communicatie tussen de MCU’s:
Afbeeldingslocatie: https://tweakers.net/i/JcrmzSlDqDft2mVwTYLberUUCDc=/800x/filters:strip_exif()/f/image/7I6o2jXzO0YsOkpU0c81CkdW.png?f=fotoalbum_large
En van communicatie met buitenunit (3 & 4) en met Opentherm (5 en 6):
Afbeeldingslocatie: https://tweakers.net/i/Qmi2iGynR4sLkaJWYA-nztGgoHI=/800x/filters:strip_exif()/f/image/PYgmejysppIEfXvmPnUoOZI8.png?f=fotoalbum_large
Meest interessant is wat de control box naar de buitenunit stuurt. Hier is een compleet ‘gesprekje’ van 433 mS:
Afbeeldingslocatie: https://tweakers.net/i/1I6y1bpEU71jwkPwTn_bEB2VNSQ=/800x/filters:strip_exif()/f/image/58io4Qz8erOESmkEzyR3EmYQ.png?f=fotoalbum_large
Het lijkt erop dat de buitenunit (3) begint, en dat de control box dan antwoord. Dit antwoord wordt 1-op-1 teruggestuurd door de buitenunit. Daarna volgt een kleine pauze, en komt de buitenunit met data. Hierin zullen de water- en buitentemperaturen zitten.
Aanzetten van UART of serieel op kanaal 5 en 6 leverde voor mij niets bruikbaars op.

Nu komen we wel aan de grenzen van mijn kennis 😉

Op basis van de pulsgrootte van 1.6 ms heb ik afgeschat dat de baudrate wel eens 590 bit/s zou kunnen zijn. Gek getal, want dat wordt normaal niet gebruikt en ontstaat ook niet door 8 MHz steeds door 2 te delen.
Vervolgens heb ik een CH340 printje (serieel naar USB) met kanaal 4 verbonden. Met 590 baud, 7 databits en 1 stopbit kreeg ik ‘data’ binnen in de vorm van een repeterend ‘ZaJ i] R M’ terwijl de buitenunit uit stond. Ging die aan, dan veranderde alleen de laatste letter ‘M’ naar 6b, %e of nog iets anders(?!). Er leek een goede relatie tussen die code en het opgenomen vermogen. Dus ik vermoed dat het laatste deel van het signaal de vermogensvraag naar de buitenunit is. Eigenlijk is dat het enige dat ons interesseert.

Hier houdt mijn huidige kennis helaas op. Als iemand weet hoe dit verder te analyseren houd ik me aanbevolen!

Het ziet er naar uit dat alleen het uitgaande signaal hoeft worden gemanipuleerd op het moment dat de buitenunit aangeeft klaar voor ontvangst te zijn. Het zijn maar kleine bursts aan data, dus iemand met verstand van zaken heeft waarschijnlijk zo achterhaald, hoe dit protocol in elkaar steekt.

Acties:
  • 0 Henk 'm!

  • Freeckje
  • Registratie: Juni 2019
  • Laatst online: 16:46
WackoH schreef op maandag 16 december 2024 @ 14:35:
Ik zou een poging willen maken dit draadje weer op te pakken.

Sinds een week heb ik een 2de hands Aurea in gebruik en laat ik voorzichtig zeggen: er is ruimte voor verbetering bij de besturing zoals velen al hebben opgemerkt.

Ik doe al jarenlang simpele elektronica dingen, waaronder Arduino en zo, maar een expert ben ik absoluut niet (om te verwachtingen wat te temperen 8) ).

De besturing in de cotrol box is op basis van twee ouderwetse maar doodnormale microcontrollers, nl. de Atmel Atmega8L uit 2003 op 8 MHz en met 8 kB flash geheugen. Dit is een zwakkere broeder van degene die in een van de eerste Arduino’s zit (Atmega 168 met 16k flash geheugen en tot max. 20 MHz). Ze lijken pin compatibel, dus bv. met de Arduino IDE een alternatief programma maken, dat in een Atmega 168 of 328 zetten en vervolgens de chip(s) in de control box omwisselen lijkt geen enkel probleem. Dan kan de rest van de control box gewoon worden gebruikt. Nog mooier is natuurlijk iets met ESPhome en Over-The-Air updates.

Ik heb de printplaat gedeeltelijk gereversed engineered. Als ik een keer tijd heb, zal ik m'n schetsje in een schema omzetten, maar vooralsnog gaat het om de communicatie. Observaties:
  • De aansluitingen gemarkeerd met Smile en Anna, en die bij ‘emergency’ zijn 1-op-1 doorverbonden zonder verbinding met de rest van de elektronica.
  • De communicatie met buitenunit verloopt via de ‘N’ en ‘S’ draden die naar twee afzonderlijke optocouplers gaan (zal wel voor veiligheid zijn)
  • De N-draad lijkt de verbinding van de buitenunit naar de control box. Deze komt op pin 27 van de linker MCU (IC 1) terecht
  • De S-draad lijkt zend-draad van de control box naar de buitenunit. Deze komt van pin 26 van de linker MCU
  • De rechter MCU (IC2) is via een dubbele optocoupler met de Opentherm in- en uitgang verbonden. Hier is pin 11 de zender en 5 de ontvanger
  • Beide MCU communiceren met elkaar via pin 2 --> 3, en 3 --> 2. Dit loopt langs de soldeerpads PAD1, PAD2, resp. PAD3 is de ‘0’ (of massa).
  • De reset knop geeft een reset signaal naar beide microcontroller (pin 1)
  • Verder zitten beide MCU’s met pin 25 aan elkaar
  • IC1 neemt is ook aangesloten op de schakelaar en bestuurt de LED en vermoedelijk ook nog 1 of meerdere relais. Dat stuk heb ik verder niet in detail uitgezocht
  • Dus IC2 lijkt primair de Opentherm communicatie te doen, en IC2 de rest
Nu de communicatielijnen bekend zijn, is de volgende stap om te gaan kijken wat voor signalen daar zijn. Ik heb m’n eenvoudige logic analyser (Labnation Smartscope) aangesloten en ben eens gaan kijken.

[Afbeelding]

Dat gaf meteen een mooi beeld:

[Afbeelding]
Kanaal 0 (IC 1 Pin 3, IC2 pin 2) en 1 (IC 1 Pin 2, IC2 pin 3: Communicatie tussen beide MCU’s
Kanaal 2: Pin 25 van beide MCU’s
Kanaal 3: IC1, pin 27: Signaal uit buitenunit
Kanaal 4: IC1, pin 26: Signaal naar buitenunit
Kanaal 5 en 6: Opentherm in en uit. Voorlopig niet interessant.
Dus de MCU wisselen iedere 5 s data uit. De buitenunit praat een stuk meer dan de control box. Er lijkt geen communicatie op de pinnen 25.

Hier een detail van de communicatie tussen de MCU’s:
[Afbeelding]
En van communicatie met buitenunit (3 & 4) en met Opentherm (5 en 6):
[Afbeelding]
Meest interessant is wat de control box naar de buitenunit stuurt. Hier is een compleet ‘gesprekje’ van 433 mS:
[Afbeelding]
Het lijkt erop dat de buitenunit (3) begint, en dat de control box dan antwoord. Dit antwoord wordt 1-op-1 teruggestuurd door de buitenunit. Daarna volgt een kleine pauze, en komt de buitenunit met data. Hierin zullen de water- en buitentemperaturen zitten.
Aanzetten van UART of serieel op kanaal 5 en 6 leverde voor mij niets bruikbaars op.

Nu komen we wel aan de grenzen van mijn kennis 😉

Op basis van de pulsgrootte van 1.6 ms heb ik afgeschat dat de baudrate wel eens 590 bit/s zou kunnen zijn. Gek getal, want dat wordt normaal niet gebruikt en ontstaat ook niet door 8 MHz steeds door 2 te delen.
Vervolgens heb ik een CH340 printje (serieel naar USB) met kanaal 4 verbonden. Met 590 baud, 7 databits en 1 stopbit kreeg ik ‘data’ binnen in de vorm van een repeterend ‘ZaJ i] R M’ terwijl de buitenunit uit stond. Ging die aan, dan veranderde alleen de laatste letter ‘M’ naar 6b, %e of nog iets anders(?!). Er leek een goede relatie tussen die code en het opgenomen vermogen. Dus ik vermoed dat het laatste deel van het signaal de vermogensvraag naar de buitenunit is. Eigenlijk is dat het enige dat ons interesseert.

Hier houdt mijn huidige kennis helaas op. Als iemand weet hoe dit verder te analyseren houd ik me aanbevolen!

Het ziet er naar uit dat alleen het uitgaande signaal hoeft worden gemanipuleerd op het moment dat de buitenunit aangeeft klaar voor ontvangst te zijn. Het zijn maar kleine bursts aan data, dus iemand met verstand van zaken heeft waarschijnlijk zo achterhaald, hoe dit protocol in elkaar steekt.
Leuk dat je onderwerp weer oppikt en er zoveel energie insteekt. Ik heb sinds begin dit jaar een Quatt aangeschaft, en ben daar tevreden over. Ook daar lopen discussies over uitlezen en aansturen van de wp. De communicatie loopt over een standaard modbus protocol en is inmiddels gedecodeerd.
Vraag: uit het eerdere antwoord van de importeur is sprake van powerline communicatie. Zie je componenten die dat verzorgen via de 220 Vac lijn? Tsja, en als dat vervolgens een proprietry protocol is.....dan kan dat decoderen een lastige klus worden.

NH, vrijstaand huis 70m2 bg vvw/40m2 boven radiatoren, Gas 2023:1500m3, 2024:200m3, Quatt 4kw, TS:DIY-Arduino, 6400 Wp panelen/SolarEdge.


Acties:
  • +1 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
In het bericht van de importeur/leverancier zie ik geen vermelding naar 'powerline communicatie'.

Dat stukje heb ik wel uitgezocht maar is hier niet relevant omdat er optocouplers (2x H11D1 om precies te zijn) tussen dat deel, en het laagspanningsdeel met de mirocontrollers zitten,

Hier op hoofdlijnen hoe de print is onderverdeeld (tot hoever ik het heb uitgezocht). In de groene ovalen de optocouplers die hoogspanning (of 18V in geval van OpenTherm) van de rest scheiden):
Afbeeldingslocatie: https://tweakers.net/i/L4me7XQ7a4HFmnLn6TbhyX5aSjM=/800x/filters:strip_exif()/f/image/1eXgMeAOP1sG8b7b6hYFn8hB.png?f=fotoalbum_large

Rond de chips is het gewoon 5V en hele lage snelheid. Niets spannends.

Overigens zag ik dat de normale, uitgebreide, uitvoering van de buitenunit ook Modbus heeft. Dus best een kans dat deze communicatie dat ook is. Wat waarom zouden ze speciaal voor deze toepassing iets nieuws bedenken?

Modbus RTU is volgens Wikipedia de meest gebruikte seriele implementatie van Modbus. Iedere byte (8 bits) zou moeten bestaan uit 11 bits:
  • 1 start bit
  • 8 bit data/message, least significant bit sent first
  • 1 bit parity
  • 1 stop bit
Ga ik dat maar eens proberen :*)

Acties:
  • 0 Henk 'm!

  • Freeckje
  • Registratie: Juni 2019
  • Laatst online: 16:46
Ja, niet letterlijk maar wel: "De warmtepomp wordt via een eigen modulerend protocol aangestuurd door de controlbox." Jij noemt dat "hoogspanningcommunicatie". Dat is powerline communicatie...daar draait het nu juist wel om, om dat uit te vogelen. Die modbus aansluiting is bedoeld voor een handbedieningsunit.

NH, vrijstaand huis 70m2 bg vvw/40m2 boven radiatoren, Gas 2023:1500m3, 2024:200m3, Quatt 4kw, TS:DIY-Arduino, 6400 Wp panelen/SolarEdge.


Acties:
  • 0 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
De communicatie naar de buitenunit geeft mooie consistente data als ik de logic analyser op meest gebruikte '8N1' instel (dus 8 bits, geen parity en 1 stopbit) Er worden dan berichten met 8 en 12 bytes zichtbaar (de ene laat ik in decimaal zien, de andere in hexadecimaal):
Afbeeldingslocatie: https://tweakers.net/i/yeWMB5leceY2_V7Hc835vC2mQ7g=/800x/filters:strip_exif()/f/image/8lvXZw5wcRWW3vnkDnHTAaXg.png?f=fotoalbum_large
Afbeeldingslocatie: https://tweakers.net/i/ROdct-GymgBH0bjBg-qa0V914FQ=/fit-in/4000x4000/filters:no_upscale():strip_exif()/f/image/JmmG8iAXdxDGwGPIIDqj7O6U.png?f=user_large
ASCI is het helaas niet want die omzetting geeft onzin (vandaar dat de directe aansluiting op een UART-naar-USB geen goede resultaten gaf).

De data vanuit de buitenunit bevat gegevens die we elders kunnen zien zoals drie temperaturen. Helaas gaat decoderen van die data niet zo makkelijk vanwege die langere start-puls aan het begin. Ik zou hiervoor de decoder moeten aanpassen.

We bedoelen natuurlijk hetzelfde maar 'Powerline communicatie' klinkt nogal intimiderend. Strikt genomen klopt het ook niet helemaal omdat er namelijk omdat de communicatie deels over een eigen draad (gemerkt 'S' = signal?) loopt. De andere loopt over de nul ('N'). De hardware implementatie van de communicaitie is verder ook niet relevant.

Acties:
  • 0 Henk 'm!

  • Freeckje
  • Registratie: Juni 2019
  • Laatst online: 16:46
WackoH schreef op dinsdag 17 december 2024 @ 10:42:
De communicatie naar de buitenunit geeft mooie consistente data als ik de logic analyser op meest gebruikte '8N1' instel (dus 8 bits, geen parity en 1 stopbit) Er worden dan berichten met 8 en 12 bytes zichtbaar (de ene laat ik in decimaal zien, de andere in hexadecimaal):
[Afbeelding]
[Afbeelding]
ASCI is het helaas niet want die omzetting geeft onzin (vandaar dat de directe aansluiting op een UART-naar-USB geen goede resultaten gaf).

De data vanuit de buitenunit bevat gegevens die we elders kunnen zien zoals drie temperaturen. Helaas gaat decoderen van die data niet zo makkelijk vanwege die langere start-puls aan het begin. Ik zou hiervoor de decoder moeten aanpassen.

We bedoelen natuurlijk hetzelfde maar 'Powerline communicatie' klinkt nogal intimiderend. Strikt genomen klopt het ook niet helemaal omdat er namelijk omdat de communicatie deels over een eigen draad (gemerkt 'S' = signal?) loopt. De andere loopt over de nul ('N'). De hardware implementatie van de communicaitie is verder ook niet relevant.
Het kon zijn dat die powerline communicatie op een standaard gebaseerd was en dat je op basis daarvan de data zou kunnen decoderen. Maar Aurea heeft de data versleuteld. Ik kan je hiermee helaas niet verder helpen aangezien ik deze wp niet heb.

NH, vrijstaand huis 70m2 bg vvw/40m2 boven radiatoren, Gas 2023:1500m3, 2024:200m3, Quatt 4kw, TS:DIY-Arduino, 6400 Wp panelen/SolarEdge.


Acties:
  • 0 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
Snap ik, verwaht ik ook niet en geeft niet

Ik kan met niet voorstellen dat de data is versleuteld. Waarom zouden ze dat doen? Erg spannend voor derden is de communicatie niet.

Mooiste zou zijn om het protocol te snappen zodat gericht opdrachten naar de buitenunit kunnen worden verstuurd.
Maar als dat niet lukt, is een alternatief wellicht sequenties kopieren waarbij de WP de gewenste dingen deed en die via een Arduino of zo 'afspelen' wanneer nodig.
Volgens week ga ik eens wat bouwen om ik de data in een computer te krijg zodat ik meer data heb om te vergelijken.

Acties:
  • 0 Henk 'm!

  • Freeckje
  • Registratie: Juni 2019
  • Laatst online: 16:46
Misschien heb je hier nog wat aan:
https://github.com/aerona-chofu-ashp/modbus

NH, vrijstaand huis 70m2 bg vvw/40m2 boven radiatoren, Gas 2023:1500m3, 2024:200m3, Quatt 4kw, TS:DIY-Arduino, 6400 Wp panelen/SolarEdge.


Acties:
  • 0 Henk 'm!

  • Candymirror
  • Registratie: November 2003
  • Laatst online: 20:20
@WackoH Ik ben niet volledig thuis in alle atmega varianten, maar volgens mij valt serieel en modbus af. Volgens mij ben je voor modbus een onderliggende seriele poort nodig. aangezien de rx/tx poorten van de atmega8 al in gebruik zijn voor de onderlinge communicatie tussen beide atmega's, blijft er dan alleen nog softserial over en die kan voor rx alleen op pd2 of pd3 (pin 4 of 5) bij een atmega8.

Wellicht is het het 1-wire protocol of een variant hiervan. Je zou het eens door sigrok kunnen proberen te halen.

Als laatste zou er nog gekeken kunnen worden of het flash lock bit staat op de atmega, grote kans van wel, maar niet geschoten altijd mis. Staat het lock bit niet, dan zou dump door een disasembler gegooid kunnen worden.

BTW ik heb deze warmtepomp gisteren via marktplaats kunnen overnemen voor een leuk bedrag. Zodra die bij mij aangesloten is en draait zal ik ook eens kijken of er wat valt te ontdekken.

[ Voor 11% gewijzigd door Candymirror op 07-01-2025 22:44 ]


Acties:
  • +2 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
@Candymirror Leuk dat je hier ook meen aan de gang wilt gaan!
Uitlezen van de code heb ik overwogen, maar wat ik er over heb gelezen is het disassambeleren niet eenvoudig.
In de tussentijd ben ik een stuk verder gekomen, maar nog net niet tot het punt dat ik de warmtepomp kan aansturen.
Op dit moment ben ik in het buitenland (vandaar de rare tijd van reageren; het is hier ochtend). Later deze week reis ik terug en zal ik uitgebreider reageren. Maar misschien herken je de protocol opzet.

Even uit m'n hoofd:
  • De communicatie vindt plaats met ~560 bps en is geen ModBus
  • Iedere 500 ms initieert de warmtepomp (WP) communicatie dmv een puls van ~8ms.
  • Daarop reageert de controlbox met een bericht van een aantal bytes die vrijwel instantaan door de WP worden herhaalt
  • Na de 9 tot 12 bytes van de controlbox volgen nog een aantal bytes die de WP zendt (en die de controlbox niet direct herhaalt).
  • De communicatie duurt ruim 400 ms, gevolgd door een pauze om de 500ms vol te maken.
  • De controlbox zendt repeterend vier verschillende berichten
  • Er lijken maar vier bytes te veranderen in een van de vier berichten
  • Het eerste byte is een aan/uit signaal (draait de WP dan is deze '1', anders '0')
  • De tweede lijkt de vermogensvraag te zijn: Deze varieert tussen de 1 (opgenomen elektrisch vermogen ~240W) en 12 (~1800W)
  • Van de 3de en 4de bytes weet ik niet wat die doen. Ik hoop niet dat dat een checksum of zo is.
Met een Arduino Mega2560 ben ik bezig een 'Proof of concept' te maken om te kijken of ik de WP met die eerste twee bytes aan kan sturen.

Hiervoor heb ik een aftap op het het signaal van de ontvangende optocoupler naar de linker ATMega gemaakt, en heb het signaalpad van de naar de zendende optocoupler onderbroken zodat ik daar zelf een signaal op kan zetten.

Ik ben al eind met de code maar heb het helaas voor mijn vertrek nog niet kunnen testen.

Als dit gaat, is volgende stap om het om te zetten naar ESPhome. Mijn is uiteindelijk doel is om via Home Assistant de WP aan te sturen zoals ik dat wil, gebruik makend van de info die via de Plugwise integratie binnenkomt. Voor de info vanuit de WP (watertemperaturen en buitentemperatuur) blijf ik dan de controlbox gebruiken. Ik kan eenvoudig voorkomen dat de CV aan gaat door deze op alleen sanitair warm water te zetten.

Acties:
  • +1 Henk 'm!

  • dingo35
  • Registratie: Februari 2008
  • Laatst online: 16:53
Die laatste zin maakt dat je oplossingsrichting voor de meesten van ons niet bruikbaar is; bij mijn Nefit ketels kun je alleen de tapwater voorziening uitzetten, niet het CV gedeelte.

Terwijl dat nu juist is wat de meesten hier willen: ook bij lage buitentemperaturen op de WP stoken ipv de CVketel.

Acties:
  • 0 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
Die laatste zin was meer een losse opmerking die ook niet echt werkt; het voorkomt weliswaar dat de CV gaan verwarmen maar de hybride box zet de buitenunit wel een uur uit.

Voorkomen dat de buitenunit op een lage buitentemperatuur uit gaat is het makkelijkste te voorkomen door de externe temperatuursensor van de buitenunit in het deel boven de compressor te leggen.

Dat heb ik nu ruim twee weken geleden gedaan en de unit gaat niet meer op buitentemperatuur uit. Bij -2oC vannacht is de buitenunit blijven draaien. De defrosts lijken er niet onder te lijden:
Afbeeldingslocatie: https://tweakers.net/i/2wGv2KG9pBa5iJnfEt_U6dk2wwc=/800x/filters:strip_exif()/f/image/aCUrGdSzIxN5VdVWcbHSJh4z.png?f=fotoalbum_large
Ik heb weer thuis en heb nu bij drie defrosts gekeken. Die lijken gewoon prima op het juiste moment te komen, dus als de verdamper begint dicht te slibben met ijs (rijp),

Maar dat 'delta_T 5oC'-probleem blijft.
De unit heeft twee weken gedraaid met een aanvoertemperatuur installing van 35oC (wat dan in het echt 31oC wordt :? ). Maar toen ik er 1 graad bij deed (dus 36oC) schakelde de hybride box een paar defrosts later toch de buiteunit uit. :X

Nieuwere iteraties van de software in de hybride box lijken wel beter. Dus eigenlijk zou Atlantic chips met geupdate software met de wensen van hun klanten tegen onkosten beschikbaar moeten stellen, want zij hebben een halfbakken product geleverd.

Er zijn verschillende oplossingsrichtingen. Eerst maar het proof-of-principle voor elkaar krijgen.

Acties:
  • 0 Henk 'm!

  • wybe-V
  • Registratie: Augustus 2023
  • Laatst online: 06-07 21:07
dingo35 schreef op zaterdag 11 januari 2025 @ 09:45:
Die laatste zin maakt dat je oplossingsrichting voor de meesten van ons niet bruikbaar is; bij mijn Nefit ketels kun je alleen de tapwater voorziening uitzetten, niet het CV gedeelte.

Terwijl dat nu juist is wat de meesten hier willen: ook bij lage buitentemperaturen op de WP stoken ipv de CVketel.
Ik heb de cv kachel niet aan gesloten op de controle box maar de eigen thermostaat terug gezet zodat ik zel kan bepalen af ik bepaal af ik de cv aan wil zetten

Acties:
  • +1 Henk 'm!

  • mhr-zip
  • Registratie: Januari 2001
  • Laatst online: 21:57
wybe-V schreef op zaterdag 11 januari 2025 @ 14:55:
[...]

Ik heb de cv kachel niet aan gesloten op de controle box maar de eigen thermostaat terug gezet zodat ik zel kan bepalen af ik bepaal af ik de cv aan wil zetten
Hier heb ik een paar vragen over als het mag.
- Je hebt geen CV aangesloten op je controlbox. Werkt de WP dan gewoon, of heb je iets moeten omzeilen?

-Op een gegeven moment gaat de controlbox de CV aan proberen te sturen, wat niet gaat. Wat gebeurt er dan? X tijd niks en dat dat de WP weer bijschakelt of?

Zijn vijftig was nog schoon. Drinkwaterkaart.nl Drinkwaterspots


Acties:
  • 0 Henk 'm!

  • wybe-V
  • Registratie: Augustus 2023
  • Laatst online: 06-07 21:07
mhr-zip schreef op zaterdag 11 januari 2025 @ 16:55:
[...]


Hier heb ik een paar vragen over als het mag.
- Je hebt geen CV aangesloten op je controlbox. Werkt de WP dan gewoon, of heb je iets moeten omzeilen?

-Op een gegeven moment gaat de controlbox de CV aan proberen te sturen, wat niet gaat. Wat gebeurt er dan? X tijd niks en dat dat de WP weer bijschakelt of?
Heb de CV-uitgang van de controle box vast gesoldeerd op het reset knopje van de controle box
Als de wp wil overschakelen naar de cv wordt er een reset gegeven en gaat de wp weer starten voor 20 min op ca 700watt
Mogelijk komt na deze reset de delta-T wel binnen de 5 graden en gaat de wp hierna naar een hoger vermogen

Acties:
  • +4 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
Ik had nog belooft met meer info over de werking van de control box te komen. Bij deze een lange post...

Inleiding

Het hybride warmtepomp systeem Aurea van Atlantic bestaat uit verschillende delen:
  • Een Chofu AEYC-0643XU buitenunit met versimpelde (elektrische) aansluitingen ten opzichte van de basis versie. Dit is een al wat ouder model maar ‘made in Japan’. Dus wellicht niet de stilste en meest efficiënte, maar betrouwbaar en zonder kinderziekten.
  • Een control box besturing unit die Atlantic vermoedelijk zelf heeft (laten) ontwikkelen. Dit is veruit de zwakste schakel in het systeem door wat onhandige ontwerpkeuzes, en lijdend voorwerp van dit topic.
  • Een Plugwise Anna thermostaat met Smile gateway. Wat mij betreft een typische ‘slimme thermostaat’ die voor het doel voldoet.
  • Installatiemateriaal met terugslagkleppen, vuilfilter, etc. De charme van dit systeem is de de relatief eenvoudige installatie. Installatie kostte me in m’n eentje een weekend.

Werking van de control box

Hardware
De control box bevat een printplaat met twee oude en eenvoudige Atmel microcontrollers waarop het controle systeem is gebaseerd. De een (IC1) doet de communicatie met de buitenunit, de ander (IC2) het Opentherm stuk. Ze communiceren ook met elkaar om data tussen Opentherm en buitenunit uit te wisselen. Ik heb al eerder het een en ander over de hardware geschreven en foto’s gepost. Zie hier en hier.
Lang verhaal kort. In de basis voldoet de hardware prima, het is alleen een verouderde en te simpele basis want er is geen software update mogelijk zonder chips te wisselen, en bv. dip switches om de gebruiker keuzes over gedrag van de warmtepomp te laten maken, zijn er niet (met uitzondering van aan/uit of Opentherm thermostaat).
De aansluiting waarmee communicatie met de buitenunit verloopt is apart. Je zou iets van RS485 als hardware met ModBus als protocol verwachten (dat ondersteunt de buitenunit ook in de standaarduitvoering!). Het is echter een combinatie van stroomvoorziening vanuit de buitenunit naar de control box en communicatie.
Fysiek is de verbinding met vier draden: Fase, Nul, Signaal en Aarde. Fase en nul voorzien de unit van stroom, en via een wat bijzondere manier (rood en zwart banaan chassisdeel op de printplaat voor een USB adapter) ook de Smile gateway en Anna thermostaat. De communicatie verloopt via de nul- (van buitenunit naar control box) en signaaldraad (control box naar buitenunit). Wat hier precies gebeurt heb ik verder niet bekeken, anders dan dat een multimeter aangaf dat spanningen enkele tientalen volt heen en weer sprongen. Daarom durfde ik m’n eenvoudige USB oscilloscoop met max. +/- 35V hier niet op aan te sluiten.
Dat was ook niet nodig want de Atmel microcontrollers werken op 5V en de omzetting naar deze spanning zit natuurlijk gewoon op de printplaat. Ik heb van dat deel een schema gemaakt. Helaas nog steeds in potlood-uitvoering dus ik zal het hier nog niet posten. Hieronder voor de volledigheid nog wel een keer de foto van de print:

Afbeeldingslocatie: https://tweakers.net/i/L4me7XQ7a4HFmnLn6TbhyX5aSjM=/800x/filters:strip_exif()/f/image/1eXgMeAOP1sG8b7b6hYFn8hB.png?f=fotoalbum_large

De signalen van en naar de buitenunit worden met behulp van twee optocouplers (H11D1) gescheiden van netspanning. Degene met het signaal van de buitenunit (OK1) is via een 4.7 kOhm weerstand (R9) met IC1 verbonden, en dat IC stuurt zo’n opdrachten via een 1 kOhm weerstand (R14), een BC547 transistor (T2) en een 330 Ohm weesrtand (R10) naar de andere optocoupler (OK2).
Hier een overzicht van de aansluitingen op de microcontrollers:

PinIC1IC2
1ResetReset
2IC2 pin3IC1 pin3
3IC2 pin2IC1 pin2
4NCNC
5NCOpentherm in
6NCNC
7VCCVCC
8GNDGND
9X-talX-tal
10X-talX-tal
11NCOpentherm uit
12NCNC
13NCGND
14LED 1NC
15NCNC
16Relais 1Relais 1
17NCNC
18Schakelaar-LEDNC
19NCNC
20SchakelaarVCC
21SchakelaarVCC
22GNDNC
23SchakelaarNC
24NCNC
25IC2 25IC1 25
26WP in (OK2)NC
27WP uit (OK1)Schakelaar-LED
28NCNC

Primair van belang zijn pin 26 en 27 van IC1 die bij resp. weerstand R14 (1 kOhm) en R9 (4.7 kOhm) zijn af te tappen omdat hiermee de communicatie met de buitenunit verloopt. Primair heb ik me hier op gericht om het protocol in kaart te gaan brengen. De communicatie tussen beide IC’s verloopt langs PAD1 (IC1 pin 3 naar IC2 pin 2) , PAD2 (pin 2 → 3) en PAD3 (GND).
Software
De software van de control box zit ‘geflashed’ in beide microcontrollers. In beginsel kan dit worden uitgelezen. Maar dat lijkt me een tamelijk kansloze missie. Het lock bit kan actief zijn waardoor uitlezen wordt geblokkeerd. Maar als uitlezen wel mogelijk, is doorgronden en aanpassen van het programma vermoedelijk erg moeilijk. We weten bv. al niet welke ontwikkelsoftware is gebruikt (logisch zou de ontwikkelsoftware van Atmel, nog onderdeel van Microchip, zijn). Gezien de taken (vier communicatielijnen managen) van deze relatief zwakke chips (ze draaien maar op 8 MHz en hebben slechts 8 kB flash geheugen, 512 bytes EEPROM en 1 kB RAM) is er ook een kans dat er complexe low level code is gebruikt.
Protocol
Ik heb dus wel de communicatie met een eenvoudige logic analyser bekeken. Dat geeft dit overzichtsbeeld. Signaal 1 en 2 is de communicatie tussen beide microcontroller. Die sturen dus iedere 5 s een bericht van een paar bytes. Er lijkt verder geen bijzonder afstemming te zijn.
Signaal 3 is de communicatie van de buitenunit (warmtepomp, WP) naar control box, en 4 is die in omgekeerde richting. Dit is op basis van een wat geavanceerder protocol:
Afbeeldingslocatie: https://tweakers.net/i/_Z-rMg2qvqCgj986WqhMZb-5yow=/800x/filters:strip_exif()/f/image/9TvyoRO4EzOiPWgWOvTs8dvV.png?f=fotoalbum_large
Hier nog wat verder ingezoomd:

Afbeeldingslocatie: https://tweakers.net/i/z8cAtbdOWyO7FZdzDjOXHQopzUk=/800x/filters:strip_exif()/f/image/YZOgW3HneltPu36lXDJXpZ7E.png?f=fotoalbum_large
En nog wat verder (nu met 5 en 6 de inter-IC communicatie):
Afbeeldingslocatie: https://tweakers.net/i/fwjshKGWvYw-ONBocSpxz0Pe2jE=/800x/filters:strip_exif()/f/image/oYLuftB0db72eF41dugr1IUA.png?f=fotoalbum_large
Protocol opbouw
Dit is wat ik tot dusverre weet over dat protocol:

  • Bits duren 1.5 ms. Dat betekent dat de communicatie op een ongebruikelijke 666 bps verloopt.
  • Nemen we de control box (CB) als centrale eenheid dan is CB naar de warmtepomp (WP) ‘transmit (Tx), en WP → CB ‘receive’ (Rx).
  • Uitgangspositie is dat beide datalijnen laag zijn.
  • CB maakt Tx hoog
  • WP reageert hier op door Rx hoog te maken
  • 8 ms later begint de CB op Tx te zenden, dit wordt vrijwel direct door de WP op Rx teruggestuurd, wellicht om controle mogelijk te maken.
  • De opbouw van het bericht van CB naar WP is voor zover ik kan nagaan als volgt:
  • ◦ Eerste byte is altijd 19 hexadecimaal (25 in decimaal),
  • ◦ Tweede is een volgnummer (0, 1, 2, of 3).
  • ◦ Derde byte is de lengte van het totale bericht van de CB: Resp. 8, 12, 8 en 8 bytes
  • ◦ Dan volgt de inhoud van 3 of 7 bytes
  • ◦ Als laatste vermoedelijk twee bytes met een checksum
  • Als de CB na 8 of 12 bytes klaar is, gaat de BU door met zenden op de Rx waarbij zaken als temperatuur aan- en afvoer en buitentemperatuur overgedragen zullen worden. Dit gebeurt overigens niet als de checksum van de CB communicatie niet klopt!
  • De opbouw van het bericht van WP naar CB is:
  • ◦ Byte 1: Startnummer (91 in hexadecimaal, dus 145 in decimaal)
  • ◦ Byte 2: Volgnummer (achtereenvolgens 1, 0, 2 en 3)
  • ◦ Byte 3: Lengte met resp. 13, 12, 18 en 20 bytes
  • ◦ Inhoud van 8, 7, 13 en 15 bytes
  • ◦ Laatste twee bytes vermoedelijk weer de checksum
  • ◦ Als laatste nog een byte met ‘0’(die niet meetelt voor de lengte
  • Vervolgens stuurt de CB weer het eerste bericht.
  • De pauze tussen de berichten is steeds 98~100 ms. Omdat de berichten verschillende lengtes hebben, betekent dit dat de start niet met een vast interval is. Het lijkt er meer op dat de CB steeds wacht op een hoog worden van de Rx en dan na 8 ms gaat zenden.
Ik heb niet gevonden waar dit soort communicatie nu vandaan komt, behalve dat het lijkt op het RFC 916: Reliable Asynchronous Transfer Protocol (RATP) wat terug gaat tot tenminste 1984(!).
Protocol inhoud
Dan de inhoud. Dit zijn voorbeelden van de vier berichten die de CB heeft verstuurt. In blauw en rood getallen die ik heb zie veranderen. In decimaal is het:
Afbeeldingslocatie: https://tweakers.net/i/xbiUEsP0hhfd3hpN1UMrXMc9_SA=/fit-in/4000x4000/filters:no_upscale():strip_exif()/f/image/Pd2fXTcWymE4PSIbHCa0qNr4.png?f=user_large

Het 4de en 5de byte van bericht ‘2’ zijn cruciaal. Byte 4 is 0 (uit) of 1 (aan) en byte 5 geeft het gevraagde vermogen, vermoedelijk tussen 0 en 12. Het is een lineaire lijn:
Afbeeldingslocatie: https://tweakers.net/i/xlknbl1HlPYk_VbgEJIn5LgYv7E=/fit-in/4000x4000/filters:no_upscale():strip_exif()/f/image/A0SVg9KKzc4hPFZnC2LUOCoq.png?f=user_large

Verder heb ik tot dusverre geen andere getallen van CB naar WP gezien. Dat betekent dat de WP aansturen best eenvoudig is; alleen deze twee bytes hoeven te worden aangepast.

De WP stuurt meer terug. In decimaal:
Afbeeldingslocatie: https://tweakers.net/i/08jzdCXNa4IbxzRjzR0_ahzl_go=/fit-in/4000x4000/filters:no_upscale():strip_exif()/f/image/IPo4qBY0XBNfl12ouDHWmSFK.png?f=user_large
Ook hier weer in blauw getallen die veranderden. Dat zijn er dus best veel en vooralsnog heb ik geen idee wat het is. Het zijn geen voor de hand liggende getallen die bv. een watertemperatuur vertegenwoordigen.

Lastig tot dusverre is dat het me niet is gelukt te achterhalen hoe de checksum wordt berekend. Ideeën zijn welkom!
Alternatieve aansturing
Wat ik nu heb gedaan is een Arduino Mega2560 koppelen aan de communicatie tussen CB en WP. Het signaal vanaf de de WP wordt afgetapt, dat naar de WP heb ik onderbroken door 1 kant van R14 los te halen en hier voed ik een seriële uitgang van de Arduino op in.

Wat het programma nu op hoofdlijnen doet:
  • Tenminste 80 ms wachten nadat WP Rx laag heeft gemaakt, tot deze 7.7 ms hoog is geweest (dit komt nauw)
  • Dan beginnen met het versturen van het eerste bericht. Dit heb ik gewoon gekopieerd zoals ik het heb opgevangen
  • Vervolgens weer 80 ms bij Rx is laag wachten tot Rx 7.7 ms hoog is geworden. Dan tweede bericht sturen
  • Zelfde bij derde bericht, behalve dat ik hier bytes 4 en 5 vervang door aan/uit en het gewenste vermogen. Op dit moment kan ik dat lokaal met een rotary decoder instellen en aflezen met een OLED schermpje. Ik maak een voorselectie tussen 0 en 10 en activeer die dan door de draaiknop in te drukken.
  • Als laatste het 4de bericht.
Verder heb ik al voorbereid om tegelijkertijd op een andere seriële poort Rx uit te lezen. Maar hier doe ik nog even niets mee.

Hoe verder?

Doel 1 is bereikt: Ik kan zelf beslissen met hoeveel vermogen de warmtepomp werkt. Alleen zal de CB nu nog wel de CV aan gaan sturen. Dan heb ik in mijn geval voorkomen door deze op alleen sanitair te zetten.

Uiteindelijk is een soort ‘man-in-the-middle’ nodig die de berichten over en weer ontvangt, daar waar nodig aanpast en doorstuurt
Verder ontrafelen van het protocol tussen CB en WP gaat echter nog best wat moeite kosten: Wat is wat en hoe wordt die checksum berekend.

Makkelijker kan zijn om die andere communicatie te manipuleren, nl. die tussen beide microcontrollers. Hier komt de interessant info als watertemperatuur en wellicht ook de buitentemperatuur voorbij. We weten op dit moment echter niet welke van de twee beslist of de CV of de WP wordt gebruikt.
Afluisteren van de communicatie is eenvoudig. Toen ik de print er toch uit had, heb ik alvast pinnen in de pads gesoldeerd. Maar ingrijpen op de communicatie vereist doorkrassen van printsporen.
De communicatie zelf lijkt een heel stuk eenvoudiger. In detail heb ik er nog niet naar gekeken. Maar iedere 5 s gaan er van het ene IC een paar bytes naar de ander, en een paar tienden later de andere kant. Zo op het eerste gezicht veel eenvoudiger dan wat er naar de WP gaat.

Wordt vervolgd...

Acties:
  • 0 Henk 'm!

  • Freeckje
  • Registratie: Juni 2019
  • Laatst online: 16:46
Prima werk hoor, ik had het bijltje er al lang bij neergegooid...
Ik zou als ik jou was, na het decoderen van het communicatie protocol met de buitenunit, die controlbox met aanverwante kastjes, vervangen door een esp32 , met een opentherm master en slave interface. Heb ik zelf als opentherm gateway in gebruik.

NH, vrijstaand huis 70m2 bg vvw/40m2 boven radiatoren, Gas 2023:1500m3, 2024:200m3, Quatt 4kw, TS:DIY-Arduino, 6400 Wp panelen/SolarEdge.


Acties:
  • 0 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
Ik heb nog naar de communicatie tussen de microcontrollers gekeken door eerst met een logic analyzer verbonden met Pad 1 en 2 op de printplaat de bit tijdsduur te vinden, en vervolgens met een serieel naar USB omzetter te zien of ik logica in de getallen kon ontdekken.

De communicatie is, zoals al voor de hand lag, een stuk eenvoudiger.

Een bit duurt ~100us wat dus betekent dat de communicatie op een standaard 9600 bps werkt.

De berichten die over en weer worden gestuurd bevatten grotendeels dezelfde informatie en zijn steeds 12 bytes lang. De laatste byte is de checksum. Deze is de rest van de som van de voorgaande 11 bytes gedeeld door 256.

Ik heb de functie van de 12 bytes voor een redelijk deel kunnen achterhalen. Verdere suggesties zijn welkom:
  1. Doeltemperatuur vanuit thermostaat. Wordt 240 op pad 2 als de WP uit moet (door te lage buitentemperatuur bv.)
  2. ? Meestal 20, maar ook 23 gezien
  3. ? Meestal 179, maar 77 als het 2de byte 23 is
  4. Reserve?, want altijd 0
  5. Reserve?, want altijd 0
  6. Vermoedelijk temperatuur setpoint naar WP (want paar oC lager dan byte 1 zoals we in de praktijk ook zien. Deze wordt op Pad 2 0 als de WP uit gaat
  7. ? 0 en 14 gezien. Is er een link met byte 11 want als byte 6 0 wordt, wordt byte 11 vaak 1
  8. WP: Aanvoer temperatuur
  9. WP: Retour temperatuur
  10. WP: Buitentemperatuur
  11. ? Wordt 1 als byte 7 0 is
  12. Checksum
Byte 1 op Pad 2 wordt 240 als de WP uit moet, op Pad 1 blijft die de doeltemperatuur aangeven. Dus vermoedelijk loopt via Pad 1 de communicatie van IC2 (OT) naar IC1 (WP), en via Pad 2 die in omgekeerde richting.

De microcontroller voor de WP (IC1) lijkt de WP uit te zetten, en de OT microcontroller (IC2) daarvan via getal '240' (F0 in hex) 'op de hoogte te stellen'. Vermoedelijk zit in IC1 dan ook die 'delta 5oC' check op aanvoer en retour.

Als gevolg hiervan zal het via ingrijpen in de relatief eenvoudige commnicatie tussen IC1 en IC2 niet lukken om het vervelende gedrag van de control box te voorkomen.

Acties:
  • +2 Henk 'm!

  • maanlander69
  • Registratie: December 2023
  • Laatst online: 04-09 09:14
Protocol CB <-> buitenunit:
De laatste 2 bytes corresponderen met CRC-CCITT (0xFFFF).

Voorbeeld 1:
25 3 8 178 2 0 193 154 (dec)
omzetten naar
19 03 08 B2 02 00 C1 9A (hex)

https://www.lammertbies.nl/nl/comm/info/crc-calculation
Als je bericht 19 03 08 B2 02 00 invult in de calculator (invoer type hex aanvinken) geeft dit C1 9A als uitkomst.

Voorbeeld 2:
Werking als boven
25 1 12 0 0 0 0 0 0 0 170 53 (dec)
19 01 0C 00 00 00 00 00 00 00 AA 35 (hex)

[ Voor 5% gewijzigd door maanlander69 op 01-02-2025 16:11 ]


Acties:
  • +4 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
Bedankt! Het klopt idd.

Ik ben nu deze opzet aan het testen:
Afbeeldingslocatie: https://tweakers.net/i/Whh3yrdri-KhTv1mDyWspS80Na4=/800x/filters:strip_exif()/f/image/DOAPVBtoT2fP2VM4ITPRR1GQ.png?f=fotoalbum_large
  • Aftappen van de output van de OT microcontroller om daaruit het aanvoer setpoint te halen (op basis van wat Anna thermostaat vraagt)
  • Afvangen van de communicatie van WP naar de OT microcontroller voor aanvoer, retour temperatuur en buitentemperatuur.
  • Aangepast bericht van WP naar OT microcontroller maken zodat CV en warmtepomp niet tegelijk gaan werken, maar ook dat de CV het van de warmtepomp overneemt bij een bepaalde buitentemperatuur (-2 in mijn geval)
  • Aangepast bericht van control box naar buitenunit maken met een vermogensvraag op basis van een PID regelaar die met de gewenste temperatuur en gemeten aanvoer temperatuur wordt gevoed.
Maar nu we weten hoe de checksum wordt bepaald, zijn we weer een stapje verder om de warmtepomp MCU door een eigen controller te vervangen. Dan hoeft er niet meer aan de print gesoldeerd te worden.

Een eigen Atmel ATmega328P zou kunnen want de MCU zit gelukking in een voetje en die is pin compatibel.

Omdat van de 28 pinnen maar 11 voor signaaloverdracht gebruikt, waaronder een 2 voor LEDS (kan worden gemist) en 1 voor een relais, is ook een mogelijkheid om een ESP32 te nemen en die jumpers met dupont connectoren te verbinden. Zo komen over-the-air updates en verdere integrate in Home Assistant in bereik.

Maar dan moeten we wel gaan snappen wat er precies in de communicatie tussen buitenunit en control box zit.

Acties:
  • +2 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
Met dat laatste maak ik ook stapjes.

Metingen die in handleidingen van Atlantic en Chofu worden genoemd als waardes die display van buitenunit kan laten zien (en dus ook worden doorgegeven?!) zijn:
Afbeeldingslocatie: https://tweakers.net/i/UGFENRzvncbKNPaj1sNrKua5itg=/fit-in/4000x4000/filters:no_upscale():strip_exif()/f/image/ZKezjjFdoYvkPiVzcoKhd0iR.png?f=user_large

Op basis van het bovenstaande lijkt het me waarschijnlijk dat het setpoint naar de buitenunit (1~10) naar een werkfrequentie voor de compressor (d1) wordt vertaald.

Als ik naar data kijk, correleert bv. het 4de byte van het 4de bericht van de buitenunit heel sterk met het vermogen dat ik extern heb gemeten (correlatiecoefficient 0.99!) --> zal dus wel d3 zijn.
Als ik hier het verbruik van de besturing (~10W) en fan & pomp (~60 W) vanaf haal
De relatie (trendlijn) is: P_compressor [W] = 15 * (M4B3) - 350
(M = Message, B = Byte, 1ste byte is Byte 0. )
De hellingscoefficient (15) wijst in de richting van hexadecimaal (grondgetal 16).
Kan ook goed zijn dat zowel byte 3 als 4 het vermogen representeren. Alleen als ik dat omzet naar decimaal en deel door 16, kom ik wel een paar onderd watt te hoog uit .
Dus nog genoeg te doen

Acties:
  • +2 Henk 'm!

  • Grannetia
  • Registratie: Februari 2025
  • Laatst online: 17-06 15:45
Wauw!
Ik ben hard op zoek naar een manier om deze warmtepomp te laten doen wat ik wil, ipv wat de regeling zelf wil 😅. Stuit ik ineens op dit toch wel zeer actuele en interessante draadje.
Momenteel werk ik met een home assistant scripts geïnspireerd op deze https://community.home-as...tpump-controller/795111/2 om de warmtevraag van Anna zo te manipuleren dat de warmtepomp aan blijft en niet de ketel het niet te pas en te onpas overneemt. Voor mijn woonsituatie is de 5kw warmtepomp misschien iets aan de kleine kant, waardoor het echt een uitdaging is om de gewenste aanvoer te halen binnen de door de regeling gestelde tijd. Zeker aangezien de regeling ook nog bij een lage aanvoer temperatuur, het eerste kwartier blijft hangen rond 600watt :? Ik had in home assistant inmiddels een scriptje gemaakt om de maximale gevraagde aanvoer bij te stellen tot maximaal 4,5° boven de huidige aanvoer. Dit had tot gevolg dat Anna helemaal geen warmtevraag meer kon plaatsen wanneer mijn aanvoer te laag was na een lange tussenpauze in de warmtevraag (ik werk met een buffervat dat ik na een warmtevraag oplaad en leegtrek alvorens een nieuwe warmtevraag te plaatsen. Dit om mooie lange runs te krijgen). Enfin, hier ook weer iets voor verzonnen, de regeling mag nu zn gang gaan gedurende de eerste 15minuten en pas daarna ga ik de maximale aanvoer aanpassen. Ik verwacht begin volgende week de OpenTherm gateway om daarmee de gevraagde aanvoer temperatuur te kunnen manipuleren ipv deze te begrenzen door middel van de maximale waarde aan te passen.
Echter wat jij aan het doen bent is wel echt next level. Althans voor mij in ieder geval. Super tof dat je dit doet en je bevindingen deelt. Als je er werkelijk in slaagd om een programma te maken welke op een chip te schrijven is die gewoon in de bestaande socket kan worden geprikt, hoop ik dat je deze ook wil delen :)
Thank you and keep the good work up _/-\o_

Acties:
  • +5 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
Ik heb meer tijd geinvesteerd om het protocol van de Chofu unit (warmtepomp) naar de conrtolbox verder te snappen. Hervoor heb ik data tijdens een defrost, met de buitentemperatuursensor in warm water, en tusen twee bevroren loempia's (moest toch wat om de temperatuur negatief te krijgen...). Hierdoor ben ik aardig opgeschoten. Eerst even een samenvatting van de basis:

De Chofu en control box communiceren via een Universal Asynchronous Receiver/Transmitter-protocol dat op enkele tientallen volts loopt. Hoeveel weet ik niet precies.
In de control box wordt dit naar 0~5 V omgezet. Dat heb ik afgetapt.

Een paar berichten eerder heb ik beschreven hoe de communicatie wordt geinitieerd. De Control box is blijkbaar zo geprogrammeerd dat die direct (in de praktijk na 8 ms) weer begin te zenden als de Chofu heeft aangegeven daar klaar voor te zijn (100 ms wachttijd).

Het blijkt echter niet nodig om op een puls vanuit de Chofu te wachten; de controller kan gewoon gaan zenden en de Chofu begin dan terug te sturen, mits de wachttijd van 100 ms wordt overschreden. Het interval tussen de berichten hoeft ook niet constant te zijn en lijkt naast het minimum van 100ms, ook rustig 50ms of langer te mogen zijn.
Positief gevolg is dat een puls-detectie niet nodig is. Gewoon iedere 600 ms een bericht sturen werkt ook,

Daarna verloopt de communicatie dus net als 'buiten' via een 'Universal Asynchronous Receiver/Transmitter' (UART) protocol met 666 bps (1.5 ms per bit), 8N1: 1 start bit, 8 data bits, geen parity en 1 stop bit.

Visuele opbouw van een bericht:
Afbeeldingslocatie: https://tweakers.net/i/0kGMkmeu9KWi2mPZEqF_Gjs-cs4=/800x/filters:strip_exif()/f/image/ayZ2M7vBIZ8ocWgBDDaGGW3m.png?f=fotoalbum_large


UnitIndentificatieBericht nummer Data lengtePayload
Chofu0x910x00~0x038, 12, 8 & 8 bytes incl. checksum3, 7, 3 & 3 bytes
Control box0x190x00~0x0312, 13, 18, 20 bytes, incl. checksum, inclusief 'stop 0'7, 8, 13 & 15 bytes

Checksum methode: CRC-CCITT (0xFFFF), aka CRC-16/IBM-3740 met poly 0x1021 en init 0xFFFF

Nu is wat ik toe nu heb kunnen bevestigen of vermoeden over de communicatie.
  • Die uit de controlbox is eenvoudig: Slechts 2 bytes (en 2 checksum bytes natuurlijk) heb ik zien veranderen.
  • Er komt echt een zee van dat uit de Chofu waarvan ik ~1/3 (de meest nuttige ook) vrijwel zeker weet. De codering hieronder is: Identifier (19 of 91), bericht nummer (0~3) en byte nummer in bericht (1st byte is '0'):
Control box to Chofu
19-0,3? 0x00, no change observed
19-0,4? 0x00, no change observed
19-0,5? 0x00, no change observed
19-1,3? 0x00, no change observed
19-1,4? 0x00, no change observed
19-1,5? 0x00, no change observed
19-1,6? 0x00, no change observed
19-1,7? 0x00, no change observed
19-1,8? 0x00, no change observed
19-1,9? 0x00, no change observed
19-2,3Compressor Off(0) - On(1)
19-2,4Compressor speed setpoint (0x00~0x0A)
19-2,5? 0x00, no change observed
19-3,3? 0xB2, no change observed
19-3,4? 0x02, no change observed
19-3,5? 0x00, no change observed


Chofu to controlbox
91-0,30x0D, no change observed
91-0,40x07, no change observed
91-0,50x04, no change observed
91-0,60x2F, no change observed
91-0,70x5A, no change observed
91-0,80xCE, no change observed
91-0,90xE1, no change observed
91-1,3??Mode of operation ? 0x40 = defrost, 0x50 = normal operation?
91-1,4Indicates defrost ongoing (0x00 Normal, 0x01 defrost)
91-1,5? 0x00, no change observed
91-1,6? 0x00, no change observed
91-1,7? 0x00, no change observed
91-1,8? 0x00, no change observed
91-1,9?Possibly compressor on (0x8A) and off (0x00). Is ~1 s ahaead of actual power registration
91-1,10??Possibly fan speed
91-2,3Return temperature Low byte
91-2,4Return temperature High byte
91-2,5Supply temperature Low byte
91-2,6Supply temperature High byte
91-2,7Outside temperature Low byte
91-2,8Outside temperature high byte
91-2,9Switch for alternating data (Either 0x00 or 0x01)
91-2,10Alternating data
91-2,11Alternating data. Probably defrost sensor (at bottom of evaporator)
91-2,12Alternating data. Possibly discharge temperature compressor
91-2,13Alternating data
91-2,14Alternating data. Possibly discharge temperature compressor
91-2,15Alternating data. Some relation with compressor
91-3,3?? Possibly LSB of pump speed?
91-3,4?? Possibly MSB of pump speed?
91-3,5Probably pump speed (hex2dec x10)
91-3,6?? Gas temperature or pressure??
91-3,7Has some relation with mode of unit.
91-3,80x00 or 0x01. Indicates defrost or switch for something?
91-3,9Compressor speed in % or Hz, conversion Hex2Dec
91-3,10Compressor power uptake in Watt, conversion (Hex2Dec*256/10)
91-3,110x02, no change observed
91-3,120x00, no change observed
91-3,130x18, no change observed
91-3,140x00, no change observed
91-3,150x00, no change observed
91-3,160x00, no change observed
91-3,170x00, no change observed


De instelling van het vermogen via vermoedelijk het toerentaal van compressor (in Hz of %) is niet triviaal. De Chofu heeft een eigen willetje...
Na opstart altijd 20 a 30 minuten zijn alleen stand 2 (~400 W) of 3 (~650W) mogelijk. Daarna kon ik tot stand 7 gaan, maar het laatste stap was maar ~100 W extra (iets is maximaal?) Ook wilde stand 1 (240W) niet, terwijl ik dat eerder 100% zeker heb gezien. Kan zijn dat de buitentemperatuur een rol speelt.

Temperaturen (voor zover tot nu heb gezien) worden als 2 bytes in ‘little endian’ notatie doorgegeven. Omrekening met 1 formule zodat negatieve temperatuur (in ieder geval relevant voor buitentemperatuur) correct wordt weergegeven, kan als volgt:
T [oC] = ((MSB*256)+LSB)-(65536*IF(MSB=255;1;0)))/10

Ergens zal ook ruimte zijn voor communicatie van foutmeldingen. Maar gelukkig heb ik die (nog) niet. Misschien ga ik die later eens induceren door foutmeldingen die in de handleidingen worden genoemd in relatie tot bv. temperatuur sensoren, op te roepen door bv. stekkertjes los te halen. Maar dat is ook wel wat griezelig want ik wil geen schade veroorzaken natuurlijk.

[ Voor 10% gewijzigd door WackoH op 06-02-2025 20:27 ]


Acties:
  • 0 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
Voro de aardigheid wat grafieken.

Vermogen en temperatuur tijdens een defrost:
Afbeeldingslocatie: https://tweakers.net/i/rxX0LfxVn-Ogp6gWymqNQMvNfZM=/800x/filters:strip_exif()/f/image/oSqh9SnfF2GPOLmPU7xEZECt.png?f=fotoalbum_large

Negatieve temperaturen:
Afbeeldingslocatie: https://tweakers.net/i/Yc_bqckseqp78OE3aJJsQ3-IAIY=/fit-in/4000x4000/filters:no_upscale():strip_exif()/f/image/BjRUc0uNap65066J7Hsxpdnc.png?f=user_large

Dan die gekke 'alternating data'. Byte 91-2,9 flipt steeds tussen '0' (2 berichten lang) en '1' (1 bericht lang). Vooral bij '0' laten bytes 91-2,10~15 data zien die wellicht ook iets met de gas temperaturen te maken heeft want ze veranderen tijdens de defrost. Maar wat het precies is...
Afbeeldingslocatie: https://tweakers.net/i/aXaFDQXJ7EfR3mIxOJSe6qDS1CI=/800x/filters:strip_exif()/f/image/yiRK0x7qJgbW7DEno3dsifo0.png?f=fotoalbum_large

Acties:
  • +1 Henk 'm!

  • Marc001
  • Registratie: Augustus 2022
  • Laatst online: 09-09 09:19
Hoop werk!
Tja en die loempia's.... _O-

Interessant om te lezen dat die 20min startup na lange stilstand dus een hard gegeven zijn.

Dat is wel vervelend omdat je eigenlijk meteen naar hogere vermogens wil.
Zal dit te maken hebben dat er minimaal een bepaalde gasdruk / temperatuur nodig is voordat hogere vermogens van de compressor gevraagd mogen worden.... om zo componenten van de Chofu te beschermen.

Acties:
  • 0 Henk 'm!

  • Azbest
  • Registratie: April 2000
  • Laatst online: 10-09 21:33
Dit is best wel een knap staaltje reverse engineering. Ik heb ook zo'n onding thuis en vroeg me in de eerste week al af of die controlbox ook kan vliegen. Ik probeer nu met een arduino op elektronisch niveau de boel te hacken. Huidige status is om met een digitale potentiometer de temperatuur te beïnvloeden.

Ondertussen zijn er ook gedachten om gewoon een VFD te pakken en de pomp en de fan rechtstreeks aan te sturen met een arduino. Punt is alleen dat ik te weinig weet van warmtepompen om zelf een programma te maken.

Acties:
  • +5 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
Ik heb in het begin een keer met een hamer in m'n hand gestaan om die controlbox van de muur te meppen... Gelukkig ben ik daarvoor behandeld (bij wijze van spreken dan ;) ) en heb die wraakgevoelens kunnen ombuigen naar een drang tot vreedzaam reverse engineeren...

Direct aansturen van fan of compressor is kansloos. En gelukking ook absoluut onnodig!
Die Chofy unit is prima en laat 'm maar lekker zijn (haar?) ding doen.

Beter de aansturing overnemen. Zo blijven defrosts en vorst- en andere beveiligingen gewoon functioneren.

De simpelste Arduino op 8 MHz (is op basis van zelfde microcontroller als in de controlbox...) doet dat met gemak! Het enige wat je moet doen is de juiste weerstand op de print aan 1 kant loshalen, en daar een (soft) serial van een Arduino op aansluiten, Dat is op mijn print (rev 1.00) R14 (1 kOhm). Dat doe je dan aan de kant van de pin 26 van de microcontroller zodat je via die R14 je Arduino op T2 aan kunt sluiten.

Je moet dan steeds vier berichten achter elkaar op 666 bps (met 100 ms tussenpozen maar dat komt niet kritisch kwam ik recent achter, en standaard 8N1 met je Arduino versturen.

Deze berichten bevatten dan:
byte data0[] = { 0x19, 0x0, 0x8, 0x0, 0x0, 0x0, 0xd9, 0xb5 };
byte data1[] = { 0x19, 0x1, 0x0c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xaa, 0x35 };
byte data2[] = { 0x19, 0x2, 0x8, 0x1, 0x1, 0x0, 0x99, 0x37 };
byte data3[] = { 0x19, 0x3, 0x8, 0xb2, 0x2, 0x0, 0xc1, 0x9a }

Het enige wat je moet doen om de Chofu te bedienen zijn bytes 4 en 5 van data 2 wijzigen (staan hierboven op 1 en 1;dat betekent 'aan' op stand '1' = ~240W, en de checksum (bytes 7 en 8) overeenkomstig aanpaasen.
Byte 4 is dus 0 (uit) of 1 (aan), bytes 5 is stad 0~10 waarbij stand 1 ~240 Watt is. Ruim 600 watt krijg je op stand 3.
Die bijborende checksum voor byte 7 en 8 staat hieronder als resp. checksum1[] en checksum2[] )

byte checksum1[] = { 0x9d, 0x99, 0xcc, 0xff, 0x66, 0x55, 0x00, 0x33, 0x23, 0x10, 0x45 };
byte checksum2[] = { 0x36, 0x37, 0x64, 0x55, 0xc2, 0xf3, 0xa0, 0x91, 0xaf, 0x9e, 0xcd };

Je kunt de checksum ook berekenen:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}


Ik heb een rotary switch als input op de Arduino gebruikt om de stand te selecteren. Maar dat kan natuurlijk op verschillende anderen manieren.

PS; De controlbox raakt hiervan wel een beetje in de stress want de signalen die van Chofu komen zijn niet meer in overeenstemming met wat er wordt heengestuurd. Maar 'who cares' 8) Het enige effect is dat je niet altijd meer de aanvoer en retour watertemperatuur met de Plugwise app kunt uitlezen.

Ik ga ook kijken wat ik met een ESP8266 of EPS32 kan doen zodat de unit ook via wifi is aan te sturen,

[ Voor 7% gewijzigd door WackoH op 14-02-2025 01:27 ]


Acties:
  • 0 Henk 'm!

  • wybe-V
  • Registratie: Augustus 2023
  • Laatst online: 06-07 21:07
https://durocan.com/downloads/?v=1a13105b7e4e
Dit vond ik nog op internet oa over de buiten unit
Staat ook nog een document bij met software
Mogelijk kan dit helpen voor aanpassen van de controle box 🤗

Acties:
  • 0 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
Bedankt. Ik heb de documenten doorgekeken maar helaas niet iets gevonden wat direct nuttig was.

Acties:
  • 0 Henk 'm!

  • P1 modubus
  • Registratie: Maart 2023
  • Laatst online: 01-09 13:38
Goedendag allen, heb de WP geïnstalleerd, deed zeker niet wat ik wilde, nadien meer info gezocht en ben zodoende ook bij jullie uitgekomen, zag ook bij de wp documentatie een modbus aansluiting, daar kon ik nog iets mee, maar het verhaal wat er verder werd geschreven kon ik het niet meer goed volgen, nu ik de wp een beetje begrijp heb ik op mijn kennis een oplossing gezocht, zover mogelijk bij het ordinaal gebleven.
De bijpas zo ingeregeld, bij normale flow van de installatie, dat deze op een delta T van 5 grd C draait, de cv ketel een Calenta ace 28C nog niet op de smile aangesloten, zal dit nog wel proberen na hier eerder het verhaal gehoord te hebben dat dit enkele malen geprobeerd moet worden. verder heb ik de weerstand van de buitenunit met 85 Mega-ohm kortgesloten( wel geschakeld)
zal dit nog wel aanpassen gaf bij een 11 grd buiten temperatuur 17 grd aan dat is voor mij te veel het moet ook rendabel blijven een COP van 2-2.5 moet het zeker hebben anders is bij mij het stoken op gas een goedkopere optie, maar dat de pomp blijft lopen dat stoort ook mag rustig tot o grd C uit blijven, zodoende zal ik wel op een weerstand van 200 M-ohm uitkomen bij vorst, voor de COP van 2.5 moet ik nog achterhalen, als dit ook bij 0-1 grd zit dan kan ik de 200 -ohm vast aansluiten, anders maak ik een schakeling dat deze waarde kortsluit met de buitentemperatuurweerstand.
Bij mij draait deze nu rustig en moduleert prima, zie plaatje. Heb vloerverwarming die zoals hier eerder geschreven moeilijk is te regelen zodat de binnen temperatuur mooi constant blijft, hier is het zo sla de vloer niet koud is, is het altijd aangenaam ook tussen de 18-21 grd, bij 22- 23 grd kan de vloer uit dan blijft het toch aangenaam.
Heb reeds gemaild naar de fabrikant WP als Plugwise, het regelgedrag dus het algoritme is geheim wordt niet vrijgegeven, maar het wordt steeds duidelijker dat het om zeer veel berekeningen en data gaat, te veel verschil in gewenst en gemeten ruimtetemperatuur gaat de ketel stoken, zal zeker een berekening zijn van de gewenste watertemperatuur.
Hier koelt het niet veel af dus het verschil is niet groot dat helpt zeker.
de wp loopt tot nu toe goed en constant. tot zover weer.

Acties:
  • 0 Henk 'm!

  • Marc001
  • Registratie: Augustus 2022
  • Laatst online: 09-09 09:19
@WackoH

Als jij via je Arduino na lange stilstand (meer dan 3 uur) de Chofu wil opstarten met meer dan 600W.
Is dit dan mogelijk? Of word je dan begrenst op die 600W?

Kortom is de 600W een begrenzing van de Controlbox of de begrenzing van de Chofu zelf?

Dank je.

Acties:
  • 0 Henk 'm!

  • P1 modubus
  • Registratie: Maart 2023
  • Laatst online: 01-09 13:38
De WarmtePomp is geen cv ketel die op elk moment aan en uit kan, een cv ketel start ook niet maximaal, een WP past het beste bij een vloerverwarming, het gaat allemaal iets rustiger aan toe, ook de ruimtetemperatuur laat je ook niet ver uit elkaar lopen, ik stook meestal niet meer na 20.00 uur, laat de binnen temperatuur middags iets oplopen, bij een buitentemperatuur >11 grd hoef morgens nog niet te stoken.
Heb ook gezien dat de WP, nadat de bypass is ingeregeld, ook op modulerend ongeveer op een Dt van 5 grd draait.
Heb een beeldplaatje geüpload, maar zie in deze niet terug, ben hier nieuw, kan me iemand schrijven hoe dit moet?

Tot nu gaat het een heel stuk beter, is wel jammer dat je er een studie van moet maken, de ketel koppelen Opentherm is nog niet gelukt,

Acties:
  • 0 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
Het is na stilstand van meerdere uren een begrenzing van de Chofu zelf

Toevallig gisteren nog gezien toen de buitenunit vanwege het warme langere tijd had had uitgestaan.

Ik weet niet bij welke tijdsduur van stilstand het omslagpunt ligt, en of de controlbox ook nog een beperking oplegt.

Het kan zijn dat de buitentemperatuur ook een rol speelt. Zo zag ik bij +10 wel weer dat de buiteunit op stand 1 (~240W) wil draaien, wat rond de 0oC niet ging (dan was het minimum stand 2, ~450W)

Acties:
  • 0 Henk 'm!

  • Marc001
  • Registratie: Augustus 2022
  • Laatst online: 09-09 09:19
P1 modubus schreef op maandag 24 februari 2025 @ 11:44:
De WarmtePomp is geen cv ketel die op elk moment aan en uit kan, een cv ketel start ook niet maximaal, een WP past het beste bij een vloerverwarming, het gaat allemaal iets rustiger aan toe, ook de ruimtetemperatuur laat je ook niet ver uit elkaar lopen, ik stook meestal niet meer na 20.00 uur, laat de binnen temperatuur middags iets oplopen, bij een buitentemperatuur >11 grd hoef morgens nog niet te stoken.
Heb ook gezien dat de WP, nadat de bypass is ingeregeld, ook op modulerend ongeveer op een Dt van 5 grd draait.
Heb een beeldplaatje geüpload, maar zie in deze niet terug, ben hier nieuw, kan me iemand schrijven hoe dit moet?

Tot nu gaat het een heel stuk beter, is wel jammer dat je er een studie van moet maken, de ketel koppelen Opentherm is nog niet gelukt,
Dat klopt niet helemaal, de Chofu kan wel degelijk in één keer vanuit stilstand naar zijn max van 2200W.
Dit is ook duidelijk te zien in homewizard en plugwise.

Echter wil hij niet naar vol vermogen bij langere stilstand, dan blijft hij hangen op 20min 600W.

Vooral na opstart in de nacht of een warme dag (WP uit) en einde dag wordt het kouder (WP aan).
Er wordt dan gestart met 600W 20min.
Ondertussen bouwt de Anna de setpoints op, gevolg is dat de WP niet kan bijbenen (blijft hangen op 600W).
Waardoor de ControlBox de opdracht aan de CV geeft.

Overigens is de bypass puur voor de vorst periodes.
Niet om je delta T naar 5 graden te krijgen.
Je verspilt onnodig opgewekte warmte van de WP rechtstreeks naar de retour.
Dat sommigen dit doen, kan, maar het is beter je systeem waterzijdig in te regelen.

@WackoH Thx :)

Acties:
  • 0 Henk 'm!

  • P1 modubus
  • Registratie: Maart 2023
  • Laatst online: 01-09 13:38
Afbeeldingslocatie: https://tweakers.net/i/XDCJcX4k2B2yr6v0T3lgOcGj-ek=/fit-in/4920x3264/filters:max_bytes(3145728):no_upscale():strip_icc():strip_exif()/f/image/OqMhFHmeuOrnENtlDyIRy1nQ.jpg?f=user_large
Het is zeker niet alleen voor de vorst periode, het is zeker dat je de installatie goed inregelt, als je op volle capaciteit1600 Watt draait met een Dt van 5-6 grd dan boet je zeker niet in, koelmachines draaien meestal met een Dt van 6 grd ook Remeha, alles draait perfect ook als het hydraulisch minder wordt dan zorgt de bypass voor geen geruis en een perfecte Dt, zag ook dan modulerend perfect gaat Dt blijft goed, de warmtepomp doet dit perfect, en gaat ook minder aan uit, tijdens het schrijven is de WP zelfs pas na 25 minuten van 600 Watt naar 1200 Watt gegaan 1ste opstart vandaag, wist niet dat dit alleen bij een grotere stilstand gebeurd.
Weet niet of het plaatje zichtbaar wordt, zie dit dadelijk.

Acties:
  • 0 Henk 'm!

  • P1 modubus
  • Registratie: Maart 2023
  • Laatst online: 01-09 13:38
Afbeeldingslocatie: https://tweakers.net/i/pfqm98QsDxp8G7H7G1S4MkLm6s8=/fit-in/4920x3264/filters:max_bytes(3145728):no_upscale():strip_icc():strip_exif()/f/image/29H5xCUM4cINXqja3BICrkBu.jpg?f=user_large
Zie nu het plaatje nu in deellast, je kunt de temperaturen erbij zien, ook de retour van de installatie, ook de Dt, vermogen in Amp en Watt meterstand is voor je niet interessant die gegevens komen van een goedgekeurde E-tussenmeter .

Acties:
  • 0 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
Die geregelde bypass is een mooie concept om de nukken van de combinatie warmtepomp en controlbox te omzeilen. Maar je moet het eigenlijk niet willen.

Met die extra bypass (intern is er ook al eentje) wordt een deel van het verwarmde water weer terug naar de warmtepomp gestuurd. Zo til je temperatuur van de aanvoer en retour omhoog. Hogere watertemperaturen betekenen volgens mij per definitie dat het rendement (COP) lager wordt.
Nu gaat het hier maar om een paar graden en voornamelijk tijdens de start dus verder geen ramp, maar verschil maakt het wel.

Verder lijkt me een delta_T van 5oC geen wet, maar een handige vuistregel. Het water gaat met een vast debiet rond (tenminste, ik heb nog niet gevonden dat de pomp bij deze unit actief wordt gemoduleerd, maar kan dat ook niet uitsluiten), terwijl de warmtepomp een sterk wisselende hoeveelheid warmte toevoegt. Dus dit betekent dan weer per definitie dat de delta_T tussen aanvoer en retour sterk varieert. Bijvoorbeeld in mijn geval tussen de 1oC bij minimum en 5oC bij maximum vermogen. Wat de aanvoer/retourtemperatuur dan wordt hangt af van het warmteafgifte systeem. Radiatoren geven minder efficient warmte af dan een vloerverwarming, dus in het eerste geval liggen de temperaturen hoger.

Ik zie geen enkele reden (behalve in het Aurea geval :X ) waarom je bewust het hele systeem minder efficient zou moeten gaan laten werken door de warmtepomp kort te sluiten om zo een delta_T van 5oC te bereiken. Het betektent daarnaast ook dat de verdeling door de rest van de installatie verstoort raakt omdat je daar nu wisselende hoeveelheden water heen gaat sturen.


Over de wisellend vermogen gesproken: Ik zie overigens duidelijk dat bij een vast uitsturing naar de Chofu, het opgenomen vermogen sterk afhangt van de retour/aanvoertemperatuur (hogere temperaturen --> hoger vermogen). Het gaat om tientallen procenten verschil. De reden lijkt me dat het meer moeite (groter drukverschil) kost om het koelmiddel een hogere tempeatuur op te laten wekken zodat de compressor meer vermogen op gaat nemen als die met een bepaald toerental draait.

Acties:
  • 0 Henk 'm!

  • P1 modubus
  • Registratie: Maart 2023
  • Laatst online: 01-09 13:38
Het is misschien een misverstand, de bypass is de meegeleverde bypass niet een extra, deze wordt ook niet steeds bijgeregeld, deze heb ik vast ingesteld zoals in de bijschrijving summier beschreven staat, alleen ik heb de mogelijkheid benut van een secuur systeem, om een dt van 5-6 grd te krijgen, bij een ingeregelde cv installatie dat hydraulisch bijna altijd het zelfde is of meer flow, je ziet ook aan de retour van de cv installatie maar weinig verschil, 1/2 grd C,
Ook werd er in dit platform eerder geschreven dat als het Dt te groot wordt dat dan de cv ketel in bedrijf komt,
Het is aan jullie hoe jullie het willen, maar nu ik iets meer van het systeem begrijp en zie hoe het bij mij functioneert ben ik best tevreden met weinig aanpassingen, gisteren 5.5 Kwh verbruikt, WP bijna aaneen stuk verwarmd, met een hoge piek tijdens het installeren van de CV ketel via opentherm, rest perfect gedraaid, zie plaatje
Afbeeldingslocatie: https://tweakers.net/i/7PYwKCnTYMWM-yRoPbGuB5xc40Q=/800x/filters:strip_icc():strip_exif()/f/image/L6HeYDI4x6RfrQO0C7b3pHUz.jpg?f=fotoalbum_large
Het is ook gelukt om deze te koppelen helaas geeft de ketel dan een storing geen ruimteopnemer aangesloten, zal met Remeha telefoneren of hier een oplossing voor is, anders laat ik de Remeha standalone werken, niet aangesloten op de Plugwise regeling, deze kan ik via de app als nodig laten bijstoken, thermostaat naast de ketel hangen, laat dan Plugwise aan mijn regeling aangeven dat er warmte gevraagd wordt, via een Digitale ingang.
Nogmaals de koelsystemen zijn uitgelegd met een Dt van 6 grd een CV ketel met een Dt van 20 grd, als je een Dt van 5-6 grd hebt bij een maximaal vermogen dan werkt het perfect, is de flow over de unit niet te hoog en te laag, is de Dt te hoog heb een te kleine flow, is de Dt te klein is de flow te hoog,
Zie dan dat de WP het zelf ook perfect aanpast in deelvermogens.
Misschien was ik iets te onduidelijk maar de eerder plaatjes gaven de installatie goed weer er is niets meer of extra's toegevoegd. tot zover

Acties:
  • 0 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
Ik had inderdaad de indruk dat je de bypass actief bijregelde naar een delta_T van 5oC. Maar goed om te horen dat je het met de basisspullen goed hebt gekregen! Mooi werk.

Verder schijnen er meerdere software veries te zijn. Maar dat is lastig te achterhalen omdat de enige aanwijzing het stickertje op de microntrollers in de controlbox is. Bij mij staat er bijvoorbeeld Tesla2_M op

Acties:
  • 0 Henk 'm!

  • P1 modubus
  • Registratie: Maart 2023
  • Laatst online: 01-09 13:38
Na overleg met Remeha kan het probleem, het missen van een ruimteopnemer opgelost worden, ben alleen nog het twijfelen en of ik het aansluit, de thermostaat heb ik nu opgehangen naast de ketel en is via de app benaderbaar en te bedienen.
Eenmaal was de WP gestopt en wilde zomaar zonder verklaring de CV ketel laten stoken, dus laat ik het voorlopig zo.
Ook heb ik nu na een koude nacht de schakeling aangezet dat de buitentemperatuur 6 grd hoger maakt voor de WP, normaal neemt deze 7 watt op, bij 4 grd C wordt dit 54 Watt, dan laat de WP de circulatiepomp onnodig lopen, Ik zal naar 200 Mohm (nog uitproberen na montage kijk ik dit na op de WP zelf) gaan in de winter voor 4 grd verhogen van de buitentemperatuur, dit heeft 2 voordelen
1/ de circulatiepomp gaat niet onnodig draaien, bij 0 grd C pas;
2/ de WP blijft werken tot het rendement zodanig wordt dat het niet meer loont, dit kan ik met een schakeling automatisch laten gebeuren.

Alleen heb ik gemerkt dat de regeling bij mij niet aan of uit gaat via een rooster vannacht ging deze pas om 23.30 uur uit, heb zeker 3 tijden gezet 20.00, 22.00, 23.30 uur op nacht 18.00 grd staan.
Handmatig bediening en weergave denk ik op de Anna beter is dan de online versie,.
Afbeeldingslocatie: https://tweakers.net/i/IPK8UntVMy3dsvg95WHpwUkGQcI=/x800/filters:strip_icc():strip_exif()/f/image/Lq0gpibo9A5bnjNZNS6hj17b.jpg?f=fotoalbum_large
Afbeeldingslocatie: https://tweakers.net/i/dDV0b1Bc2jw_3mUwn5yrBB7fNUQ=/x800/filters:strip_icc():strip_exif()/f/image/p0Xm4uXw25e0Y9L6Jx8X9X1A.jpg?f=fotoalbum_large
Zie de verschillen in de een is het volgens rooster en op de Anna is dit niet,
Ik vind het zelf leuk om iets te achterhalen zeker in de Meet&Regel techniek, maar vindt het jammer dat er ook geen eenvoudige rechtstreekse regeling bij kan, dit zal de WP beter verkoopbaar maken, zelf vind ik het, het beste apparaat voor mijn situatie, dat gun ik zeker iedereen.

Acties:
  • +2 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
Hallo,

Het is misschien wel aardig om te zien hoe eenvoudig de Chofu warmtepomp kan worden aangestuurd.
Ik gebruik hiervoor een Arduino Diecimila met een rotary encoder.

In de controlbox heb ik 1 kant van weerstand R14 losgehaald (deze kant is bewust zodat de uitgang van de microcontroller door R14 beschermd blijft), en in een (groen) draadje met aan beide zijden een een DuPont connector gesoldeerd. De andere kant heb ik via een 1 kOhm weerstand met pin 2 van de Diecimila verbonden.
Verder heb ik aan de losse kant van weerstand R14 een draadje met een contra DuPont connector gesoldeerd. Zo kan ik de oorspronkelijke situatie herstellen door deze twee connectoren in elkaar te schuiven:
Afbeeldingslocatie: https://tweakers.net/i/cM2yZN5e1kkKeKjPh1aGVzXT1PQ=/fit-in/4000x4000/filters:no_upscale():strip_exif()/f/image/BvD1xgame5eiuxTR6SZNKVFa.png?f=user_large

Daarnaast heb ik een min (of aarde) verbinding via PAD3 gemaakt door daar een connector in te solderen, en die met een GND van de Diecimila te verbinden. (De onderstaand foto is van voordat ik de verbinding heb gemaakt):

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

Dit is met versie 1.00 van de print en natuurlijk allemaal op eigen risico. Houd er rekening mee dat een foute verbinding (met name bij PAD 1 en 2, deze zijn zonder beschermende weerstanden met de beide microcontrollers verbonden), tot schade kan leiden.

Dit is de code:
Arduino:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/* 
Simple way to control the power setting of a Chofu AEYC-0643XU-CH heatpump with an Arduino
The power is set with a rotary switch and send via one-way serial communication at 666 bps to the heatpump, using the hardware of Atantic Aurea controlbox.
Lift the side of resistor R14 that is connected to the base of T2, and connect the output pin (default is pin 2) of the Arduino  via a 1 kOhm resistor to the base of T2.
Version 1.0, JHN 02-03-2025 */

#include "SoftwareSerialTX.h"
#include <string.h>

// Use a SoftwareSerialTX on Pin 2
SoftwareSerialTX txOnly(2);

//Define variables and constants
int PowerSetpoint;                                // Setpoint to heat pump  warmtepomp wordt gestuurd
int PowerStages = 9;                              // Number of useful steps in heatpump setpoint
// For rotary decoder
int pinCLK = 10;                                  // CLK of rotary encoder
int pinDT = 9;                                    // DT of  rotary encoder
int pinSwitch = 8;                                // Push button of rotary encoder to confirm setting
int encoderPosCount = 0;                          // Count position rotary encoder
int pinCLKLast;                                   // for rotary encoder
int aVal;                                         // for rotary encoder
bool bCW;                                         // Boolean Clockwise for rotary encoder
bool btnPressed = false;                          // Button of rotary switch pressed?
bool currentState = LOW;
const int LEDPin = 13;                            // Pin to activate LED
int TelegramCount = 0;                            // Follow-up number telegrams
volatile unsigned long Interval = 0;              // Time since last telegram
// Telegram timing 
unsigned long currentMicros = 0;                  // Storage current micro seconds
unsigned long previousMicros = 0;                 // Tracks the last pulse start time
const unsigned long SendInterval = 600000;        // Interval between pulses in microseconds 
byte data0[] = { 0x19, 0x0, 0x8, 0x0, 0x0, 0x0, 0xd9, 0xb5 };                             // Array of hexadecimal bytes for 1st message (last two butes are checksum)
byte data1[] = { 0x19, 0x1, 0x0c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xaa, 0x35 };        // Array of hexadecimal bytes for 2nd message (last two butes are checksum)
byte data2[] = { 0x19, 0x2, 0x8, 0x1, 0x1, 0x0, 0x99, 0x37 };                             // Array of hexadecimal bytes for 3rd message. This is the message to change bytes 4 (on/off) and 5 (power setpoint), and bytes 7 and 8 (checksum)
byte data3[] = { 0x19, 0x3, 0x8, 0xb2, 0x2, 0x0, 0xc1, 0x9a };                            // Array of hexadecimal bytes for 4th message (last two butes are checksum)
byte checksum1[] = { 0x9d, 0x99, 0xcc, 0xff, 0x66, 0x55, 0x00, 0x33, 0x23, 0x10, 0x45 };  //Array of 1st byte of checksum of 3rd message. First byte of the array is for 'off' and output '0', 2nd byte is for 'on' and putput '1' etc.
byte checksum2[] = { 0x36, 0x37, 0x64, 0x55, 0xc2, 0xf3, 0xa0, 0x91, 0xaf, 0x9e, 0xcd };  //Array of 2nd byte of presumed checksum. First byte of the array is for 'off' and output '0', 2nd byte is for 'on' and putput '1' etc.

void setup() {
  pinMode(LEDPin, OUTPUT);                        // Set the pulse pin as an output    

  // Rotary Encoder Setup
  pinMode(pinCLK, INPUT);
  pinMode(pinDT, INPUT);
  pinMode(pinSwitch, INPUT_PULLUP);
  pinCLKLast = digitalRead(pinCLK);               // Read Pin CLK, current pin state corresponds to latest position
  
  // Serial Setup
  Serial.begin(115200);                           // Start the Serial Monitor for debugging (Serial port 0)
  txOnly.begin(666);                              // Set-up software serial baurdrate for sending info to heat pump at 666 baud (pin 2)
  Serial.println("Setup complete...");
 }

void loop() {
// Send telegram
  currentMicros = micros();
  if (currentMicros - previousMicros >= SendInterval) {       // Start a new pulse if 500,000 µs have elapsed
    previousMicros = currentMicros;
    digitalWrite(LEDPin, HIGH);
        switch (TelegramCount) {
          case 0:                                             // Send message 0 (no changes)
            sendTelegram(data0, sizeof(data0), "Telegram 0");
            break;
          case 1:                                             // Send message 1 (no changes)
            sendTelegram(data1, sizeof(data1), "Telegram 1");
            break;
          case 2:                                             // Send and modify message 2 after modifiyng bytes 4, 5,7 and 8 according to the selected power setting
            if (PowerSetpoint > 0) {
              data2[3] = 0x1;
            } else {
              data2[3] = 0x0;
            }
            data2[4] = PowerSetpoint;
            data2[6] = checksum1[PowerSetpoint];
            data2[7] = checksum2[PowerSetpoint];
            sendTelegram(data2, sizeof(data2), "Telegram 2");
            break;
          case 3:                                             // Send message 3 (no changes)
            sendTelegram(data3, sizeof(data3), "Telegram 3");
            break;
          default:
            Serial.println("Invalid TelegramCount value");
            break;
        }
        TelegramCount = (TelegramCount + 1) % 4;              // Cycle through 0-3
        digitalWrite(LEDPin, LOW);
  }

// Check rotary encoder
   processRotaryEncoder();
}
////// End of main loop //////

// Below are the subroutines
void sendTelegram(byte *data, size_t size, const char *name) {
  for (size_t i = 0; i < size; i++) {
    txOnly.write(data[i]);
    if (i < size - 1) {
      }
  }
}

void processRotaryEncoder() {                                 // Read rotary decoder and write to screen
  int aVal = digitalRead(pinCLK);                
  if (aVal != pinCLKLast) {                                   // Knog is being turned
    // Determine direction of rotation by reading pin B
    bCW = (digitalRead(pinDT) != aVal);                       // This means that Pin A changed first --> clockwise rotation 
    encoderPosCount += bCW ? 1 : -1;                          // Otherwise Pin Bchanged first so counterclockwise rotation
    encoderPosCount = constrain(encoderPosCount, 0, PowerStages);
  }
  pinCLKLast = aVal;

  if (digitalRead(pinSwitch) == LOW && !btnPressed) {         // Button is pressed 
    btnPressed = true;
  } else if (digitalRead(pinSwitch) == HIGH && btnPressed) {  // Button is released 
    btnPressed = false;
    PowerSetpoint = encoderPosCount;
  }
}

Acties:
  • 0 Henk 'm!

  • wybe-V
  • Registratie: Augustus 2023
  • Laatst online: 06-07 21:07
Mooi om te zie dat je dit lukt en dat het werkt om op deze wijze de WP aan sturen
Ik zelf zou dit ook wil willen proberen en het aanpassen van de print is geen probleem maar met de Arduino Diecimila heb ik totaal geen ervaring
Is het misschien 🤔 een optie om dit als compleet setje aan te bieden tegen betaling en een extra vergoeding voor ontwikkeling
Dit alles natuurlijk wil op eigen risico 🤗😇🤔

Acties:
  • 0 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
Oh, die Arduino Deicimila (een echte uit Italie, geen Chinese kloon) heb ik alleen maar voor de aardigheid genoemd. Ik heb het even nagekeken: Die heb ik in april 2008(!!) voor $33.50 bij Adafruit gekocht. Die is al lang niet meer verkrijgbaar

Welke microcontrollers je gebruikt is niet kritisch. Die van het Amerikaanse Atmel (tegenwoordig MicroChip) vormden de basis voor het Arduino ecosysteem (In de controlbox zitten trouwens twee ATmega8's, de lichtere versie van de ATmega168 in de Diecimila). Ik gebruik overigens voor het complexere programma een Arduino op basis van een ATmega2560 omdat die vier hardware seriele poorten heeft.

Tegenwoordig zijn microcontroller van het Chinese Espressif populair (ESP8266 en ESP32) omdat die wifi aan boord hebben en krachtiger zijn. Die hebben echter als nadeel dat ze met 3.3 V (ipv 5 zoals de bovnegenoemde Atmels) op de in en uitgangen werken. Ik denk dat die nog steeds de WP aan kunnen sturen want transistor T2 zit er tussen om de stroom naar optocoupler OK2 te versterken, maar inlezen van de data vereist een spanningsomzetter (kan een eenvoudige spanningsdeler met weerstanden zijn omdat de snelheden laag zijn).

Het beste installeer je de Arduino IDE want daarmee kun tientallen meer of minder complexe bordjes programmeren, en zoekt een courant microcontroller bordje, bv. de Arduino Uno. Het programmeren stelt echt niets voor. Ik was eerlijk niet van plan om geprogrammeerde bordjes rond te gaan sturen ;)

Acties:
  • +5 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
Het stookseizoen zit er bijna op. De warmtepomp heeft het grootste deel van de tijd prima gedraaid en vrijwel zonder gasverbruik (de laatste maanden helemaal niets). Maar dat ging niet vanzelf...

Sinds december heb ik flink wat tijd besteed om eerst te snappen hoe de communicatie tussen de buitenunit en de controlbox verloopt, en de communicatie tussen de microcontroller in de controlbox.

Ik had al snel een simpel programma in Arduino IDE dat ik op de bovengenoemde Diecimila draaide. Hiermee kon ik de snelheid (vermogen) van de Chofu buitenunit d.m.v. een rotary decoder instellen.

Vervolgens heb ik nagedacht wel kant ik op zou gaan. Er zijn namelijk verschillende richtingen:
  1. De communicatie van en naar de warmtepomp met een eigen microcontroller afhandelen die het setpoint voor de aanvoertemperatuur via wifi van bv. Home Assistant krijgt. De elektronica van de controlbox dient alleen als hardware interface van en naar de warmtepomp.
  2. Op een eigen microcontroller bordje zowel de OpenTherm communicatie als man-in-the-middle tussen ketel en thermostaat, als die met de warmtepomp afhandelen, en de controlbox alleen als hardware interface voor beide protocollen gebruiken.
  3. Op zo’n manier in de communicatie naar de warmtepomp en tussen de microcontrollers in de controlbox ingrijpen dat de normale kamerthermostaat (Plugwise Smile + Anna) blijft werken, en bediening op afstand mogelijk is.
Optie 1 zou relatief makkelijk zijn, maar is voor mijn idee te kwetsbaar voor problemen met bv. Home Assistant; verwarming moet niet afhankelijk zijn van wifi etc. Optie 2 is de mooiste want dan geen last meer van de nukken van de controlbox. Er zijn meerdere Arduino IDE OpenTherm libraries die geschikt zijn voor verschillende microcontroller bordjes. (bv. https://reference.arduino...e/en/libraries/opentherm/). Maar om het niet al te complex te maken heb ik uiteindelijk optie 3 uitgewerkt.

Het concept heb ik in een eerdere post al uitgelegd. Hieronder staat een variant van de code zoals ik die nu op een Arduino Mega met een Atmel Mega2560 microcontroller gebruik. Het verschil met mijn eigenlijke code is dat ik alle schrijfacties naar een LCD met allerlei variabelen van warmtepomp en OpenTherm, eruit heb gehaald om de code compacter te houden en in plaats daarvan verschillende schrijfopdrachten naar de seriële poort gedaan (normaal gebruik ik die alleen voor foutmeldingen en troubleshooten). De code compileert, maar ik heb ‘m niet in het echt getest. Verder heb ik het gedrag op basis van buitentemperatuur (met name bij < -2.0oC alsnog CV inschakelen) bij gebrek aan juiste condities op het juiste moment, niet in de praktijk kunnen testen.
Tenslotte: Ik ben geen doorgewinterde programmeur, dus ik vergeef me als sommige zaken niet volgens het boekje zijn gedaan O-)

Een paar algemene opmerkingen.
  • Er wordt niets gedaan met foutmeldingen vanuit de warmtepomp. Dat kan de controlbox volgens de handleiding wel. Maar ik had geen zin om te proberen te achterhalen hoe dat allemaal is gedecodeerd. Het wordt vanzelf koud als er een probleem is...
  • De code gebruik nu 7 standen van warmtepomp. Stand 8 geeft maar een klein beetje meer vermogensopname. De COP daalt met toenemend vermogen dus begrenzen tot 6 standen (als dat genoeg warmte voor het huis oplevert) geeft een beter rendement.
  • De PID regelaar regelt conservatief als de afwijking tussen setpoint en aanvoertemperatuur minder dan 1oC is, en anders agressief. Dit voorkomt een overshoot omdat de Anna de gevraagde aanvoertemperatuur slechts binnen 0.2oC rond de gewenste kamertemperatuur regelt (in mijn geval tenminste).
  • De optimale PID parameters zullen van de karakteristiek van je eigen systeemm afhangen
  • Als er conservatief wordt geregeld, verandert het setpoint voor de WP niet sneller dan eens per 7 min. Dit op snel op en afschakelen van het vermogen te voorkomen
  • Mijn programma heeft een watchdog timer op basis van een interrupt. Die heb ik er hier uitgelaten omdat die hardware specifiek is
Al deze instellingen zijn natuurlijk wijzigen.

Hier de code voor de besturing:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
/* 
Alternative control for the Atlantic Aurea heat pump, which based on the Chofu AEYC-0643XU-AT unit.
This is a sketch to control the power setting of a heat pump with an Arduino ATMega2560 and to modify communication inside the controlbox using 3 of the 4 hardware serial ports of this board. 
The original code shows various useful information like temperatures etc. on a 480*320 TFT LCD. This has been removed here to keep the code more compact.
Version 0.1Beta, JHN 19-03-2025 */

#include <QuickPID.h>                                   // Library for PID controller, see for documentation: https://github.com/Dlloydev/QuickPID

//Define variables and constants
  // General
  String MessageString = "";

  // For heat pump
  const uint8_t SpeedStages = 7;                        // Number of speed stages to be used (7 --> ~1400 W max)
  uint8_t SpeedSetpoint = 0;                            // Speed setpoint sent to heatpump   // const uint8_t MinTempSetpoint = 25;
  uint8_t TargetTemperatureThermostat = 20;             // Target temperature as requested by the thermostat   
  const float MinTemp4HP = -2;                          // Outside temperatute below wich heat pump should stop working and CV should start
  uint16_t TelegramCount = 0;                           // Follow-up number of telegrams
  // Variables to track timings
  uint32_t Interval = 0;                                // Time since last telegram
  uint32_t currentMillis = 0;                           // Check timing for sending messages
  uint32_t LastSpeedSetpointChange = 0;                 // Keep track when last time speedsetpint has been changed in milliseconds
  uint32_t SpeedSetpointHysteresis = 420000;            // Allow a change in speedsetpoint only once per 7 minutes (when using slow PID parameter set)
  // Cycle time check
  uint32_t previousCycleMicros = 0;                     // To store the time of the last cycle start
  uint32_t currentCycleMicros = 0;                      // To store the time of the current cycle start
  uint32_t cycleTime = 0;                               // To store the calculated cycle time
  uint16_t cycleCounter = 0;                            // Counter to track cycles
  const uint16_t displayInterval = 10000;               // Display cycle time every 10000 cycles
  uint8_t lcdUpdateStep = 0;                            // Variable to spread writing to screen over multiple steps
  // Receive and send inter MCU data 
  const uint32_t McuMessageWaitTime = 200;              // Waiting time between receiving message on Pad 1 (Serial 1) and sending on pad 2 (serial 2) in ms
  uint32_t McuMessageOneReceived = 0;                   // Tie last message send in ms
  bool dataMcu2Send = false;
  uint32_t PreviousMessageSerial2Sent = 0;   
  uint32_t MessageSerial2Interval = 10000;              // Maximum interval for sending message on serial 2 (to avoid that communication stops)
  const uint8_t DataMcuLength = 12;                     // Length of each transmission between MCU's
  uint8_t dataArrayOT_IC1_IC2[DataMcuLength] = {0};     // Array to store the incoming 12 bytes for OT IC1 to IC2.
  uint8_t dataArrayOT_IC2_IC1[DataMcuLength] = {0};     // Array to store the incoming 12 bytes for OT IC2 to IC1.
  uint8_t dataArrayOT_IC1_IC2_Old[DataMcuLength] = {0}; // Array to store the previous incoming 12 bytes for OT IC1 to IC2.
  uint8_t dataArrayOT_IC2_IC1_Old[DataMcuLength] = {0}; // Array to store the previous incoming 12 bytes for OT IC2 to IC1.  
  uint16_t ChecksumMCU = 0;                             // Checksum for communication between MCU's
// Variables for communication with heat pump
  uint8_t CB_HPlength = 0;                              // Length of communcation CB to HP
  uint16_t ChecksumCBHP = 0;                            // Checksum for communication of heat pump with control box
  uint8_t ChecksumCBHPfalseCount = 0;                   // Counter for number of times that the checksum did not match. After certain number of mismatches, message will be send to the serial port
  uint8_t data0[] = { 0x19, 0x0, 0x8, 0x0, 0x0, 0x0, 0xd9, 0xb5 };                       // Array of hexadecimal bytes
  uint8_t data1[] = { 0x19, 0x1, 0x0c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xaa, 0x35 };  // Array of hexadecimal bytes
  uint8_t data2[] = { 0x19, 0x2, 0x8, 0x1, 0x1, 0x0, 0x99, 0x37 };                       // Array of hexadecimal bytes. This is the one to change bytes 4 (on/off) and 5 (setpoint), and 7 and 8 (high and low bytes of the CRC CCITT checksum)
  uint8_t data3[] = { 0x19, 0x3, 0x8, 0xb2, 0x2, 0x0, 0xc1, 0x9a };                      // Array of hexadecimal bytes

// Variables for sending messages to heat pump
bool IncomingMessageEnded = 0;                          // Last message start time in milliseconds
volatile uint32_t PreviousMessageSent = 0;              // Time when prveious message was sent in milliseconds
volatile bool IsReceiving = false;                      // Tracks if message is being received from heat pump to block sending next message to heat pump
const uint32_t SendTimeout = 2000;                      // Time in milliseconds to wait before considering the communication ended
const uint32_t SendDelay = 99;                          // Delay in milliseconds before sending information to heatpump
const uint32_t MinMessageSendInterval  = 300;           // Minimum interval in millis between sending messages when no reply is received
volatile uint32_t previousMessageInEnded = 0;

// Definitions for reading and checking heat pump data
  // Prepare array for storing data from heatpump
    const uint8_t StartByte = 0x91;                     // First byte must be 145 (0x91)
    const uint8_t Rows = 4;                            // Number of rows in heat pump data array 
    const uint8_t Cols = 80;                            // Number of columns in heat pump data array 
    uint8_t numChars = 0;                               // Number of characters to be written to screen
    uint8_t dataHeatPumpTemp[Cols][Rows] = {0};         // Array to store of four messages from heatpump before checksum check
    uint8_t dataHeatPump[Cols][Rows] = {0};             // Array to store of four messages from heatpump after checksum is correct
    uint8_t dataHeatPumpOld[Cols][Rows] = {0};          // Array to store previous data of four messages so that screen is only updayted when needed
    uint8_t extractedRow[Cols];                         // 1D array to store the row  
    const uint8_t ValidLengths[] = {13, 14, 19, 21};    // Lengths 12, 13, 18 & 20   (12+1), (13+1), (18+1), (20+1)
    uint16_t value;                                     // Temporarily storage of datapoint from array
    float valuefloat;
    uint8_t Length = 0;
    float T_supply =0;                                  // Supply water temperature by heatpump
    float T_return =0;                                  // Return water temperature to heatpump
    float T_outside =0;                                 // Outside temperature measured on heatpump
  // State variables for non-blocking serial reading
    enum SerialState {  Wait_Start,                     // Waiting for the start of a serial message  
                        Read_Header,                    // Reading the header of the message  
                        Read_Payload,                   // Reading the main data (payload)  
                        Read_End                        // Reading the end of the message  
                        };                              // Enumeration type, which helps in defining different states of serial communication
    SerialState state = Wait_Start;                     // Make waiting to start the default
    uint8_t ID = 0;
    uint8_t msgLength = 0;
    uint8_t DataLength = 0;
    uint8_t buffer[Cols] = {0};                         // Temporary buffer for incoming data
    uint8_t index = 0;
    int8_t NegT = 0;                                    // Store if temperature is negative (value of 255 in MSB)
  // For PID controller 
    float TemperaturePIDsetpoint = 20;                  // Desired supply temperature
    const float TemperaturePIDsetpointMax = 37;         // Maximum temperature setpoint
    float TemperaturePIDinput = 0;                      // Current suppy temperature (feedback from the sensor in the heatpump)
    float TemperaturePIDoutput = 0;                     // Heat pump control signal
    float TemperaturePIDoutputRounded = 0;              // Heater control signal, rounded off
    // PID tuning parameter. Default values were Kp = 2.0, Ki = 5.0, Kd = 1.0
    uint8_t PIDParameterSet = 0;                        // Keep track of whcih PID parameters set is being used so that new parameter or only written when required
    float Sensitivity = 3;                              // Sensitivity (ratio) between the slow and fast PID settings. Optionally make this chnageable via rotary encoder
    float PIDParameterThreshold = 1;                    // Threshold for switch between fast (agressive) and slow (conservative) PID parameters
    float T_GapPIDParameterSwitch = 0;                  // Gap between the setpoint and actual temperature, used to determine which set PID parameters to use 
    const float Kp0 = 0.02, Ki0 = 0.008, Kd0 = 0.01;// 
    float Kp = Kp0, Ki = Ki0, Kd = Kd0;// 
// Create PID object
  QuickPID myPID(&TemperaturePIDinput, &TemperaturePIDoutput, &TemperaturePIDsetpoint, Kp, Ki, Kd, /* OPTIONS */
                myPID.pMode::pOnError,                   /* pOnError, pOnMeas, pOnErrorMeas */
                myPID.dMode::dOnMeas,                    /* dOnError, dOnMeas */
                myPID.iAwMode::iAwCondition,             /* iAwCondition, iAwClamp, iAwOff */
                myPID.Action::direct);                   /* direct, reverse */

void setup() {
// Serial Setup
  Serial.begin(115200);                                   // Start the Serial Monitor for debugging on terminal (Serial port 0)
  Serial1.begin(9600);                                    // Start serial port 1 for receiving messages from OT MCU to HP MCU (RX1, 9600 bps)
  Serial2.begin(9600);                                    // Start serial port 2 to receive messages from HP MCU and send modified message to OT MCU (RX2 pin & TX2 9600 bps)
  Serial3.begin(666);                                     // Start serial port 3 for sending an receiving info to heat pump at 666 baud (RX3 and TX3)
  Serial1.setTimeout(100);                                // Changes the timeout for readBytes() and other blocking serial read functions (readString(), readBytesUntil(), etc.).
  Serial2.setTimeout(100);
// For QuickPID
  myPID.SetOutputLimits(0, 99);                           // Set to max 99% to have max. 4 digits ('99.9')
  myPID.SetSampleTimeUs(5000000);                         // Sample time of PID controller in microseconds. Made slower than default because of relatively slow temperature changes and heat pump response to save processing time.
  myPID.SetTunings(Kp, Ki, Kd);
  myPID.SetMode(myPID.Control::automatic);                // Set PID controller in automatic, i.e. input (PV) is perdiodically compared with setpoint (SP) and control variable (CV) is adjusted 

  Serial.println("Setup complete.");
}

//////////////// Main loop ////////////
void loop() {
  currentMillis = millis();                                 // get curent millis (ms) for sending message to heat pump
  currentCycleMicros = micros();                            // Capture the current time (in us) to calculate the cycle time of the loop
  cycleCounter++;                                           // Increment the cycle counter
  
// Check for incoming data on Serial 1 (Pad 1 from MCU 2), 2 (Pad 2 from MCU 1) and Serial 3 (data from heat pump)
readSerialMessageHP();                                      // Call this function continuously to check for data on the incoming serial port from the heat pump
if (Serial1.available() >= DataMcuLength) {                 // Check whether the serial buffer connected to Pad 1 contains DataMcuLength (12) bytes or more  
  Serial.print("Serial1.available()"); Serial.println(Serial1.available()); 
  readSerialMessageMCU(1);                                  // When so, jump to subroutine to read the bytes or clear the buffer
  McuMessageOneReceived = currentMillis;                    // Set timestamp after receiving message on pad 1 (from MCU 2 to 1)
  dataMcu2Send = true;                                      // Set flag to send message on serial port 2
  TemperaturePIDsetpoint = 1 + 0.85 * dataArrayOT_IC2_IC1[0];// Scale the setpoint from the thermostat (0, and 30~40oC) to 25~37oC
  if (TemperaturePIDsetpoint > TemperaturePIDsetpointMax){
    TemperaturePIDsetpoint = TemperaturePIDsetpointMax;
    }
  }
if (Serial2.available() >= DataMcuLength) {                 // Check whether the serial buffer connected to Pad 2 contains DataMcuLength (12) bytes or more 
  Serial.print("Serial2.available()"); Serial.println(Serial2.available()); 
  readSerialMessageMCU(2);                                  // When so, jump to subrotuine to read the bytes or clear the buffer
  }

// Write modified message from MCU 1 to 2 on serial 2, McuMessageWaitTime (default 200 ms) after receiving message on serial 1 
if ((((currentMillis - McuMessageOneReceived) >= McuMessageWaitTime) && dataMcu2Send == true) || ((currentMillis-PreviousMessageSerial2Sent) > MessageSerial2Interval))  {
  Serial2.write(dataArrayOT_IC1_IC2, sizeof(dataArrayOT_IC1_IC2));    // Write array to serial port 2
  PreviousMessageSerial2Sent = currentMillis;
  dataMcu2Send = false;
  }

// Send the next message to the heat pump 100 ms after the previous incoming message has ended, or after 1000 ms without sending anything. Use the periodic sending also to update the screen
  currentMillis = millis();                                 // get curent millis (ms) for sending message to heat pump
    if ((( (currentMillis - previousMessageInEnded >= SendDelay)  &&  (!IsReceiving))  || ( currentMillis - previousMessageInEnded >= SendTimeout )) && (currentMillis - PreviousMessageSent >= ( MinMessageSendInterval ))) 
      {
      switch (TelegramCount) {
        case 0:
          Serial3.write(data0, sizeof(data0));
          break;
        case 1:
          Serial3.write(data1, sizeof(data1));
          break;
        case 2:
          SpeedSetpoint = (int)round( TemperaturePIDoutput / (100 / SpeedStages )) ;    // Scale the PID controller output from 0~100% to 0~8 speed setpoints, and convert from double to rounded integer
          if (SpeedSetpoint == 0) {
            data2[3] = 0x0;
            data2[4] = 0x0;
            } else {
              if ((PIDParameterSet == 0) && (currentMillis - LastSpeedSetpointChange > SpeedSetpointHysteresis)) { // Add hysteresis when slow PID parameter set is active
                data2[3] = 0x1;
                data2[4] = SpeedSetpoint;                 // Write calculate speed setpoint in array  
                LastSpeedSetpointChange = currentMillis;
                } else {
                  if ((PIDParameterSet > 0) && (currentMillis - LastSpeedSetpointChange > SpeedSetpointHysteresis/5)) 
                    { // Hysteresis 1/5 of default when fast PID parameter set is active to allow for fast respons
                    data2[3] = 0x1;
                    data2[4] = SpeedSetpoint;             // Write calculate speed setpoint in array  
                    }
                  }
              } 
          CB_HPlength = sizeof(data2);
          calculate_CRC_CCITT_Checksum(data2, CB_HPlength-2, &ChecksumCBHP);  
          data2[6] = (ChecksumCBHP >> 8) & 0xFF;          // Replace byte 6 with checksum High Byte
          data2[7] = ChecksumCBHP & 0xFF;                 // Replace byte 7 with checksum Low Byte
          Serial3.write(data2, sizeof(data2));
          break;
        case 3:
          Serial3.write(data3, sizeof(data3));
          break;
        default:
          Serial.print("Invalid TelegramCount value:  ");Serial.println(TelegramCount); 
          break;
        }
//        Serial.print("Message ");Serial.print(TelegramCount);Serial.println(" sent!");  // Confirm that message has bene sent
        TelegramCount = (TelegramCount + 1) % 4;          // Cycle through 0-3
        PreviousMessageSent = millis();
      }

   if (cycleCounter >= displayInterval) {
        if (previousCycleMicros != 0) {
          cycleTime = ( currentCycleMicros - previousCycleMicros ) / displayInterval;  // Time difference between cycles
          previousCycleMicros = currentCycleMicros;           // Update the previous cycle time
      cycleCounter = 0;                                   // Reset the cycle counter
      } else {
        previousCycleMicros = currentCycleMicros;
      }
    switch (lcdUpdateStep) {
      case 0:                                             // Print the cycle time etc. The timing for sneding message to the heatpump wil be oof when the cycle time is too long (> ~50 ms)
        Serial.println("Cycle time "+String(cycleTime)+" ms");
      break;
      case 1:                                             // Print PID settings
      break;
      case 2:                                             // Compresor speed 91-3,9
        if (dataHeatPump[9][3] != dataHeatPumpOld[9][3]) {  // Data has changed?
          Serial.print("Speed setpoint: "); Serial.println(dataHeatPump[9][3]);
          }
      break;
      case 3:                                             // Compresor power 91-3,10
        if (dataHeatPump[10][3] != dataHeatPumpOld[10][3]) {
          MessageString = String((25.6 * dataHeatPump[10][3]),0);
          Serial.print("Compressor power: "); Serial.println(MessageString);
          dataHeatPumpOld[10][3] = dataHeatPump[10][3];
          }
        // Compressor mode 91-1,3
        if (dataHeatPump[3][1] != dataHeatPumpOld[3][1]) {
          Serial.print("Compressor mode: "); Serial.println(dataHeatPump[3][1]);
          dataHeatPumpOld[3][1] = dataHeatPump[3][1];
          }
        // Defrost?   91-1,4
        if (dataHeatPump[4][1] != dataHeatPumpOld[4][1]) {
          Serial.print("Defrost ongoing? "); Serial.println(dataHeatPump[4][1]);
          dataHeatPumpOld[4][1] = dataHeatPump[4][1];
          }
      break;
      case 4:
        // ?Data? 91-3,5
        if (dataHeatPump[5][3] != dataHeatPumpOld[5][3]) {
          Serial.print("Generic info "); Serial.println(dataHeatPump[5][3]);
          dataHeatPumpOld[5][3] = dataHeatPump[5][3];
          } 
      break;
      case 5:                                             // Temperatures
        // Supply temperature   91-2,3~4
        if (dataHeatPump[3][2] != dataHeatPumpOld[3][2]) {
          value = dataHeatPump[4][2];                             // MSB of temperature; previous byte is the MSB
          T_supply = (((value*256+dataHeatPump[3][2])-(65536*((value == 255) ? 1 : 0)))/10.0);  // Do the conversion to temperature in such a way that also negative temperature can be shown
          TemperaturePIDinput = T_supply;                       // Make the input the PID controller equal to the current supply water temperature   
          MessageString = String(T_supply,1);
          Serial.print("Supply temperature "); Serial.println(MessageString);
          dataHeatPumpOld[3][2] = dataHeatPump[3][2];
        }
        // Return temperature 91-2,5~6
        if (dataHeatPump[5][2] != dataHeatPumpOld[5][2]) {
          value = dataHeatPump[6][2];               // MSB of temperature; previous byte is the MSB
          T_return = (((value*256+dataHeatPump[5][2])-(65536*((value == 255) ? 1 : 0)))/10.0);  // Do the conversion to temperature in such a way that also negative temperature can be shown
          MessageString = String(T_return,1);
          Serial.print("Return temperature "); Serial.println(MessageString);
          dataHeatPumpOld[5][2] = dataHeatPump[5][2];
        }
        break;
      case 6:
        // Outside temperature 282 91-2,7~8
        if (dataHeatPump[7][2] != dataHeatPumpOld[7][2]) {  
          value = dataHeatPump[8][2];               // MSB of temperature; previous byte is the MSB
          T_outside = (((value*256+dataHeatPump[7][2])-(65536*((value == 255) ? 1 : 0)))/10.0);  // Do the conversion to temperature in such a way that also negative temperature can be shown
          MessageString = String(T_outside,1);
          Serial.print("Outside temperature "); Serial.println(MessageString);
          }
 
      break;
      case 7:                                             // Do PID calculation
        // Evaluate PID calculation    
        T_GapPIDParameterSwitch = abs(TemperaturePIDsetpoint - TemperaturePIDinput);    // Check the gap between the temperature setpoint adn the actual temperature
        if ((T_GapPIDParameterSwitch < PIDParameterThreshold) && (PIDParameterSet = 1)) { // If the gap is smaller than the threshold and the PID controller is currently using the fast set, then
          Kp = Kp0; Ki = Ki0; Kd = Kd0;                       // Choose slow (conservative) settings
          myPID.SetTunings(Kp, Ki, Kd);                       // Write a new set of control parameter
          PIDParameterSet = 0;                                // Set parameter to default default (low) settings
          } else {
            if (PIDParameterSet = 1) {                          // Gap larger than threshold but slow parameters are used, then 
              Kp = Sensitivity*Kp0;                           // Choose fast or aggressive settings by larger gap
              Ki = Sensitivity*Ki0; 
              Kd = Sensitivity*Kd0;
              myPID.SetTunings(Kp, Ki, Kd);                   // Write a new set of control parameter
              PIDParameterSet = 1;                            // Set parameter to default default (low) settings
              }
            }
          break;
      case 8:                                             // Print inter MCU communication 
      break;
      case 9:
        TemperaturePIDoutputRounded = round(TemperaturePIDoutput*10.0)/10.0;
    }
    lcdUpdateStep = (lcdUpdateStep + 1) % 10;  // Cycle through screen updates
  }
}////////////// End of main loop  //////////////////////////

////////  Below are the subroutines //////////////////////
void readSerialMessageMCU(uint8_t MCU) {
uint8_t TempMcuData[DataMcuLength] = {0};
uint8_t TemperatureCategory;  // Variable to categorize temperature
  ////// Inter MCU communication
    switch (MCU) {
      case 1:          // 1. Communication IC2 (OT) to IC1 (HP): This communication is via PAD 1 and therefore used at serial 1. Check if enough data has arrived in the serial buffer and read it
        // for (int i = 0; i < DataMcuLength; i++) {
        //   TempMcuData[i] = Serial1.read();                 // Read the bytes into the array
        // }
        Serial1.readBytes(TempMcuData, DataMcuLength);        // Clean way of reading fixed number of bytes into array. Beware: Blocks execution until all bytes arrive or timeout (default timeout is 1000ms).
        calculateMCUChecksum(TempMcuData, DataMcuLength, ChecksumMCU);// Calculate and check the checksum by determining the sum of the first 11 bytes and taken modules 256
        if (ChecksumMCU == TempMcuData[DataMcuLength-1]) { // Check if the checksum matches
          // for (int i = 0; i < DataMcuLength; i++) {
          //   dataArrayOT_IC2_IC1[i] = TempMcuData[i];          
          //   }
          memcpy(dataArrayOT_IC2_IC1, TempMcuData, DataMcuLength); // Checksum OK so copy temp array to permanent array, cleaner and faster code than a loop  
          ChecksumMCU = false;                                // Make checksum false as start value for next time  
          } else {                                            // Checksum incorrect because communication possibly halfway received. 
              clearSerialBuffer(Serial1);                     // Clears the read buffer of serial 1 if the checksu was wrong to make a fresh start
              Serial.println("Serial buffer 1 cleared");
            }
      break;
      case 2:         // 2. Communication IC1 (HP) to IC2 (OT): This communication is via PAD 2 and therefore used at serial 2. Check if enough data has arrived in the serial buffer, read it, modify when required and resend
        Serial2.readBytes(TempMcuData, DataMcuLength);      // Clean way of reading fixed number of bytes into array. Beware: Blocks execution until all bytes arrive or timeout (default timeout is 1000ms).
        calculateMCUChecksum(TempMcuData, DataMcuLength, ChecksumMCU);// Calculate and check the checksum by determining the sum of the first 11 bytes and taken modules 256
        if (ChecksumMCU == TempMcuData[DataMcuLength-1]) { // Check if the checksum matches
          memcpy(dataArrayOT_IC1_IC2, TempMcuData, DataMcuLength); // Cleaner and faster code than a loop
          ChecksumMCU = false;                            // Make checksum false as start value for next time
          if (T_outside < MinTemp4HP) {
            TemperatureCategory = 0;                             // Too cold for heat pump, switch on cental heating
            } else if (T_outside < 4) {
              TemperatureCategory = 1;  // Below 4°C             // Prevent that controlbox starts CV 
              } else if (SpeedSetpoint == 0) {
                TemperatureCategory = 2;                         // SpeedSetpoint is zero
                } else {
                  TemperatureCategory = 3;                       // In all other cases use the temperature setpoint for water as read from Plugwise Anna via MCU2
                  }                 
          switch (TemperatureCategory) {
            case 0:                                     // Heat pump is not running because it is too cold so central heating should be started
              dataArrayOT_IC1_IC2[0] = 0xf0;
              break;
            case 1:                                     // Below 4°C but heat pump can run, adjust values to prevent central heating activation
              dataArrayOT_IC1_IC2[0] = dataArrayOT_IC2_IC1[0];
              dataArrayOT_IC1_IC2[9] = 0x04;
              dataArrayOT_IC1_IC2[4] = 0;               // Write 0's to force MCU2 to transfer HP temperature to OpenTherm 
              dataArrayOT_IC1_IC2[5] = 0;
              dataArrayOT_IC1_IC2[6] = 0;
              dataArrayOT_IC1_IC2[10] = 0;
              break;
              case 2:                                     // Heat pump is not running (Speedsetpoint  is zero), then let central heating boiler temperatures go to OpenTherm
              break;
              case 3:                                     // In all other cases use the temperature setpoint for water as read from Plugwise Anna via MCU2
                dataArrayOT_IC1_IC2[0] = dataArrayOT_IC2_IC1[0];
                dataArrayOT_IC1_IC2[4] = 0;             // Write 0's to force MCU2 to transfer HP temperature to OpenTherm 
                dataArrayOT_IC1_IC2[5] = 0;
                dataArrayOT_IC1_IC2[6] = 0;
                dataArrayOT_IC1_IC2[10] = 0; 
              break;
            }
            dataArrayOT_IC1_IC2[7] = round(T_supply);     // Write the actual outside temperature in the array. Will not work for temperatures below zero because array is 8 bits unsigned!
            dataArrayOT_IC1_IC2[8] = round(T_return);     //
            dataArrayOT_IC1_IC2[9] = round(T_outside);    // Write the actual outside temperature in the array. Will not wok for temperatures below zero because array is 8 bits unsigned!
            // Serial.print("T_supply: ");Serial.print(T_supply)     
            // Serial.print("T_return: ");Serial.print(T_return)
            // Serial.print("T_outside: ");Serial.print(T_outside)       
            calculateMCUChecksum(dataArrayOT_IC1_IC2, DataMcuLength, ChecksumMCU);// Recalculate the checksum
            dataArrayOT_IC1_IC2[11] = round(ChecksumMCU); // Replace old checksum value with new calculated one
            } else {                                      // Checksum incorrect because communication possibly halfway received. 
              clearSerialBuffer(Serial2);                 // Clears the read buffer of serial 2 if the checksu was wrong to make a fresh start
              Serial.println("Serial buffer 2 cleared");
              }
      break; 
    }
  }

void clearSerialBuffer(HardwareSerial &serialPort) {  
  while (serialPort.available() > 0) {
    serialPort.read();                                  // Read and discard the data in the buffer
  }
}

void calculateMCUChecksum(uint8_t dataArray[], int Length, unsigned int &checksum) {  // Calculate the checksum by determining the sum of the first 11 bytes and then take modules 256
  checksum = 0; // Initialize the checksum
      if (Length <= 1) {    // Ensure length is valid
        checksum = 0;
        return;
    }
    // Calculate the checksum for the first (Length-1) bytes in the specified row
    for (int i = 0; i < Length - 1; i++) {
        checksum += dataArray[i];
    }
    checksum %= 256;                                        // Modulus 256 to keep it in a single byte
}

// Function to calculate the CRC-CCITT (0xFFFF) checksum for the communication to and from the heat pump
void calculate_CRC_CCITT_Checksum(uint8_t *data, uint8_t Length, uint16_t *checksum) {
    uint16_t crc = 0xFFFF; // Initial value
    for (uint8_t i = 0; i < Length; i++) {
        crc ^= (uint16_t)data[i] << 8;
        for (uint8_t j = 0; j < 8; j++) {
            if (crc & 0x8000) {
                crc = (crc << 1) ^ 0x1021;
            } else {
                crc <<= 1;
            }
        }
    }
    *checksum = crc;
}

// Function that reads the serial data coming from the heat pump
void readSerialMessageHP() {
    while (Serial3.available()) {  // Check if data is available on Serial3
      IsReceiving = true;
      uint8_t byteReceived = Serial3.read();
       // Serial.print(String(byteReceived,DEC)+" ");
      IsReceiving = true;
        switch (state) {
            case Wait_Start:                            // Code to wait for the start character
                if (byteReceived == StartByte) {
                    state = Read_Header;
                    index = 0;
                    IncomingMessageEnded = false; // Reset if any other byte is received to indicate
                }
                break;
            case Read_Header:                           // Code to process header bytes
                if (index == 0) {
                    ID = byteReceived;
                    if (ID > 3) {  // ID must be 0-3
                        Serial.println("Invalid ID, resetting...");
                        state = Wait_Start;
                        return;
                    }
                } else if (index == 1) {
                    msgLength = byteReceived+1;
                    if (!IsValidLength(msgLength)) {
                        Serial.println("Invalid Length! Resetting...");
                        state = Wait_Start;  // Reset
                        return;
                    }
                    DataLength = msgLength - 3;  // Exclude first 3 bytes and last byte
                    index = 0;
                    state = Read_Payload;
                }
                index++;
                break;

            case Read_Payload:                          // Code to process data payload
                buffer[index++] = byteReceived;
                if (index >= DataLength) {
                    state = Read_End;
                }
                break;

            case Read_End:                              // Code to check that last byte of message corresponds to '0'
                if (byteReceived == 0) {  // Ensure last byte is 0
                    StoreMessage(ID, DataLength, buffer);
                    IncomingMessageEnded = true;                          // If the end of the incoming message is reached, set the infication boolean
                    previousMessageInEnded = millis();                    // Set timestamp after receiving end of incoming message             
                } else {
                    Serial.println("Warning: Last byte is not 0! Resetting...");
                }
                IsReceiving = false;
                state = Wait_Start;                                     // Reset for next message
                break;
                default:
                Serial.println("Error in readSerialMessageHP state"); 
                break;
        }
    }
}

// Function to check if the Length is valid
bool IsValidLength(uint8_t Length) {
    for (uint8_t i = 0; i < sizeof(ValidLengths) / sizeof(ValidLengths[0]); i++) {
        if (Length == ValidLengths[i]) return true;
    }
    return false;
}

// Function to store messages in separate arrays based on ID
void StoreMessage(uint8_t ID, uint8_t DataLength, uint8_t *message) {
    for (uint8_t i = 0; i < (DataLength); i++) {
      dataHeatPumpTemp[i+2][ID] = message[i];
      }
    dataHeatPumpTemp[0][ID] = StartByte;
    dataHeatPumpTemp[1][ID] = ID;
    dataHeatPumpTemp[2][ID] = DataLength+2; // 12, 13, 18, 20
     for (int j = 0; j < DataLength+2; j++) {                                  // Copy row 
       extractedRow[j] = dataHeatPumpTemp[j][ID];  
       } 
    calculate_CRC_CCITT_Checksum(extractedRow, DataLength+2, &ChecksumCBHP);   // Calculate checksum, including the two checksum bytes --> result will be zero if mesage has been sent error free
    if (ChecksumCBHP == 0) {                                                   // Check that result is indeed zero
      memcpy(dataHeatPump[ID], dataHeatPumpTemp[ID], Cols * sizeof(uint8_t));  // Transfer the temporarily array to the permanent array
      ChecksumCBHPfalseCount = 0; 
      Serial.println("Checksum heat pump data received not OK");               // Print a message if there is an error     
      } else {
        ChecksumCBHPfalseCount++;                                              // Increase counter for false checksums. Nog actie bedebken als threshold wordt bereikt
        Serial.print("Checksum error! ");                                      // Print a message if there is an error
        Serial.print("Now ");Serial.print(ChecksumCBHPfalseCount);Serial.print(" Checksum errors (of max 10) counted!");
        }
}
Verder heb ik een eenvoudige quick & dirty control box simulator van slechts 30 [edit: 55 regels. Ik heb simulate van MCU communicatie ook toegevoegd (met iets meer onderling verschil dan in het echt, nl. 480 vs. 200 ms] om het programma offline op de hardware te kunnen testen:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/* Test program for serial communication related to Atlantic Aurea 
Hardware connections:
  Serial connections:
    Serial port 0: Monitoring and troubleshooting to USB port
    Serial port 1 (Orange): Connect Pin 19 (Rx1) to Pad 1: Receiving messages from OT MCU to HP MCU (9600 bps). This to extract thermostat setpoint     
    Serial port 2 (Brown/white): Connect Pin 16 & 17 (Tx2 (brown)& Rx2(white)) to Pad 2. Receive messages from HP MCU and send modified message to OT MCU (9600 bps)
    Serial port 3 (Connect: Pin 14 & optionally 15 (Tx3 & Rx3) (Green): Send messages with requested power to heatpump and optionally receive messages from HP for monitoring purposes and  (666 bps)
*/

//Define variables and constants
// Make pulse 
const uint32_t MessageSerialMCUInterval = 4200;                   // Interval for mesages send in between MCU (add delay for sending othr data)
uint32_t currentMillis = 0;                                       // Check timing for sending messages
uint32_t PreviousMessageSerial1Sent = 0;                        
bool SendSerial2 = false;

// Variables to receive inter MCU data
byte dataArrayOT_IC2_IC1[] = {44,20,154,0,0,33,14,32,29,8,0,78};  // Array for testing communication picked up at Pad 1 on PCB
byte dataArrayOT_IC1_IC2[] = {37,20,154,0,0,33,14,32,29,2,0,65};  // Array for testing communication picked up at Pad 2 on PCB

// Example of data send from heatpump to control box
byte dataHeatPump0[] = {25,0,8,0,0,0,217,181,         145,0,12,13,7,4,47,90,206,225,230,223,0};                       // Length 12
byte dataHeatPump1[] = {25,1,12,0,0,0,0,0,0,0,170,53, 145,1,13,80,0,0,0,0,0,138,82,164,39,0};                         // Length 13
byte dataHeatPump2[] = {25,2,8,1,5,0,85,243,          145,2,18,18,1,253,0,49,0,1,0,2,0,0,38,216,23,240,0};            // Length 18 
byte dataHeatPump3[] = {25,3,8,178,2,0,193,154,       145,3,20,120,81,168,151,21,0,83,53,2,0,24,0,0,0,0,233,241,0};   // Length 20

void setup() {
  // Serial Setup
  Serial1.begin(9600);                          // Start serial port 1 for receiving messages from OT MCU to HP MCU (9600 bps)
  Serial2.begin(9600);                          // Start serial port 2 to receive messages from HP MCU and send modified message to OT MCU (9600 bps)
  Serial3.begin(666);                           // Start serial port 3 for sending an receiving info to heat pump at 666 baud (pin 16)
  Serial.println("Setup complete.");
}

void loop() {
currentMillis = millis();                                 // get curent millis (ms) for sending message in between MCU's
if (currentMillis-PreviousMessageSerial1Sent > MessageSerialMCUInterval)  {
  Serial1.write(dataArrayOT_IC1_IC2, sizeof(dataArrayOT_IC1_IC2));    // Write array to serial port 1
  PreviousMessageSerial1Sent = currentMillis;
    SendSerial2 = true;
  }

Serial3.write(dataHeatPump0,sizeof(dataHeatPump0));
  delay(412);
if (SendSerial2)  {
  Serial2.write(dataArrayOT_IC1_IC2, sizeof(dataArrayOT_IC1_IC2));    // Write array to serial port 2
  SendSerial2 = false;
  }
Serial3.write(dataHeatPump1, sizeof(dataHeatPump1));
  delay(487);
Serial3.write(dataHeatPump2,sizeof(dataHeatPump2));
  delay(502);
Serial3.write(dataHeatPump3,sizeof(dataHeatPump3));
  delay(531);
}
Aan de hardware kant zij verder ook nog verbeteringen denkbaar. De belangrijkste die medetweakers ook noemden zijn:
  • Een of twee relais gebruiken die in onbekrachtigde toestand de oorspronkelijke situatie herstellen
  • De communicatie op de print aftappen door met tussenvoetjes bij de IC’s te werken. Zo zijn wijzigingen op de print niet nodig (in het geval van optie 1 en 2 hoeft dat zelfs niet want dan kunnen de microcontrollers worden verwijderd!)
  • Toevoegen van een flowmeter om de COP te kunnen berekenen.
Hier een foto van het display in actie:
Afbeeldingslocatie: https://tweakers.net/i/QVjuhsPtOvuC092J7equWxoUkYo=/fit-in/4000x4000/filters:no_upscale():strip_exif()/f/image/tvweTsHtszUAzBnsC4jTvDuO.png?f=user_large

Dan nog de potlood schets van het relevante deel van de print (ik was van plan dit netjes met een tool uit te tekenen, maar had geen zin in de leercurve voor deze ene keer). Het stukje rechts op de print waar twee relais zitten, ontbreekt grotendeels want dat stuk had ik niet nodig:
Afbeeldingslocatie: https://tweakers.net/i/dU6KFKlv9QK5qPFLWjJcedqjv7w=/800x/filters:strip_exif()/f/image/MWWyT4eD3krjkFZABO6jWFGs.png?f=fotoalbum_large

En tenslotte waar het om gaat: Een paar mooie trends (ik log alles wel in Home Assistant; thermostaat stond op 20.5oC, en om 23:40 naar 19.5oC):

Doeltemperatuur woonkamer:
Afbeeldingslocatie: https://tweakers.net/i/dXq3sVgUk88o8ymAP1ftfgNosNk=/800x/filters:strip_exif()/f/image/o33kg9Nbk2hzM9e3ygilorbt.png?f=fotoalbum_large

Doeltemperatuur aanvoer:
Afbeeldingslocatie: https://tweakers.net/i/fpGVuSzudTbODd3czC12k8V-bE0=/800x/filters:strip_exif()/f/image/e4fXSxCSp9M4QovmMbljWpZy.png?f=fotoalbum_large

Opgenomen vermogen:
Afbeeldingslocatie: https://tweakers.net/i/GBAQCzZJLZpGzMrs1JB4RerxR8c=/800x/filters:strip_exif()/f/image/ern13HxnZb9lQfXIyhbekOPI.png?f=fotoalbum_large

Temperatuur aanvoer (als de WP uit is, wordt afwisseld ook de CV (sanitair water) aanvoer getoond):
Afbeeldingslocatie: https://tweakers.net/i/8tcLfl8o8b13JuRwGO2Dl4Yzjqw=/800x/filters:strip_exif()/f/image/XTcfu14f6bBibsNbBkxfxJRF.png?f=fotoalbum_large

Temperatuur return (idem):
Afbeeldingslocatie: https://tweakers.net/i/uA5elUtVPR_qY4sLjBCz8c2YuJI=/800x/filters:strip_exif()/f/image/lZD2qqK3exCX3Qjnrw5LlvSI.png?f=fotoalbum_large

Het is hoogste tijd voor andere projecten :P

[ Voor 102% gewijzigd door WackoH op 21-03-2025 23:19 ]


Acties:
  • 0 Henk 'm!

  • Grannetia
  • Registratie: Februari 2025
  • Laatst online: 17-06 15:45
Ha Jacco,
ik zat nog eens aandachtig naar je programma te kijken. Grote klasse weer hoor! Ik vroeg mij alleen nog af: is het juist opgemerkt dat niet alleen de communicatie bij R14 van de controlbox moet worden onderbroken, maar ook de communicatie van de aurea mcu naar de ot mcu bij pad 1? Zo ja, mag ik dan vragen hoe/waar je dat gedaan hebt? en heb je verder nog weerstanden moeten toevoegen om de communicatie aan alle kanten te laten slagen?
Bedankt weer! en veel plezier met de volgende uitdaging :D

edit: Misschien moet pad 2 ook wel ontkoppelt, anders zal de ot mcu mogelijk tegenstrijdige berichten ontvangen. Is het niet?

[ Voor 11% gewijzigd door Grannetia op 23-03-2025 16:14 . Reden: aangepaste veronderstelling ]


Acties:
  • +1 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
Klopt. Schematisch heb ik het hier weergegeven: WackoH in "Aurea 5 hybrid: interfaces met de buitenunit en thermostaat"

- R14 van1 kOhm tussen IC4 (microcontroller voor de warmtepomp) en T2 heb ik aan de bovenkant losgemaakt.
- Op het vrijgekomen gaatje heb ik TX3 van de ATmega2560 via een nieuwe 1kOhm weerstand (op T2 dus) aangesloten
- Via het losse pootje van R14 kan ik eventueel uitlezen wat IC4 naar de warmtepomp zou willen sturen. De weerstand beschermt IC dan tegen bv. per ongeluk kortsluiting maken

Het is inderdaad essentieel om de verbinding tussen IC4 en IC2 die langs Pad 2 loopt, te onderbreken als je hier een nieuw signaal op in wilt voeden, want anders bestaat het risico dat de uitgang van IC4 kapot gaat. Dat zou niet best zijn.
Ik heb dat gedaan door het printspoor tussen IC4 en Pad 2 door te snijden (alternatief is via met ene tussenvoetje werken, of het betreffende pootje uit het voetje te houden en er een draadje aan te solderen). Vervolgens heb ik op het printspoor tussen IC4 en de onderbreking een draadje gesoldeerd. Zo ook in pad2.
Enerzijds kan ik zo de oorspronkelijke situatie herstellen door beide draadjes te verbinden. Maar ik heb nu de uitgang van IC4 via een 1kOhm weerstand met de RX2, en Pad 2 met TX2 van de ATmega2560, verbonden.

Hieronder nog twee foto's die het hopelijk wat duidelijker te maken. Ja, heeft hoog prototype gehalte ;) Het handige van het breadboardje is dat ik gemakkelijk een logic analyzer en serial naar USB omzetter aan kan sluiten.

Afbeeldingslocatie: https://tweakers.net/i/I_B_zfzmjbPuGQBL9RqnMwRmVoE=/800x/filters:strip_exif()/f/image/qYRkPxgyHymeLfNggw7cWBkq.png?f=fotoalbum_largeAfbeeldingslocatie: https://tweakers.net/i/o5UnfJiDEsRCFz3W54rSJgbbLmI=/800x/filters:strip_exif()/f/image/7xstdfhBjIqKUuG6ZsashCFF.png?f=fotoalbum_large


PS: In eerdere berichten en code heb ik het over IC1 (WP / HP). Dat is op printplaat echter IC4.

[ Voor 4% gewijzigd door WackoH op 25-03-2025 00:10 ]


Acties:
  • 0 Henk 'm!

  • P1 modubus
  • Registratie: Maart 2023
  • Laatst online: 01-09 13:38
Goedendag De Flowmeter is geïnstalleerd. eenmaal even kunnen meten was toen 10.9 ltr/min

Afbeeldingslocatie: https://tweakers.net/i/PoPL6ss04Hy9UF7XD2S_uWcJDzk=/fit-in/4920x3264/filters:max_bytes(3145728):no_upscale():strip_icc():strip_exif()/f/image/VvZdwj3qKEjUBnPAJT9IPne7.jpg?f=user_large


Plaatje is ook klaar nu moet de WP nog draaien, helaas na het installeren van de flowmeter gaf de Anna weer niet thuis kreeg geen verbinding met de Smile, weer alles opnieuw geïnstalleerd na aantal malen geprobeerd te hebben weer contact, nu gaf deze weer geen stookwens naar de WP, kamer temp. 23 grd gevraagd, weer verlaagd na tijdje weer verhoogd en zo gelaten, cv ketel maar even handmatig aangezet Vanmorgen was het warm, gezien dat de WP om 23.05 uur inkwam en niet meer uitgegaan was, 22 grd kamer. Namiddag weer aangezet de Anna vragend gezet 23 grd. maar komt niet in wel 22 grd. in de kamer, testfase natuurlijk, wil het middags hoger stoken i.v.m. zonne-energie, avonds uit omdat de vloer opgewarmd is, kan normaal WP uit tot volgende dag, middags dan weer het zelfde verhaal,
het moet toch kunnen zonder veel aanpassingen dit geregeld te krijgen, ben wel meet en regel man maar kan niet zoals hierboven de print met software aanpassen.

Vind optie 3 ook het beste alles zoveel mogelijk origineel laten, de echtgenote moet ook simpel verder kunnen mocht ik plotseling wegvallen, alles zo gemaakt dat het dan naar de originele status gaat,

De buitentemperatuur beïnvloeding van 4 grd, is schakelend gemaakt zodat de WP de circulatiepomp niet onnodig laat draaien, en bij lage buitentemperatuur toch inkomt, maar niet bevriezen kan, bij hogere buitentemperatuur dit weer normaal zet, dan is dat toch overbodig en werkt alles normaal

De vraag aan de cv Ketel doorgezet naar de testknop dit kan ook gemonitord worden met telwerk en tijd als dit nodig zou zijn, kan ook nog gestart worden zonder cv ketel vraag, maar moet wel vanuit de Smile warmte gevraagd worden, helaas is dit nu niet het geval.

Net weer een mail naar Plugwise gestuurd, hop een een positief bericht, de Anna/ Smile is een mooi systeem als het werkt, alleen is dit niet vaak het geval, in het begin deed als het prima, maar nu een en al problemen, jammer toch begrijp niet dat het niet eenvoudig geregeld kan worden, en jullie er zoveel aan moeten passen zodat de WP naar wens draait.

Heeft iemand de originele uitgebreide printplaat getest of geïnstalleerd met modbus aansluiting.?
Weet niet wat er dan nodig aan aanpassingen nodig zijn en of dit nu nog kan.
Heb reeds de hamer klaar liggen.

Acties:
  • 0 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
Haha, herkenbaar :*)

Zelf heb ik de aanschaf van een flowmeter toch even geparkeerd. Ik ben namelijk vooral in de relative COP geïnteresseerd. Dus of de COP beter is bij lagere vermogens dan bij hogere. Als ik er van uit ga dat het debiet nauwelijks verandert, kan ik een aanname over het debiet doen en constant houden. Dan hoef ik alleen met de gemeten temperaturen en vermogen te werken.

Ik heb die berekening aan de software toegevoegd en met 9 kg/min krijg ik ene relatische waarde (niet perse de juiste) dat komt redelijk goed overeen met de kleine 11 die jij hebt gemeten) heb ik realistische COP waardes van bv. 4.0.

Wat me bij Plugwise opvalt, is dat de Opentherm communicatie tussen controlbox en ketel na inschakelen vaak niet goed op gang komt. Wat ik doen is de Homewizard Heatlink die in de lijn zit, even aanzetten. Die strijkt dan op de een van andere manier de rimpels glad en de communicatie is daarna goed. Misschien helpt een keertje reset in de controlbox drukken ook wel.

Acties:
  • 0 Henk 'm!

  • _JGC_
  • Registratie: Juli 2000
  • Laatst online: 00:46
@WackoH Ik overweeg om dit ding tweedehands of mogelijk nieuw te kopen. Deze warmtepomp kost geen drol (1300 nieuw, 750 tweedehands) en ik heb aan 5KW meer dan voldoende. Met behulp van dit topic moet dat ding all-electric te maken zijn lijkt me.

Begrijp ik nou goed dat je 1 van de beide controllers in de box compleet buitenspel zet en zelf tussen de OpenTherm controller en de warmtepomp controller gaat zitten?

Acties:
  • +1 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
Ja daar komt het eigenlijk wel op neer.

Het is een projectje wat is gegroeid in de tijd. De reden voor deze opzet is dat ik de functionaliteit van de Plugwise omgeving zoveel mogelijk wilde behouden. Vandaar dat de warmtepomp microcontroller nog wel mee mag doen om WP data aan Opentherm door te geven.

Met de kennis van nu zou ik beide microcontroller buiten spel zetten en alleen maar de delen uit de controlbox gebruiken die de spanningsomzetting voor de communicatie doen (dus zowel naar de WP als naar Opentherm).
Want het lijkt eigenlijk niet zo ingewikkeld om Opentherm communicatie in een microcontroller te zetten; daar zijn verschillende bibliotheken voor.
En dan eventueel nog communicatie met bv. HomeAssistant over WiFi toevoegen om meer data uit te lezen en eventueel te sturen.

Want ik nog niet weet, is hoe de foutcodes van de WP uit lezen. De controlbox kan dat wel (de rode LED gaat dan knipperen) maar ik heb het nog niet aangedurfd om fouten bewust op te wekken (door bv. stekkertjes van sensoren los te trekken) terwijl de communicatie wordt gemonitord.
Maar goed, als de WP het niet doet, merk je dat vanzelf wel. En dan is de foutcode op de WP zelf af te lezen.

Acties:
  • 0 Henk 'm!

  • _JGC_
  • Registratie: Juli 2000
  • Laatst online: 00:46
@WackoH Ik heb eigenlijk geen behoefte aan OpenTherm of plugwise. Heb nu een DIYLESS Thermostat2, die stuurt temperaturen naar de ketel en ketel doet altijd 6KW. Soms gaat alleen de pomp aan omdat de overshoot van de vorige stooksessie nog boven de vraag ligt. 3 minuten water uit de vloer in de berging door de radiatoren pompen zonder stoken en vervolgens gaat de ketel weer uit.

Verwacht dat ik met jouw tekeningen en voorbeeldcode de hele thermostaatregeling wel over durf te nemen, mogelijk kan die code ook gewoon op de DIYLESS draaien.

Acties:
  • 0 Henk 'm!

  • ThinkPad
  • Registratie: Juni 2005
  • Laatst online: 01:43
@WackoH Heb je AI al eens om hulp gevraagd voor het reverse engineeren? Ik heb gemerkt dat bijv. Claude AI goed is in code. Niet lullig bedoeld overigens, prima uitzoekwerk gedaan _O_ Maar juist als je al een 'haakje' hebt en dat voert aan AI dan kan deze je soms prima verder helpen om de laatste vraagtekens voor je op te lossen.

Acties:
  • +1 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
Jazeker heb ik AI gebruikt, maar dan voornamelijk voor:
  • Indentificeren van de versleutelingsprotocollen (dat lukte bij de communcaitie tussen beide microntroller wel, tussen warmtepomp en controlbox niet. Voor dat laatste heeft een medetweaker me op het goede spoor gezet).
  • Maken van code voor de subroutines
Zonder AI had het me met name dat laatste veel meer tijd gekost, en (nog) slechtere code opgeleverd.

Waar ik het niet voor heb gebruikt is patroonherkenning om meer parameters te identificeren die de WP uitstuurt. Dat zou leuke extra info opleveren en zou ik nog wel eens kunnen proberen.

Over die foutcodes: Ook AI kan geen foutcodes herkennen als die er niet zijn. (of komt misschien juist met een fout antwoord). Het werk zit niet in het indentificeren van de foutcodes, maar in het uitlokken. Daarbij kan in theorie ook iets kapot gaan. Dus dat doe ik maar liever niet.

Als ik trouwens nu ChatGPT vraag hoe de Chofu warmtepomp met de controller communiceert, komt eerst dat het via Modbus gaat.
Als ik zeg dat dat niet klopt, komt dit:
Afbeeldingslocatie: https://tweakers.net/i/3fOu9b-gk-KotRXxm-OrNOF9eYI=/fit-in/4000x4000/filters:no_upscale():strip_exif()/f/image/MG4dQ9RWLkMt6P9n2OGSPuKr.png?f=user_large
Ik ben door ChatGPT gecyborgd! _O-
Laat ik het maar al compliment beschouwen :P
Er is een bronverwijzing naar het draadje in Tweakers, dus da's wel netjes.

[ Voor 3% gewijzigd door WackoH op 09-08-2025 20:38 ]


Acties:
  • 0 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
@_JGC_ Als je de 'intelligentie' van de controlbox helemaal niet nodig hebt, is het makkelijkste om de linker microcontroller (IC1) uit z'n voetje te halen en dan via het lege voetje op een of ander manier een verbinding met de seriele send (pin 26) en de receive (pin 27) naar de buitenunit te maken.
Die werken op 5V, dus met een moderne microcontroller is het verstandig een level shifter naar 3.3V toe te voegen.
Zo houd je het veilig. Ik weet niet precies welke spanningen naar de buitenunit gaan. Maar er is een link met netspanning, dus daar wil je ver van wegblijven.

En je kunt de buitenunit dus ook laten koelen:
0 = uit, 1 = verwarmen en 2 = koelen.

Acties:
  • +1 Henk 'm!

  • _JGC_
  • Registratie: Juli 2000
  • Laatst online: 00:46
Ding opgehaald vandaag. Verkoper (installateur) deed me even uit de doeken hoe koud het was afgelopen winter want regeling was heel slecht en ketel werd vaak ook niet aangestuurd.
Toen ik 'm vertelde dat ik de chipjes ging wippen en de thermostaten niet gebruik maar de machine zelf aanstuur keek ie alsof ie water zag branden.

Zitten overigens de meest recente firmware chipjes in want Atlantic is af en aan geweest om dingen te testen.

Acties:
  • 0 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
Dan snap ik niet waarom ze het niet beter werkend hebben gekregen. In essentie is de combinatie Chofu / Atlantic / Plugwise namelijk niet slecht, hooguit wat gedateerd.

Atlantic had meer rekening moeten houden met:
  • De eigenschappen van de buitenunit. Die draait de eerst 20 min na een (eerste) start bij koud weer op ~400 W. Tja, dan ga je natuurlijk niet snel een aanvoersetpoint halen, al helemaal niet als een vloer de warmte opzuigt. Dit vermogen komt gewoon binnen, dus was kleine moeite geweest dit mee te nemen.
  • Waarom mensen een WP kopen. Nl. om gas te sparen, voor klimaat of portemonnee. Dus niet zo snel naar CV schakelen en tot lagere buitentemperaturen doordraaien .
Wat ik heb gedaan heeft natuurlijk hoog hobby/prototype gehalte.
Een plug en play oplossing waar veel mensen mee geholpen zouden zijn, is een open source projectje met:
  • Een printje dat in plaats van beide microcontrollers komt (dus in de bestaande voetjes prikt).
  • Daarop zit dan een microcontroller met Wifi voor OtA updates en integratie met bv. Home Assistant.
  • Wat dipswitches om bepaald gedrag in te stellen (voor degene die geen HA op willen tuigen):
  • - Via Plugwise (OpenTherm) of HA aansturen (dan ook koelen mogelijk)
  • - Tot welke buitentemperatuur moet de WP blijven werken (4,1, -2, altijd)
  • - Modus om omschakelen naar CV te bepalen: Eco (altijd WP) of comfort (een beetje zoals nu).
  • - Regelstrategie (radiatoren, vloer, beide)
  • - Mag maximaal vermogen of moet tandje minder (=hogere COP)
  • - Er is vast nog meer te bedenken
Helaas heb ik er geen tijd voor.

Acties:
  • +1 Henk 'm!

  • _JGC_
  • Registratie: Juli 2000
  • Laatst online: 00:46
@WackoH Het ding is verkocht als universele aanvulling op je ketel. Meneer had een pompverdeler, die doet niks met 35 graden, dus schakelt continu je ketel in want je huis wordt koud.
Hij draait nu full electric en heeft nog steeds de pompverdeler... iets met dwarse installateurs die doen wat ze altijd doen. Heb hem verteld dat ie die pompverdeler zsm moest lozen. Hij gaf de schuld aan de bypass.

Wat ik voor komende winter ga doen is een Arduino Uno kopen met een Ethernet shield. Die hang ik met CAT5 in mijn netwerk. De ketel stel ik in als HR Solo Tap en de thermostaat laat ik hangen. Thermostaat vraagt een setpoint aan de ketel en de ketel negeert 'm volkomen, maar ondertussen vis ik via MQTT de aanvoer uit de thermostaat en stuur ik die door naar de controller. Als thermostaat afschakelt omdat temperatuur bereikt is laat ik de warmtepomp op laagste vermogen doordraaien.

Ik heb nog gekeken naar OpenTherm Slave, maar de meeste sturingen gaan over master mode (thermostaat), ik weet niet wat ik allemaal moet implementeren om een werkende aansturing met OpenTherm te krijgen.
Kan evt altijd nog de opentherm pinnen uit de tweede socket aan de Uno hangen en OpenTherm implementeren. Dat wordt project voor volgend jaar als er een boilervat en driewegklep komt en de ketel verdwijnt.

Edit: misschien toch maar een 3/5V logic shifter aanschaffen en een Wemos d1 kloon toepassen die ik nog heb. Arduino Uno is wel heel basic zie ik.

[ Voor 5% gewijzigd door _JGC_ op 11-08-2025 18:47 ]


Acties:
  • 0 Henk 'm!

  • mhr-zip
  • Registratie: Januari 2001
  • Laatst online: 21:57
_JGC_ schreef op maandag 11 augustus 2025 @ 18:33:
@WackoH Het ding is verkocht als universele aanvulling op je ketel. Meneer had een pompverdeler, die doet niks met 35 graden, dus schakelt continu je ketel in want je huis wordt koud.
Hij draait nu full electric en heeft nog steeds de pompverdeler... iets met dwarse installateurs die doen wat ze altijd doen. Heb hem verteld dat ie die pompverdeler zsm moest lozen. Hij gaf de schuld aan de bypass.

Wat ik voor komende winter ga doen is een Arduino Uno kopen met een Ethernet shield. Die hang ik met CAT5 in mijn netwerk. De ketel stel ik in als HR Solo Tap en de thermostaat laat ik hangen. Thermostaat vraagt een setpoint aan de ketel en de ketel negeert 'm volkomen, maar ondertussen vis ik via MQTT de aanvoer uit de thermostaat en stuur ik die door naar de controller. Als thermostaat afschakelt omdat temperatuur bereikt is laat ik de warmtepomp op laagste vermogen doordraaien.

Ik heb nog gekeken naar OpenTherm Slave, maar de meeste sturingen gaan over master mode (thermostaat), ik weet niet wat ik allemaal moet implementeren om een werkende aansturing met OpenTherm te krijgen.
Kan evt altijd nog de opentherm pinnen uit de tweede socket aan de Uno hangen en OpenTherm implementeren. Dat wordt project voor volgend jaar als er een boilervat en driewegklep komt en de ketel verdwijnt.

Edit: misschien toch maar een 3/5V logic shifter aanschaffen en een Wemos d1 kloon toepassen die ik nog heb. Arduino Uno is wel heel basic zie ik.
Het is niet alleen bij die verkoper of dat hij water zag branden.

Zijn vijftig was nog schoon. Drinkwaterkaart.nl Drinkwaterspots


Acties:
  • 0 Henk 'm!

  • _JGC_
  • Registratie: Juli 2000
  • Laatst online: 00:46
mhr-zip schreef op maandag 11 augustus 2025 @ 19:22:
[...]


Het is niet alleen bij die verkoper of dat hij water zag branden.
Mijn zwager is ook installateur die vanuit de CV ketels de wereld van de warmtepompen is ingerold. Die heeft dus ook dit ding en was altijd van mening dat warmtepompen waardeloos waren en we na 2050 nog gas stoken, want "onder de 5 graden doen ze niks". Heeft ook al vanalles aan zijn installatie verbouwd om het rendement beter te krijgen, maar zet geen zoden aan de dijk.

Acties:
  • 0 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
_JGC_ schreef op maandag 11 augustus 2025 @ 18:33:
@WackoH Het ding is verkocht als universele aanvulling op je ketel. Meneer had een pompverdeler, die doet niks met 35 graden, dus schakelt continu je ketel in want je huis wordt koud.
Hij draait nu full electric en heeft nog steeds de pompverdeler... iets met dwarse installateurs die doen wat ze altijd doen. Heb hem verteld dat ie die pompverdeler zsm moest lozen. Hij gaf de schuld aan de bypass.
De pompverdeler hoeft geen probleem te zijn als die voldoende aanvoer kan krijgen en de pomp niet onnodig hard draait.
In een hybride opstelling is het zelf wel verstandig om die te hebben zodat de vloer bij een storing niet loeiheet wordt (en de kans op zo'n storing is aanzienlijk in de huidige opzet!!).

Ik heb zelf een (sjieke) pompverdeler (Therminon Flex line LTV met instelbare recycle; mooi modulair systeem) met 26 mm aan- en afvoer en dat werkt prima. De (grote) thermostaatkraan werkt als temperatuur beveiliging op de toevoer. Laat ik de pomp uit dan gaat de meeste warmte naar radiatoren. Zet ik 'm aan dan zuigt de pomp zoveel water aan dat er een mooie verdeling tussen radiatoren en vloer ontstaat.
Wat ik voor komende winter ga doen is een Arduino Uno kopen met een Ethernet shield. Die hang ik met CAT5 in mijn netwerk. De ketel stel ik in als HR Solo Tap en de thermostaat laat ik hangen. Thermostaat vraagt een setpoint aan de ketel en de ketel negeert 'm volkomen, maar ondertussen vis ik via MQTT de aanvoer uit de thermostaat en stuur ik die door naar de controller. Als thermostaat afschakelt omdat temperatuur bereikt is laat ik de warmtepomp op laagste vermogen doordraaien.
Denk ook aan de PAF (Partner Acceptance Factor). Het moet ook werken als je servertje is gecrashed.
Ik heb nog gekeken naar OpenTherm Slave, maar de meeste sturingen gaan over master mode (thermostaat), ik weet niet wat ik allemaal moet implementeren om een werkende aansturing met OpenTherm te krijgen.
Er zijn Opentherm libraries in de Arduino IDE. Bv. deze https://github.com/jpraus...erm/blob/master/README.md bevat volgens de beschrijving master, slave en gateway modes.
Kan evt altijd nog de opentherm pinnen uit de tweede socket aan de Uno hangen en OpenTherm implementeren. Dat wordt project voor volgend jaar als er een boilervat en driewegklep komt en de ketel verdwijnt.

Edit: misschien toch maar een 3/5V logic shifter aanschaffen en een Wemos d1 kloon toepassen die ik nog heb. Arduino Uno is wel heel basic zie ik.
Ik heb zelf wel een zwak voor de Arduino hardware vanwege de Italiaanse roots. Een Uno is een brede serie (vanaf vrijwel zelfde MCU als in Controlbox tot aan iets met cryptoprocessor). De eenvoudigste kan de buitenunit probleemloos aansturen. Maar Ethernet erbij wordt wel tricky want de timing met de buitenunit moet wel kloppen. Een Mega is dan nodig. Heeft ook voldoende seriele poorten. Maar met een WeMOs i.c.m. Arduino IDE heb je een goedkope oplossing,

Acties:
  • 0 Henk 'm!

  • _JGC_
  • Registratie: Juli 2000
  • Laatst online: 00:46
Net nog even gekeken naar de ESPHome versie van de DIYLESS, maar die heeft eigenlijk helemaal niet zoveel OpenTherm dingen nodig. ch_setpoint accepteren, flame_on, ch_active, ch_temperature, rel_mod_level teruggeven en dan heeft 'ie al meer data dan ie nu van de Intergas ketel krijgt.

Acties:
  • 0 Henk 'm!

  • _JGC_
  • Registratie: Juli 2000
  • Laatst online: 00:46
@WackoH Heb jij enig idee wat de controller doet om de ketel het over te laten nemen? Als ik je pin overzicht zo lees zou het relais dat moeten doen.

Acties:
  • 0 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
Het grote relais (K3) kan de OpenTherm aansluiting naar de ketel kortsluiten, gelijk aan wat een aan-uit thermostaat doet. Dus ik vermoed dat de controlbox op deze manier de ketel aan/uit zet als de dipswitches van S3 op 'off' staan.

Beide andere relais (K1 verbonden Plugwise smile met en K2 met ketel) verbinden de Smile en ketel direct met elkaar in bekrachtigde toestand. Dit is wellicht hoe de controlbox de ketel in OpenTherm-mode aan zet. Maar ik heb dat verder niet precies uitgezocht.

Acties:
  • 0 Henk 'm!

  • Maartenkromhout
  • Registratie: Augustus 2025
  • Laatst online: 05-09 21:43
Dat is een mooie toevoeging op het geheel.

Vraag heeft deze ook Wifi mogelijkheden zodat je hem in Home assistant kan uitlezen? Zit hard te denken om deze eerdaags te gaan aanpassen dat ik dit ook kan toepassen.

Acties:
  • 0 Henk 'm!

  • P1 modubus
  • Registratie: Maart 2023
  • Laatst online: 01-09 13:38
Goedendag allemaal, het stookseizoen zal weldra beginnen, zal dan ook weer deelnemen en mijn ervaring delen, inmiddels de 2de warmtepomp bij familie inbedrijf gezet, met de nodige problemen, maar de test gebeurd pas met koudere weersomstandigheden.
Om zoveel mogelijk de energie van de zonnepanelen te benutten heb ik het volgende ingesteld,
Thermostaat morgens 8 uur 20.0 Grd C middags naar 22 grd, dan koelt de buitentemperatuur en gaat de WP draaien als nodig.
Afbeeldingslocatie: https://tweakers.net/i/kUvcjaL3xoV1xBT_I_FKDWFnjtY=/800x/filters:strip_icc():strip_exif()/f/image/33rirPW9TYunwNKkgWg1COa0.jpg?f=fotoalbum_large
Plugwise had mij doorgegeven dat het systeem Anna, Smile, Warmtepomp met Zonnepanelen geen beste combinatie was, zoals ik het wilde regelen.
Het laatste van het stookseizoen ging het prima met deze instellingen, middags opwarmen van de vloer en avonds werd opgeslagen warmte weer afgegeven aan de kamer, natuurlijk ga ik deze bijstellen als dit nodig zal zijn, denk aan de stooklijn nu maximaal 43 grd watertemperatuur.
Heb de cv ketel niet aangesloten op deze regeling, de warmtevraag naar de cv. ketel heb ik een externe regeling aangeboden, kan een alarm genereren met een melding als ik dit nodig vind, kan ook de warmtevraag automatisch doorzetten naar de WP aanvraag voor 20 minuten (drukker WP even kortsluiten)
De buiten temperatuur weerstand van de WP kan ik verlagen met een aantal graden zodat de WP langer in bedrijf blijft bij lage buitentemperaturen, meer heb ik niet aangepast.
Heb ook een testkoffer gemaakt zodat ik de bypass optimaal kan instellen, bij Maximaal bedrijf, Dt van 5-6 grd vooral als jullie de regeling overnemen is het belangrijk dat het hydraulisch goed werkt.
Zelf houd ik het zo origineel mogelijk, hoef ook niet te koelen, de Cv ketel regeling heb ik gewoon gelaten op de originele Remeha thermostaat, deze kan ik via de app bedienen als het nodig mocht zijn, de instellingen.
Deze aanpassingen zouden ook met een Arduino mogelijk zijn zolang je de maar potentiaal vrij maakt,
Tot zover, tot in het stookseizoen

Acties:
  • 0 Henk 'm!

  • Maartenkromhout
  • Registratie: Augustus 2025
  • Laatst online: 05-09 21:43
Misschien een gekke vraag. Maar zou jij in 1 post kunnen uitleggen wat je precies moet doen om het te laten werken. Ik vind nu overal stukjes bij elkaar. heb wel het idee dat het gaat lukken alleen durf ik het net niet aan.

En welk TFT scherm heb jij op de Arduino Mega?

Acties:
  • 0 Henk 'm!

  • workzone
  • Registratie: November 2023
  • Laatst online: 11-09 07:26
Geweldige informatie @WackoH! Ongeveer twee jaar geleden had ik de Aurea 5 op mijn lijstje staan, alleen het uitschakelen onder 4/5 graden heeft me toen tegen gehouden. Als deze info toen beschikbaar was geweest dan wist ik het wel.

De meeste andere WP's waren toen 3400,- + en ik heb toen maar zelf een WP gemaakt van een AUX AC + Heatexchanger + Pomp, HA stuurt de AUX & Pomp aan op vermogen via een PID temp controller.

MP II 5000 | 25kWh LFP | Solar 5.4kWp Oost / 1.2kWp Zuid / 1.7kWp West / 0.6kWp Noord | L/L & L/W WP | Tubbergen


Acties:
  • 0 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
@workzone Dat klinkt als dat het aansturen van Chofu buitenunit van de Aurea voor jou een eitje zou zijn geweest.
Ik heb de mijne afgelopen december voor 600 Euro tweedehands gekocht en zelf aangesloten (~100 Euro materiaal en weekendje werk). Nog een paar wintermaanden en dan is mijn financiele investering al terugverdiend *O*
De tijd die ik er in heb gestopt om het kreng fatsoenlijk aan de praat te krijgen... :X moeten we maar als hobby en hulp aan wereld zien. :9B

@Maartenkromhout Ik zou idd. de info die over veel post verspreid staat eens een keer samen moeten vatten. Ik heb dat wel een paar keer gedeeltelijk gedaan, maar niet als geheel.
Overigens is deze info inmiddels onderdeel van Ch*tG#T is geworden (inclusief verwijzing naar de Tweakers draadjes). Dus die kun je ook om raad vragen.

Het scherm dat ik op de Mega2560 heb is deze https://www.tinytronics.n...s-mega-compatible-ili9486

Acties:
  • 0 Henk 'm!

  • _JGC_
  • Registratie: Juli 2000
  • Laatst online: 00:46
Ben hier iets meer kwijt: 750 voor de WP, 250 voor materialen en gereedschap, maar dan hang ik ook een T33 op in een slaapkamer omdat het water er dan toch uit is...

Maar zoals het nu lijkt met de informatie die ik uit dit topic heb en mijn huidige gasverbruik wordt het terugverdienen in 2 jaar tijd. Als het deze winter bevalt komt er ook nog een boilervat en driewegklep bij, dan kan de ketel weg en kan Energiewacht opgezegd worden.

Buurman was afgelopen maandag al druk aan het SMS'en over angst voor lawaai van dit ding. Hij had alleen de grindbak en de rubber voetjes gezien.

Toch leuk hoe je op Marktplaats aan het zoeken bent naar een boiler om je terugleverboete terug te dringen, dan een Inventum Ecolution combi50 tegenkomt, dat ding afschiet vanwege hoogte en klachten over lawaai en kort daarna erachter komt dat die Atlantic Aurea dingen voor een habbekrats te krijgen zijn. Eerste zoekactie op GoT wees me naar dit topic en even later was het idee "doen we gewoon!"

Acties:
  • +2 Henk 'm!

  • Maartenkromhout
  • Registratie: Augustus 2025
  • Laatst online: 05-09 21:43
Ik heb een code gemaakt voor een R4 met Wifi en de Mega.

Ik zal kijken of ik hem hier kan plaatsen. Is wel echt een eerste versie. heb hem nog niet hangen aan de WP maar het web deel werkt wel grappig.

Afbeeldingslocatie: https://tweakers.net/i/U04-tklcDxl9pmmKEemdOa8MAOg=/800x/filters:strip_icc():strip_exif()/f/image/1tx2ylCLT0c4smCC3QUpzKGm.jpg?f=fotoalbum_large

Dit is voor de Mega
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
/*
 * Arduino Mega 2560 - Complete Atlantic Aurea Heat Pump Interface
 * + Communication with R4 WiFi for web interface
 * 
 * Hardware connections:
 * Heat Pump Interface:
 *   Serial1 (Pin 19): OT MCU communication (9600 bps) - Pad 1
 *   Serial2 (Pin 17/16): HP MCU bidirectional (9600 bps) - Pad 2  
 *   Serial3 (Pin 15/14): Heat Pump direct (666 bps)
 *   
 * R4 WiFi Communication:
 *   Pin 18 (TX1) → R4 Pin 2 (RX)
 *   Pin 19 (RX1) ← R4 Pin 3 (TX)
 *   GND ↔ GND
 */

// Heat pump timing constants
const uint32_t MESSAGE_SERIAL_MCU_INTERVAL = 4200;       // 4.2 seconds between MCU messages
const uint16_t HP_MSG_DELAYS[] = {412, 487, 502, 531};   // Heat pump message delays in ms
const uint8_t HP_MSG_COUNT = 4;
const uint32_t R4_UPDATE_INTERVAL = 2000;                // Send data to R4 every 2 seconds

// Timing variables
uint32_t currentMillis = 0;
uint32_t previousMessageSerial1Sent = 0;
uint32_t previousR4Update = 0;
uint8_t currentHPMessageIndex = 0;
bool sendSerial2 = false;
bool r4Connected = false;

// Message counters for monitoring
uint32_t messageCounters[4] = {0, 0, 0, 0}; // serial1, serial2, serial3, received

// Communication buffers
const uint8_t MAX_BUFFER_SIZE = 64;
uint8_t serial1Buffer[MAX_BUFFER_SIZE];
uint8_t serial2Buffer[MAX_BUFFER_SIZE];
uint8_t serial3Buffer[MAX_BUFFER_SIZE];
uint8_t bufferIndex1 = 0, bufferIndex2 = 0, bufferIndex3 = 0;

// Atlantic Aurea test data arrays
const byte dataArrayOT_IC2_IC1[] = {44,20,154,0,0,33,14,32,29,8,0,78};
const byte dataArrayOT_IC1_IC2[] = {37,20,154,0,0,33,14,32,29,2,0,65};

// Heat pump message structure
struct HeatPumpMessage {
  const byte* data;
  uint8_t length;
};

const byte dataHeatPump0[] = {25,0,8,0,0,0,217,181,145,0,12,13,7,4,47,90,206,225,230,223,0};
const byte dataHeatPump1[] = {25,1,12,0,0,0,0,0,0,0,170,53,145,1,13,80,0,0,0,0,0,138,82,164,39,0};
const byte dataHeatPump2[] = {25,2,8,1,5,0,85,243,145,2,18,18,1,253,0,49,0,1,0,2,0,0,38,216,23,240,0};
const byte dataHeatPump3[] = {25,3,8,178,2,0,193,154,145,3,20,120,81,168,151,21,0,83,53,2,0,24,0,0,0,0,233,241,0};

const HeatPumpMessage heatPumpMessages[] = {
  {dataHeatPump0, sizeof(dataHeatPump0)},
  {dataHeatPump1, sizeof(dataHeatPump1)},
  {dataHeatPump2, sizeof(dataHeatPump2)},
  {dataHeatPump3, sizeof(dataHeatPump3)}
};

void setup() {
  // Serial initialization
  Serial.begin(115200);   // Debug output
  Serial1.begin(9600);    // OT MCU communication (was heat pump, now using Serial2/3)
  Serial2.begin(9600);    // HP MCU communication
  Serial3.begin(666);     // Heat pump direct communication (666 baud - special)
  
  Serial.println("=== Arduino Mega 2560 - Atlantic Aurea Complete System ===");
  Serial.println("Heat Pump Interface:");
  Serial.println("  Serial1: OT MCU (9600 bps)");
  Serial.println("  Serial2: HP MCU (9600 bps)"); 
  Serial.println("  Serial3: Heat Pump Direct (666 baud)");
  Serial.println("R4 WiFi Communication:");
  Serial.println("  Pin 18 → R4 Pin 2, Pin 19 ← R4 Pin 3");
  Serial.println("");
  
  delay(2000); // Give R4 time to start
  initR4Communication();
  
  Serial.println("Starting Atlantic Aurea heat pump monitoring...");
}

void loop() {
  currentMillis = millis();
  
  // Handle all heat pump communications
  handleSerial1Communication();  // OT MCU
  handleSerial2Communication();  // HP MCU  
  handleSerial3Communication();  // Heat pump direct
  
  // Process received data from heat pump
  processReceivedData();
  
  // Send data to R4 WiFi for web interface
  if (r4Connected && (currentMillis - previousR4Update >= R4_UPDATE_INTERVAL)) {
    sendDataToR4();
    previousR4Update = currentMillis;
  }
  
  // Check R4 responses
  checkR4Response();
  
  delay(50); // Small delay for system stability
}

void initR4Communication() {
  Serial.println("Initializing R4 WiFi communication...");
  
  // Send init command (using Serial1 for R4 comm via pins 18/19)
  Serial1.println("MEGA_INIT");
  delay(500);
  
  // Wait for R4 response
  unsigned long timeout = millis() + 5000;
  String response = "";
  
  while (millis() < timeout) {
    if (Serial1.available()) {
      response = Serial1.readStringUntil('\n');
      response.trim();
      if (response.length() > 0) break;
    }
    delay(100);
  }
  
  if (response.indexOf("R4_READY") != -1 || response.indexOf("R4:") != -1) {
    r4Connected = true;
    Serial.println("✅ R4 WiFi connected!");
    Serial.println("R4 Response: " + response);
  } else {
    Serial.println("⚠️ R4 WiFi not responding properly");
    Serial.println("Response: '" + response + "'");
    Serial.println("Continuing without R4...");
  }
}

void handleSerial1Communication() {
  // NOTE: Serial1 is now used for R4 communication
  // Original OT MCU communication moved to Serial2 for simplicity
  
  // Send periodic message on Serial2 (simulating OT MCU)
  if (currentMillis - previousMessageSerial1Sent >= MESSAGE_SERIAL_MCU_INTERVAL) {
    Serial2.write(dataArrayOT_IC1_IC2, sizeof(dataArrayOT_IC1_IC2));
    previousMessageSerial1Sent = currentMillis;
    sendSerial2 = true; // Trigger second message
    messageCounters[0]++; // Count as Serial1 equivalent
    
    Serial.print("Sent OT MCU message: ");
    printByteArray(dataArrayOT_IC1_IC2, sizeof(dataArrayOT_IC1_IC2));
  }
}

void handleSerial2Communication() {
  // Send follow-up message when triggered
  if (sendSerial2) {
    delay(100); // Small delay between messages
    Serial2.write(dataArrayOT_IC2_IC1, sizeof(dataArrayOT_IC2_IC1));
    sendSerial2 = false;
    messageCounters[1]++;
    
    Serial.print("Sent HP MCU message: ");
    printByteArray(dataArrayOT_IC2_IC1, sizeof(dataArrayOT_IC2_IC1));
  }
}

void handleSerial3Communication() {
  // Heat pump direct communication (666 baud)
  static uint32_t lastHPMessageTime = 0;
  static bool waitingForDelay = false;
  
  if (!waitingForDelay) {
    const HeatPumpMessage& currentMsg = heatPumpMessages[currentHPMessageIndex];
    Serial3.write(currentMsg.data, currentMsg.length);
    messageCounters[2]++;
    
    Serial.print("Heat Pump Msg " + String(currentHPMessageIndex) + ": ");
    printByteArray(currentMsg.data, currentMsg.length);
    
    lastHPMessageTime = currentMillis;
    waitingForDelay = true;
  }
  
  if (waitingForDelay && (currentMillis - lastHPMessageTime >= HP_MSG_DELAYS[currentHPMessageIndex])) {
    waitingForDelay = false;
    currentHPMessageIndex = (currentHPMessageIndex + 1) % HP_MSG_COUNT;
  }
}

void processReceivedData() {
  // Check Serial2 for heat pump responses
  while (Serial2.available() && bufferIndex2 < MAX_BUFFER_SIZE - 1) {
    serial2Buffer[bufferIndex2++] = Serial2.read();
  }
  
  // Check Serial3 for heat pump responses  
  while (Serial3.available() && bufferIndex3 < MAX_BUFFER_SIZE - 1) {
    serial3Buffer[bufferIndex3++] = Serial3.read();
  }
  
  // Process received messages
  if (bufferIndex2 > 0) {
    Serial.print("Received from HP MCU: ");
    printByteArray(serial2Buffer, bufferIndex2);
    messageCounters[3]++;
    bufferIndex2 = 0;
  }
  
  if (bufferIndex3 > 0) {
    Serial.print("Received from Heat Pump: ");
    printByteArray(serial3Buffer, bufferIndex3);
    messageCounters[3]++;
    bufferIndex3 = 0;
  }
}

void sendDataToR4() {
  if (!r4Connected) return;
  
  // Create JSON data for web interface
  String jsonData = "{";
  jsonData += "\"timestamp\":" + String(currentMillis);
  jsonData += ",\"uptime\":" + String(currentMillis / 1000);
  jsonData += ",\"counters\":{";
  jsonData += "\"ot_mcu\":" + String(messageCounters[0]);
  jsonData += ",\"hp_mcu\":" + String(messageCounters[1]); 
  jsonData += ",\"heat_pump\":" + String(messageCounters[2]);
  jsonData += ",\"received\":" + String(messageCounters[3]);
  jsonData += "},\"status\":{";
  jsonData += "\"current_hp_msg\":" + String(currentHPMessageIndex);
  jsonData += ",\"system\":\"Atlantic Aurea\"";
  jsonData += ",\"free_memory\":" + String(freeMemory());
  
  // Calculate message rate (messages per minute)
  uint32_t totalMessages = messageCounters[0] + messageCounters[1] + messageCounters[2];
  float messageRate = (float)totalMessages / ((float)currentMillis / 60000.0);
  if (currentMillis < 60000) messageRate = 0; // Avoid crazy numbers at startup
  
  jsonData += ",\"message_rate\":" + String(messageRate, 1);
  jsonData += "}}";
  
  // Send to R4 WiFi
  Serial1.println("DATA:" + jsonData);
  Serial.println("📤 Data sent to R4 WiFi");
}

void checkR4Response() {
  if (Serial1.available()) {
    String response = Serial1.readStringUntil('\n');
    response.trim();
    
    if (response.length() > 0) {
      Serial.println("📨 R4 WiFi: " + response);
      
      if (response.indexOf("WIFI_CONNECTED") != -1) {
        int colonPos = response.indexOf(":");
        if (colonPos != -1) {
          String ip = response.substring(colonPos + 1);
          Serial.println("");
          Serial.println("🌐 SUCCESS! Atlantic Aurea Web Monitor Available:");
          Serial.println("🌐 http://" + ip);
          Serial.println("");
        }
      }
    }
  }
}

void printByteArray(const byte* data, uint8_t length) {
  for (uint8_t i = 0; i < length; i++) {
    if (data[i] < 16) Serial.print("0");
    Serial.print(data[i], HEX);
    if (i < length - 1) Serial.print(" ");
  }
  Serial.println();
}

int freeMemory() {
  char top;
  extern char *__brkval;
  extern char __bss_end;
  return __brkval ? &top - __brkval : &top - &__bss_end;
}

[ Voor 3% gewijzigd door Maartenkromhout op 05-09-2025 21:38 ]


Acties:
  • +2 Henk 'm!

  • Maartenkromhout
  • Registratie: Augustus 2025
  • Laatst online: 05-09 21:43
Dit is de code voor de R4 paste niet in 1 post
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
/*
 * Arduino Uno R4 WiFi - Atlantic Aurea with Home Assistant MQTT Discovery
 * Full heat pump monitoring and control with auto-discovery
 * Made by Maarten K
 * 
 * Hardware connections:
 * Pin 2 (RX) ← Arduino Mega Pin 18 (TX)
 * Pin 3 (TX) → Arduino Mega Pin 19 (RX)  
 * GND ↔ GND
 */

#include <WiFiS3.h>
#include <SoftwareSerial.h>
#include <ArduinoMqttClient.h>
#include <ArduinoJson.h>

// WiFi credentials
const char* ssid = "";
const char* password = "";

// Anna Thermostat settings
const char* annaIP = "192.168.1.";
const int annaPort = 80;
const char* annaUsername = "smile";
const char* annaPassword = "";

// MQTT Settings
const char* mqttBroker = "192.168.1.";
const int mqttPort = 1883;
const char* mqttUser = "";
const char* mqttPass = "";
const char* mqttClientId = "ArduinoR4_HeatPump";

// MQTT Topics
const char* mqttTopicPrefix = "atlantic_aurea/";
const char* mqttAnnaPrefix = "anna/";
const char* discoveryPrefix = "homeassistant/";

// Communication with Mega
SoftwareSerial megaComm(2, 3); // RX, TX

// Web server and MQTT
WiFiServer server(80);
WiFiClient wifiClient;
MqttClient mqttClient(wifiClient);

// Data from Arduino Mega
String lastMegaData = "{}";
unsigned long lastDataReceived = 0;
bool megaConnected = false;
bool wifiConnected = false;
bool mqttConnected = false;
bool discoveryPublished = false;

// Heat Pump Data Structure
struct HeatPumpData {
  float waterTemp = 0;
  float outsideTemp = 0;
  float returnTemp = 0;
  float evaporatorTemp = 0;
  float compressorTemp = 0;
  float targetTemp = 0;
  float currentPower = 0;
  bool compressorActive = false;
  bool defrostActive = false;
  bool heatingActive = false;
  bool coolingActive = false;
  bool dhwActive = false;  // Domestic Hot Water
  String operationMode = "standby";
  String errorCode = "none";
  int fanSpeed = 0;
  float cop = 0;  // Coefficient of Performance
} hpData;

// Anna thermostat data
float annaCurrentTemp = 0.0;
float annaSetpoint = 0.0;
String annaPresetMode = "home";
bool annaHeating = false;
unsigned long lastAnnaUpdate = 0;
const unsigned long annaUpdateInterval = 30000;

// MQTT timing
unsigned long lastMqttPublish = 0;
const unsigned long mqttPublishInterval = 30000;
unsigned long lastMqttReconnect = 0;
unsigned long lastDiscoveryPublish = 0;

// System info
unsigned long startTime = 0;
String systemStatus = "Starting...";
int refreshInterval = 30;

// Device info for Home Assistant
String deviceName = "Atlantic Aurea Heat Pump";
String deviceId = "atlantic_aurea_001";
String softwareVersion = "2.0.0";

void setup() {
  Serial.begin(115200);
  megaComm.begin(9600);
  
  startTime = millis();
  
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);
  
  Serial.println("=== Arduino R4 WiFi - Atlantic Aurea HA Integration ===");
  Serial.println("Version: " + String(softwareVersion));
  
  // Startup LED sequence
  for (int i = 0; i < 3; i++) {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(300);
    digitalWrite(LED_BUILTIN, LOW);
    delay(300);
  }
  
  initWiFi();
  
  if (wifiConnected) {
    connectMQTT();
  }
  
  megaComm.println("R4_READY");
  Serial.println("Sent R4_READY to Mega");
  
  if (wifiConnected) {
    updateAnnaData();
  }
}

void loop() {
  handleMegaCommunication();
  
  if (wifiConnected) {
    handleWebServer();
    
    if (millis() - lastAnnaUpdate > annaUpdateInterval) {
      updateAnnaData();
    }
    
    // MQTT operations
    if (!mqttClient.connected() && millis() - lastMqttReconnect > 5000) {
      connectMQTT();
      lastMqttReconnect = millis();
    }
    
    if (mqttClient.connected()) {
      mqttClient.poll();
      
      // Publish discovery if needed
      if (!discoveryPublished || millis() - lastDiscoveryPublish > 3600000) { // Re-publish every hour
        publishHomeAssistantDiscovery();
        lastDiscoveryPublish = millis();
      }
      
      // Publish data periodically
      if (millis() - lastMqttPublish > mqttPublishInterval) {
        publishMQTTData();
        lastMqttPublish = millis();
      }
    }
  }
  
  updateStatusLED();
  delay(50);
}

void initWiFi() {
  systemStatus = "Connecting to WiFi...";
  Serial.println("Connecting to WiFi: " + String(ssid));
  
  if (WiFi.status() == WL_NO_MODULE) {
    systemStatus = "WiFi module not found!";
    Serial.println("ERROR: WiFi module not found!");
    return;
  }
  
  WiFi.begin(ssid, password);
  
  int attempts = 0;
  while (WiFi.status() != WL_CONNECTED && attempts < 30) {
    delay(500);
    Serial.print(".");
    attempts++;
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  }
  
  if (WiFi.status() == WL_CONNECTED) {
    wifiConnected = true;
    systemStatus = "WiFi Connected";
    
    Serial.println("\nWiFi connected successfully!");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
    Serial.print("Signal strength: ");
    Serial.print(WiFi.RSSI());
    Serial.println(" dBm");
    
    server.begin();
    Serial.println("Web server started on port 80");
    Serial.println("===================================");
    Serial.println("Atlantic Aurea Monitor available at:");
    Serial.println("http://" + WiFi.localIP().toString());
    Serial.println("===================================");
    
    megaComm.println("WIFI_CONNECTED:" + WiFi.localIP().toString());
    
    for (int i = 0; i < 5; i++) {
      digitalWrite(LED_BUILTIN, HIGH);
      delay(100);
      digitalWrite(LED_BUILTIN, LOW);
      delay(100);
    }
  } else {
    systemStatus = "WiFi connection failed";
    Serial.println("\nWiFi connection failed!");
  }
}

void connectMQTT() {
  if (strlen(mqttBroker) == 0) return;
  
  Serial.print("Connecting to MQTT broker: ");
  Serial.println(mqttBroker);
  
  mqttClient.setId(mqttClientId);
  
  if (strlen(mqttUser) > 0) {
    mqttClient.setUsernamePassword(mqttUser, mqttPass);
  }
  
  // Set will message for availability
  String availTopic = String(mqttTopicPrefix) + "availability";
  mqttClient.beginWill(availTopic, false, true);
  mqttClient.print("offline");
  mqttClient.endWill();
  
  if (mqttClient.connect(mqttBroker, mqttPort)) {
    mqttConnected = true;
    discoveryPublished = false; // Force re-publish discovery
    Serial.println("MQTT connected successfully!");
    
    // Publish online status
    mqttClient.beginMessage(availTopic, true);
    mqttClient.print("online");
    mqttClient.endMessage();
    
    // Subscribe to control topics
    subscribeToControlTopics();
    
    // Set message callback
    mqttClient.onMessage(onMqttMessage);
    
    Serial.println("MQTT setup complete");
    
  } else {
    mqttConnected = false;
    Serial.print("MQTT connection failed! Error: ");
    Serial.println(mqttClient.connectError());
  }
}

void subscribeToControlTopics() {
  // Anna control topics
  mqttClient.subscribe(String(mqttAnnaPrefix) + "setpoint/set");
  mqttClient.subscribe(String(mqttAnnaPrefix) + "preset/set");
  
  // Heat pump control topics
  mqttClient.subscribe(String(mqttTopicPrefix) + "target_temp/set");
  mqttClient.subscribe(String(mqttTopicPrefix) + "mode/set");
  mqttClient.subscribe(String(mqttTopicPrefix) + "power/set");
  mqttClient.subscribe(String(mqttTopicPrefix) + "dhw/set");
  
  Serial.println("Subscribed to all control topics");
}

void publishHomeAssistantDiscovery() {
  Serial.println("Publishing Home Assistant Discovery messages...");
  
  // Device info for all entities
  String deviceInfo = "\"device\":{";
  deviceInfo += "\"identifiers\":[\"" + deviceId + "\"],";
  deviceInfo += "\"name\":\"" + deviceName + "\",";
  deviceInfo += "\"model\":\"Atlantic Aurea\",";
  deviceInfo += "\"manufacturer\":\"Atlantic\",";
  deviceInfo += "\"sw_version\":\"" + softwareVersion + "\"";
  deviceInfo += "}";
  
  // Anna Thermostat as Climate entity
  publishClimateDiscovery("anna", "Anna Thermostat", deviceInfo);
  
  // Heat Pump sensors
  publishSensorDiscovery("water_temp", "Water Temperature", "°C", "temperature", deviceInfo);
  publishSensorDiscovery("outside_temp", "Outside Temperature", "°C", "temperature", deviceInfo);
  publishSensorDiscovery("return_temp", "Return Temperature", "°C", "temperature", deviceInfo);
  publishSensorDiscovery("evaporator_temp", "Evaporator Temperature", "°C", "temperature", deviceInfo);
  publishSensorDiscovery("compressor_temp", "Compressor Temperature", "°C", "temperature", deviceInfo);
  publishSensorDiscovery("target_temp", "Target Temperature", "°C", "temperature", deviceInfo);
  publishSensorDiscovery("current_power", "Current Power", "kW", "power", deviceInfo);
  publishSensorDiscovery("cop", "COP", "", "power_factor", deviceInfo);
  publishSensorDiscovery("fan_speed", "Fan Speed", "rpm", "", deviceInfo);
  
  // Binary sensors
  publishBinarySensorDiscovery("compressor_active", "Compressor Active", "running", deviceInfo);
  publishBinarySensorDiscovery("defrost_active", "Defrost Active", "cold", deviceInfo);
  publishBinarySensorDiscovery("heating_active", "Heating Active", "heat", deviceInfo);
  publishBinarySensorDiscovery("cooling_active", "Cooling Active", "cold", deviceInfo);
  publishBinarySensorDiscovery("dhw_active", "DHW Active", "moisture", deviceInfo);
  
  // Selects and controls
  publishSelectDiscovery("mode", "Operation Mode", "auto,heat,cool,dhw,standby", deviceInfo);
  publishNumberDiscovery("target_temp_control", "Target Temperature Control", 15, 55, 0.5, deviceInfo);
  publishSwitchDiscovery("power", "Power", deviceInfo);
  publishSwitchDiscovery("dhw_boost", "DHW Boost", deviceInfo);
  
  // System sensors
  publishSensorDiscovery("uptime", "System Uptime", "s", "duration", deviceInfo);
  publishSensorDiscovery("wifi_rssi", "WiFi Signal", "dBm", "signal_strength", deviceInfo);
  publishSensorDiscovery("message_rate", "Message Rate", "msg/min", "", deviceInfo);
  publishSensorDiscovery("error_code", "Error Code", "", "problem", deviceInfo);
  
  discoveryPublished = true;
  Serial.println("Discovery messages published!");
}

void publishClimateDiscovery(String id, String name, String deviceInfo) {
  String topic = String(discoveryPrefix) + "climate/" + deviceId + "/" + id + "/config";
  String payload = "{";
  payload += "\"name\":\"" + name + "\",";
  payload += "\"unique_id\":\"" + deviceId + "_" + id + "\",";
  payload += "\"current_temperature_topic\":\"" + String(mqttAnnaPrefix) + "current_temp\",";
  payload += "\"temperature_state_topic\":\"" + String(mqttAnnaPrefix) + "setpoint\",";
  payload += "\"temperature_command_topic\":\"" + String(mqttAnnaPrefix) + "setpoint/set\",";
  payload += "\"mode_state_topic\":\"" + String(mqttAnnaPrefix) + "preset\",";
  payload += "\"mode_command_topic\":\"" + String(mqttAnnaPrefix) + "preset/set\",";
  payload += "\"modes\":[\"home\",\"away\",\"sleep\",\"vacation\"],";
  payload += "\"min_temp\":5,";
  payload += "\"max_temp\":30,";
  payload += "\"temp_step\":0.5,";
  payload += "\"availability_topic\":\"" + String(mqttTopicPrefix) + "availability\",";
  payload += deviceInfo;
  payload += "}";
  
  mqttClient.beginMessage(topic, true);
  mqttClient.print(payload);
  mqttClient.endMessage();
}

void publishSensorDiscovery(String id, String name, String unit, String deviceClass, String deviceInfo) {
  String topic = String(discoveryPrefix) + "sensor/" + deviceId + "/" + id + "/config";
  String payload = "{";
  payload += "\"name\":\"" + name + "\",";
  payload += "\"unique_id\":\"" + deviceId + "_" + id + "\",";
  payload += "\"state_topic\":\"" + String(mqttTopicPrefix) + id + "\",";
  if (unit.length() > 0) {
    payload += "\"unit_of_measurement\":\"" + unit + "\",";
  }
  if (deviceClass.length() > 0) {
    payload += "\"device_class\":\"" + deviceClass + "\",";
  }
  payload += "\"availability_topic\":\"" + String(mqttTopicPrefix) + "availability\",";
  payload += deviceInfo;
  payload += "}";
  
  mqttClient.beginMessage(topic, true);
  mqttClient.print(payload);
  mqttClient.endMessage();
}

void publishBinarySensorDiscovery(String id, String name, String deviceClass, String deviceInfo) {
  String topic = String(discoveryPrefix) + "binary_sensor/" + deviceId + "/" + id + "/config";
  String payload = "{";
  payload += "\"name\":\"" + name + "\",";
  payload += "\"unique_id\":\"" + deviceId + "_" + id + "\",";
  payload += "\"state_topic\":\"" + String(mqttTopicPrefix) + id + "\",";
  payload += "\"payload_on\":\"ON\",";
  payload += "\"payload_off\":\"OFF\",";
  if (deviceClass.length() > 0) {
    payload += "\"device_class\":\"" + deviceClass + "\",";
  }
  payload += "\"availability_topic\":\"" + String(mqttTopicPrefix) + "availability\",";
  payload += deviceInfo;
  payload += "}";
  
  mqttClient.beginMessage(topic, true);
  mqttClient.print(payload);
  mqttClient.endMessage();
}

void publishSelectDiscovery(String id, String name, String options, String deviceInfo) {
  String topic = String(discoveryPrefix) + "select/" + deviceId + "/" + id + "/config";
  String payload = "{";
  payload += "\"name\":\"" + name + "\",";
  payload += "\"unique_id\":\"" + deviceId + "_" + id + "\",";
  payload += "\"state_topic\":\"" + String(mqttTopicPrefix) + id + "\",";
  payload += "\"command_topic\":\"" + String(mqttTopicPrefix) + id + "/set\",";
  payload += "\"options\":[";
  
  // Parse options
  int start = 0;
  bool first = true;
  while (start < options.length()) {
    int end = options.indexOf(',', start);
    if (end == -1) end = options.length();
    
    if (!first) payload += ",";
    payload += "\"" + options.substring(start, end) + "\"";
    
    first = false;
    start = end + 1;
  }
  
  payload += "],";
  payload += "\"availability_topic\":\"" + String(mqttTopicPrefix) + "availability\",";
  payload += deviceInfo;
  payload += "}";
  
  mqttClient.beginMessage(topic, true);
  mqttClient.print(payload);
  mqttClient.endMessage();
}

void publishNumberDiscovery(String id, String name, float min, float max, float step, String deviceInfo) {
  String topic = String(discoveryPrefix) + "number/" + deviceId + "/" + id + "/config";
  String payload = "{";
  payload += "\"name\":\"" + name + "\",";
  payload += "\"unique_id\":\"" + deviceId + "_" + id + "\",";
  payload += "\"state_topic\":\"" + String(mqttTopicPrefix) + "target_temp\",";
  payload += "\"command_topic\":\"" + String(mqttTopicPrefix) + "target_temp/set\",";
  payload += "\"min\":" + String(min) + ",";
  payload += "\"max\":" + String(max) + ",";
  payload += "\"step\":" + String(step) + ",";
  payload += "\"unit_of_measurement\":\"°C\",";
  payload += "\"availability_topic\":\"" + String(mqttTopicPrefix) + "availability\",";
  payload += deviceInfo;
  payload += "}";
  
  mqttClient.beginMessage(topic, true);
  mqttClient.print(payload);
  mqttClient.endMessage();
}

void publishSwitchDiscovery(String id, String name, String deviceInfo) {
  String topic = String(discoveryPrefix) + "switch/" + deviceId + "/" + id + "/config";
  String payload = "{";
  payload += "\"name\":\"" + name + "\",";
  payload += "\"unique_id\":\"" + deviceId + "_" + id + "\",";
  payload += "\"state_topic\":\"" + String(mqttTopicPrefix) + id + "\",";
  payload += "\"command_topic\":\"" + String(mqttTopicPrefix) + id + "/set\",";
  payload += "\"payload_on\":\"ON\",";
  payload += "\"payload_off\":\"OFF\",";
  payload += "\"availability_topic\":\"" + String(mqttTopicPrefix) + "availability\",";
  payload += deviceInfo;
  payload += "}";
  
  mqttClient.beginMessage(topic, true);
  mqttClient.print(payload);
  mqttClient.endMessage();
}

void onMqttMessage(int messageSize) {
  String topic = mqttClient.messageTopic();
  String payload = "";
  
  while (mqttClient.available()) {
    payload += (char)mqttClient.read();
  }
  
  Serial.print("MQTT message - Topic: ");
  Serial.print(topic);
  Serial.print(" | Payload: ");
  Serial.println(payload);
  
  // Handle control commands
  if (topic.endsWith("setpoint/set")) {
    float newSetpoint = payload.toFloat();
    if (newSetpoint >= 5.0 && newSetpoint <= 30.0) {
      setAnnaTemperature(newSetpoint);
    }
  } else if (topic.endsWith("preset/set")) {
    setAnnaPreset(payload);
  } else if (topic.endsWith("target_temp/set")) {
    float targetTemp = payload.toFloat();
    sendHeatPumpCommand("SET_TARGET", String(targetTemp));
  } else if (topic.endsWith("mode/set")) {
    sendHeatPumpCommand("SET_MODE", payload);
  } else if (topic.endsWith("power/set")) {
    sendHeatPumpCommand("POWER", payload);
  } else if (topic.endsWith("dhw/set")) {
    sendHeatPumpCommand("DHW", payload);
  }
}

void sendHeatPumpCommand(String command, String value) {
  // Send command to Mega
  String cmd = "HP_CMD:" + command + ":" + value;
  megaComm.println(cmd);
  Serial.println("Sent to Mega: " + cmd);
}

void publishMQTTData() {
  if (!mqttClient.connected()) return;
  
  Serial.println("Publishing MQTT data...");
  
  // Parse heat pump data from Mega
  parseHeatPumpData();
  
  // Publish Heat Pump data
  publishMQTTValue(String(mqttTopicPrefix) + "water_temp", String(hpData.waterTemp));
  publishMQTTValue(String(mqttTopicPrefix) + "outside_temp", String(hpData.outsideTemp));
  publishMQTTValue(String(mqttTopicPrefix) + "return_temp", String(hpData.returnTemp));
  publishMQTTValue(String(mqttTopicPrefix) + "evaporator_temp", String(hpData.evaporatorTemp));
  publishMQTTValue(String(mqttTopicPrefix) + "compressor_temp", String(hpData.compressorTemp));
  publishMQTTValue(String(mqttTopicPrefix) + "target_temp", String(hpData.targetTemp));
  publishMQTTValue(String(mqttTopicPrefix) + "current_power", String(hpData.currentPower));
  publishMQTTValue(String(mqttTopicPrefix) + "cop", String(hpData.cop));
  publishMQTTValue(String(mqttTopicPrefix) + "fan_speed", String(hpData.fanSpeed));
  publishMQTTValue(String(mqttTopicPrefix) + "mode", hpData.operationMode);
  publishMQTTValue(String(mqttTopicPrefix) + "error_code", hpData.errorCode);
  
  // Binary states
  publishMQTTValue(String(mqttTopicPrefix) + "compressor_active", hpData.compressorActive ? "ON" : "OFF");
  publishMQTTValue(String(mqttTopicPrefix) + "defrost_active", hpData.defrostActive ? "ON" : "OFF");
  publishMQTTValue(String(mqttTopicPrefix) + "heating_active", hpData.heatingActive ? "ON" : "OFF");
  publishMQTTValue(String(mqttTopicPrefix) + "cooling_active", hpData.coolingActive ? "ON" : "OFF");
  publishMQTTValue(String(mqttTopicPrefix) + "dhw_active", hpData.dhwActive ? "ON" : "OFF");
  
  // Anna data
  publishMQTTValue(String(mqttAnnaPrefix) + "current_temp", String(annaCurrentTemp));
  publishMQTTValue(String(mqttAnnaPrefix) + "setpoint", String(annaSetpoint));
  publishMQTTValue(String(mqttAnnaPrefix) + "preset", annaPresetMode);
  publishMQTTValue(String(mqttAnnaPrefix) + "heating", annaHeating ? "ON" : "OFF");
  
  // System data
  String uptime = extractJSONValue(lastMegaData, "uptime");
  String msgRate = extractJSONValue(lastMegaData, "message_rate");
  
  publishMQTTValue(String(mqttTopicPrefix) + "uptime", uptime);
  publishMQTTValue(String(mqttTopicPrefix) + "wifi_rssi", String(WiFi.RSSI()));
  publishMQTTValue(String(mqttTopicPrefix) + "message_rate", msgRate);
  
  Serial.println("MQTT data published");
}

void publishMQTTValue(String topic, String value) {
  mqttClient.beginMessage(topic);
  mqttClient.print(value);
  mqttClient.endMessage();
}

void parseHeatPumpData() {
  // Extract heat pump values from JSON
  // This assumes the Mega sends detailed data
  hpData.waterTemp = extractJSONValue(lastMegaData, "water_temp").toFloat();
  hpData.outsideTemp = extractJSONValue(lastMegaData, "outside_temp").toFloat();
  hpData.returnTemp = extractJSONValue(lastMegaData, "return_temp").toFloat();
  hpData.evaporatorTemp = extractJSONValue(lastMegaData, "evaporator_temp").toFloat();
  hpData.compressorTemp = extractJSONValue(lastMegaData, "compressor_temp").toFloat();
  hpData.targetTemp = extractJSONValue(lastMegaData, "target_temp").toFloat();
  hpData.currentPower = extractJSONValue(lastMegaData, "power").toFloat();
  hpData.cop = extractJSONValue(lastMegaData, "cop").toFloat();
  hpData.fanSpeed = extractJSONValue(lastMegaData, "fan_speed").toInt();
  
  // States
  hpData.compressorActive = extractJSONValue(lastMegaData, "compressor") == "1";
  hpData.defrostActive = extractJSONValue(lastMegaData, "defrost") == "1";
  hpData.heatingActive = extractJSONValue(lastMegaData, "heating") == "1";
  hpData.coolingActive = extractJSONValue(lastMegaData, "cooling") == "1";
  hpData.dhwActive = extractJSONValue(lastMegaData, "dhw") == "1";
  
  hpData.operationMode = extractJSONValue(lastMegaData, "mode");
  hpData.errorCode = extractJSONValue(lastMegaData, "error");
  
  // Calculate COP if not provided
  if (hpData.cop == 0 && hpData.currentPower > 0) {
    // Estimate based on temperatures
    float deltaT = abs(hpData.waterTemp - hpData.outsideTemp);
    if (deltaT > 0) {
      hpData.cop = 5.0 - (deltaT * 0.1); // Simple estimation
      if (hpData.cop < 1.0) hpData.cop = 1.0;
      if (hpData.cop > 6.0) hpData.cop = 6.0;
    }
  }
}

void updateAnnaData() {
  WiFiClient annaClient;
  
  Serial.println("Updating Anna thermostat data...");
  
  if (annaClient.connect(annaIP, annaPort)) {
    String request = "GET /core/appliances HTTP/1.1\r\n";
    request += "Host: " + String(annaIP) + "\r\n";
    request += "Authorization: Basic " + base64Encode(String(annaUsername) + ":" + String(annaPassword)) + "\r\n";
    request += "Connection: close\r\n\r\n";
    
    annaClient.print(request);
    
    unsigned long timeout = millis();
    while (!annaClient.available() && millis() - timeout < 5000) {
      delay(10);
    }
    
    String response = "";
    while (annaClient.available()) {
      response += annaClient.readString();
    }
    
    parseAnnaXML(response);
    
    annaClient.stop();
    lastAnnaUpdate = millis();
    
    Serial.print("Anna: ");
    Serial.print(annaCurrentTemp);
    Serial.print("°C / ");
    Serial.print(annaSetpoint);
    Serial.println("°C");
    
  } else {
    Serial.println("Failed to connect to Anna");
  }
}

void setAnnaTemperature(float temperature) {
  WiFiClient annaClient;
  
  Serial.print("Setting Anna temperature to: ");
  Serial.println(temperature);
  
  if (annaClient.connect(annaIP, annaPort)) {
    String xmlPayload = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
    xmlPayload += "<thermostat><setpoint>" + String(temperature) + "</setpoint></thermostat>";
    
    String request = "PUT /core/appliances/thermostat HTTP/1.1\r\n";
    request += "Host: " + String(annaIP) + "\r\n";
    request += "Authorization: Basic " + base64Encode(String(annaUsername) + ":" + String(annaPassword)) + "\r\n";
    request += "Content-Type: text/xml\r\n";
    request += "Content-Length: " + String(xmlPayload.length()) + "\r\n";
    request += "Connection: close\r\n\r\n";
    request += xmlPayload;
    
    annaClient.print(request);
    
    delay(500);
    while (annaClient.available()) {
      annaClient.read();
    }
    
    annaClient.stop();
    
    Serial.println("Temperature command sent");
    
    delay(1000);
    updateAnnaData();
    
    if (mqttClient.connected()) {
      publishMQTTValue(String(mqttAnnaPrefix) + "setpoint", String(temperature));
    }
  }
}

void setAnnaPreset(String preset) {
  WiFiClient annaClient;
  
  Serial.print("Setting Anna preset to: ");
  Serial.println(preset);
  
  if (annaClient.connect(annaIP, annaPort)) {
    String xmlPayload = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
    xmlPayload += "<preset_mode>" + preset + "</preset_mode>";
    
    String request = "PUT /core/appliances/thermostat/preset HTTP/1.1\r\n";
    request += "Host: " + String(annaIP) + "\r\n";
    request += "Authorization: Basic " + base64Encode(String(annaUsername) + ":" + String(annaPassword)) + "\r\n";
    request += "Content-Type: text/xml\r\n";
    request += "Content-Length: " + String(xmlPayload.length()) + "\r\n";
    request += "Connection: close\r\n\r\n";
    request += xmlPayload;
    
    annaClient.print(request);
    
    delay(500);
    while (annaClient.available()) {
      annaClient.read();
    }
    
    annaClient.stop();
    
    Serial.println("Preset command sent");
    
    delay(1000);
    updateAnnaData();
  }
}

void parseAnnaXML(String xml) {
  int tempPos = xml.indexOf("<temperature>");
  if (tempPos != -1) {
    int endPos = xml.indexOf("</temperature>", tempPos);
    String tempStr = xml.substring(tempPos + 13, endPos);
    annaCurrentTemp = tempStr.toFloat();
  }
  
  int setpointPos = xml.indexOf("<setpoint>");
  if (setpointPos != -1) {
    int endPos = xml.indexOf("</setpoint>", setpointPos);
    String setpointStr = xml.substring(setpointPos + 10, endPos);
    annaSetpoint = setpointStr.toFloat();
  }
  
  annaHeating = xml.indexOf("<heating>true</heating>") != -1;
  
  int presetPos = xml.indexOf("<active_preset>");
  if (presetPos != -1) {
    int endPos = xml.indexOf("</active_preset>", presetPos);
    annaPresetMode = xml.substring(presetPos + 15, endPos);
  }
}

String base64Encode(String input) {
  const char* b64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  String output = "";
  
  int i = 0;
  while (i < input.length()) {
    int a = i < input.length() ? input[i++] : 0;
    int b = i < input.length() ? input[i++] : 0;
    int c = i < input.length() ? input[i++] : 0;
    
    output += b64chars[a >> 2];
    output += b64chars[((a & 3) << 4) | (b >> 4)];
    output += b64chars[((b & 15) << 2) | (c >> 6)];
    output += b64chars[c & 63];
  }
  
  return output;
}

void handleMegaCommunication() {
  if (megaComm.available()) {
    String command = megaComm.readStringUntil('\n');
    command.trim();
    
    if (command.length() > 0 && command.length() < 1000) {
      Serial.println("From Mega: " + command);
      
      if (command == "MEGA_INIT") {
        megaComm.println("R4_READY");
        megaConnected = true;
        Serial.println("Sent R4_READY response");
        
      } else if (command.startsWith("DATA:")) {
        lastMegaData = command.substring(5);
        lastDataReceived = millis();
        megaConnected = true;
        megaComm.println("R4:DATA_OK");
        Serial.println("Received JSON data (" + String(lastMegaData.length()) + " chars)");
        
        // Immediately publish if significant changes detected
        if (mqttClient.connected()) {
          checkForSignificantChanges();
        }
        
      } else {
        megaComm.println("R4:OK");
      }
    }
  }
  
  if (millis() - lastDataReceived > 15000 && lastDataReceived > 0) {
    megaConnected = false;
    Serial.println("Mega communication timeout");
  }
}

void checkForSignificantChanges() {
  static float lastWaterTemp = 0;
  static bool lastCompressor = false;
  
  float currentWaterTemp = extractJSONValue(lastMegaData, "water_temp").toFloat();
  bool currentCompressor = extractJSONValue(lastMegaData, "compressor") == "1";
  
  // Publish immediately if significant change
  if (abs(currentWaterTemp - lastWaterTemp) > 1.0 || currentCompressor != lastCompressor) {
    publishMQTTData();
    lastMqttPublish = millis();
  }
  
  lastWaterTemp = currentWaterTemp;
  lastCompressor = currentCompressor;
}

String extractJSONValue(String json, String key) {
  int startPos = json.indexOf("\"" + key + "\":");
  if (startPos == -1) return "0";
  
  startPos += key.length() + 3;
  int endPos = json.indexOf(",", startPos);
  if (endPos == -1) endPos = json.indexOf("}", startPos);
  
  String value = json.substring(startPos, endPos);
  value.replace("\"", "");
  value.trim();
  
  return value;
}

String formatUptime(int seconds) {
  int hours = seconds / 3600;
  int minutes = (seconds % 3600) / 60;
  int secs = seconds % 60;
  
  String result = "";
  if (hours > 0) result += String(hours) + "h ";
  if (minutes > 0) result += String(minutes) + "m ";
  result += String(secs) + "s";
  
  return result;
}

void updateStatusLED() {
  static unsigned long lastBlink = 0;
  static bool ledState = false;
  
  unsigned long interval;
  if (wifiConnected && megaConnected && mqttConnected) {
    interval = 3000; // Slow blink = all systems go
  } else if (wifiConnected && megaConnected) {
    interval = 1500; // Medium = no MQTT
  } else if (wifiConnected) {
    interval = 500;  // Fast = no Mega
  } else {
    interval = 100;  // Very fast = no WiFi
  }
  
  if (millis() - lastBlink > interval) {
    lastBlink = millis();
    ledState = !ledState;
    digitalWrite(LED_BUILTIN, ledState);
  }
}

void handleWebServer() {
  WiFiClient client = server.available();
  
  if (client) {
    Serial.println("New web client connected");
    
    String request = "";
    String requestBody = "";
    bool isPost = false;
    int contentLength = 0;
    
    while (client.connected() && client.available()) {
      String line = client.readStringUntil('\n');
      
      if (request.length() == 0) {
        request = line;
        if (line.indexOf("POST") != -1) {
          isPost = true;
        }
      }
      
      if (line.indexOf("Content-Length: ") != -1) {
        contentLength = line.substring(16).toInt();
      }
      
      if (line == "\r") {
        if (isPost && contentLength > 0) {
          delay(10);
          requestBody = client.readString();
        }
        break;
      }
    }
    
    // Handle requests
    if (request.indexOf("POST /setrefresh") != -1) {
      int valuePos = requestBody.indexOf("interval=");
      if (valuePos != -1) {
        refreshInterval = requestBody.substring(valuePos + 9).toInt();
        if (refreshInterval < 5) refreshInterval = 5;  
        if (refreshInterval > 300) refreshInterval = 300;
      }
      client.println("HTTP/1.1 303 See Other");
      client.println("Location: /");
      client.println("Connection: close");
      client.println();
      
    } else if (request.indexOf("POST /anna/settemp") != -1) {
      int valuePos = requestBody.indexOf("temperature=");
      if (valuePos != -1) {
        float newTemp = requestBody.substring(valuePos + 12).toFloat();
        setAnnaTemperature(newTemp);
      }
      client.println("HTTP/1.1 303 See Other");
      client.println("Location: /");
      client.println("Connection: close");
      client.println();
      
    } else if (request.indexOf("POST /anna/setpreset") != -1) {
      int valuePos = requestBody.indexOf("preset=");
      if (valuePos != -1) {
        String preset = requestBody.substring(valuePos + 7);
        preset.trim();
        setAnnaPreset(preset);
      }
      client.println("HTTP/1.1 303 See Other");
      client.println("Location: /");
      client.println("Connection: close");
      client.println();
      
    } else if (request.indexOf("POST /hp/command") != -1) {
      // Handle heat pump commands from web
      int cmdPos = requestBody.indexOf("cmd=");
      int valPos = requestBody.indexOf("value=");
      if (cmdPos != -1 && valPos != -1) {
        String cmd = requestBody.substring(cmdPos + 4, requestBody.indexOf("&"));
        String val = requestBody.substring(valPos + 6);
        sendHeatPumpCommand(cmd, val);
      }
      client.println("HTTP/1.1 303 See Other");
      client.println("Location: /");
      client.println("Connection: close");
      client.println();
      
    } else if (request.indexOf("GET /api/data") != -1) {
      sendJSONResponse(client);
      
    } else {
      sendHTMLResponse(client);
    }
    
    client.stop();
    Serial.println("Client disconnected");
  }
}

void sendJSONResponse(WiFiClient &client) {
  // Parse current data
  parseHeatPumpData();
  
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: application/json");
  client.println("Access-Control-Allow-Origin: *");
  client.println("Connection: close");
  client.println();
  
  client.print("{");
  
  // System status
  client.print("\"status\":{");
  client.print("\"wifi\":" + String(wifiConnected ? "true" : "false") + ",");
  client.print("\"mega\":" + String(megaConnected ? "true" : "false") + ",");
  client.print("\"mqtt\":" + String(mqttConnected ? "true" : "false") + ",");
  client.print("\"rssi\":" + String(WiFi.RSSI()) + ",");
  client.print("\"uptime\":" + String((millis() - startTime) / 1000));
  client.print("},");
  
  // Heat pump data
  client.print("\"heatpump\":{");
  client.print("\"water_temp\":" + String(hpData.waterTemp) + ",");
  client.print("\"outside_temp\":" + String(hpData.outsideTemp) + ",");
  client.print("\"return_temp\":" + String(hpData.returnTemp) + ",");
  client.print("\"evaporator_temp\":" + String(hpData.evaporatorTemp) + ",");
  client.print("\"compressor_temp\":" + String(hpData.compressorTemp) + ",");
  client.print("\"target_temp\":" + String(hpData.targetTemp) + ",");
  client.print("\"power\":" + String(hpData.currentPower) + ",");
  client.print("\"cop\":" + String(hpData.cop) + ",");
  client.print("\"fan_speed\":" + String(hpData.fanSpeed) + ",");
  client.print("\"mode\":\"" + hpData.operationMode + "\",");
  client.print("\"compressor\":" + String(hpData.compressorActive ? "true" : "false") + ",");
  client.print("\"defrost\":" + String(hpData.defrostActive ? "true" : "false") + ",");
  client.print("\"heating\":" + String(hpData.heatingActive ? "true" : "false") + ",");
  client.print("\"cooling\":" + String(hpData.coolingActive ? "true" : "false") + ",");
  client.print("\"dhw\":" + String(hpData.dhwActive ? "true" : "false") + ",");
  client.print("\"error\":\"" + hpData.errorCode + "\"");
  client.print("},");
  
  // Anna data
  client.print("\"anna\":{");
  client.print("\"current_temp\":" + String(annaCurrentTemp) + ",");
  client.print("\"setpoint\":" + String(annaSetpoint) + ",");
  client.print("\"mode\":\"" + annaPresetMode + "\",");
  client.print("\"heating\":" + String(annaHeating ? "true" : "false"));
  client.print("},");
  
  // Raw data
  client.print("\"raw_data\":" + lastMegaData);
  
  client.println("}");
}

void sendHTMLResponse(WiFiClient &client) {
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: text/html; charset=UTF-8");
  client.println("Connection: close");
  client.println();
  
  // Parse current data
  parseHeatPumpData();
  
  client.println("<!DOCTYPE html>");
  client.println("<html>");
  client.println("<head>");
  client.println("<meta charset='UTF-8'>");
  client.println("<meta name='viewport' content='width=device-width, initial-scale=1.0'>");
  client.print("<meta http-equiv='refresh' content='");
  client.print(refreshInterval);
  client.println("'>");
  client.println("<title>Atlantic Aurea Control Center</title>");
  client.println("<style>");
  
  // Enhanced CSS
  client.println("body { font-family: 'Segoe UI', Arial, sans-serif; margin: 0; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; }");
  client.println(".container { max-width: 1400px; margin: 0 auto; padding: 20px; }");
  client.println(".header { background: rgba(255,255,255,0.95); border-radius: 15px; padding: 20px; margin-bottom: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); text-align: center; }");
  client.println(".grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin-bottom: 20px; }");
  client.println(".card { background: white; border-radius: 10px; padding: 20px; box-shadow: 0 5px 15px rgba(0,0,0,0.1); }");
  client.println(".card h2 { margin-top: 0; color: #333; border-bottom: 2px solid #667eea; padding-bottom: 10px; }");
  client.println(".status-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; }");
  client.println(".status-item { background: linear-gradient(45deg, #667eea, #764ba2); color: white; padding: 10px; border-radius: 8px; text-align: center; }");
  client.println(".status-item.temp { background: linear-gradient(45deg, #f093fb, #f5576c); }");
  client.println(".status-item.active { background: linear-gradient(45deg, #4facfe, #00f2fe); }");
  client.println(".status-item.warning { background: linear-gradient(45deg, #fa709a, #fee140); }");
  client.println(".status-item.success { background: linear-gradient(45deg, #30cfd0, #330867); }");
  client.println(".label { font-size: 0.8em; opacity: 0.9; margin-bottom: 5px; }");
  client.println(".value { font-size: 1.5em; font-weight: bold; }");
  client.println(".control-panel { background: #f8f9fa; padding: 15px; border-radius: 8px; margin-top: 15px; }");
  client.println(".control-row { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; flex-wrap: wrap; }");
  client.println(".btn { background: #667eea; color: white; border: none; padding: 8px 16px; border-radius: 5px; cursor: pointer; }");
  client.println(".btn:hover { background: #5a67d8; }");
  client.println(".btn-danger { background: #dc3545; }");
  client.println(".btn-danger:hover { background: #c82333; }");
  client.println(".btn-success { background: #28a745; }");
  client.println(".btn-success:hover { background: #218838; }");
  client.println("input, select { padding: 8px; border: 1px solid #ddd; border-radius: 5px; }");
  client.println(".indicator { display: inline-block; width: 12px; height: 12px; border-radius: 50%; margin-right: 5px; }");
  client.println(".indicator.on { background: #28a745; animation: pulse 2s infinite; }");
  client.println(".indicator.off { background: #6c757d; }");
  client.println("@keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; } }");
  client.println("</style>");
  client.println("</head>");
  client.println("<body>");
  
  client.println("<div class='container'>");
  
  // Header
  client.println("<div class='header'>");
  client.println("<h1>🔥 Atlantic Aurea Heat Pump Control Center</h1>");
  client.print("<p>System Status: ");
  if (megaConnected && wifiConnected && mqttConnected) {
    client.print("<span class='indicator on'></span>All Systems Online");
  } else {
    client.print("<span class='indicator off'></span>Limited Connectivity");
  }
  client.print(" | Home Assistant: ");
  client.print(discoveryPublished ? "Discovered ✓" : "Not Published");
  client.println("</p>");
  client.println("</div>");
  
  // Main grid
  client.println("<div class='grid'>");
  
  // Heat Pump Temperatures Card
  client.println("<div class='card'>");
  client.println("<h2>🌡️ Temperatures</h2>");
  client.println("<div class='status-grid'>");
  
  client.println("<div class='status-item temp'>");
  client.println("<div class='label'>Water</div>");
  client.print("<div class='value'>");
  client.print(hpData.waterTemp, 1);
  client.println("°C</div></div>");
  
  client.println("<div class='status-item temp'>");
  client.println("<div class='label'>Outside</div>");
  client.print("<div class='value'>");
  client.print(hpData.outsideTemp, 1);
  client.println("°C</div></div>");
  
  client.println("<div class='status-item temp'>");
  client.println("<div class='label'>Return</div>");
  client.print("<div class='value'>");
  client.print(hpData.returnTemp, 1);
  client.println("°C</div></div>");
  
  client.println("<div class='status-item temp'>");
  client.println("<div class='label'>Target</div>");
  client.print("<div class='value'>");
  client.print(hpData.targetTemp, 1);
  client.println("°C</div></div>");
  
  client.println("<div class='status-item temp'>");
  client.println("<div class='label'>Evaporator</div>");
  client.print("<div class='value'>");
  client.print(hpData.evaporatorTemp, 1);
  client.println("°C</div></div>");
  
  client.println("<div class='status-item temp'>");
  client.println("<div class='label'>Compressor</div>");
  client.print("<div class='value'>");
  client.print(hpData.compressorTemp, 1);
  client.println("°C</div></div>");
  
  client.println("</div>");
  client.println("</div>");
  
  // Heat Pump Status Card
  client.println("<div class='card'>");
  client.println("<h2>⚡ Heat Pump Status</h2>");
  client.println("<div class='status-grid'>");
  
  client.println("<div class='status-item " + String(hpData.compressorActive ? "active" : "") + "'>");
  client.println("<div class='label'>Compressor</div>");
  client.print("<div class='value'>");
  client.print(hpData.compressorActive ? "ON" : "OFF");
  client.println("</div></div>");
  
  client.println("<div class='status-item " + String(hpData.heatingActive ? "active" : "") + "'>");
  client.println("<div class='label'>Heating</div>");
  client.print("<div class='value'>");
  client.print(hpData.heatingActive ? "ON" : "OFF");
  client.println("</div></div>");
  
  client.println("<div class='status-item " + String(hpData.dhwActive ? "active" : "") + "'>");
  client.println("<div class='label'>DHW</div>");
  client.print("<div class='value'>");
  client.print(hpData.dhwActive ? "ON" : "OFF");
  client.println("</div></div>");
  
  client.println("<div class='status-item " + String(hpData.defrostActive ? "warning" : "") + "'>");
  client.println("<div class='label'>Defrost</div>");
  client.print("<div class='value'>");
  client.print(hpData.defrostActive ? "ON" : "OFF");
  client.println("</div></div>");
  
  client.println("<div class='status-item'>");
  client.println("<div class='label'>Power</div>");
  client.print("<div class='value'>");
  client.print(hpData.currentPower, 1);
  client.println(" kW</div></div>");
  
  client.println("<div class='status-item success'>");
  client.println("<div class='label'>COP</div>");
  client.print("<div class='value'>");
  client.print(hpData.cop, 1);
  client.println("</div></div>");
  
  client.println("</div>");
  
  // Heat Pump Control
  client.println("<div class='control-panel'>");
  client.println("<h3>Heat Pump Control</h3>");
  client.println("<form method='POST' action='/hp/command' style='display: flex; gap: 10px; flex-wrap: wrap;'>");
  client.println("<select name='cmd'>");
  client.println("<option value='SET_MODE'>Set Mode</option>");
  client.println("<option value='SET_TARGET'>Set Target Temp</option>");
  client.println("<option value='POWER'>Power On/Off</option>");
  client.println("<option value='DHW'>DHW On/Off</option>");
  client.println("</select>");
  client.println("<input type='text' name='value' placeholder='Value' style='width: 100px;'>");
  client.println("<button type='submit' class='btn'>Send Command</button>");
  client.println("</form>");
  client.println("</div>");
  
  client.println("</div>");
  
  // Anna Thermostat Card
  client.println("<div class='card'>");
  client.println("<h2>🏠 Anna Thermostat</h2>");
  client.println("<div class='status-grid'>");
  
  client.println("<div class='status-item temp'>");
  client.println("<div class='label'>Current</div>");
  client.print("<div class='value'>");
  client.print(annaCurrentTemp, 1);
  client.println("°C</div></div>");
  
  client.println("<div class='status-item temp'>");
  client.println("<div class='label'>Setpoint</div>");
  client.print("<div class='value'>");
  client.print(annaSetpoint, 1);
  client.println("°C</div></div>");
  
  client.println("<div class='status-item'>");
  client.println("<div class='label'>Mode</div>");
  client.print("<div class='value'>");
  client.print(annaPresetMode);
  client.println("</div></div>");
  
  client.println("<div class='status-item " + String(annaHeating ? "active" : "") + "'>");
  client.println("<div class='label'>Heating</div>");
  client.print("<div class='value'>");
  client.print(annaHeating ? "ON" : "OFF");
  client.println("</div></div>");
  
  client.println("</div>");
  
  // Anna Control
  client.println("<div class='control-panel'>");
  client.println("<h3>Thermostat Control</h3>");
  client.println("<div class='control-row'>");
  client.println("<form method='POST' action='/anna/settemp' style='display: flex; gap: 10px;'>");
  client.print("<input type='number' name='temperature' min='5' max='30' step='0.5' value='");
  client.print(annaSetpoint, 1);
  client.println("' style='width: 80px;'>");
  client.println("<button type='submit' class='btn'>Set Temp</button>");
  client.println("</form>");
  client.println("<form method='POST' action='/anna/setpreset' style='display: flex; gap: 10px;'>");
  client.println("<select name='preset'>");
  client.print("<option value='home'");
  if (annaPresetMode == "home") client.print(" selected");
  client.println(">Home</option>");
  client.print("<option value='away'");
  if (annaPresetMode == "away") client.print(" selected");
  client.println(">Away</option>");
  client.print("<option value='sleep'");
  if (annaPresetMode == "sleep") client.print(" selected");
  client.println(">Sleep</option>");
  client.print("<option value='vacation'");
  if (annaPresetMode == "vacation") client.print(" selected");
  client.println(">Vacation</option>");
  client.println("</select>");
  client.println("<button type='submit' class='btn'>Set Mode</button>");
  client.println("</form>");
  client.println("</div>");
  client.println("</div>");
  
  client.println("</div>");
  
  client.println("</div>"); // End grid
  
  // Refresh control
  client.println("<div class='card'>");
  client.println("<form method='POST' action='/setrefresh' style='display: flex; align-items: center; gap: 10px;'>");
  client.println("<label>Auto-refresh interval:</label>");
  client.print("<input type='number' name='interval' min='5' max='300' value='");
  client.print(refreshInterval);
  client.println("' style='width: 80px;'>");
  client.println("<span>seconds</span>");
  client.println("<button type='submit' class='btn'>Update</button>");
  client.print("<span style='margin-left: auto;'>Next refresh in <span id='countdown'>");
  client.print(refreshInterval);
  client.println("</span> seconds</span>");
  client.println("</form>");
  client.println("</div>");
  
  client.println("</div>"); // End container
  
  // JavaScript
  client.println("<script>");
  client.println("var seconds = " + String(refreshInterval) + ";");
  client.println("setInterval(function() {");
  client.println("  seconds--;");
  client.println("  if (seconds >= 0) document.getElementById('countdown').innerText = seconds;");
  client.println("}, 1000);");
  client.println("</script>");
  
  client.println("</body>");
  client.println("</html>");
}

Acties:
  • 0 Henk 'm!

  • WackoH
  • Registratie: November 2012
  • Laatst online: 21:39
Ziet er al indrukwekkend uit!
Ik ben benieuwd hoe het zich ontwikkeld.

PS: Het is mogelijk om van de codevelden in je post uitklapbaar te maken. Het trucje hiervoor schijnt te zijn om het binnen een quote-blok te zetten. Dat houdt het forum wat toegankelijker ;)

[ Voor 57% gewijzigd door WackoH op 05-09-2025 17:17 ]


Acties:
  • 0 Henk 'm!

  • Maartenkromhout
  • Registratie: Augustus 2025
  • Laatst online: 05-09 21:43
Dank voor de tip ik had zitten zoeken maar kon het niet vinden. plaatst niet zo veel hier O-)
Pagina: 1