• MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57

Victron - Mijn ESS - MESS



Dit verslag beschrijft een persoonlijke opzet van een methodiek voor het laden en ontladen van een Victron batterij-systeem.
Oorspronkelijke Inhoudsopgave
Wijzigingen najaar 2025

MESS Inleiding
Hardware
Programmeertalen
Gegevens-bronnen
Solar
DayAhead Electriciteits-prijzen
Home Assistant
SolarEdge

Het MESS programma
Flow - Solar
Flow - DayAhead
Flow - Targets
Flow - Auto Laadstation
Flow - Home Assistant

Kwartiertarieven
Tibber en de Tibber API
Tibber dagelijks ophalen kwartier-prijzen
Over de Context Global Geheugenruimte
Lees dit en verder over de parameters, statussen en functies van MESS
Zoek de juiste kwartieren - Hoog, Laag etc.
Programma Find High and Low QH


* werk in uitvoering *
December 2025
Het is alweer een jaar geleden dat ik de eerste versie van deze bijdrage heb geschreven. Sindsdien is er veel veranderd.
Ik zal in de komende tijd verslag doen van deze veranderingen. De wijzigingen kun je hierboven lezen in de kolom naast de inhoudsopgave.
• Sinds 1 oktober 2025 berekenen dynamische energie-leveranciers hun prijzen per kwartier in plaats van per uur. Dit heeft grote invloed op de planning van laden, ontladen, actief verkopen, extra inkopen etc.
• In mijn eerste opzet ben ik voor het gebruik van dayahead-prijzen uitgegaan van ENTSO-E. Helaas kwam het nogal eens voor dat de gegevens niet beschikbaar waren. Dan werkte de prognose en het keuze-algoritme niet meer. Inmiddels ben ik overgegaan op Tibber voor het ophalen van de elektriciteits-prijzen.
• Deze zaken en een heleboel voortschrijdend inzicht hebben ook het 'brein' van het programma beïnvloed. Naast wijzigingen in de strategie (het algoritme) heb ik her en der fout-controles toegevoegd.

MESS Inleiding
Victron heeft voor haar batterij-systemen een besturingssysteem ESS
"Energy Storage System". Dit past de stroomvoorziening aan, aan de tijd van de dag, maakt opslag van PV-energie mogelijk, biedt ondersteuning aan netstroom en levert stroom terug naar het elektriciteitsnet.
Voor gebruik in combinatie met een dynamisch energie contract biedt Victron ook een Dynamisch ESS aan. Dit DESS plant het laden en ontladen van de batterij, daarbij rekening houdend met actuele en komende electriciteits-prijzen en eventueel het te verwachten gebruik en zonne-energie-voorspellingen.
In Het Grote Victron aansturing topic zijn meerdere bijdragen aan dit systeem gewijd.
Hoewel dit laatste systeem in principe doet wat het moet doen kunnen er redenen zijn om voor een eigen implementatie te kiezen. In dit artikel beschrijf ik zo'n eigen methodiek.

Disclaimer: Dit betreft mijn persoonlijke opzet van een systeem voor het laden en ontladen van mijn Victron systeem: "Mijn ESS", ofwel MESS. Het is gebaseerd op mijn persoonlijke kennis, kunde en ervaring. Zonder enige twijfel kunnen zaken anders en/of beter. Voor andere situaties, capaciteiten, vermogens, enzovoort zullen zaken anders ingesteld kunnen en moeten worden.

Aan de andere kant: Voor mensen die geïnteresseerd zijn in hetgeen bij een energie-beheersysteem komt kijken, die zelf met instellingen lopen te tobben of die inspiratie zoeken om een eigen oplossing te bouwen hoop ik dat mijn bevindingen iets kunnen bijdragen.



Hardware
Voor een ESS is geen sprake van benodigde hardware of verplichte hardware. Je moet uitgaan van aanwezige hardware. In mijn geval zijn dit drie stuks Victron Multiplus II 48/3000/35-32 omvormers, een Victron Cerbo GX voor de besturing, drie batterijen met 5,12 kWh opslag-capaciteit, een drie-fase SolarEdge se6k omvormer met genoeg panelen, een MPPT met twee panelen als opstart-hulp én een Victron auto-laadstation.



Programmeertalen
Voor het aansturen van de verschillende Victron-entiteiten heb ik gebruik gemaakt van Node Red. Dit is een programmeer-omgeving die kan worden toegevoegd als onderdeel van het Venus OS Large image. Node Red is gebaseerd op het via flow-lijnen koppelen van nodes die bijvoorbeeld invoer- en uitvoer-acties verzorgen.
Voor ingewikkelder logica gebruikt Node Red de programmeertaal JavaScript die in functie-nodes wordt toegepast.
Het installeren van Venus OS Large en het leren programmeren met Node Red of JavaScript vallen buiten het bestek van deze bijdrage.



Gegevens-bronnen
Met gegevens-bronnen bedoel ik hier de plaatsen waar de voor de besturings-beslissingen benodigde data vandaan komen.
Alleereerst zijn dit de te verwachten electriciteits-prijzen, zonne-opbrengst en eigen verbruik. Daarnaast zijn er de data uit het Victron-syseem zelf, de batterijen, de elektriciteits-meter(s) en eventueel een auto-laadstation. Deze laatste gegevens worden real-time geactualiseerd.
Naast het Victron-systeem gebruik ik ook huis-automatisering, namelijk Home Assistant.
Ik heb er nadrukkelijk voor gekozen om de besturing van het MESS niet in Home Assistant uit te voeren. Voor een belangrijk iets als mijn huis-elektriciteit wil ik geen afhankelijkheden van een hobby-systeem. Wel heb ik uitwisseling van gegevens voor weergave in een dashboard van Home Assistant. Ook kan ik in Home Assistant parameters instellen die worden uitgelezen door het MESS. Echter, in geen geval is het MESS afhankelijk van Home Assistant.



Solar
Om 'logische' beslissingen te kunnen nemen is het relevant om te verwachten zonne-opbrengst mee in beschouwing te nemen.
Voor kleinschalig/particulier gebruik kun je gebruik maken van Forecast.Solar. Deze dienst heeft een programmeer-interface waarmee je vanuit (bijvoorbeeld) Node Red een overzicht kunt opvragen van de te verwachten zonne-opbrengst op een bepaalde locatie met een bepaalde zonnepanelen-capaciteit met de daarbij behorende richting en hellingshoek. Om hiervan gebruik te kunnen maken moet je een account aanvragen.
In Node Red kan eventueel ook een uitbreiding worden geïnstalleerd, maar deze is niet noodzakelijk.



DayAhead Electriciteits-prijzen
Zoals gezegd zijn dynamische electriciteits-prijzen de kern van dit MESS. Ik ga ervan uit dat lezers van deze bijdrage bekend zijn met het principe van dynamische electriciteits-prijzen. Ook deze gegevens zijn op internet voor kleinschalig/particulier gebruik kosteloos beschikbaar bij ENTSO-E. En ook hier is een account nodig om gebruik te kunnen maken van hun programmeer-interface.



Home Assistant
Indien je uitwisseling wilt met Home Assistant moet je in Node Red een uitbreiding installeren.



SolarEdge
De electriciteit van mijn SolarEdge zonnepanelen-systeem loopt via een extra electriciteitsmeter (een ET340). Hiermee zijn de gegevens binnen het Victron-systeem direct beschikbaar zonder een interface met het SolarEdge-systeem zelf. De productie-gegevens worden ook per uur verzameld en bijgehouden. Hiermee wordt het verschil tussen de prognose van zonne-energie en de werkelijke productie bepaald. Op basis hiervan wordt de prognose voor de rest van de dag bijgestuurd.






Het MESS programma

Wanneer je alle hiervoor genoemde hobbels hebt genomen kun je beginnen aan het eigenlijke programmeerwerk.
Ik heb bij elkaar horende activiteiten en programma-stappen (nodes) ondergebracht in aparte flows (tabbladen).

Afbeeldingslocatie: https://tweakers.net/i/zS8sKb_8pXcn6YG7O-v3CTyhjMQ=/800x/filters:strip_icc():strip_exif()/f/image/JjecxUEbcPwcU5jTUT1AcaTm.jpg?f=fotoalbum_large

Ieder tabblad bevat een flow. Bij Node Red is een flow een eenheid van nodes (activiteiten) en functies (logica) die samen een programma vormen.
De verschillende flows worden hierna uitgewerkt.

Flow - Solar
Ik heb zonnepanelen in drie richtingen, oost, zuid en west. Deze hebben een verschillende capaciteit en hellingshoek. Om deze reden wordt de prognose van zonne-energie (de forecast) drie maal apart opgevraagd en vervolgens gecombineerd.
Voor de aanvraag wordt aldus drie maal een URL samengesteld en als http-request aan https://api.forecast.solar/ gezonden. Het antwoord komt retour met in de payload een json-bestand.
Ieder json-bestand wordt uitgepakt en opgeslagen in een global-context entiteit.
Als er aldus drie json bestanden zijn ontvangen worden deze samengevoegd tot één "Solar Forecast" bestand.

In de Solar-flow wordt ook de productie van SolarEdge bijgehouden. Ieder uur wordt de productie tot dat moment in een tabel vastgelegd.
Op basis hiervan wordt er een vergelijking gemaakt tussen de prognose en de feitelijk door SolarEdge gerealiseerde zonne-opbrengst. Aldus wordt de prognose verhoogd of (meestal) verlaagd. Bij negatieve stroomprijzen wordt de prognose voor de betreffende uren op nul gesteld. Dit omdat ik dan mijn zonnepanelen uitschakel.

Afbeeldingslocatie: https://tweakers.net/i/TXj-qf1sysfBjmxrBtpYgBeUXS4=/x800/filters:strip_icc():strip_exif()/f/image/WaPVGchpUKJeoJMlfTEH0Lme.jpg?f=fotoalbum_large

Toelichting op de onderdelen:

1) Om te zorgen dat het systeem nieuwe gegevens gaat ophalen worden ieder uur de eerder ontvangen Json gegevens uitgewist.

2) Vervolgens controleert het systeem of de Json gegevens bestaan of niet bestaan. Als de gegevens niet (meer) bestaan, dan worden parameters voor de betreffende panelen-groep ingesteld. Deze controle wordt tevens iedere minuut uitgevoerd indien de gegevens om een andere reden verwijderd zijn of ververst moeten worden.

2a) Bij deze stap wordt gecontroleerd of Solar Forecast (eventueel) afwezig is ('is null'). Zo ja, dan wordt de bovenste node-exit gebruikt en wordt Solar Forecast opnieuw gemaakt op basis van reeds aanwezige Json gegevens.

3) De parameters worden naar een functie-node 'Make URL' gestuurd. Deze combineert de parameters tot een juiste URL welke via een http-request aan Solar.Forecast wordt gezonden.
Dit is Change-node 'East param':
Setmsg.topic
to the valueEast
Setmsg.declination
to the value50
Setmsg.azimuth
to the value-109
Setmsg.power
to the value6
Kies hier de juiste waardes voor je specifieke zonne-panelen installatie.

Dit is Change-node 'General param':
Setmsg.latitude
to the value52.01111
Setmsg.longitude
to the value4.43333
Kies hier de juiste waardes voor je persoonlijke locatie in Nederland

De JavaScript van functie-node 'Make URL' is opgenomen in de Flow-tekst.

Dit is een voorbeeld van zo'n URL:
code:
1
https://api.forecast.solar/estimate/watthours/period/52.01111/4.43333/50/-109/6


Het antwoord van de website wordt omgezet in Json-tekst.

4) In de msg met parameters voor de URL is als topic de term East, South of West meegegeven. Deze topic-naam is nog steeds aanwezig in de uitvoer-msg van de json-conversie.
Op basis hiervan wordt een splitsing gemaakt en wordt het resultaat opgeslagen in een global-context entiteit met daarin het betreffend naam-deel. Na 'East' wordt de aanvraag herhaald voor 'South' en als het resultaat van 'South' is ontvangen, normaals voor 'West'.

Zo'n Json object ziet er als volgt uit:
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
{"result":
{"2024-05-13 05:50:12":0,
"2024-05-13 06:00:00":14,
"2024-05-13 07:00:00":392,
"2024-05-13 08:00:00":904,
"2024-05-13 09:00:00":1377,
"2024-05-13 10:00:00":1668,
"2024-05-13 11:00:00":1946,
"2024-05-13 12:00:00":1936,
"2024-05-13 13:00:00":1666,
"2024-05-13 14:00:00":1493,
"2024-05-13 15:00:00":1337,
"2024-05-13 16:00:00":1212,
"2024-05-13 17:00:00":1091,
"2024-05-13 18:00:00":904,
"2024-05-13 19:00:00":657,
"2024-05-13 20:00:00":387,
"2024-05-13 21:00:00":174,
"2024-05-13 21:27:01":22,
"2024-05-14 05:48:39":0,
"2024-05-14 06:00:00":38,
"2024-05-14 07:00:00":836,
"2024-05-14 08:00:00":1578,
"2024-05-14 09:00:00":1921,
"2024-05-14 10:00:00":1869,
"2024-05-14 11:00:00":1753,
"2024-05-14 12:00:00":1695,
"2024-05-14 13:00:00":1614,
"2024-05-14 14:00:00":1488,
"2024-05-14 15:00:00":1331,
"2024-05-14 16:00:00":1195,
"2024-05-14 17:00:00":1059,
"2024-05-14 18:00:00":871,
"2024-05-14 19:00:00":636,
"2024-05-14 20:00:00":374,
"2024-05-14 21:00:00":166,
"2024-05-14 21:28:34":22
},
"message":
{"code":0,
"type":"success",
"text":"",
"pid":"xxxxxxxx",
"info":
{"latitude":52.0111,
"longitude":4.4311,
"distance":0,
"place":"Adres huisnummer, Postcode Plaatsnaam, Netherlands",
"timezone":"Europe/Amsterdam",
"time":"2024-05-13T18:00:04+02:00",
"time_utc":"2024-05-13T16:00:04+00:00"},
"ratelimit":{
"zone":"IP nnn.nnn.nnn.nnn",
"period":3600,
"limit":12,
"remaining":8}
}
}
5) Als de gegevens van Json West binnen zijn kunnen de drie separate prognoses tot één geheel worden samengevoegd. Dit gebeurt (voor het gemak) in drie separate functie-nodes. Als derde stap wordt de prognose vergeleken met behaalde zonne-opbrengst tot dat moment. Zonodig wordt de prognose overeenkomstig aangepast.

De JavaScript van functie-node 'Make 3 x Solar Forecast' is opgenomen in de Flow-tekst.

De JavaScript van functie-node 'Combine Solar Forecast' is opgenomen in de Flow-tekst.

De JavaScript van functie-node 'Make Solar Forecast Modified' is opgenomen in de Flow-tekst.

6) Iedere dag om 10:00 uur stuur ik de solar-forecast gegevens ook naar mijn e-mail-adres voor archiverings-doeleinden.

7) Ook de consumptie-prognose wordt in de Solar-flow aangemaakt en opgeslagen voor gebruik bij latere berekeningen en prognoses. Ik gebruik hiervoor geen feitelijke registratie maar ben uitgegaan van persoonlijke schattingen per uur van de dag.

De JavaScript van functie-node 'Make Consump Forecast' is opgenomen in de Flow-tekst.

8) Dit onderdeel staat eigenlijk los van de Solar Forecast stappen. Het betreft de monitoring en uur-registratie van de feitelijke productie van Zonne-energie van mijn SolarEdge installatie.

De JavaScript van functie-node 'Monitor SolarEdge' is opgenomen in de Flow-tekst.





Flow - DayAhead
Iedere dag om circa 15:00 uur publiceert Entso-e de gegevens van de electriciteits-prijzen per uur tot de volgende avond.
Dat betekent dat vóór 15:00 alleen de gegevens van de zelfde dag beschikbaar zijn en ná 15:00 uur ook de gegevens van de volgende dag. Dit beperkt de horizon tot wanneer plannen gemaakt kunnen worden.
Bij Entso-e worden de gewenste gegevens opgevraagd door een URL met daarin de juiste parameters te sturen via een http-request. Het antwoord komt terug als xml-bestand. Mijn flow bewaart deze gegevens in een global.context entiteit.
In een volgende stap worden de xml-gegevens verder verwerkt en omgezet naar een tabel met bruikbare gegevens met voor ieder uur de bijbehorende kWh-prijs. Ook wordt er een tekst-regel gemaakt met de uren met speciale tarieven. Deze wordt aan Home Assistant gestuurd voor weergave in een dashboard.

Afbeeldingslocatie: https://tweakers.net/i/cbPXDP_Sf_mHhcDSj8PnE83U_w8=/x800/filters:strip_icc():strip_exif()/f/image/KCVsFkIK6vebLFyRPuD2xMEK.jpg?f=fotoalbum_large

1) Omdat vanaf 15:00 uur nieuwe gegevens gepubliceerd kunnen worden probeert het MESS ieder uur of de aanwezige gegevens reeds 48 uur betreffen. Als dat zo is zijn de nieuwe gegevens namelijk reeds binnen.

2) Als de bestaande gegevens minder dan 48 uur betreffen (in casu 24) wordt de bestaand XML met de eerdere gegevens verwijderd (4).

3) Ook worden iedere dag om 00:06 uur de bestaande gegevens verwijderd, dus daarmee de oude gegevens van de voorgaande dag.

4) Door het verwijderen van de bestaande XML kan een nieuwe verwerking worden gestart.

5) In alle gevallen wordt iedere minuut gecontroleerd of er ...

6) ... een XML aanwezig is. Zo ja, dan is XML niet null en wordt controle (7) uitgevoerd. Als de XML niet bestaat (dus wel null) met deze worden opgehaald en zal stap (9) worden geactiveerd.

7) Indien de tabel met prijs-gegevens 'PriceArray' niet bestaat (is null) dan wordt deze bij (17) aangemaakt.

8) Hiermee kan (9) op verzoek worden gestart.

9) In deze functie-node wordt de URL opgebouwd om de prijs-gegegevsn bij entso-e aan te vragen.

10) Voor controle en debugging wordt de URL in een global.context entiteit bewaard.

11) de URL wordt met een http-request aan entso-e gezonden.

12) Voor controle en debugging wordt de HTTP in een global.context entiteit bewaard.

13) Het resultaat wordt ontvangen in de vorm van XML gegevens.

14) Voor controle en debugging wordt de XML in een global.context entiteit bewaard.

15) Indien de ontvangen XML niet correct is, of leeg wordt er een foutmelding gemaakt.

16) Een foutmelding wordt via e-mail gezonden.

17) Als de XML met DayAhead-gegevens correct is ontvangen wordt een tabel gemaakt met daarin de prijs-gegevens per uur. De prijzen worden omgerekend naar cent per kWh.

18) Een eventuele fout wordt per e-mail gemeld en in het debug-scherm getoond.

19) Ook deze prijs-gegevens worden in een global.context entiteit bewaard voor gebruik door andere functies die de prijs-gegevens interpreteren.

20) Voor testdoeleinden (een speciale verwerking in Home Assistant) wordt een samenvatting in tekstvorm gegenereerd en aan Home Assistant gezonden. Dit is niet essentieel voor het MESS.

21) Voor leesbaarheid worden uren gegroepeerd getoond, zoals "negative", "zero", "very low", "low", "high" etc.

22) de resultaten worden gestuurd naar nodes die de gegevens gebruiken bij interpretaties en beslissingen.

23) Ook worden de gegevens naar Home Assistant gezonden voor weergave in een dashboard.

24) De status van de actuele prijs "negative", "zero" etc wordt eveneens gezonden.





Flow - Targets
Wanneer de 'externe' gegevens zoals Zonne-prognose en electriciteits-prijzen aldus bekend zijn kunnen deze worden gecombineerd met de real time gegevens van mijn Victron-installatie.

Afbeeldingslocatie: https://tweakers.net/i/2ApIiD8QnCrfdI8_Lt36j2sTtbY=/x800/filters:strip_icc():strip_exif()/f/image/mNQuHPTIZaQZOvj9h2nlaVtb.jpg?f=fotoalbum_large

1) De beschikbare zonne-energie wordt gebaseerd op de SolarEdge-productie. Die wordt verminderd met het verbruik van het 'huis' en van zogenaamde non-critical loads. Twee aparte zonne-panelen leveren via een MPPT kastje nog een kleine plus-bijdrage.

2) Er zijn meerdere parameters die de mogelijkheden en opties bepalen. Daarnaast is de actuele SoC (State-of-Charge) van de batterijen erg relevant en de beschikbaarheid van zonne-energie. Met name dit laatste fluctueert per seconde.

Dit alles wordt samengebracht in een functie-node "Calculate Targets" die de instellingen berekent waarmee het Victron-systeem het gewenste gedrag uitvoert. Zie (5).

3) De belangrijkste parameters zijn:
• De Max Current.
• Het minimum SoC percentage.
• De max Inverter Power.
Onder omstandigheden kan het laden van de batterijen nog helemaal worden uitgeschakeld (Charge Disable), kan het MPPT systeem worden uitgeschakeld (Charger MPPT On/Off) en kan via een relais (Relay 2) in de Cerbo de SolarEdge installatie worden uitgeschakeld.

4) Voor gebruik in een dashboard worden de ESS settings gezonden aan Home Assistant.

5) De logica van het programma in functie-node 'Calculate Targets' is het feitelijke 'Brein' van het MESS:

MESS - het Brein
De beslissingen van het MESS zijn gebaseerd op vaste waardes (meestal bepaald door de hardware), per dag wisselende gegevens (zoals DayAhead electriciteits-prijzen), per uur wisselende gegevens (zonne-prognose) en real-time wisselende gegevens (verbruik, zonne-opbrengst).
Al deze gegevens worden continu aangeleverd aan de functie-node 'Calculate Targets'. Iedere tien seconden wordt een berekenings-routine gestart die de instellingen van het Victron systeem (her)berekent en aanpast.

Het volledige JavaScript programma staat hieronder. De meeste functie-namen verklaren zichzelf.
Ik bechrijf de grote lijnen.
• SolarMakeForecasts
De zonne-prognose voor de gehele dag wordt separaat bepaald in Flow 'Solar'. De functie 'SolarMakeForecasts' gebruikt deze gegevens om te berekenen hoe de prognose verdeeld is over periodes met lage en hoge electriciteits-tarieven.

• BatteryCalculateLevels
Op basis van de actuele SoC en de prognoses van zonne-energie en consumptie wordt een verwachting gemaakt van de SoC van de batterijen. Hierbij wordt rekening gehouden dat een batterij niet voller kan worden dan vol en niet leger kan worden dan een minimum SoC.

• CalculateEnergyToFullSoC
Eén van de doelen is dat de batterij-capaciteit optimaal wordt gebruikt. Daarvoor wordt berekend hoeveel energie nodig is om de batterijen vol te krijgen. Liefst vandaag, eventueel pas morgen.

• FindCheapChargeHour
Een tweede doel is om te 'handelen' met electriciteit. Dat wil zeggen verkopen als de prijs hoog is en later weer laden als de prijs laag is. De keuze om te handelen is afhankelijk van het prijsverschil tussen actuele verkoop-prijs en latere 'vervangings-prijs'.

• FindHighSellHour
Deze functie werkt samen met de voorgaande functie om vast te stellen of het actuele uur al het juiste is om te verkopen.

• FindEnergyToSell
Bij het verkopen moet energie overblijven voor eigen gebruik. De functie 'FindEnergyToSell' bepaalt op basis van prognoses hoeveel energie beschikbaar is om te verkopen.

• NegativePriceComes / ZeroPriceComes / VeryLowPriceComes
Voor het nemen van beslissingen is het nodig om te weten of er mogelijk gunstiger momenten komen. De genoemde functies helpen daarbij.

• SetSolarAvailable
Het laden van de batterijen kan vanaf het grid, maar ook vanaf zonne-energie. Bij voldoende zonne-energie kan gewacht worden tot de electriciteits-tarieven laag zijn. Dan wordt eerst, bij hoge(re) tarieven, zonne-energie aan het net geleverd en worden de batterijen pas geladen bij lage(re) tarieven.

• TryAllRules
Deze functie staat achterin het programma. Het is de hoofd-routine waarmee alle mogelijke opties worden aangeroepen. De mogelijke opties staan in functies met een naam 'Try...'. Zodra zo'n functie 'slaagt' worden de Victron parameters overeenkomstig bepaald. De opvolgende opties worden niet uitgevoerd. Als een functie verderop in de lijst 'slaagt' is ook zeker dat de voorgaande opties niet van toepassing zijn.

• TryRule_DESS
Indien (vanuit Home Assistant instellingen) is ingesteld dat het systeem het officiële DESS moet volgen zal deze functie 'slagen'. Het MESS zal dan geen eigen wijzigingen toepassen.

• TryRule_FinishLowSoCCharging
Bij een eerdere verwerking kan zijn vastgesteld dat de SoC lager is dan het toegestane minimum. Het systeem zal dan (blijven) laden van het Grid voor tenminste vijf minuten om aan/uit knipperen te voorkomen.

• TryRule_SoCBelowBottom
Dit is de functie die vaststelt dat de SoC lager is dan het toegestande minimum. Het systeem zal gaan laden van het Grid voor tenminste vijf minuten. Daarna zal de SoC opnieuw worden bezien.

• TryRule_SellHighPrice
In het kader van handelen bij hoge electriciteits-tarieven zal het MESS, afhankelijk van het huidige tarief en te verwachten tarieven de daarvoor beschikbare energie aan het Grid terugleveren.
• TryRule_NegativePriceWait
Als er negatieve electriciteits-prijzen komen kan het nuttig zijn om daarop te wachten voor eventueel laden van het grid of zelfs zonne-energie plaatsvindt. Deze functie zoekt de combinatie met de meest-negatieve tarieven.

• TryRule_NegativePriceNow
Als de electriciteits-prijs op dit moment negatief is wordt de batterij van het grid geladen. Mogelijk wordt gewacht op nóg lagere prijzen.

• TryRule_ZeroPriceWait
Als er 'nul' electriciteits-prijzen komen kan het nuttig zijn om daarop te wachten voor eventueel laden van het grid of zelfs zonne-energie plaatsvindt.

• TryRule_ZeroPriceNow
Als de electriciteits-prijs op dit moment nul is wordt de batterij van het grid geladen.

• TryRule_VeryLowPriceWait
Als er heel lage electriciteits-prijzen komen kan het nuttig zijn om daarop te wachten voor eventueel laden van het grid of zelfs zonne-energie plaatsvindt. Heel laag wordt (subjectief) bepaald indien de absolute prijs lager is dan 2 cent.

• TryRule_VeryLowPriceNow
Als de electriciteits-prijs op dit moment heel laag is wordt de batterij van het grid geladen.

• TryRule_LowPriceNow
Als de electriciteits-prijs op dit moment laag is wordt de batterij van het grid geladen. 'Laag' kan per dag verschillen en is afhankelijk van het verschil met het gemiddelde van die dag.

• TryRule_HighPriceNow
Als de electriciteits-prijs op dit moment hoog is wordt de batterij niet van het grid geladen. 'Hoog' kan per dag verschillen en is afhankelijk van het verschil met het gemiddelde van die dag.

• TryRule_SoCBelowMinimum
De SoC is lager dan het gewenste minimum. Het systaam zal proberen te laden.

• TryRule_BetterPriceLater
Er zijn geen speciale lage of hoge prijzen. Laden van zonne-energie is prima, maar eventueel (bij)laden vanaf het grid kan beter worden uitgesteld.

• TryRule_NoSpecialPrice
Er zijn geen speciale lage of hoge prijzen. Laden van zonne-energie is prima, maar (bij)laden vanaf het grid niet.

• CreateReturnMessages
De (nieuwe) instellingen voor het Victron-systeem worden in flow-messages doorgegeven.

• Start of the Program

De logica van het MESS programma is opgedeeld in functies die hiervoor zijn beschreven.
Het feitelijke programma is daardoor relatief kort.





Flow - Auto Laadstation
Niet iedereen zal een auto laadstation hebben en als dat wel het geval is zal dat ook niet altijd van Victron zijn.
Indien wel, dan maakt het het een stuk eenvoudiger om alle onderdelen op elkaar af te stemmen.

De belangrijkste taak van de flow 'Auto Laadstation' is het regelen en optimaliseren van de ingaande- en uitgaande energie-stromen door de electriciteitsmeter. Ik heb een drie-fase 25 ampère aansluiting en deze moet niet worden overbelast. Bij het gelijktijdig laden van de auto en de victron-batterijen op volle kracht wordt de grens al overschreden, laat staan als er nog huis-verbruik, een wasmachine, vaatwasser of oven bij komt.

Hier moet dus worden gemoniteerd en gereguleerd, en zonodig snel ook. Dit wordt door functie-node 'Calc EV Charge Settings' gedaan.

Afbeeldingslocatie: https://tweakers.net/i/Btjxcyxbnr82jl5ZHeYFkmMpnZ8=/800x/filters:strip_icc():strip_exif()/f/image/8sjaX27x5giO5LxB4VDYI7YR.jpg?f=fotoalbum_large

1) De calculatie-functie gebruikt gegevens van meerdere Victron entiteiten.

2) De resultaten van de berekening worden als nieuwe settings aan Victron entiteiten gezonden.

3) De berekeningen worden door functie-node 'Calc EV Charge Settings' uitgevoerd.





Flow - Home Assistant
Voor de weergave van gegevens in een dashboard en voor het ophalen van instellingen is er een interface met Home Assistant gemaakt.
In flow 'Home Assistant' worden iedere acht seconden enkele 'Helper' entiteiten van Home Assistant uitgelezen. Hiermee kan ik eenvoudig het gewenste gedrag van het MESS beïnvloeden.

Afbeeldingslocatie: https://tweakers.net/i/JZ1WdHlJF0yPWwobZa4qZBuqGQY=/x800/filters:strip_icc():strip_exif()/f/image/Fkaq2XVrSuy4Sd3zwBL3rVVi.jpg?f=fotoalbum_large

1) De actuele status van het Victron DESS wordt aan Home Assistant gestuurd voor weergave in een dashboard.

2) Tevens wordt iedere 8 seconden een cyclus gestart om mogelijk gewijzigde gegevens bij Home Assistant op te halen.
Ik weet dat hier ook andere mogelijkheden voor bestaan, maar de gekozen methode vond ik het eenvoudigste en (vooral!) meest robuust.

In Home Assistant heb ik Helper-entiteiten aangemaakt met keuze lijsten. Deze kan ik in een Home Assistant dashboard eenvoudig selecteren. Een wijziging zal aldus binnen 8 seconden in het Victron MESS bekend zijn.

3) In kan ook kiezen om het standaard DESS te gebruiken (Off, Auto, Buy, Sell).

4) De minimaal gewenste SoC (State-of-Charge). Normaal kies ik hier 20, 15 of 10, maar voor test-doeleinden is het makkelijk om andere waarden in te kunnen stellen.

5) Het Grid Setpoint wordt gebruikt voor het (geforceerd) terugleveren van electriciteit aan het Grid. Ook hier van een standaard-waarde worden gekozen. Normaal kies ik hier 10.

6) Hoe wil ik het auto laadstation gebruiken? Naast de standaard modus-waardes 0: manual, 1: auto (= PV afhankelijk), en 2: scheduled heb ik 'eigen' waardes toegevoegd:
3: Scheduled (HA Schedule), 4 Optimized (Node Red) en 5: Niet gebruiken.
Deze laatste keuzes worden besproken bij Flow auto laadstation.

7) Als gebruik gemaakt worden van modus 3 (HA Schedule) kan het start-uur van laden worden opgegeven.

8) Evenzo kan het laatste uur van laden worden opgegeven.





Afsluiting
Geenszins wil ik pretenderen iets unieks of heel bijzonders te hebben gemaakt.
Het bovenstaande is het resultaat van mijn persoonlijke interesse en hobby.
Ik heb het hier gepubliceerd voor anderen om er plezier aan te beleven en eventueel iets van op te steken.
Succes!


[ Voor 255% gewijzigd door MJ de Bruijn op 02-12-2025 14:21 ]


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
Dit is de volledige Flow "Solar"

Dit is de definitie van de Flow "Solar". Kopieer de onderstaande tekst en kies in Nod Red "Import Nodes". Plak dan deze tekst en kies voor import.
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
[
    {
        "id": "ae598dcb0a2083ca",
        "type": "tab",
        "label": "Solar",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "9616f99d608b06bf",
        "type": "inject",
        "z": "ae598dcb0a2083ca",
        "name": "60 min",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "0 0-22 * * *",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 108,
        "y": 144,
        "wires": [
            [
                "dd1e3cdb1d81bd9d"
            ]
        ]
    },
    {
        "id": "6faaf63e80e9a759",
        "type": "function",
        "z": "ae598dcb0a2083ca",
        "name": "Make 3 x Solar Forecast",
        "func": "// Generate Solar Forecast Array from Json\n\nlet Now = new Date()\nlet NowDate = (\"0\" + Now.getDate()).slice(-2);\nlet NowMonth = (\"0\" + (Now.getMonth() + 1)).slice(-2);\nlet NowYear = Now.getFullYear();\nlet NowHour = (\"0\" + Now.getHours()).slice(-2);\nlet NowMin = (\"0\" + Now.getMinutes()).slice(-2);\nlet NowSec = (\"0\" + Now.getSeconds()).slice(-2);\n\nlet NowAsString = NowYear + \"-\" + NowMonth + \"-\" + NowDate\n                + \" \" + NowHour + \":\" + NowMin + \":\" + NowSec;\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n            + (\"0\" + Now.getMinutes()).slice(-2);\n\nlet tomorrow = new Date()\ntomorrow.setDate(tomorrow.getDate() + 1);\nlet tomorrowDate = (\"0\" + tomorrow.getDate()).slice(-2);\nlet tomorrowMonth = (\"0\" + (tomorrow.getMonth() + 1)).slice(-2);\nlet tomorrowYear = tomorrow.getFullYear();\nlet tomorrowHour = (\"0\" + tomorrow.getHours()).slice(-2);\nlet tomorrowMin = (\"0\" + tomorrow.getMinutes()).slice(-2);\nlet tomorrowSec = (\"0\" + tomorrow.getSeconds()).slice(-2);\n\nlet TomorrowAsString = tomorrowYear + \"-\" + tomorrowMonth + \"-\" + tomorrowDate\n                + \" \" + tomorrowHour + \":\" + tomorrowMin + \":\" + tomorrowSec;\n\nvar Solar_Forecast=[];\nvar Solar_Sunrise = 0;\nvar Solar_Sunset = 0;\nvar SolarGroup = [ \"East\", \"South\", \"West\" ];\n\nfor (G = 0; G < SolarGroup.length; G++) \n{ // process East, South and West\n    Solar_Json = global.get(\"Victron_MESS.Solar.Json.\" + SolarGroup[G]);\n    \n    if (Solar_Json > \"\") {\n        \n        Solar_Forecast = new Array(48);\n        Solar_Forecast.fill(0);\n        \n        let ResultObject = Solar_Json.result;\n\n        if (ResultObject == null) {\n            node.warn(\"Solar_Json.result: \" + ResultObject);\n        }\n        else if (ResultObject.toString().indexOf(\"Rate\") > -1) {\n            node.warn(ResultObject);\n        }\n        else\n        { // Json is OK\n            let ResultEntries = Object.entries(ResultObject);\n            // node.warn(ResultEntries);\n\n            let MessageObject =  Solar_Json.message;\n            // node.warn(MessageObject);\n            let MessageEntries = Object.entries(MessageObject);\n            // node.warn(MessageEntries);\n            \n                // substring (from, up to but not including)\n            // the value is from the previous time upto the mentioned time\n            for (let i = 0; i < ResultEntries.length; i++) \n            {\n                ResultDate = ResultEntries[i][0].substring(0,10);\n                ResultHour = ResultEntries[i][0].substring(11,13);\n                ResultMinutes = ResultEntries[i][0].substring(14,16);\n                ResultValue = ResultEntries[i][1];\n        \n                if (ResultDate == NowAsString.substring(0,10)) \n                {   // first entry is also sunrise\n                    if (Solar_Sunrise == 0) {\n                        Solar_Sunrise = ResultHour + \":\" + ResultMinutes;\n                    }\n                    // update sunset until last entry. This is real is sunset.\n                    Solar_Sunset = ResultHour + \":\" + ResultMinutes; // the last item\n                    \n                    if (Solar_Forecast[Number(ResultHour)-1] == 0) \n                    {\n                        Solar_Forecast[Number(ResultHour)-1] = ResultValue;\n                    }\n                    else\n                    { // the last item\n                        Solar_Forecast[Number(ResultHour)] = ResultValue;\n                    }\n                } // today\n        \n                if (ResultDate == TomorrowAsString.substring(0,10)) \n                {\n                    if (Solar_Forecast[Number(ResultHour)-1+24] == 0) \n                    {\n                        Solar_Forecast[Number(ResultHour)-1+24] = ResultValue;\n                    }\n                    else\n                    { // the last item\n                        Solar_Forecast[Number(ResultHour)+24] = ResultValue;\n                    }\n                } // tomorrow\n        \n            } // All Entries\n        \n            //  node.warn(\"Solar_Sunrise: \"+ Solar_Sunrise);\n            // node.warn(\"Solar_Sunset: \"+ Solar_Sunset);\n        \n            node.status( {fill:\"yellow\", shape:\"dot\",\n                          text: Solar_Sunrise + \" - \" + Solar_Sunset}\n                       );\n            \n            global.set(\"Victron_MESS.Solar.Sunrise\", Solar_Sunrise);\n            \n            global.set(\"Victron_MESS.Solar.Sunset\", Solar_Sunset);\n            \n            global.set(\"Victron_MESS.Solar.Forecast.\"+ SolarGroup[G], Solar_Forecast);\n\n        } // Json was OK\n        \n    } // if there is a valid Json available\n    \n} // process East, South and West\n\nnode.status( \n  { fill:\"green\", \n    shape:\"dot\",\n    text: \"@ \" + NowTime + \": \" + Solar_Sunrise + \"-\" + Solar_Sunset\n  } );\n\nreturn msg;\n",
        "outputs": 1,
        "timeout": "4",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 366,
        "y": 880,
        "wires": [
            [
                "9b54c014dbfbd70a"
            ]
        ]
    },
    {
        "id": "9405237a623e626b",
        "type": "debug",
        "z": "ae598dcb0a2083ca",
        "name": "Solar Forecast",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 656,
        "y": 928,
        "wires": []
    },
    {
        "id": "ae428baed63b9bb8",
        "type": "inject",
        "z": "ae598dcb0a2083ca",
        "name": "1 min",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "60",
        "crontab": "",
        "once": true,
        "onceDelay": "3",
        "topic": "VerifySolarForecast",
        "payload": "",
        "payloadType": "date",
        "x": 114,
        "y": 304,
        "wires": [
            [
                "db67186ac6e5243c"
            ]
        ]
    },
    {
        "id": "caf83103f66fd1bc",
        "type": "inject",
        "z": "ae598dcb0a2083ca",
        "name": "On Demand",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 138,
        "y": 880,
        "wires": [
            [
                "6faaf63e80e9a759"
            ]
        ]
    },
    {
        "id": "9b54c014dbfbd70a",
        "type": "function",
        "z": "ae598dcb0a2083ca",
        "name": "Combine Solar Forecast",
        "func": "// Combine Solar Forecast from East, South and West\n\ncontext = context || {};\n\nvar Now = new Date();\nlet NowHour = Now.getHours();\nlet NowMinutes = Now.getMinutes();\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n            + (\"0\" + Now.getMinutes()).slice(-2);\n\n\nvar Solar_Today_Wh = 0;\nvar Solar_Remain_Wh = 0;\nvar Solar_Tomorrow_Wh = 0;\n\nSolar_Forecast_East = global.get(\"Victron_MESS.Solar.Forecast.East\");\n\nSolar_Forecast_South = global.get(\"Victron_MESS.Solar.Forecast.South\");\n\nSolar_Forecast_West = global.get(\"Victron_MESS.Solar.Forecast.West\");\n\nSolar_Forecast = new Array(48); // initialize\nSolar_Forecast.fill(0);\n\nif ( (Solar_Forecast_East != null)\n  && (Solar_Forecast_South != null)\n  && (Solar_Forecast_West != null) )\n{ // if Solar Arrays are available\n\n    if ( (Solar_Forecast_East.length == 48)\n      && (Solar_Forecast_South.length == 48)\n      && (Solar_Forecast_West.length == 48) ) {\n          \n        for (let i = 0; i < Solar_Forecast.length; i++) {\n            Solar_Forecast[i] =\n              Solar_Forecast_East[i] \n              + Solar_Forecast_South[i] \n              + Solar_Forecast_West[i];\n        }\n        \n        for (let H = 0; H < 24; H++) {\n            Solar_Today_Wh += Solar_Forecast[H];\n        } // Next Hour until midnight\n        \n        for (let H = NowHour; H < 24; H++) {\n            Solar_Remain_Wh += Solar_Forecast[H];\n        } // Next Hour until midnight\n      \n        for (let H = 24; H < 48; H++) {\n            Solar_Tomorrow_Wh += Solar_Forecast[H];\n        } // Next Hour until midnight\n        \n        \n        node.status( {fill:\"green\", shape:\"dot\",\n                      text: \"@ \" + NowTime + \": \"\n                      + Math.round(Solar_Remain_Wh/100)/10\n                      + \" / \" \n                      + Math.round(Solar_Today_Wh/100)/10 + \"; +\"\n                      + Math.round(Solar_Tomorrow_Wh/100)/10\n                      + ' kWh'\n                    } );\n    \n        global.set(\"Victron_MESS.Solar.Forecast.All\", Solar_Forecast);\n        \n        return {topic: \"Solar_Forecast\", payload: Solar_Forecast} ;\n      \n    } // if Arrays have correct length\n      \n} // if Arrays exist\n  \n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 366,
        "y": 928,
        "wires": [
            [
                "f29e221e89f1a978",
                "9405237a623e626b"
            ]
        ]
    },
    {
        "id": "dd1e3cdb1d81bd9d",
        "type": "change",
        "z": "ae598dcb0a2083ca",
        "name": "Delete Json East",
        "rules": [
            {
                "t": "delete",
                "p": "Victron_MESS.Solar.Json.East",
                "pt": "global"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 306,
        "y": 144,
        "wires": [
            [
                "0c97ca6f0a50a14e"
            ]
        ]
    },
    {
        "id": "0c97ca6f0a50a14e",
        "type": "change",
        "z": "ae598dcb0a2083ca",
        "name": "Delete Json South",
        "rules": [
            {
                "t": "delete",
                "p": "Victron_MESS.Solar.Json.South",
                "pt": "global"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 306,
        "y": 192,
        "wires": [
            [
                "7d33ff7184db2116"
            ]
        ]
    },
    {
        "id": "7d33ff7184db2116",
        "type": "change",
        "z": "ae598dcb0a2083ca",
        "name": "Delete Json West",
        "rules": [
            {
                "t": "delete",
                "p": "Victron_MESS.Solar.Json.West",
                "pt": "global"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 306,
        "y": 240,
        "wires": [
            [
                "db67186ac6e5243c"
            ]
        ]
    },
    {
        "id": "d0edc89183f88593",
        "type": "switch",
        "z": "ae598dcb0a2083ca",
        "name": "Solar Forecast is null Y/N",
        "property": "Victron_MESS.Solar.Forecast",
        "propertyType": "global",
        "rules": [
            {
                "t": "null"
            },
            {
                "t": "nnull"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 326,
        "y": 752,
        "wires": [
            [
                "6faaf63e80e9a759"
            ],
            []
        ]
    },
    {
        "id": "bf2362d6bf6abd9a",
        "type": "function",
        "z": "ae598dcb0a2083ca",
        "name": "Make URL",
        "func": "msg.url = 'https://api.forecast.solar/';\n\nmsg.url += \"estimate\" + '/';\n\nmsg.url += \"watthours/period\" + '/';\n\nmsg.url += msg.latitude + \"/\"\n         + msg.longitude + \"/\"\n         + msg.declination + \"/\" \n         + msg.azimuth + \"/\"\n         + msg.power;\n\nreturn msg;",
        "outputs": 1,
        "timeout": "",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 742,
        "y": 416,
        "wires": [
            [
                "7f82eb7fd04bb36b",
                "76ea5599fedbc138"
            ]
        ]
    },
    {
        "id": "76ea5599fedbc138",
        "type": "http request",
        "z": "ae598dcb0a2083ca",
        "name": "",
        "method": "GET",
        "ret": "txt",
        "paytoqs": "ignore",
        "url": "",
        "tls": "",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 742,
        "y": 464,
        "wires": [
            [
                "05df2a626cfe78aa"
            ]
        ]
    },
    {
        "id": "05df2a626cfe78aa",
        "type": "json",
        "z": "ae598dcb0a2083ca",
        "name": "Convert to json",
        "property": "payload",
        "action": "",
        "pretty": false,
        "x": 752,
        "y": 512,
        "wires": [
            [
                "a636d184b892e15a",
                "89ab5cf8a6915005"
            ]
        ]
    },
    {
        "id": "7a2afbc52fd14321",
        "type": "change",
        "z": "ae598dcb0a2083ca",
        "name": "Set Json East",
        "rules": [
            {
                "t": "set",
                "p": "Victron_MESS.Solar.Json.East",
                "pt": "global",
                "to": "payload",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 496,
        "y": 592,
        "wires": [
            [
                "67d282ec486795ba"
            ]
        ]
    },
    {
        "id": "89fbe1111a4cb58a",
        "type": "change",
        "z": "ae598dcb0a2083ca",
        "name": "East param",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "East",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "declination",
                "pt": "msg",
                "to": "50",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "azimuth",
                "pt": "msg",
                "to": "-109",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "power",
                "pt": "msg",
                "to": "6",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 538,
        "y": 304,
        "wires": [
            [
                "1ed49fdec0e29140"
            ]
        ]
    },
    {
        "id": "a636d184b892e15a",
        "type": "switch",
        "z": "ae598dcb0a2083ca",
        "name": "on topic",
        "property": "topic",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "East",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "South",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "West",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 3,
        "x": 276,
        "y": 608,
        "wires": [
            [
                "7a2afbc52fd14321"
            ],
            [
                "ab155f4e6a049b35"
            ],
            [
                "59f393acc6f15488"
            ]
        ]
    },
    {
        "id": "ab155f4e6a049b35",
        "type": "change",
        "z": "ae598dcb0a2083ca",
        "name": "Set Json South",
        "rules": [
            {
                "t": "set",
                "p": "Victron_MESS.Solar.Json.South",
                "pt": "global",
                "to": "payload",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 496,
        "y": 640,
        "wires": [
            [
                "16cdd0491ff13d68"
            ]
        ]
    },
    {
        "id": "59f393acc6f15488",
        "type": "change",
        "z": "ae598dcb0a2083ca",
        "name": "Set Json West",
        "rules": [
            {
                "t": "set",
                "p": "Victron_MESS.Solar.Json.West",
                "pt": "global",
                "to": "payload",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 496,
        "y": 688,
        "wires": [
            [
                "cf8b05a2cb51bf07"
            ]
        ]
    },
    {
        "id": "56475d5d4dfa9e7a",
        "type": "change",
        "z": "ae598dcb0a2083ca",
        "name": "South param",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "South",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "declination",
                "pt": "msg",
                "to": "30",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "azimuth",
                "pt": "msg",
                "to": "-20",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "power",
                "pt": "msg",
                "to": "3.3",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 542,
        "y": 352,
        "wires": [
            [
                "1ed49fdec0e29140"
            ]
        ]
    },
    {
        "id": "872e332c637b0b62",
        "type": "change",
        "z": "ae598dcb0a2083ca",
        "name": "West param",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "West",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "declination",
                "pt": "msg",
                "to": "50",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "azimuth",
                "pt": "msg",
                "to": "67",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "power",
                "pt": "msg",
                "to": "3.9",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 542,
        "y": 400,
        "wires": [
            [
                "1ed49fdec0e29140"
            ]
        ]
    },
    {
        "id": "db67186ac6e5243c",
        "type": "switch",
        "z": "ae598dcb0a2083ca",
        "name": "Json_East is null Y/N",
        "property": "Victron_MESS.Solar.Json.East",
        "propertyType": "global",
        "rules": [
            {
                "t": "null"
            },
            {
                "t": "nnull"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 316,
        "y": 304,
        "wires": [
            [
                "89fbe1111a4cb58a"
            ],
            [
                "c919b83806b00851"
            ]
        ]
    },
    {
        "id": "c919b83806b00851",
        "type": "switch",
        "z": "ae598dcb0a2083ca",
        "name": "Json South is null Y/N",
        "property": "Victron_MESS.Solar.Json.South",
        "propertyType": "global",
        "rules": [
            {
                "t": "null"
            },
            {
                "t": "nnull"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 316,
        "y": 352,
        "wires": [
            [
                "56475d5d4dfa9e7a"
            ],
            [
                "a8b314bea0ec4881"
            ]
        ]
    },
    {
        "id": "a8b314bea0ec4881",
        "type": "switch",
        "z": "ae598dcb0a2083ca",
        "name": "Json West is null Y/N",
        "property": "Victron_MESS.Solar.Json.West",
        "propertyType": "global",
        "rules": [
            {
                "t": "null"
            },
            {
                "t": "nnull"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 316,
        "y": 400,
        "wires": [
            [
                "872e332c637b0b62"
            ],
            [
                "d0edc89183f88593"
            ]
        ]
    },
    {
        "id": "67d282ec486795ba",
        "type": "delay",
        "z": "ae598dcb0a2083ca",
        "name": "5 sec",
        "pauseType": "delay",
        "timeout": "5",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 674,
        "y": 592,
        "wires": [
            [
                "c919b83806b00851"
            ]
        ]
    },
    {
        "id": "16cdd0491ff13d68",
        "type": "delay",
        "z": "ae598dcb0a2083ca",
        "name": "5 sec",
        "pauseType": "delay",
        "timeout": "5",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 674,
        "y": 640,
        "wires": [
            [
                "a8b314bea0ec4881"
            ]
        ]
    },
    {
        "id": "cf8b05a2cb51bf07",
        "type": "delay",
        "z": "ae598dcb0a2083ca",
        "name": "5 sec",
        "pauseType": "delay",
        "timeout": "5",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 674,
        "y": 688,
        "wires": [
            [
                "6faaf63e80e9a759"
            ]
        ]
    },
    {
        "id": "7f82eb7fd04bb36b",
        "type": "debug",
        "z": "ae598dcb0a2083ca",
        "name": "Solar URL",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "url",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 950,
        "y": 416,
        "wires": []
    },
    {
        "id": "5f3565a5d470693e",
        "type": "change",
        "z": "ae598dcb0a2083ca",
        "name": "Get Solar_Forecast",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "Solar Forecast",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "Victron_MESS.Solar.Forecast.All",
                "tot": "global"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 482,
        "y": 1088,
        "wires": [
            [
                "ac0b1366054a938f"
            ]
        ]
    },
    {
        "id": "0cae168a3e9fa074",
        "type": "inject",
        "z": "ae598dcb0a2083ca",
        "name": "at 10:00",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "00 10 * * *",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 116,
        "y": 1088,
        "wires": [
            [
                "646ae6ce2f8889a9"
            ]
        ]
    },
    {
        "id": "ac0b1366054a938f",
        "type": "link out",
        "z": "ae598dcb0a2083ca",
        "name": "Solar_Forecast to E-mail",
        "mode": "link",
        "links": [
            "1159231654345797"
        ],
        "x": 617,
        "y": 1088,
        "wires": []
    },
    {
        "id": "646ae6ce2f8889a9",
        "type": "delay",
        "z": "ae598dcb0a2083ca",
        "name": "",
        "pauseType": "delay",
        "timeout": "2",
        "timeoutUnits": "minutes",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 308,
        "y": 1088,
        "wires": [
            [
                "5f3565a5d470693e"
            ]
        ]
    },
    {
        "id": "af9bb39fa9fa42d9",
        "type": "victron-input-pvinverter",
        "z": "ae598dcb0a2083ca",
        "service": "com.victronenergy.pvinverter/31",
        "path": "/Ac/Energy/Forward",
        "serviceObj": {
            "service": "com.victronenergy.pvinverter/31",
            "name": "SolarEdge (ET340)"
        },
        "pathObj": {
            "path": "/Ac/Energy/Forward",
            "type": "float",
            "name": "Total energy (kWh)"
        },
        "name": "SolarEdge Energy",
        "onlyChanges": false,
        "roundValues": "1",
        "x": 122,
        "y": 48,
        "wires": [
            [
                "277a42dcc0759079"
            ]
        ]
    },
    {
        "id": "3abd102ea8ff8a7b",
        "type": "debug",
        "z": "ae598dcb0a2083ca",
        "name": "SolarEdge_Reading_kWh",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 722,
        "y": 48,
        "wires": []
    },
    {
        "id": "277a42dcc0759079",
        "type": "function",
        "z": "ae598dcb0a2083ca",
        "name": "Monitor SolarEdge",
        "func": "// Monitor SolarEddge\n\ncontext = context || {};\n\nvar Today = new Date()\nvar NowHour = Today.getHours();\nlet NowTime = (\"0\" + Today.getHours()).slice(-2) + \":\"\n            + (\"0\" + Today.getMinutes()).slice(-2);\n\nvar SolarEdge_Energy = 0;\n\nif (context.LastHour == null) {\n    context.LastHour = -1;\n}\nif (context.LastReading == null) {\n    context.LastReading = -1;\n}\nif (context.SolarEdge_Reading_kWh == null) {\n    context.SolarEdge_Reading_kWh = global.get(\"SolarEdge_Reading_kWh\");\n    context.LastHour = -1;\n    context.LastReading = -1;\n}\nif (context.SolarEdge_Reading_kWh == null) {\n    // e.g. after reboot\n    context.SolarEdge_Reading_kWh = new Array(24); // initialize\n    context.SolarEdge_Reading_kWh.fill(0);\n}\n\n\nswitch (msg.topic) {\n    \n  case \"SolarEdge Energy\":\n    SolarEdge_Energy = msg.payload;  \n    msg = null;\n    break;\n\n  default: \n    node.warn(\"Unknown Topic: \" + msg.topic \n              + \" Payload: \" + msg.payload);\n    msg = null;\n    break;\n\n} // switch for input all variables\n\nlet newMsg = null;\n\nif (SolarEdge_Energy != null) // a value was received\n{\n    if ( (NowHour != context.LastHour)\n      || (context.SolarEdge_Reading_kWh == null) )\n    { // a new hour has started\n    \n        // node.warn(\"a new hour has started\");\n    \n        if (NowHour == 0) \n        { //send old values per e-mail\n            newMsg = { topic: \"SolarEdge_Reading_kWh\",\n                       payload: context.SolarEdge_Reading_kWh };\n        } // if new day\n        \n        if ( (NowHour == 0) || (context.LastHour == 23) )\n        { // New day, clear history\n            context.SolarEdge_Reading_kWh = new Array(24); // initialize\n            context.SolarEdge_Reading_kWh.fill(0);\n        } // if new day\n        \n        // make sure current value is processed\n        context.LastReading = -1; \n        context.LastHour = NowHour;\n    }\n\n    if (SolarEdge_Energy != context.LastReading)\n    { // the value has changed\n\n        context.SolarEdge_Reading_kWh[NowHour] = SolarEdge_Energy;\n        global.set(\"Victron_MESS.SolarEdge.Reading\", context.SolarEdge_Reading_kWh);\n    \n        node.status( \n          { fill:\"green\", \n            shape:\"dot\",\n            text: \"@ \" + NowTime + \": \"\n                + context.SolarEdge_Reading_kWh[NowHour] + \" kWh\" } \n        );\n\n        context.LastReading = SolarEdge_Energy;\n    }\n    \n} // if there was a value received\n\nreturn newMsg;\n",
        "outputs": 1,
        "timeout": "4",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 398,
        "y": 48,
        "wires": [
            [
                "3abd102ea8ff8a7b",
                "f1c0ed5f6e4bf567"
            ]
        ]
    },
    {
        "id": "f1c0ed5f6e4bf567",
        "type": "link out",
        "z": "ae598dcb0a2083ca",
        "name": "SolarEdge to E-mail",
        "mode": "link",
        "links": [
            "1159231654345797"
        ],
        "x": 629,
        "y": 96,
        "wires": []
    },
    {
        "id": "b90ba71de262da79",
        "type": "function",
        "z": "ae598dcb0a2083ca",
        "name": "Make Consump_Forecast_Wh",
        "func": "// Make Consumption Forecast Array\n\nvar Now = new Date()\nlet NowDate = (\"0\" + Now.getDate()).slice(-2);\nlet NowMonth = (\"0\" + (Now.getMonth() + 1)).slice(-2);\nlet NowYear = Now.getFullYear();\nlet NowHour = (\"0\" + Now.getHours()).slice(-2);\nlet NowMin = (\"0\" + Now.getMinutes()).slice(-2);\nlet NowSec = (\"0\" + Now.getSeconds()).slice(-2);\n\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n            + (\"0\" + Now.getMinutes()).slice(-2);\n\nConsump_Forecast_Wh = new Array(48);\n\nConsump_Forecast_Wh[0] = Math.round(400);\nConsump_Forecast_Wh[1] = Math.round(300);\nConsump_Forecast_Wh[2] = Math.round(300);\nConsump_Forecast_Wh[3] = Math.round(300);\nConsump_Forecast_Wh[4] = Math.round(300);\n\nConsump_Forecast_Wh[5] = Math.round(400);\nConsump_Forecast_Wh[6] = Math.round(300);\nConsump_Forecast_Wh[7] = Math.round(400);\nConsump_Forecast_Wh[8] = Math.round(800);\nConsump_Forecast_Wh[9] = Math.round(600);\n\nConsump_Forecast_Wh[10] = Math.round(600);\nConsump_Forecast_Wh[11] = Math.round(600);\nConsump_Forecast_Wh[12] = Math.round(800);\nConsump_Forecast_Wh[13] = Math.round(800);\nConsump_Forecast_Wh[14] = Math.round(600);\n\nConsump_Forecast_Wh[15] = Math.round(600);\nConsump_Forecast_Wh[16] = Math.round(600);\nConsump_Forecast_Wh[17] = Math.round(800);\nConsump_Forecast_Wh[18] = Math.round(1000);\nConsump_Forecast_Wh[19] = Math.round(1000);\n\nConsump_Forecast_Wh[20] = Math.round(1200);\nConsump_Forecast_Wh[21] = Math.round(1000);\nConsump_Forecast_Wh[22] = Math.round(600);\nConsump_Forecast_Wh[23] = Math.round(400);\n\nConsump_Per_Day_Wh = 0; // estimate Wh\n\nfor (let i = 0; i < 24; i++) // next day \n{\n    Consump_Per_Day_Wh +=  Consump_Forecast_Wh[i];\n    Consump_Forecast_Wh[i+24] = Consump_Forecast_Wh[i];\n}\n\nglobal.set(\"Victron_MESS.Consump.Forecast\", Consump_Forecast_Wh);\n\nmsg.topic = \"Consump_Forecast_Wh\";\nmsg.payload = Consump_Forecast_Wh;\n\nnode.status( \n  { fill:\"green\", \n    shape:\"dot\",\n    text: \"@ \" + NowTime\n  } );\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 378,
        "y": 1152,
        "wires": [
            [
                "4322f98a7141a17d",
                "65dea081f2ae905b"
            ]
        ]
    },
    {
        "id": "754b7db441c4a59f",
        "type": "inject",
        "z": "ae598dcb0a2083ca",
        "name": "At Deploy",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 120,
        "y": 1152,
        "wires": [
            [
                "b90ba71de262da79"
            ]
        ]
    },
    {
        "id": "9cf45aa883fbaac1",
        "type": "api-call-service",
        "z": "ae598dcb0a2083ca",
        "name": "Send Solar Forecast",
        "server": "289362b880855228",
        "version": 5,
        "debugenabled": false,
        "domain": "input_text",
        "service": "set_value",
        "areaId": [],
        "deviceId": [],
        "entityId": [
            "input_text.helper_dayahead_solar_forecast"
        ],
        "data": "{\"value\":\"{{payload}}\"}",
        "dataType": "json",
        "mergeContext": "",
        "mustacheAltTags": false,
        "outputProperties": [],
        "queue": "all",
        "x": 676,
        "y": 1024,
        "wires": [
            []
        ]
    },
    {
        "id": "4322f98a7141a17d",
        "type": "api-call-service",
        "z": "ae598dcb0a2083ca",
        "name": "Send Consump Forecast",
        "server": "289362b880855228",
        "version": 5,
        "debugenabled": false,
        "domain": "input_text",
        "service": "set_value",
        "areaId": [],
        "deviceId": [],
        "entityId": [
            "input_text.helper_dayahead_consump_forecast"
        ],
        "data": "{\"value\":\"{{payload}}\"}",
        "dataType": "json",
        "mergeContext": "",
        "mustacheAltTags": false,
        "outputProperties": [],
        "queue": "all",
        "x": 686,
        "y": 1152,
        "wires": [
            []
        ]
    },
    {
        "id": "65dea081f2ae905b",
        "type": "debug",
        "z": "ae598dcb0a2083ca",
        "name": "Consump Forecast Wh",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 676,
        "y": 1200,
        "wires": []
    },
    {
        "id": "f29e221e89f1a978",
        "type": "function",
        "z": "ae598dcb0a2083ca",
        "name": "Make Solar Forecast Modified",
        "func": "// Make Solar Forecast Modified\n\n// This function reads the real Solar production \n// and modifies the forecast accordingly\n// higher or lower.\n\nlet Now = new Date()\nlet NowHour = Now.getHours();\nlet NowMinutes = Now.getMinutes();\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n            + (\"0\" + Now.getMinutes()).slice(-2);\n\nlet Solar_Forecast = global.get(\"Victron_MESS.Solar.Forecast.All\");\nif (Solar_Forecast == null) {\n    node.warn(\"Victron_MESS.Solar.Forecast.All: null\");\n}\n\nlet Sunrise_Hour = 9;\nlet Solar_Sunrise =  global.get(\"Victron_MESS.Solar.Sunrise\");\nif (Solar_Sunrise == null) {\n    node.warn(\"Victron_MESS.Solar.Sunrise: null\");\n}\nelse {\n    Sunrise_Hour = Number(Solar_Sunrise.substring(0,2)) + 1;    \n}\n    \nlet SolarEdge_Reading_kWh = global.get(\"SolarEdge_Reading_kWh\");\nif (SolarEdge_Reading_kWh == null) {\n    node.warn(\"SolarEdge_Reading_kWh: null\");\n    SolarEdge_Reading_kWh = new Array(24);\n    SolarEdge_Reading_kWh.fill(0);\n}\n\nlet DayAhead_PriceArray_Ct =  global.get(\"DayAhead_PriceArray_Ct\");\nif (DayAhead_PriceArray_Ct == null) {\n    node.warn(\"DayAhead_PriceArray_Ct: null\");\n    DayAhead_PriceArray_Ct = new Array(24);\n    DayAhead_PriceArray_Ct.fill(0);\n}\n\nlet Solar_Forecast_Modified = [];\nlet SolarEdge_Energy_Until_Now = 0;\nlet Solar_Forecast_Until_Now = 0;\nlet Solar_Correction_Factor = 1;\n\n\n// Find differences between Solar Forecast and reality\n\nfor (let H = 0; H < NowHour; H++) {\n    Solar_Forecast_Until_Now += Solar_Forecast[H];\n}\n\nSolar_Forecast_Until_Now += \n  Math.round(Solar_Forecast[NowHour] * NowMinutes / 60);\n\nif (SolarEdge_Reading_kWh != null) {\n    if (NowHour > Sunrise_Hour) {\n        if ( (SolarEdge_Reading_kWh[0] > 0)\n          && (SolarEdge_Reading_kWh[NowHour] > 0) )\n        {\n        SolarEdge_Energy_Until_Now =\n          Math.floor( \n            (SolarEdge_Reading_kWh[NowHour] - SolarEdge_Reading_kWh[0]) * 1000);\n\n        }\n        if ( (SolarEdge_Energy_Until_Now > 0)\n          && (Solar_Forecast_Until_Now > 0) )\n        {  \n            Solar_Correction_Factor = \n              SolarEdge_Energy_Until_Now / Solar_Forecast_Until_Now;\n        }\n    }\n}\n\nSolar_Correction_Factor = \n  Math.min(1.5, Math.max(0.25, Solar_Correction_Factor) ); \n\nfor (let H = 0; H < Solar_Forecast.length; H++) \n{\n    if (Math.round(DayAhead_PriceArray_Ct[H]) < 0) {\n        Solar_Forecast_Modified[H] = 0;\n    }\n    else {\n        Solar_Forecast_Modified[H] = \n          Math.round(Solar_Correction_Factor * Solar_Forecast[H]);\n    }\n}\n\nnode.status( \n  { fill:\"green\", \n    shape:\"dot\",\n    text: \"@ \" + NowTime + \"; Corr.factor: \" + Solar_Correction_Factor\n  } );\n\nglobal.set(\"Victron_MESS.Solar.Forecast.Modified\", Solar_Forecast_Modified);\n\nmsg.topic = \"Solar_Forecast_Modified\";\nmsg.payload = Solar_Forecast_Modified;\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 382,
        "y": 976,
        "wires": [
            [
                "9cf45aa883fbaac1",
                "8af277612b4e45e2"
            ]
        ]
    },
    {
        "id": "8af277612b4e45e2",
        "type": "debug",
        "z": "ae598dcb0a2083ca",
        "name": "Solar Forecast Modified",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 686,
        "y": 976,
        "wires": []
    },
    {
        "id": "89ab5cf8a6915005",
        "type": "debug",
        "z": "ae598dcb0a2083ca",
        "name": "Solar Json",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 950,
        "y": 512,
        "wires": []
    },
    {
        "id": "1ed49fdec0e29140",
        "type": "change",
        "z": "ae598dcb0a2083ca",
        "name": "General param",
        "rules": [
            {
                "t": "set",
                "p": "latitude",
                "pt": "msg",
                "to": "52.01016",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "longitude",
                "pt": "msg",
                "to": "4.43628",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 736,
        "y": 352,
        "wires": [
            [
                "bf2362d6bf6abd9a"
            ]
        ]
    },
    {
        "id": "289362b880855228",
        "type": "server",
        "name": "Home Assistant Api",
        "version": 5,
        "addon": false,
        "rejectUnauthorizedCerts": true,
        "ha_boolean": "y|yes|true|on|home|open",
        "connectionDelay": true,
        "cacheJson": true,
        "heartbeat": false,
        "heartbeatInterval": "30",
        "areaSelector": "friendlyName",
        "deviceSelector": "friendlyName",
        "entitySelector": "friendlyName",
        "statusSeparator": ": ",
        "statusYear": "hidden",
        "statusMonth": "short",
        "statusDay": "numeric",
        "statusHourCycle": "default",
        "statusTimeFormat": "h:m",
        "enableGlobalContextStore": false
    }
]
Afbeeldingslocatie: https://tweakers.net/i/qYBq30xTY5eM7WghsWrRGpxUty8=/full-fit-in/4920x3264/filters:max_bytes(3145728):no_upscale():strip_icc():fill(white):strip_exif()/f/image/EgEx8Ro0npze9tSxl6BCKZ8H.jpg?f=user_large

[ Voor 99% gewijzigd door MJ de Bruijn op 14-05-2024 15:11 ]


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
Dit is de volledige Flow "DayAhead"

Dit is de definitie van de Flow "DayAhead". Kopieer de onderstaande tekst en kies in Nod Red "Import Nodes". Plak dan deze tekst en kies voor import.
[
{
"id": "4f6322bd0b7fd9fa",
"type": "tab",
"label": "DayAhead",
"disabled": false,
"info": "",
"env": []
},
{
"id": "6f45a0577a249ee2",
"type": "function",
"z": "4f6322bd0b7fd9fa",
"name": "Create Entsoe URL",
"func": "// Create URL for Entsoe\n// to call today's day-ahead-prices\n\ndelete global[\"DayAhead_XML\"];\n\nconst today = new Date()\nconst tomorrow = new Date(today)\ntomorrow.setDate(tomorrow.getDate() + 1)\n\nlet EntsoeApiKey = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx\"\n\nlet todayDate = (\"0\" + today.getDate()).slice(-2)\nlet todayMonth = (\"0\" + (today.getMonth() + 1)).slice(-2);\nlet todayYear = today.getFullYear();\nlet todayString = todayYear + todayMonth + todayDate;\nlet NowTime = (\"0\" + today.getHours()).slice(-2) + \":\"\n + (\"0\" + today.getMinutes()).slice(-2);\n\nlet tomorrowDate = (\"0\" + tomorrow.getDate()).slice(-2)\nlet tomorrowMonth = (\"0\" + (tomorrow.getMonth() + 1)).slice(-2);\nlet tomorrowYear = tomorrow.getFullYear();\nlet tomorrowString = tomorrowYear + tomorrowMonth + tomorrowDate;\n\nvar UrlAddr=\"https://web-api.tp.entsoe.eu/api\" // New server-address\nUrlAddr = UrlAddr + \"?securityToken=\" + EntsoeApiKey\nUrlAddr = UrlAddr + \"&documentType=A44\" // Price Document \nUrlAddr = UrlAddr + \"&in_Domain=10YNL----------L\" // Netherlands\nUrlAddr = UrlAddr + \"&out_Domain=10YNL----------L\"\nUrlAddr = UrlAddr + \"&periodStart=\" + todayString\nUrlAddr = UrlAddr + \"0000&periodEnd=\" + tomorrowString + \"2300\"\n\nmsg.url = UrlAddr;\n\nnode.status( { fill:\"green\", \n shape:\"dot\",\n text: \"@ \" + NowTime } \n );\n\nreturn msg;\n",
"outputs": 1,
"timeout": "4",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 346,
"y": 192,
"wires": [
[
"74bfd95ffb14c074",
"1392e6bbd5e85054"
]
]
},
{
"id": "a57276171c35b798",
"type": "inject",
"z": "4f6322bd0b7fd9fa",
"name": "At 00:06",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "06 00 * * *",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 124,
"y": 96,
"wires": [
[
"5f2cf11930a2e7ff"
]
]
},
{
"id": "6b10a3e017075c14",
"type": "http request",
"z": "4f6322bd0b7fd9fa",
"name": "",
"method": "GET",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 326,
"y": 288,
"wires": [
[
"6dee0dc43a43c582",
"b25c69e0b418a692"
]
]
},
{
"id": "6dee0dc43a43c582",
"type": "xml",
"z": "4f6322bd0b7fd9fa",
"name": "",
"property": "payload",
"attr": "",
"chr": "",
"x": 306,
"y": 384,
"wires": [
[
"ad3a6bc7227fe408"
]
]
},
{
"id": "ce665e4a447fdddb",
"type": "function",
"z": "4f6322bd0b7fd9fa",
"name": "Generate DayAhead PriceArrayCt",
"func": "// Generate Price Array from Entsoe XML\n\nconst Now = new Date()\nlet NowDate = (\"0\" + Now.getDate()).slice(-2);\nlet NowMonth = (\"0\" + (Now.getMonth() + 1)).slice(-2);\nlet NowYear = Now.getFullYear();\nlet NowHour = (\"0\" + Now.getHours()).slice(-2);\nlet NowMin = (\"0\" + Now.getMinutes()).slice(-2);\nlet NowSec = (\"0\" + Now.getSeconds()).slice(-2);\n\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2);\n\nvar PriceArray=[];\nvar PriceArrayCt= [];\n\n// global.set(\"DayAhead_PriceArray\", null);\n\nvar objPayload = global.get('Victron_MESS.DayAhead.Entsoe.XML') || \"\";\n// node.warn(\"After DayAhead_XML\");\n\nif (objPayload != \"\") {\n \n var MarketDocument = objPayload.Publication_MarketDocument;\n // series_0 starts yesterday 22:00\n var TimeSeries_0 = MarketDocument.TimeSeries[0];\n var Periods_0 = TimeSeries_0.Period[0];\n \n var TimeInterval = Periods_0.timeInterval[0];\n var TimeIntervalStart = TimeInterval.start[0];\n var NumberOfPeriods = 0;\n \n // verify if data are for current date\n // if so, start was yesterday 23:00\n // node.warn(TimeIntervalStart.substring(0,10));\n \n let yesterday = new Date();\n yesterday.setDate(yesterday.getDate() - 1);\n let yesterdayDate = (\"0\" + yesterday.getDate()).slice(-2)\n let yesterdayMonth = (\"0\" + (yesterday.getMonth() + 1)).slice(-2);\n let yesterdayYear = yesterday.getFullYear();\n let yesterdayText = yesterdayYear + \"-\" + yesterdayMonth + \"-\" +yesterdayDate;\n // node.warn(yesterdayText);\n \n if (TimeIntervalStart.substring(0,10) == yesterdayText) {\n // if MarketDocument is for the current date\n \n // today starts in array[2]\n NumberOfPoints = Periods_0.Point.length;\n for (let i = 0; i < NumberOfPoints; i++) {\n \n PriceHour = Periods_0.Point[i][\"position\"][0];\n PriceArray[i] = Periods_0.Point[i][\"price.amount\"][0];\n // node.warn(i + \": \"+ PriceHour + \" = \"+ PriceArray[i]);\n }\n\n if (MarketDocument.TimeSeries[1] != null) {\n // series_1 starts today 22:00\n \n var TimeSeries_1 = MarketDocument.TimeSeries[1];\n var Periods_1 = TimeSeries_1.Period[0];\n // node.warn(\"After Periods_1\");\n // last two hours of today are in Periods_1\n // node.warn(Periods_1);\n \n NumberOfPoints = Periods_1.Point.length;\n // node.warn(NumberOfPoints);\n \n for (let i=0; i < NumberOfPoints; i++) {\n // the index of the PriceArray is the hour of that price\n PriceHour = Periods_1.Point[i][\"position\"][0];\n PriceArray[i+24] = Periods_1.Point[i][\"price.amount\"][0];\n // node.warn(i + \": \"+ PriceHour + \" = \"+ PriceArray[i+24]);\n }\n } // if there is a second day of prices available\n\n } // if source has right day\n \n} // if there is a valid XML available\n\n// Euro per 1000 kWh to Cent per KwH\nfor (let i=0; i<PriceArray.length; i++)\n{\n if (PriceArray[i] == null) // summertime/wintertime\n {\n PriceArrayCt[i] = Math.round(PriceArray[i+1]*100)/1000;\n }\n else\n {\n PriceArrayCt[i] = Math.round(PriceArray[i]*100)/1000;\n }\n}\n\n\n\nlet InfoTime = new Date(TimeIntervalStart);\nnode.status({fill:\"green\",shape:\"dot\",\n text: \"@ \" + NowTime + \": \"\n + PriceArrayCt[Number(NowHour)] + \" ct; \" \n + PriceArrayCt.length + \" elements.\"\n });\n\nlet msgPriceArrayCt = \n { topic: \"PriceArrayCt\", payload: PriceArrayCt };\n \nreturn msgPriceArrayCt;\n",
"outputs": 1,
"timeout": "4",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 204,
"y": 704,
"wires": [
[
"5e1c5297522f05dc",
"925e2713a4884189",
"7dc012703ccb5f1a"
]
]
},
{
"id": "dbc821ad7ac2e521",
"type": "debug",
"z": "4f6322bd0b7fd9fa",
"name": "DayAhead XML",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 928,
"y": 480,
"wires": []
},
{
"id": "e6b1645356c93808",
"type": "inject",
"z": "4f6322bd0b7fd9fa",
"name": "60 min",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "0 0-22 * * *",
"once": true,
"onceDelay": "5",
"topic": "GeneratePriceArray",
"payload": "",
"payloadType": "date",
"x": 124,
"y": 640,
"wires": [
[
"e4187d41abc1a343"
]
]
},
{
"id": "5e815491529a9dda",
"type": "debug",
"z": "4f6322bd0b7fd9fa",
"name": "Entsoe URL",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 918,
"y": 288,
"wires": []
},
{
"id": "4cd4a2952a083404",
"type": "debug",
"z": "4f6322bd0b7fd9fa",
"name": "Entsoe HTTP",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 928,
"y": 384,
"wires": []
},
{
"id": "1bc232149754d8a1",
"type": "function",
"z": "4f6322bd0b7fd9fa",
"name": "PriceArrayCt < 48 items",
"func": "// Verify if PriceArray exists in Global\n// After a restart, Global will be cleared\n\nlet Now = new Date()\nlet NowHour = (\"0\" + Now.getHours()).slice(-2);\n\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2);\n\n\nPriceArrayCt = global.get(\"DayAhead_PriceArray_Ct\");\n\nlet StatusText = \"\";\n\nif (PriceArrayCt == null)\n{\n StatusText = \"PriceArrayCt is null.\";\n}\nelse if (Number(NowHour) < 15) \n{\n StatusText = \"Time before 15:00 hr.\";\n msg = null; // No return\n}\nelse if (PriceArrayCt.length < 48) \n{\n StatusText = PriceArrayCt.length + \" elements.\";\n}\nelse\n{ // length should be OK\n StatusText = PriceArrayCt.length + \" elements.\";\n msg = null; // No return\n}\n\nnode.status( \n { fill:\"green\", \n shape:\"dot\",\n text: \"@ \" + NowTime + \": \" + StatusText } \n);\n\nreturn msg;\n",
"outputs": 1,
"timeout": "4",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 366,
"y": 48,
"wires": [
[
"5f2cf11930a2e7ff"
]
]
},
{
"id": "8c6d51ca669a5689",
"type": "inject",
"z": "4f6322bd0b7fd9fa",
"name": "15:00-20:00",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "0 15-19 * * *",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 134,
"y": 48,
"wires": [
[
"1bc232149754d8a1"
]
]
},
{
"id": "bfb76f72073dc599",
"type": "change",
"z": "4f6322bd0b7fd9fa",
"name": "",
"rules": [
{
"t": "set",
"p": "Victron_MESS.DayAhead.Entsoe.XML",
"pt": "global",
"to": "payload",
"tot": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 710,
"y": 432,
"wires": [
[
"90b4735b4ee5fa47",
"dbc821ad7ac2e521"
]
]
},
{
"id": "599edd62d6c522c0",
"type": "switch",
"z": "4f6322bd0b7fd9fa",
"name": "if DayAhead_XML is Null",
"property": "Victron_MESS.DayAhead.Entsoe.XML",
"propertyType": "global",
"rules": [
{
"t": "null"
},
{
"t": "nnull"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 366,
"y": 144,
"wires": [
[
"6f45a0577a249ee2"
],
[
"7b13c697dfdcf97f"
]
]
},
{
"id": "7b13c697dfdcf97f",
"type": "switch",
"z": "4f6322bd0b7fd9fa",
"name": "if PriceArray is Null",
"property": "Victron_MESS.DayAhead.PriceArray",
"propertyType": "global",
"rules": [
{
"t": "null"
}
],
"checkall": "false",
"repair": false,
"outputs": 1,
"x": 614,
"y": 144,
"wires": [
[
"e4187d41abc1a343"
]
]
},
{
"id": "eb930b12b5534cdd",
"type": "inject",
"z": "4f6322bd0b7fd9fa",
"name": "Every 1 min",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "60",
"crontab": "",
"once": true,
"onceDelay": "5",
"topic": "",
"payload": "",
"payloadType": "date",
"x": 134,
"y": 144,
"wires": [
[
"599edd62d6c522c0"
]
]
},
{
"id": "5f2cf11930a2e7ff",
"type": "change",
"z": "4f6322bd0b7fd9fa",
"name": "",
"rules": [
{
"t": "delete",
"p": "Victron_MESS.DayAhead.Entsoe.XML",
"pt": "global"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 446,
"y": 96,
"wires": [
[
"599edd62d6c522c0"
]
]
},
{
"id": "925e2713a4884189",
"type": "change",
"z": "4f6322bd0b7fd9fa",
"name": "set global.DayAhead_PriceArray_Ct",
"rules": [
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "DayAhead_PriceArray_Ct",
"tot": "str"
},
{
"t": "set",
"p": "Victron_MESS.DayAhead.PriceArray_Ct",
"pt": "global",
"to": "payload",
"tot": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 202,
"y": 864,
"wires": [
[
"80a020848175e92c"
]
]
},
{
"id": "90b4735b4ee5fa47",
"type": "switch",
"z": "4f6322bd0b7fd9fa",
"name": "if DayAhead_XML is Null",
"property": "Victron_MESS.DayAhead.Entsoe.XML",
"propertyType": "global",
"rules": [
{
"t": "null"
},
{
"t": "nnull"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 638,
"y": 528,
"wires": [
[
"79d22e6f28fd0d91"
],
[
"e4187d41abc1a343"
]
]
},
{
"id": "79d22e6f28fd0d91",
"type": "change",
"z": "4f6322bd0b7fd9fa",
"name": "",
"rules": [
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "DayAhead_XML is NULL",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 874,
"y": 528,
"wires": [
[
"657b2124c1406398"
]
]
},
{
"id": "657b2124c1406398",
"type": "link out",
"z": "4f6322bd0b7fd9fa",
"name": "To E-mail",
"mode": "link",
"links": [
"1159231654345797"
],
"x": 927,
"y": 560,
"wires": []
},
{
"id": "d3dc810be6647526",
"type": "change",
"z": "4f6322bd0b7fd9fa",
"name": "",
"rules": [
{
"t": "set",
"p": "Victron_MESS.DayAhead.Entsoe.URL",
"pt": "global",
"to": "payload",
"tot": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 700,
"y": 240,
"wires": [
[
"5e815491529a9dda"
]
]
},
{
"id": "412953ae0e3814f2",
"type": "change",
"z": "4f6322bd0b7fd9fa",
"name": "",
"rules": [
{
"t": "set",
"p": "Victron_MESS.DayAhead.Entsoe.HTTP",
"pt": "global",
"to": "payload",
"tot": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 710,
"y": 336,
"wires": [
[
"4cd4a2952a083404"
]
]
},
{
"id": "74bfd95ffb14c074",
"type": "change",
"z": "4f6322bd0b7fd9fa",
"name": "",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "url",
"tot": "msg"
},
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "Entsoe_URL",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 600,
"y": 192,
"wires": [
[
"d3dc810be6647526"
]
]
},
{
"id": "257e19b06616368f",
"type": "inject",
"z": "4f6322bd0b7fd9fa",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 124,
"y": 192,
"wires": [
[
"6f45a0577a249ee2"
]
]
},
{
"id": "b25c69e0b418a692",
"type": "change",
"z": "4f6322bd0b7fd9fa",
"name": "",
"rules": [
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "Entsoe_HTTP",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 590,
"y": 288,
"wires": [
[
"412953ae0e3814f2"
]
]
},
{
"id": "ad3a6bc7227fe408",
"type": "change",
"z": "4f6322bd0b7fd9fa",
"name": "",
"rules": [
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "Entsoe_XML",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 590,
"y": 384,
"wires": [
[
"bfb76f72073dc599"
]
]
},
{
"id": "80a020848175e92c",
"type": "function",
"z": "4f6322bd0b7fd9fa",
"name": "High and Low Hours",
"func": "// Find High and Low Hours from PriceArray\n\ncontext = context || {};\n\nvar Now = new Date()\nvar NowHour = Now.getHours();\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2);\n\n\nvar PriceArrayCt = null;\nvar HoursOfHighPrice = [];\nvar HoursOfLowPrice = [];\nvar HoursOfVeryLowPrice = [];\nvar HoursOfZeroPrice = [];\nvar HoursOfNegativePrice = [];\nvar DayAhead_Hours = \"\";\n\nvar HighPriceArray = [];\nvar LowPriceArray = [];\nvar VeryLowPriceArray = [];\nvar ZeroPriceArray = [];\nvar NegativePriceArray = [];\n\nconst NegativePriceThreshold = -0.01; // -0.01\nconst ZeroPriceThreshold = 0.1; // 0.1\nconst VeryLowPriceThreshold = 2;\n\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n// each pair contains [ hour, price ]\n\nconst PairSortFn = (a, b) =>\n{\n if ( ( (a[0] <= 23) && (b[0] <= 23) )\n || ( (a[0] > 23) && (b[0] > 23) )\n ) // same day\n {\n if (a[1] < b[1])\n { return -1 }\n else { return 1 }\n \n } \n else if ( (a[0] <= 23) && (b[0] > 23) )\n { return -1 } \n else { return 1 } \n \n}\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\nconst PairSortDescFn = (a, b) =>\n{\n if ( ( (a[0] <= 23) && (b[0] <= 23) )\n || ( (a[0] > 23) && (b[0] > 23) )\n ) // same day\n {\n if (b[1] < a[1])\n { return -1 }\n else { return 1 }\n \n } \n else if ( (a[0] <= 23) && (b[0] > 23) )\n { return -1 } \n else { return 1 } \n \n}\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\nfunction IsInArrayOfPairs(p_ArrayOfPairs, p_SearchHours)\n{ // See if any of the items within array p_SearchHours\n // are in array p_ArrayOfPairs{hour, value]\n let Result = false;\n \n for (let i = 0; i < p_SearchHours.length; i++) {\n for ( let j = 0; j < p_ArrayOfPairs.length; j++)\n {\n if ( p_ArrayOfPairs[j][0] == p_SearchHours[i] ) \n {\n Result = true;\n break;\n } // Item was found\n } // for j\n if (Result) break;\n } // all items\n \n return Result; // not found\n \n} // IsInArrayOfPairs\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n\nswitch (msg.topic) {\n \n case \"PriceArrayCt\":\n PriceArrayCt = msg.payload;\n msg = null;\n break;\n \n case \"DayAhead_PriceArray_Ct\":\n PriceArrayCt = msg.payload;\n msg = null;\n break;\n \n default: \n node.warn( \"Unknown Topic: \" + msg.topic \n + \" Payload: \" + msg.payload);\n\n} // switch for input all variables\n\nlet DebugText = \"\";\n\nif (PriceArrayCt != null)\n{\n // find hours of Highest and Lowest prices\n let Today_HighestPrice = \n PriceArrayCt.slice(0,23).reduce((a, b) => Math.max(a, b), -Infinity);\n \n let Today_LowestPrice = \n PriceArrayCt.slice(0,23).reduce((a, b) => Math.min(a, b), Infinity);\n \n let Today_Sum = PriceArrayCt.slice(0, 23).reduce((a, b) => a + b, 0);\n let Today_Count = PriceArrayCt.slice(0, 23).length;\n let Today_AveragePrice = Math.round(1000*Today_Sum/Today_Count)/1000;\n\n let Today_HighPrice_Threshold = \n Math.round((Today_AveragePrice + (Today_HighestPrice - Today_AveragePrice) / 2) * 1000) / 1000;\n \n let Today_LowPrice_Threshold = \n Math.round((Today_AveragePrice - (Today_AveragePrice - Today_LowestPrice) * 0.75) * 1000) / 1000;\n\n let Tomorrow_HighestPrice = \n PriceArrayCt.slice(24).reduce((a, b) => Math.max(a, b), -Infinity);\n \n let Tomorrow_LowestPrice = \n PriceArrayCt.slice(24).reduce((a, b) => Math.min(a, b), Infinity);\n \n let Tomorrow_Sum = PriceArrayCt.slice(24).reduce((a, b) => a + b, 0);\n let Tomorrow_Count = PriceArrayCt.slice(24).length;\n let Tomorrow_AveragePrice = Math.round(1000*Tomorrow_Sum/Tomorrow_Count)/1000;\n\n let Tomorrow_HighPrice_Threshold = \n Math.round((Tomorrow_AveragePrice + (Tomorrow_HighestPrice - Tomorrow_AveragePrice) / 2)* 1000) / 1000;\n \n let Tomorrow_LowPrice_Threshold = \n Math.round((Tomorrow_AveragePrice - (Tomorrow_AveragePrice - Tomorrow_LowestPrice) / 2) * 1000) / 1000;\n\nDebugText +=\n\"Today_HighestPrice: \" + Today_HighestPrice + \"\\r\"\n+ \"Today_LowestPrice: \" + Today_LowestPrice + \"\\r\"\n+ \"Today_AveragePrice: \" + Today_AveragePrice + \"\\r\"\n+ \"Today_HighPrice_Threshold: \" + Today_HighPrice_Threshold + \"\\r\"\n+ \"Today_LowPrice_Threshold: \" + Today_LowPrice_Threshold + \"\\r\"\n+ \"\\r\"\n+ \"Tomorrow_HighestPrice: \" + Tomorrow_HighestPrice + \"\\r\"\n+ \"Tomorrow_LowestPrice: \" + Tomorrow_LowestPrice + \"\\r\"\n+ \"Tomorrow_AveragePrice: \" + Tomorrow_AveragePrice + \"\\r\"\n+ \"Tomorrow_HighPrice_Threshold: \" + Tomorrow_HighPrice_Threshold + \"\\r\"\n+ \"Tomorrow_LowPrice_Threshold: \" + Tomorrow_LowPrice_Threshold + \"\\r\"\n;\n\n// node.warn(DebugText);\n\n for (let H = NowHour; H <= PriceArrayCt.length; H++) \n {\n if (PriceArrayCt[H] <= NegativePriceThreshold)\n { NegativePriceArray.push([H, PriceArrayCt[H]] ) }\n \n else if (PriceArrayCt[H] <= ZeroPriceThreshold)\n { ZeroPriceArray.push([H, PriceArrayCt[H]] ) }\n \n else if (PriceArrayCt[H] <= VeryLowPriceThreshold)\n { VeryLowPriceArray.push([H, PriceArrayCt[H]] ) }\n \n else if ( ( (H <= 23) && (PriceArrayCt[H] < Today_LowPrice_Threshold) )\n || ( (H > 23) && (PriceArrayCt[H] < Tomorrow_LowPrice_Threshold) )\n )\n { LowPriceArray.push([H, PriceArrayCt[H]] ) }\n \n else if ( ( (H <= 23) && (PriceArrayCt[H] > Today_HighPrice_Threshold) )\n || ( (H > 23) && (PriceArrayCt[H] > Tomorrow_HighPrice_Threshold) )\n )\n { HighPriceArray.push([H, PriceArrayCt[H]] ) }\n \n else { // normal hours\n // \n }\n\n } // try all prices\n\n // Sort High to Low\n HighPriceArray.sort( PairSortDescFn ) ;\n \n // Sort Low to High\n LowPriceArray.sort( PairSortFn) ;\n VeryLowPriceArray.sort( PairSortFn ) ;\n ZeroPriceArray.sort( PairSortFn ) ;\n NegativePriceArray.sort( PairSortFn ) ;\n\n\n DayAhead_Hours = PriceArrayCt.length + \" hrs,\" ;\n\n DayAhead_Hours += \" Now: \" + \n PriceArrayCt[NowHour] + \" ct.\";\n\n\n if (NegativePriceArray.length > 0)\n { \n DayAhead_Hours += \" Negative: [\"\n for (let i = 0; i < NegativePriceArray.length; i++) {\n if (NegativePriceArray[i][0] < 24) {\n DayAhead_Hours += NegativePriceArray[i][0].toString();\n }\n else {\n DayAhead_Hours += \"+\" + (NegativePriceArray[i][0]-24).toString();\n }\n if (i != NegativePriceArray.length-1){\n DayAhead_Hours += \",\";\n }\n }\n DayAhead_Hours += \"],\"\n }\n\n if (ZeroPriceArray.length > 0)\n { \n DayAhead_Hours += \" Zero: [\"\n for (let i = 0; i < ZeroPriceArray.length; i++) {\n if (ZeroPriceArray[i][0] < 24) {\n DayAhead_Hours += ZeroPriceArray[i][0].toString();\n }\n else {\n DayAhead_Hours += \"+\" + (ZeroPriceArray[i][0]-24).toString();\n }\n if (i != ZeroPriceArray.length-1){\n DayAhead_Hours += \",\";\n }\n }\n DayAhead_Hours += \"],\"\n }\n \n if (VeryLowPriceArray.length > 0)\n { \n DayAhead_Hours += \" Very Low: [\"\n for (let i = 0; i < VeryLowPriceArray.length; i++) {\n if (VeryLowPriceArray[i][0] < 24) {\n DayAhead_Hours += VeryLowPriceArray[i][0].toString();\n }\n else {\n DayAhead_Hours += \"+\" + (VeryLowPriceArray[i][0]-24).toString();\n }\n if (i != VeryLowPriceArray.length-1){\n DayAhead_Hours += \",\";\n }\n }\n DayAhead_Hours += \"]\"\n }\n \n if (LowPriceArray.length > 0)\n { \n DayAhead_Hours += \" Low: [\"\n for (let i = 0; i < LowPriceArray.length; i++) {\n if (LowPriceArray[i][0] < 24) {\n DayAhead_Hours += LowPriceArray[i][0].toString();\n }\n else {\n DayAhead_Hours += \"+\" + (LowPriceArray[i][0]-24).toString();\n }\n if (i != LowPriceArray.length-1){\n DayAhead_Hours += \",\";\n }\n }\n DayAhead_Hours += \"],\" ;\n }\n \n if (HighPriceArray.length > 0)\n { \n DayAhead_Hours += \" High: [\" ;\n for (let i = 0; i < HighPriceArray.length; i++) {\n if (HighPriceArray[i][0] < 24) {\n DayAhead_Hours += HighPriceArray[i][0].toString();\n }\n else {\n DayAhead_Hours += \"+\" + (HighPriceArray[i][0]-24).toString();\n }\n if (i != HighPriceArray.length-1){\n DayAhead_Hours += \",\";\n }\n }\n DayAhead_Hours += \"]\";\n }\n \n // - - - - - - - - - - - - - - - - - - - - - - - - - -\n \n msg_High = {topic: \"HighPriceArray\", payload: HighPriceArray };\n \n msg_Low = {topic: \"LowPriceArray\", payload: LowPriceArray };\n \n msg_VeryLow = {topic: \"VeryLowPriceArray\", payload: VeryLowPriceArray };\n \n msg_Zero = {topic: \"ZeroPriceArray\", payload: ZeroPriceArray };\n \n msg_Negative = {topic: \"NegativePriceArray\", payload: NegativePriceArray };\n \n msg_DayAhead_Hours = { topic: \"DayAhead_Hours\", payload: DayAhead_Hours };\n\n let Price_State = \"Unknown\";\n\n if (IsInArrayOfPairs(NegativePriceArray, [NowHour]) ) {\n Price_State = \"Negative\";\n }\n else if (IsInArrayOfPairs(ZeroPriceArray, [NowHour]) ) {\n Price_State = \"Zero\";\n }\n else if (IsInArrayOfPairs(HighPriceArray, [NowHour]) ) {\n Price_State = \"High\";\n }\n else if (IsInArrayOfPairs(VeryLowPriceArray, [NowHour]) ) {\n Price_State = \"Very Low\";\n }\n else if (IsInArrayOfPairs(LowPriceArray, [NowHour]) ) {\n Price_State = \"Low\";\n }\n else {\n Price_State = \"Normal\";\n }\n\n msg_Price_State = { topic: \"Price_State\", payload: Price_State };\n\n node.status({fill:\"green\",\n shape:\"dot\",\n text: \"@ \" + NowTime + \"; \" + DayAhead_Hours});\n\n node.send(msg_High);\n node.send(msg_Low);\n node.send(msg_VeryLow);\n node.send(msg_Zero);\n node.send(msg_Negative);\n node.send(msg_DayAhead_Hours);\n node.send(msg_Price_State);\n node.done();\n \n} // if PriceArray available\n",
"outputs": 1,
"timeout": "4",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 156,
"y": 944,
"wires": [
[
"39de05eee99d3558"
]
]
},
{
"id": "2f41f395a7d8c0d5",
"type": "switch",
"z": "4f6322bd0b7fd9fa",
"name": "DayAhead_Hours",
"property": "topic",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "DayAhead_Hours",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 146,
"y": 1056,
"wires": [
[
"524e178541654819"
]
]
},
{
"id": "524e178541654819",
"type": "api-call-service",
"z": "4f6322bd0b7fd9fa",
"name": "Send DayAhead Hours",
"server": "289362b880855228",
"version": 5,
"debugenabled": false,
"domain": "input_text",
"service": "set_value",
"areaId": [],
"deviceId": [],
"entityId": [
"input_text.helper_dayahead_hours"
],
"data": "{\"value\":\"{{payload}}\"}",
"dataType": "json",
"mergeContext": "",
"mustacheAltTags": false,
"outputProperties": [],
"queue": "all",
"x": 612,
"y": 1056,
"wires": [
[]
]
},
{
"id": "48cd761b688d3b60",
"type": "link out",
"z": "4f6322bd0b7fd9fa",
"name": "From Hours to Targets",
"mode": "link",
"links": [
"8803291847335701"
],
"x": 305,
"y": 1008,
"wires": []
},
{
"id": "1392e6bbd5e85054",
"type": "delay",
"z": "4f6322bd0b7fd9fa",
"name": "max 1 per 15 min",
"pauseType": "rate",
"timeout": "5",
"timeoutUnits": "seconds",
"rate": "1",
"nbRateUnits": "15",
"rateUnits": "minute",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": true,
"allowrate": false,
"outputs": 1,
"x": 354,
"y": 240,
"wires": [
[
"6b10a3e017075c14"
]
]
},
{
"id": "5e1c5297522f05dc",
"type": "switch",
"z": "4f6322bd0b7fd9fa",
"name": "if PriceArrayCt is Null",
"property": "Victron_MESS.DayAhead.PriceArray_Ct",
"propertyType": "global",
"rules": [
{
"t": "null"
}
],
"checkall": "false",
"repair": false,
"outputs": 1,
"x": 532,
"y": 704,
"wires": [
[
"746b9d29bbd1991b"
]
]
},
{
"id": "746b9d29bbd1991b",
"type": "change",
"z": "4f6322bd0b7fd9fa",
"name": "",
"rules": [
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "DayAhead_PriceArray_Ct is NULL",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 790,
"y": 704,
"wires": [
[
"2e4d91798a7084e1",
"774a37af1765ae9e"
]
]
},
{
"id": "2e4d91798a7084e1",
"type": "link out",
"z": "4f6322bd0b7fd9fa",
"name": "To E-mail",
"mode": "link",
"links": [
"1159231654345797"
],
"x": 913,
"y": 688,
"wires": []
},
{
"id": "ad7052b99acf5a25",
"type": "switch",
"z": "4f6322bd0b7fd9fa",
"name": "Price_State",
"property": "topic",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "Price_State",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 126,
"y": 1104,
"wires": [
[
"3377df15d16d89ed",
"58695df549a1a8c5"
]
]
},
{
"id": "6d88ab14e49976f1",
"type": "api-call-service",
"z": "4f6322bd0b7fd9fa",
"name": "Send DayAhead Price_State",
"server": "289362b880855228",
"version": 5,
"debugenabled": false,
"domain": "input_select",
"service": "select_option",
"areaId": [],
"deviceId": [],
"entityId": [
"input_select.helper_dayahead_price_state"
],
"data": "{\"option\":\"{{payload}}\"}",
"dataType": "json",
"mergeContext": "",
"mustacheAltTags": false,
"outputProperties": [],
"queue": "all",
"x": 632,
"y": 1120,
"wires": [
[]
]
},
{
"id": "5c6a25613db1c877",
"type": "api-call-service",
"z": "4f6322bd0b7fd9fa",
"name": "Send DayAhead PriceArray Ct",
"server": "289362b880855228",
"version": 5,
"debugenabled": false,
"domain": "input_text",
"service": "set_value",
"areaId": [],
"deviceId": [],
"entityId": [
"input_text.helper_dayahead_pricearray"
],
"data": "{\"value\":\"{{payload}}\"}",
"dataType": "json",
"mergeContext": "",
"mustacheAltTags": false,
"outputProperties": [],
"queue": "all",
"x": 590,
"y": 784,
"wires": [
[]
]
},
{
"id": "7dc012703ccb5f1a",
"type": "function",
"z": "4f6322bd0b7fd9fa",
"name": "Text to Value",
"func": "// Change text array to number array\n\nconst Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2);\n\n\nInputArr = msg.payload;\nOutputArr = new Array(InputArr.length);\n\nfor (let i=0; i < InputArr.length; i++)\n{\n OutputArr[i] = Math.round(100*InputArr[i])/100;\n}\n\nmsg.payload = OutputArr;\n\nnode.status({fill:\"green\",shape:\"dot\",\n text: \"@ \" + NowTime + \": \"\n + msg.payload\n });\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 134,
"y": 800,
"wires": [
[
"5c6a25613db1c877"
]
]
},
{
"id": "3377df15d16d89ed",
"type": "link out",
"z": "4f6322bd0b7fd9fa",
"name": "To Main Function",
"mode": "link",
"links": [
"26931988f2071217"
],
"x": 305,
"y": 1104,
"wires": []
},
{
"id": "e4187d41abc1a343",
"type": "delay",
"z": "4f6322bd0b7fd9fa",
"name": "",
"pauseType": "rate",
"timeout": "5",
"timeoutUnits": "seconds",
"rate": "1",
"nbRateUnits": "5",
"rateUnits": "second",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": true,
"allowrate": false,
"outputs": 1,
"x": 416,
"y": 640,
"wires": [
[
"ce665e4a447fdddb"
]
]
},
{
"id": "774a37af1765ae9e",
"type": "debug",
"z": "4f6322bd0b7fd9fa",
"name": "PriceArrayCt is NULL",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 860,
"y": 752,
"wires": []
},
{
"id": "58695df549a1a8c5",
"type": "function",
"z": "4f6322bd0b7fd9fa",
"name": "Show Payload",
"func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n + (\"0\" + Now.getSeconds()).slice(-2)\n ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n text: \"@ \" + NowTime + \": \"\n + msg.topic + \": \" + msg.payload }\n );\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 368,
"y": 1136,
"wires": [
[
"6d88ab14e49976f1"
]
]
},
{
"id": "39de05eee99d3558",
"type": "rbe",
"z": "4f6322bd0b7fd9fa",
"name": "Only Changes",
"func": "rbe",
"gap": "",
"start": "",
"inout": "out",
"septopics": true,
"property": "payload",
"topi": "topic",
"x": 136,
"y": 1008,
"wires": [
[
"48cd761b688d3b60",
"2f41f395a7d8c0d5",
"ad7052b99acf5a25"
]
]
},
{
"id": "289362b880855228",
"type": "server",
"name": "Home Assistant Api",
"version": 5,
"addon": false,
"rejectUnauthorizedCerts": true,
"ha_boolean": "y|yes|true|on|home|open",
"connectionDelay": true,
"cacheJson": true,
"heartbeat": false,
"heartbeatInterval": "30",
"areaSelector": "friendlyName",
"deviceSelector": "friendlyName",
"entitySelector": "friendlyName",
"statusSeparator": ": ",
"statusYear": "hidden",
"statusMonth": "short",
"statusDay": "numeric",
"statusHourCycle": "default",
"statusTimeFormat": "h:m",
"enableGlobalContextStore": false
}
]
Afbeeldingslocatie: https://tweakers.net/i/azKIB8H-2QsPuibKiYcpTAFYrVM=/full-fit-in/4920x3264/filters:max_bytes(3145728):no_upscale():strip_icc():fill(white):strip_exif()/f/image/mM0OSfVz7rMx7MKBleMvJspA.jpg?f=user_large

[ Voor 185% gewijzigd door MJ de Bruijn op 14-05-2024 15:11 ]


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
Deel 1 van de Flow "Targets"

Dit is de definitie van de Flow "Targets". Kopieer de onderstaande tekst en kies in Nod Red "Import Nodes". Plak dan deze tekst en kies voor import.
[
{
"id": "4c84754039c72f25",
"type": "tab",
"label": "Flow 1",
"disabled": false,
"info": "",
"env": []
},
{
"id": "23a23f815062998a",
"type": "function",
"z": "4c84754039c72f25",
"name": "Calculate Targets",
"func": "// Function Calculate Targets\n\n",
"outputs": 1,
"timeout": "4",
"noerr": 0,
"initialize": "// Code added here will be run once\n// whenever the node is started.\n\ncontext = context || {};\n\n// Variables for input messages\ncontext.RunNow = false;\ncontext.DESS_Mode = null;\ncontext.Energy_from_Solar_Now_W = null;\ncontext.NegativePriceArray = null;\ncontext.ZeroPriceArray = null;\ncontext.VeryLowPriceArray = null;\ncontext.LowPriceArray = null;\ncontext.HighPriceArray = null;\ncontext.SoC_Now_Perc = null;\ncontext.SoC_Min_Venus = null;\ncontext.Max_Charge_A = null;\ncontext.Max_Charge_V = null;\ncontext.Grid_Setpoint = null;\ncontext.Charge_Disable = null;\ncontext.Solar_Charger = null;\ncontext.Relay_2 = null;\ncontext.Grid_to_Batt_Limit_A = 120; // dafault\ncontext.DebugOn = false;\n\n// results from previous calculations\ncontext.Batt_Grid_End_Time = 0;\n",
"finalize": "",
"libs": [],
"x": 428,
"y": 496,
"wires": [
[]
]
},
{
"id": "02d85b3059719c8d",
"type": "inject",
"z": "4c84754039c72f25",
"name": "Debug",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "Debug",
"payload": "1",
"payloadType": "num",
"x": 128,
"y": 544,
"wires": [
[
"23a23f815062998a"
]
]
},
{
"id": "7fa818493fc4b1c7",
"type": "inject",
"z": "4c84754039c72f25",
"name": "Every...",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "10",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "RunNow",
"payload": "",
"payloadType": "date",
"x": 134,
"y": 496,
"wires": [
[
"23a23f815062998a"
]
]
},
{
"id": "50b880b89269853d",
"type": "victron-input-vebus",
"z": "4c84754039c72f25",
"service": "com.victronenergy.vebus/276",
"path": "/Ac/Out/L1/P",
"serviceObj": {
"service": "com.victronenergy.vebus/276",
"name": "MultiPlus-II 48/3000/35-32"
},
"pathObj": {
"path": "/Ac/Out/L1/P",
"type": "float",
"name": "Output power phase 1 (W)"
},
"name": "AC_OUT_L1_W",
"onlyChanges": true,
"x": 114,
"y": 48,
"wires": [
[
"f0a0379037fe21c6"
]
]
},
{
"id": "af7e1d5d18f5d4f1",
"type": "victron-input-vebus",
"z": "4c84754039c72f25",
"service": "com.victronenergy.vebus/276",
"path": "/Ac/Out/L2/P",
"serviceObj": {
"service": "com.victronenergy.vebus/276",
"name": "MultiPlus-II 48/3000/35-32"
},
"pathObj": {
"path": "/Ac/Out/L2/P",
"type": "float",
"name": "Output power phase 2 (W)"
},
"name": "AC_OUT_L2_W",
"onlyChanges": true,
"x": 114,
"y": 96,
"wires": [
[
"f0a0379037fe21c6"
]
]
},
{
"id": "3d217b26d4f76ba8",
"type": "victron-input-vebus",
"z": "4c84754039c72f25",
"service": "com.victronenergy.vebus/276",
"path": "/Ac/Out/L3/P",
"serviceObj": {
"service": "com.victronenergy.vebus/276",
"name": "MultiPlus-II 48/3000/35-32"
},
"pathObj": {
"path": "/Ac/Out/L3/P",
"type": "float",
"name": "Output power phase 3 (W)"
},
"name": "AC_OUT_L3_W",
"onlyChanges": true,
"x": 114,
"y": 144,
"wires": [
[
"f0a0379037fe21c6"
]
]
},
{
"id": "e512049d378e99a9",
"type": "victron-input-battery",
"z": "4c84754039c72f25",
"service": "com.victronenergy.battery/512",
"path": "/Soc",
"serviceObj": {
"service": "com.victronenergy.battery/512",
"name": "Totle Power"
},
"pathObj": {
"path": "/Soc",
"type": "float",
"name": "State of charge (%)"
},
"name": "SoC_Now",
"onlyChanges": true,
"x": 110,
"y": 400,
"wires": [
[
"12d0c3608e598191",
"23a23f815062998a"
]
]
},
{
"id": "0b6a9daff11995d8",
"type": "victron-input-battery",
"z": "4c84754039c72f25",
"service": "com.victronenergy.battery/512",
"path": "/Info/MaxChargeVoltage",
"serviceObj": {
"service": "com.victronenergy.battery/512",
"name": "Totle Power"
},
"pathObj": {
"path": "/Info/MaxChargeVoltage",
"type": "float",
"name": "CVL - Charge Voltage Limit (V)"
},
"name": "Max_Charge_Voltage_V",
"onlyChanges": true,
"roundValues": "0",
"x": 164,
"y": 304,
"wires": [
[
"23a23f815062998a"
]
]
},
{
"id": "2893e5dcb7cb59f5",
"type": "victron-input-system",
"z": "4c84754039c72f25",
"service": "com.victronenergy.system/0",
"path": "/Control/ActiveSocLimit",
"serviceObj": {
"service": "com.victronenergy.system/0",
"name": "Venus system"
},
"pathObj": {
"path": "/Control/ActiveSocLimit",
"type": "integer",
"name": "ESS active SOC limit (%)"
},
"name": "SoC_Min_Venus",
"onlyChanges": true,
"x": 418,
"y": 256,
"wires": [
[
"23a23f815062998a"
]
]
},
{
"id": "3539d7d5c0b9a28c",
"type": "victron-input-ess",
"z": "4c84754039c72f25",
"service": "com.victronenergy.settings",
"path": "/Settings/SystemSetup/MaxChargeCurrent",
"serviceObj": {
"service": "com.victronenergy.settings",
"name": "Venus settings"
},
"pathObj": {
"path": "/Settings/SystemSetup/MaxChargeCurrent",
"type": "float",
"name": "DVCC Charge current limit (A)"
},
"name": "Max_Charge_Current_A",
"onlyChanges": true,
"x": 164,
"y": 352,
"wires": [
[
"23a23f815062998a"
]
]
},
{
"id": "b2ae12bbebdd3da3",
"type": "victron-input-ess",
"z": "4c84754039c72f25",
"service": "com.victronenergy.settings",
"path": "/Settings/CGwacs/AcPowerSetPoint",
"serviceObj": {
"service": "com.victronenergy.settings",
"name": "Venus settings"
},
"pathObj": {
"path": "/Settings/CGwacs/AcPowerSetPoint",
"type": "integer",
"name": "Grid set-point (W)"
},
"name": "Grid_Setpoint_W",
"onlyChanges": true,
"x": 418,
"y": 304,
"wires": [
[
"23a23f815062998a"
]
]
},
{
"id": "2f5aa1e858545980",
"type": "victron-input-vebus",
"z": "4c84754039c72f25",
"service": "com.victronenergy.vebus/276",
"path": "/Ac/ActiveIn/L1/P",
"serviceObj": {
"service": "com.victronenergy.vebus/276",
"name": "MultiPlus-II 48/3000/35-32"
},
"pathObj": {
"path": "/Ac/ActiveIn/L1/P",
"type": "float",
"name": "Input power phase 1 (W)"
},
"name": "AC_IN_L1_W",
"onlyChanges": true,
"roundValues": "0",
"x": 316,
"y": 48,
"wires": [
[
"f0a0379037fe21c6"
]
]
},
{
"id": "ac9385d7e8f24894",
"type": "victron-input-vebus",
"z": "4c84754039c72f25",
"service": "com.victronenergy.vebus/276",
"path": "/Ac/ActiveIn/L2/P",
"serviceObj": {
"service": "com.victronenergy.vebus/276",
"name": "MultiPlus-II 48/3000/35-32"
},
"pathObj": {
"path": "/Ac/ActiveIn/L2/P",
"type": "float",
"name": "Input power phase 2 (W)"
},
"name": "AC_IN_L2_W",
"onlyChanges": true,
"roundValues": "0",
"x": 316,
"y": 96,
"wires": [
[
"f0a0379037fe21c6"
]
]
},
{
"id": "0be03ff5277e5f98",
"type": "victron-input-vebus",
"z": "4c84754039c72f25",
"service": "com.victronenergy.vebus/276",
"path": "/Ac/ActiveIn/L3/P",
"serviceObj": {
"service": "com.victronenergy.vebus/276",
"name": "MultiPlus-II 48/3000/35-32"
},
"pathObj": {
"path": "/Ac/ActiveIn/L3/P",
"type": "float",
"name": "Input power phase 3 (W)"
},
"name": "AC_IN_L3_W",
"onlyChanges": true,
"roundValues": "0",
"x": 316,
"y": 144,
"wires": [
[
"f0a0379037fe21c6"
]
]
},
{
"id": "ada2f5ff50dc9b23",
"type": "victron-input-solarcharger",
"z": "4c84754039c72f25",
"service": "com.victronenergy.solarcharger/279",
"path": "/Yield/Power",
"serviceObj": {
"service": "com.victronenergy.solarcharger/279",
"name": "SmartSolar Charger MPPT 100/20 48V"
},
"pathObj": {
"path": "/Yield/Power",
"type": "float",
"name": "PV Power (W)"
},
"name": "Solar_Charger_W",
"onlyChanges": true,
"roundValues": "0",
"x": 124,
"y": 192,
"wires": [
[
"f0a0379037fe21c6"
]
]
},
{
"id": "f0a0379037fe21c6",
"type": "function",
"z": "4c84754039c72f25",
"name": "Energy Surplus from Solar Now",
"func": "// Energy from Solar\n\ncontext = context || {};\n\nconst Now = new Date()\nvar NowInSeconds = Math.floor(Now.getTime() / 1000);\n\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n + (\"0\" + Now.getSeconds()).slice(-2)\n ;\n\nvar Energy_from_Solar_Now_W = 0;\nvar Energy_to_Non_Crit_W = 0;\n\nif (context.Energy_from_Solar_Total_W == null) {\n context.Energy_from_Solar_Total_W = 0;\n}\nif (context.MsgCount == null) {\n context.MsgCount = 0;\n}\nif (context.NextOutputTime == null) {\n context.NextOutputTime = NowInSeconds;\n}\n\nswitch (msg.topic) {\n \n case \"RunNow\":\n context.RunNow = true;\n msg = null;\n break;\n \n case \"AC_IN_L1_W\":\n context.AC_IN_L1_W = msg.payload; \n msg = null;\n break;\n \n case \"AC_IN_L2_W\":\n context.AC_IN_L2_W = msg.payload; \n msg = null;\n break;\n \n case \"AC_IN_L3_W\":\n context.AC_IN_L3_W = msg.payload; \n msg = null;\n break;\n \n case \"AC_OUT_L1_W\":\n context.AC_OUT_L1_W = msg.payload; \n msg = null;\n break;\n \n case \"AC_OUT_L2_W\":\n context.AC_OUT_L2_W = msg.payload; \n msg = null;\n break;\n \n case \"AC_OUT_L3_W\":\n context.AC_OUT_L3_W = msg.payload; \n msg = null;\n break;\n\n case \"Solar_Charger_W\":\n context.Solar_Charger_W = msg.payload;\n msg = null;\n break;\n \n case \"ET340_L1_W\":\n context.ET340_L1_W = msg.payload; \n msg = null;\n break;\n \n case \"ET340_L2_W\":\n context.ET340_L2_W = msg.payload; \n msg = null;\n break;\n \n case \"ET340_L3_W\":\n context.ET340_L3_W = msg.payload; \n msg = null;\n break;\n \n default: \n ErrorText += \"Unknown Topic: \" + msg.topic \n + \" Payload: \" + msg.payload\n + \"\\r\";\n context.RunNow = true;\n \n} // switch for input all variables\n\nif ( (context.AC_IN_L1_W != null)\n && (context.AC_IN_L2_W != null)\n && (context.AC_IN_L3_W != null)\n && (context.AC_OUT_L1_W != null)\n && (context.AC_OUT_L2_W != null)\n && (context.AC_OUT_L3_W != null)\n && (context.Solar_Charger_W != null)\n && (context.ET340_L1_W != null)\n && (context.ET340_L2_W != null)\n && (context.ET340_L3_W != null)\n )\n{\n\n // if AC-OUT is positive, this is consumption,\n // if AC-OUT is negative when PV available.\n // Change sign to calculate with positive value\n Energy_from_Solar_Now_W = - context.AC_OUT_L1_W\n - context.AC_OUT_L2_W \n - context.AC_OUT_L3_W;\n\n Energy_from_Solar_Now_W += context.Solar_Charger_W;\n\n // Calculate Usage by non-critical devices\n Energy_to_Non_Crit_W = context.ET340_L1_W \n + context.ET340_L2_W \n + context.ET340_L3_W\n - context.AC_IN_L1_W \n - context.AC_IN_L2_W \n - context.AC_IN_L3_W;\n \n Energy_to_Non_Crit_W = Math.max(0, Energy_to_Non_Crit_W);\n\n Energy_from_Solar_Now_W -= Energy_to_Non_Crit_W;\n\n Energy_from_Solar_Now_W = Math.max(Energy_from_Solar_Now_W, 0);\n \n // Summarize for later Average\n context.Energy_from_Solar_Total_W += Energy_from_Solar_Now_W;\n context.MsgCount += 1;\n \n if (NowInSeconds > context.NextOutputTime) {\n \n let Average = \n Math.round(context.Energy_from_Solar_Total_W / \n Math.max(context.MsgCount, 1) );\n \n // reset\n context.Energy_from_Solar_Total_W = null;\n context.MsgCount = null;\n context.NextOutputTime = null;\n\n if (Average > 0) {\n node.status( {fill:\"green\",shape:\"dot\",\n text: \"@ \" + NowTime + \": \" + Average + \" W\"} );\n }\n else {\n node.status( {fill:\"black\",shape:\"dot\",\n text: \"--\"} );\n }\n\n return { topic: \"Energy_from_Solar_Now_W\", payload: Average }\n\n } // output only every few seconds\n\n \n} // if init OK\n\n",
"outputs": 1,
"timeout": "4",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 788,
"y": 192,
"wires": [
[
"23a23f815062998a"
]
]
},
{
"id": "a2a1628757e5936f",
"type": "victron-input-custom",
"z": "4c84754039c72f25",
"service": "com.victronenergy.settings",
"path": "/Settings/DynamicEss/Mode",
"serviceObj": {
"service": "com.victronenergy.settings",
"name": "com.victronenergy.settings"
},
"pathObj": {
"path": "/Settings/DynamicEss/Mode",
"name": "/Settings/DynamicEss/Mode",
"type": "number"
},
"name": "DESS Mode",
"onlyChanges": false,
"roundValues": "0",
"x": 124,
"y": 256,
"wires": [
[
"23a23f815062998a"
]
]
},
{
"id": "8c3f20a2f83bd285",
"type": "victron-input-ess",
"z": "4c84754039c72f25",
"service": "com.victronenergy.vebus/276",
"path": "/Hub4/DisableCharge",
"serviceObj": {
"service": "com.victronenergy.vebus/276",
"name": "MultiPlus-II 48/3000/35-32"
},
"pathObj": {
"path": "/Hub4/DisableCharge",
"type": "enum",
"name": "Disable charge",
"enum": {
"0": "No",
"1": "Yes"
}
},
"initial": "",
"name": "Charge_Disabled",
"onlyChanges": false,
"x": 418,
"y": 352,
"wires": [
[
"23a23f815062998a"
]
]
},
{
"id": "a667bfd5c6688cd8",
"type": "victron-input-gridmeter",
"z": "4c84754039c72f25",
"service": "com.victronenergy.grid/33",
"path": "/Ac/L1/Power",
"serviceObj": {
"service": "com.victronenergy.grid/33",
"name": "ET340 (Grid)"
},
"pathObj": {
"path": "/Ac/L1/Power",
"type": "float",
"name": "L1 Power (W)"
},
"name": "ET340_L1_W",
"onlyChanges": true,
"roundValues": "0",
"x": 508,
"y": 48,
"wires": [
[
"f0a0379037fe21c6"
]
]
},
{
"id": "ee18a439e884a4f6",
"type": "victron-input-gridmeter",
"z": "4c84754039c72f25",
"service": "com.victronenergy.grid/33",
"path": "/Ac/L2/Power",
"serviceObj": {
"service": "com.victronenergy.grid/33",
"name": "ET340 (Grid)"
},
"pathObj": {
"path": "/Ac/L2/Power",
"type": "float",
"name": "L2 Power (W)"
},
"name": "ET340_L2_W",
"onlyChanges": true,
"roundValues": "0",
"x": 508,
"y": 96,
"wires": [
[
"f0a0379037fe21c6"
]
]
},
{
"id": "a820229d548fee74",
"type": "victron-input-gridmeter",
"z": "4c84754039c72f25",
"service": "com.victronenergy.grid/33",
"path": "/Ac/L3/Power",
"serviceObj": {
"service": "com.victronenergy.grid/33",
"name": "ET340 (Grid)"
},
"pathObj": {
"path": "/Ac/L3/Power",
"type": "float",
"name": "L3 Power (W)"
},
"name": "ET340_L3_W",
"onlyChanges": true,
"roundValues": "0",
"x": 508,
"y": 144,
"wires": [
[
"f0a0379037fe21c6"
]
]
},
{
"id": "7e26a15ac509b24d",
"type": "link in",
"z": "4c84754039c72f25",
"name": "Entry Calculate Targets",
"links": [
"48cd761b688d3b60",
"245e3ce4e79461c7",
"3bdee4a3cf66985e",
"1399704a4343e5c7",
"6a6892ebb428921b",
"9846b437e79f8490",
"a12011e103880677",
"5d8c0ea6b0dbf109",
"0b98066451f9d823",
"b20136fbf713b612",
"2a6e21108de2abb0",
"db4c399eedd0d965",
"95d9fc256004aa1c",
"3de35a5ba8cc724c",
"4a7a63fe63eef64b"
],
"x": 81,
"y": 448,
"wires": [
[
"23a23f815062998a"
]
]
},
{
"id": "12d0dec7969aeb24",
"type": "api-call-service",
"z": "4c84754039c72f25",
"name": "Send DayAhead SoC Now",
"server": "289362b880855228",
"version": 5,
"debugenabled": false,
"domain": "input_number",
"service": "set_value",
"areaId": [],
"deviceId": [],
"entityId": [
"input_number.helper_dayahead_soc_now"
],
"data": "{\"value\":\"{{payload}}\"}",
"dataType": "json",
"mergeContext": "",
"mustacheAltTags": false,
"outputProperties": [],
"queue": "all",
"x": 778,
"y": 400,
"wires": [
[]
]
},
{
"id": "12d0c3608e598191",
"type": "rbe",
"z": "4c84754039c72f25",
"name": "Only Changes",
"func": "rbe",
"gap": "",
"start": "",
"inout": "out",
"septopics": false,
"property": "payload",
"topi": "topic",
"x": 322,
"y": 400,
"wires": [
[
"5aab899ef2eddef2"
]
]
},
{
"id": "16d438c3cd33f292",
"type": "victron-input-solarcharger",
"z": "4c84754039c72f25",
"service": "com.victronenergy.solarcharger/279",
"path": "/Mode",
"serviceObj": {
"service": "com.victronenergy.solarcharger/279",
"name": "SmartSolar Charger MPPT 100/20 48V"
},
"pathObj": {
"path": "/Mode",
"type": "enum",
"name": "Charger on/off",
"enum": {
"1": "On",
"4": "Off"
}
},
"initial": "",
"name": "Solar_Charger",
"onlyChanges": false,
"x": 674,
"y": 256,
"wires": [
[
"23a23f815062998a"
]
]
},
{
"id": "846ca886d7ed67cf",
"type": "victron-input-relay",
"z": "4c84754039c72f25",
"service": "com.victronenergy.system/0",
"path": "/Relay/1/State",
"serviceObj": {
"service": "com.victronenergy.system/0",
"name": "Venus system"
},
"pathObj": {
"path": "/Relay/1/State",
"type": "enum",
"name": "Venus relay 2 state",
"enum": {
"0": "Open",
"1": "Closed"
}
},
"initial": "",
"name": "Relay_2",
"onlyChanges": false,
"x": 654,
"y": 304,
"wires": [
[
"23a23f815062998a"
]
]
},
{
"id": "f4ee8673c086c4f3",
"type": "victron-input-ess",
"z": "4c84754039c72f25",
"service": "com.victronenergy.settings",
"path": "/Settings/CGwacs/MaxDischargePower",
"serviceObj": {
"service": "com.victronenergy.settings",
"name": "Venus settings"
},
"pathObj": {
"path": "/Settings/CGwacs/MaxDischargePower",
"type": "integer",
"name": "Max inverter power (W)"
},
"initial": "",
"name": "Max_Inverter_Power_W",
"onlyChanges": true,
"x": 694,
"y": 352,
"wires": [
[
"23a23f815062998a"
]
]
},
{
"id": "5aab899ef2eddef2",
"type": "function",
"z": "4c84754039c72f25",
"name": "Show Payload",
"func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n + (\"0\" + Now.getSeconds()).slice(-2)\n ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n text: \"@ \" + NowTime + \": \"\n + msg.topic + \": \" + msg.payload }\n );\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 530,
"y": 400,
"wires": [
[
"12d0dec7969aeb24"
]
]
},
{
"id": "289362b880855228",
"type": "server",
"name": "Home Assistant Api",
"version": 5,
"addon": false,
"rejectUnauthorizedCerts": true,
"ha_boolean": "y|yes|true|on|home|open",
"connectionDelay": true,
"cacheJson": true,
"heartbeat": false,
"heartbeatInterval": "30",
"areaSelector": "friendlyName",
"deviceSelector": "friendlyName",
"entitySelector": "friendlyName",
"statusSeparator": ": ",
"statusYear": "hidden",
"statusMonth": "short",
"statusDay": "numeric",
"statusHourCycle": "default",
"statusTimeFormat": "h:m",
"enableGlobalContextStore": false
}
]
Afbeeldingslocatie: https://tweakers.net/i/4Bx8gxgNsTNCLTkIULxTSFH7ycw=/full-fit-in/4920x3264/filters:max_bytes(3145728):no_upscale():strip_icc():fill(white):strip_exif()/f/image/aabX3c7r92LGhK6j1EIwlPBF.jpg?f=user_large

[ Voor 220% gewijzigd door MJ de Bruijn op 14-05-2024 15:12 ]


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
Deel 2 van de Flow "Targets"

Dit is het tweede deel van de definitie van de Flow "Targets". Kopieer de onderstaande tekst en kies in Nod Red "Import Nodes". Plak dan deze tekst en kies voor import. Combineer de nodes met de in de hierboven genoemde stap geïmporteerde nodes.
[
{
"id": "f5d062c2fa48f969",
"type": "tab",
"label": "Flow 2",
"disabled": false,
"info": "",
"env": []
},
{
"id": "db1190a5580afce0",
"type": "victron-output-ess",
"z": "f5d062c2fa48f969",
"service": "com.victronenergy.settings",
"path": "/Settings/SystemSetup/MaxChargeCurrent",
"serviceObj": {
"service": "com.victronenergy.settings",
"name": "Venus settings"
},
"pathObj": {
"path": "/Settings/SystemSetup/MaxChargeCurrent",
"type": "float",
"name": "Charge current limit (A)",
"writable": true
},
"name": "",
"onlyChanges": false,
"x": 880,
"y": 304,
"wires": []
},
{
"id": "f79c0381f3da7cc2",
"type": "victron-output-ess",
"z": "f5d062c2fa48f969",
"service": "com.victronenergy.settings",
"path": "/Settings/CGwacs/BatteryLife/MinimumSocLimit",
"serviceObj": {
"service": "com.victronenergy.settings",
"name": "Venus settings"
},
"pathObj": {
"path": "/Settings/CGwacs/BatteryLife/MinimumSocLimit",
"type": "integer",
"name": "Minimum Discharge SOC (%)",
"writable": true
},
"name": "",
"onlyChanges": false,
"x": 900,
"y": 352,
"wires": []
},
{
"id": "d74749374b3cd375",
"type": "victron-output-ess",
"z": "f5d062c2fa48f969",
"service": "com.victronenergy.settings",
"path": "/Settings/CGwacs/AcPowerSetPoint",
"serviceObj": {
"service": "com.victronenergy.settings",
"name": "Venus settings"
},
"pathObj": {
"path": "/Settings/CGwacs/AcPowerSetPoint",
"type": "integer",
"name": "Grid set-point (W)",
"writable": true
},
"name": "",
"onlyChanges": false,
"x": 860,
"y": 400,
"wires": []
},
{
"id": "e28ce0a9bcd4d478",
"type": "api-call-service",
"z": "f5d062c2fa48f969",
"name": "Send DayAhead Rule",
"server": "289362b880855228",
"version": 5,
"debugenabled": false,
"domain": "input_text",
"service": "set_value",
"areaId": [],
"deviceId": [],
"entityId": [
"input_text.helper_dayahead_rule"
],
"data": "{\"value\":\"{{payload}}\"}",
"dataType": "json",
"mergeContext": "",
"mustacheAltTags": false,
"outputProperties": [],
"queue": "all",
"x": 822,
"y": 240,
"wires": [
[]
]
},
{
"id": "e780ce645320aa16",
"type": "victron-output-ess",
"z": "f5d062c2fa48f969",
"service": "com.victronenergy.vebus/276",
"path": "/Hub4/DisableCharge",
"serviceObj": {
"service": "com.victronenergy.vebus/276",
"name": "MultiPlus-II 48/3000/35-32"
},
"pathObj": {
"path": "/Hub4/DisableCharge",
"type": "enum",
"name": "Disable charge",
"enum": {
"0": "No",
"1": "Yes"
},
"writable": true
},
"initial": "",
"name": "",
"onlyChanges": false,
"x": 844,
"y": 496,
"wires": []
},
{
"id": "e01c07216940316b",
"type": "victron-output-solarcharger",
"z": "f5d062c2fa48f969",
"service": "com.victronenergy.solarcharger/279",
"path": "/Mode",
"serviceObj": {
"service": "com.victronenergy.solarcharger/279",
"name": "SmartSolar Charger MPPT 100/20 48V"
},
"pathObj": {
"path": "/Mode",
"type": "enum",
"name": "Charger on/off",
"enum": {
"1": "On",
"4": "Off"
},
"writable": true
},
"initial": "",
"name": "",
"onlyChanges": false,
"x": 874,
"y": 592,
"wires": []
},
{
"id": "dd8ffb16b9123131",
"type": "inject",
"z": "f5d062c2fa48f969",
"name": "MPPT On",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "Solar_Charger_Target",
"payload": "1",
"payloadType": "num",
"x": 142,
"y": 592,
"wires": [
[
"e9b1b97e97726e2f"
]
]
},
{
"id": "dc1fcba58d4d849c",
"type": "inject",
"z": "f5d062c2fa48f969",
"name": "MPPT Off",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "Solar_Charger_Target",
"payload": "4",
"payloadType": "num",
"x": 142,
"y": 624,
"wires": [
[
"e9b1b97e97726e2f"
]
]
},
{
"id": "b8d2442e864104f7",
"type": "victron-output-relay",
"z": "f5d062c2fa48f969",
"service": "com.victronenergy.system/0",
"path": "/Relay/1/State",
"serviceObj": {
"service": "com.victronenergy.system/0",
"name": "Venus device"
},
"pathObj": {
"path": "/Relay/1/State",
"type": "enum",
"name": "Venus relay 2 state",
"enum": {
"0": "Open",
"1": "Closed"
},
"writable": true
},
"initial": "",
"name": "",
"onlyChanges": false,
"x": 814,
"y": 688,
"wires": []
},
{
"id": "29cf77ba5336e86e",
"type": "inject",
"z": "f5d062c2fa48f969",
"name": "Relay Closed = Solar Off",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "Relay_2_Target",
"payload": "1",
"payloadType": "num",
"x": 192,
"y": 688,
"wires": [
[
"a2811f0830981669"
]
]
},
{
"id": "b5fae0ef7771125d",
"type": "inject",
"z": "f5d062c2fa48f969",
"name": "Relay Open = Solar On",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "Relay_2_Target",
"payload": "0",
"payloadType": "num",
"x": 182,
"y": 720,
"wires": [
[
"a2811f0830981669"
]
]
},
{
"id": "3468e94634e5cd43",
"type": "change",
"z": "f5d062c2fa48f969",
"name": "",
"rules": [
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "0",
"fromt": "num",
"to": "0: Relay Off = Solar On",
"tot": "str"
},
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "1",
"fromt": "num",
"to": "1: Relay On = Solar Off",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 466,
"y": 736,
"wires": [
[
"0789633bd5ecadc3"
]
]
},
{
"id": "99de8ef09725c6d8",
"type": "change",
"z": "f5d062c2fa48f969",
"name": "",
"rules": [
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "1",
"fromt": "num",
"to": "1: Solar Charger On",
"tot": "str"
},
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "4",
"fromt": "num",
"to": "4: Solar Charger Off",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 466,
"y": 640,
"wires": [
[
"2913f2394b711bae"
]
]
},
{
"id": "c5f61d5d468e38dc",
"type": "change",
"z": "f5d062c2fa48f969",
"name": "",
"rules": [
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "0",
"fromt": "num",
"to": "0: --> Charge On",
"tot": "str"
},
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "1",
"fromt": "num",
"to": "1: --> Charge Off",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 466,
"y": 544,
"wires": [
[
"139dc8d3e53ec757"
]
]
},
{
"id": "b32f2443f72d9fd5",
"type": "inject",
"z": "f5d062c2fa48f969",
"name": "Disable On --> Charge Off",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "Charge_Disable_Target",
"payload": "1",
"payloadType": "num",
"x": 192,
"y": 496,
"wires": [
[
"b210ab65e25bc1d2"
]
]
},
{
"id": "128e70467b815b7d",
"type": "inject",
"z": "f5d062c2fa48f969",
"name": "Disable Off --> Charge On",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "Charge_Disable_Target",
"payload": "0",
"payloadType": "num",
"x": 192,
"y": 528,
"wires": [
[
"b210ab65e25bc1d2"
]
]
},
{
"id": "3544ee6f9c5a5fce",
"type": "switch",
"z": "f5d062c2fa48f969",
"name": "DayAhead_Rule",
"property": "topic",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "DayAhead_Rule",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 232,
"y": 256,
"wires": [
[
"acf32952ebbe3950"
]
]
},
{
"id": "399c563f28e7ea36",
"type": "switch",
"z": "f5d062c2fa48f969",
"name": "Batt_Max_Current_Target_A",
"property": "topic",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "Batt_Max_Current_Target_A",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 274,
"y": 304,
"wires": [
[
"1a16a36b3c1409cd"
]
]
},
{
"id": "2bb4c6cb936d6ea5",
"type": "switch",
"z": "f5d062c2fa48f969",
"name": "SoC_Min_Target_Perc",
"property": "topic",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "SoC_Min_Target_Perc",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 254,
"y": 352,
"wires": [
[
"404752deda3b192b"
]
]
},
{
"id": "5304d18470492d01",
"type": "switch",
"z": "f5d062c2fa48f969",
"name": "Grid_Setpoint_Target_W",
"property": "topic",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "Grid_Setpoint_Target_W",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 264,
"y": 400,
"wires": [
[
"7ab12a8286fa3e35"
]
]
},
{
"id": "b7889107ef488d1d",
"type": "switch",
"z": "f5d062c2fa48f969",
"name": "Max_Inverter_Power_Target_W",
"property": "topic",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "Max_Inverter_Power_Target_W",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 284,
"y": 448,
"wires": [
[
"304515e3e5661647"
]
]
},
{
"id": "b210ab65e25bc1d2",
"type": "switch",
"z": "f5d062c2fa48f969",
"name": "Charge_Disable_Target",
"property": "topic",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "Charge_Disable_Target",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 496,
"y": 496,
"wires": [
[
"c5f61d5d468e38dc",
"e780ce645320aa16"
]
]
},
{
"id": "e9b1b97e97726e2f",
"type": "switch",
"z": "f5d062c2fa48f969",
"name": "Solar_Charger_Target",
"property": "topic",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "Solar_Charger_Target",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 486,
"y": 592,
"wires": [
[
"99de8ef09725c6d8",
"e01c07216940316b"
]
]
},
{
"id": "a2811f0830981669",
"type": "switch",
"z": "f5d062c2fa48f969",
"name": "Relay_2_Target",
"property": "topic",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "Relay_2_Target",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 466,
"y": 688,
"wires": [
[
"3468e94634e5cd43",
"b8d2442e864104f7"
]
]
},
{
"id": "139dc8d3e53ec757",
"type": "function",
"z": "f5d062c2fa48f969",
"name": "Show Payload",
"func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n + (\"0\" + Now.getSeconds()).slice(-2)\n ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n text: \"@ \" + NowTime + \": \"\n + msg.topic + \": \" + msg.payload }\n );\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 754,
"y": 544,
"wires": [
[]
]
},
{
"id": "2913f2394b711bae",
"type": "function",
"z": "f5d062c2fa48f969",
"name": "Show Payload",
"func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n + (\"0\" + Now.getSeconds()).slice(-2)\n ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n text: \"@ \" + NowTime + \": \"\n + msg.topic + \": \" + msg.payload }\n );\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 754,
"y": 640,
"wires": [
[]
]
},
{
"id": "0789633bd5ecadc3",
"type": "function",
"z": "f5d062c2fa48f969",
"name": "Show Payload",
"func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n + (\"0\" + Now.getSeconds()).slice(-2)\n ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n text: \"@ \" + NowTime + \": \"\n + msg.topic + \": \" + msg.payload }\n );\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 754,
"y": 736,
"wires": [
[]
]
},
{
"id": "7ab12a8286fa3e35",
"type": "function",
"z": "f5d062c2fa48f969",
"name": "Show Payload",
"func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n + (\"0\" + Now.getSeconds()).slice(-2)\n ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n text: \"@ \" + NowTime + \": \"\n + msg.topic + \": \" + msg.payload }\n );\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 516,
"y": 400,
"wires": [
[
"d74749374b3cd375"
]
]
},
{
"id": "404752deda3b192b",
"type": "function",
"z": "f5d062c2fa48f969",
"name": "Show Payload",
"func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n + (\"0\" + Now.getSeconds()).slice(-2)\n ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n text: \"@ \" + NowTime + \": \"\n + msg.topic + \": \" + msg.payload }\n );\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 516,
"y": 352,
"wires": [
[
"f79c0381f3da7cc2"
]
]
},
{
"id": "1a16a36b3c1409cd",
"type": "function",
"z": "f5d062c2fa48f969",
"name": "Show Payload",
"func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n + (\"0\" + Now.getSeconds()).slice(-2)\n ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n text: \"@ \" + NowTime + \": \"\n + msg.topic + \": \" + msg.payload }\n );\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 516,
"y": 304,
"wires": [
[
"db1190a5580afce0"
]
]
},
{
"id": "acf32952ebbe3950",
"type": "function",
"z": "f5d062c2fa48f969",
"name": "Show Payload",
"func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n + (\"0\" + Now.getSeconds()).slice(-2)\n ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n text: \"@ \" + NowTime + \": \"\n + msg.topic + \": \" + msg.payload }\n );\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 514,
"y": 256,
"wires": [
[
"e28ce0a9bcd4d478"
]
]
},
{
"id": "304515e3e5661647",
"type": "function",
"z": "f5d062c2fa48f969",
"name": "Show Payload",
"func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n + (\"0\" + Now.getSeconds()).slice(-2)\n ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n text: \"@ \" + NowTime + \": \"\n + msg.topic + \": \" + msg.payload }\n );\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 516,
"y": 448,
"wires": [
[]
]
},
{
"id": "95d25651d27dc6d6",
"type": "function",
"z": "f5d062c2fa48f969",
"name": "Calculate Targets",
"func": "// Function Calculate Targets\n\n",
"outputs": 1,
"timeout": "4",
"noerr": 0,
"initialize": "// Code added here will be run once\n// whenever the node is started.\n\ncontext = context || {};\n\n// Variables for input messages\ncontext.RunNow = false;\ncontext.DESS_Mode = null;\ncontext.Energy_from_Solar_Now_W = null;\ncontext.NegativePriceArray = null;\ncontext.ZeroPriceArray = null;\ncontext.VeryLowPriceArray = null;\ncontext.LowPriceArray = null;\ncontext.HighPriceArray = null;\ncontext.SoC_Now_Perc = null;\ncontext.SoC_Min_Venus = null;\ncontext.Max_Charge_A = null;\ncontext.Max_Charge_V = null;\ncontext.Grid_Setpoint = null;\ncontext.Charge_Disable = null;\ncontext.Solar_Charger = null;\ncontext.Relay_2 = null;\ncontext.Grid_to_Batt_Limit_A = 120; // dafault\ncontext.DebugOn = false;\n\n// results from previous calculations\ncontext.Batt_Grid_End_Time = 0;\n",
"finalize": "",
"libs": [],
"x": 428,
"y": 96,
"wires": [
[
"3544ee6f9c5a5fce",
"399c563f28e7ea36",
"2bb4c6cb936d6ea5",
"5304d18470492d01",
"b7889107ef488d1d",
"b210ab65e25bc1d2",
"e9b1b97e97726e2f",
"a2811f0830981669"
]
]
},
{
"id": "289362b880855228",
"type": "server",
"name": "Home Assistant Api",
"version": 5,
"addon": false,
"rejectUnauthorizedCerts": true,
"ha_boolean": "y|yes|true|on|home|open",
"connectionDelay": true,
"cacheJson": true,
"heartbeat": false,
"heartbeatInterval": "30",
"areaSelector": "friendlyName",
"deviceSelector": "friendlyName",
"entitySelector": "friendlyName",
"statusSeparator": ": ",
"statusYear": "hidden",
"statusMonth": "short",
"statusDay": "numeric",
"statusHourCycle": "default",
"statusTimeFormat": "h:m",
"enableGlobalContextStore": false
}
]

[ Voor 220% gewijzigd door MJ de Bruijn op 14-05-2024 15:08 ]


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
Deel 3 van de Flow "Targets"

In verband met de gelimiteerde ruimte in een post is de JavaScript tekst van functie "Calculate Targets" hier separaat opgenomen.
Kopieer deze tekst en plak die in de functie "Calculate Targets"in de Node Red Flow.

Deel twee staat in de volgende post.
// Function Calculate Targets

const TraceOn = false;

const Now = new Date()
const NowHour = Now.getHours();
const NowMinutes = Now.getMinutes();
const NowInSeconds = Math.floor(Now.getTime() / 1000);

let NowTime = ("0" + Now.getHours()).slice(-2) + ":"
+ ("0" + Now.getMinutes()).slice(-2) + ":"
+ ("0" + Now.getSeconds()).slice(-2)
;

const Batt_Capacity_Wh = Math.floor(3 * 5.12 * 1000); // three batteries

const SoC_Full_Perc = 98; // when Battery is realistically full

const Grid_Setpoint_W_Limit = 10000;

const Solar_Charge_Perc = 98; // max perc to use for charging

// Sell only if price difference > n.n Ct
const Sell_Price_Limit_Ct = 3.0;
const Conversion_Loss_Factor = 0.9; // conversion losses

// Variables to read from Global
var SoC_Min_Manual_Perc = null;
var Grid_Setpoint_Manual_W = null;
var DayAhead_PriceArray_Ct = null;
var Consump_Forecast_Wh = null;
var Solar_Forecast = null;
var Solar_Forecast_Modified = null;
var Solar_Sunrise = null;
var Solar_Sunset = null;

var DebugText = "";
var ErrorText = "";

var Solar_Today_Wh = 0;
var Solar_Remain_Wh = 0;
var Solar_Remain_Zero_Price_Wh = 0;
var Solar_Remain_VeryLow_Price_Wh = 0;
var Solar_Remain_Low_Price_Wh = 0;
var Solar_Remain_Rest_Price_Wh = 0;
var Solar_Tomorrow_Wh = 0;
var Sunrise_Hour = -1;
var Sunset_Hour = -1;
var Solar_To_Batt_Perc = 0;

var Batt_Forecast_Wh = [];
var Batt_Full_Wh = 0;
var Batt_Min_Manual_Wh = 0;
var Batt_Min_Bottom_Wh = 0;
var Batt_Now_Wh = 0;
var Batt_Free_Wh = 0;
var Batt_Max_Current_Grid_A = 0;
var Batt_Max_Current_Solar_A = 0;
var Batt_Charger_Only_Target = false;

var Energy_from_Grid_Needed_Wh = 0;

var Energy_to_Sell_Wh = 0; // when negative price comes
var Energy_for_Discharge_W = 0;

var Forecast_Horizon_Hour = 0;
var Next_High_Sell_Hour = -1;
var Next_High_Sell_Price_Ct = 0;
var Next_Cheap_Charge_Hour = -1;
var Next_Cheap_Charge_Price_Ct = 999999;

var SoC_Min_Bottom_Perc = 0;
var SoC_Min_Target_Perc = 0;
var Grid_Setpoint_Unload_W = 0;
var Grid_Setpoint_Target_W = 0;
var Max_Inverter_Power_Target_W = 0;

var Charge_Disable_Target = 0; // 1 = disabled; 0 = charging enabled
var Solar_Charger_Target = 1; // 1 = ON; 4 OFF
var Relay_2_Target = 0; // 1 = ON; 0 = Off

var msg_DayAhead_Rule = null;
var msg_Batt_Max_Current_Target_A = null;
var msg_SoC_Min_Target_Perc = null;
var msg_Grid_Setpoint_Target_W = null;
var msg_Max_Inverter_Power_Target_W = null;
var msg_Charge_Disable_Target = null;
var msg_Solar_Charger_Target = null;
var msg_Relay_2_Target = null;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function AddDebug(NewText)
{
DebugText += NewText;

} // AddDebug

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function Not(p_Boolean)
{
return !p_Boolean;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function HourAsText(H)
{
if (Number(H) <= 23) {
return Number(H).toString();
}
else {
return "+" + (Number(H)-24).toString();
}
} // HourAsText

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function MinutesRemainFactor() {
return (60 - NowMinutes) / 60;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function WhAskWhText(Wh)
{
if (Wh > 1000) {
return (Round(Wh/100)/10).toString() + " kWh";
}
else {
return Round(Wh).toString() + " Wh";
}
} // WhAskWhText

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function kWhToWh(x)
{
return Round(x * 1000);

} // kWhToWh

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function Round(x, dec)
{
if (dec == null)
{ return Math.round(x) }
else if (dec == 0)
{ return Math.round(x) }
else if (dec == 1)
{ return Math.round(x*10) / 10 }
else if (dec == 2)
{ return Math.round(x*100) / 100 }
} // Round

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function IsInArrayOfPairs(p_ArrayOfPairs, p_SearchHours)
{ // See if any of the items within array p_SearchHours
// are in array p_ArrayOfPairs{hour, value]
let Result = false;

for (let i = 0; i < p_SearchHours.length; i++) {
for ( let j = 0; j < p_ArrayOfPairs.length; j++)
{
if ( p_ArrayOfPairs[j][0] == p_SearchHours[i] )
{
Result = true;
break;
} // Item was found
} // for j
if (Result) break;
} // all items

return Result; // not found

} // IsInArrayOfPairs

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function ClearContext()
{
// Clear other reference-values
context.Last_DESS_Mode = -1;
context.Last_Solar_Sunrise = "";
context.Last_Solar_Sunset = "";
context.Last_Solar_Today_Wh = 0;
context.Last_Solar_Remain_Wh = 0;
context.Last_Solar_Tomorrow_Wh = 0;

context.Last_Batt_Forecast_Wh = -1;
context.Last_SoC_Min_Manual_Perc = -1;
context.Last_SoC_Now_Perc = -1;
context.Last_Batt_High_Value = -1;
context.Last_Batt_Low_Value = -1;
context.Last_Forecast_Horizon_Hour = -1;
context.Last_Next_High_Sell_Hour = -1;
context.Last_Next_Cheap_Charge_Hour = -1;

context.Last_Energy_from_Grid_to_Stay_Above_Minimum_Wh = -100;
context.Last_Energy_from_Grid_to_Full_at_Sunset_Today_Wh = -1;
context.Last_Energy_from_Grid_to_Full_at_Sunset_Tomorrow_Wh = -1;
context.Last_Energy_to_Sell_Wh = -1;
context.Last_SoC_Min_Target_Perc = -1;

context.Last_Batt_Max_Current_Grid_A = -2;
context.Last_Batt_Max_Current_Solar_A = -2;
context.Last_Batt_Max_Current_Target_A = -2;
context.last_Max_Inverter_Power_Target_W = -2;
context.Last_Batt_Charger_Only_Target = false;
context.Last_Grid_Setpoint_Target_W = -2;
context.Last_Charge_Disable_Target = -1;
context.Last_Solar_Charger_Target = -1;
context.Last_Relay_2_Target = -1;
context.Last_DayAheadRule = "";

} // ClearContext

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function Verify_External_Data()
{
let ErrText = "";
let Result = true;

if (context.Energy_from_Solar_Now_W == null)
{ ErrText += "context.Energy_from_Solar_Now_W \r";
Result = false;
}

// From Flow DayAhead
if (context.HighPriceArray == null)
{ ErrText += "context.HighPriceArray \r";
Result = false;
}

if (context.LowPriceArray == null)
{ ErrText += "context.LowPriceArray \r";
Result = false;
}

if (context.VeryLowPriceArray == null)
{ ErrText += "context.VeryLowPriceArray \r";
Result = false;
}

if (context.ZeroPriceArray == null)
{ ErrText += "context.ZeroPriceArray \r";
Result = false;
}

if (context.NegativePriceArray == null)
{ ErrText += "context.NegativePriceArray \r";
Result = false;
}

// Victron States
if (context.DESS_Mode == null)
{ ErrText += "context.DESS_Mode \r";
Result = false;
}

if (context.SoC_Now_Perc == null)
{ ErrText += "context.SoC_Now_Perc \r";
Result = false;
}

if (context.SoC_Min_Venus == null)
{ ErrText += "context.SoC_Min_Venus \r";
Result = false;
}

if (context.Max_Charge_Voltage_V == null)
{ ErrText += "context.Max_Charge_Voltage_V \r";
Result = false;
}

if (context.Max_Charge_Current_A == null)
{ ErrText += "context.Max_Charge_Current_A \r";
Result = false;
}

if (context.Grid_Setpoint_W == null)
{ ErrText += "context.Grid_Setpoint_W \r";
Result = false;
}

if (context.Charge_Disable == null)
{ ErrText += "context.Charge_Disable \r";
Result = false;
}

if (context.Solar_Charger == null)
{ ErrText += "context.Solar_Charger \r";
Result = false;
}

if (context.Relay_2 == null)
{ ErrText += "context.Relay_2 \r";
Result = false;
}

if (context.Grid_to_Batt_Limit_A == null)
{ ErrText += "Grid_to_Batt_Limit_A \r";
Result = false;
}

if (Grid_Setpoint_Manual_W != null)
{ ErrText += "Grid_Setpoint_Manual_W \r";
Result = false;
}

if (Solar_Forecast != null)
{ ErrText += "Solar_Forecast \r";
Result = false;
}

if (Solar_Sunrise != null)
{ ErrText += "Solar_Sunrise \r";
Result = false;
}

if (Solar_Sunset != null)
{ ErrText += "Solar_Sunset \r";
Result = false;
}

if (context.Max_Inverter_Power_W == null)
{ ErrText += "Max_Inverter_Power_W \r";
Result = false;
}

if (ErrText > "")
{ node.warn("Missing Data: \r" + ErrText);
}

return Result;

} // Verify_External_Data

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function GlobalGetAll()
{
let Result = true;
// Get general settings

SoC_Min_Manual_Perc = global.get("Victron_MESS.Manual.SoC_Min_Perc");
if (SoC_Min_Manual_Perc == null) {
node.warn("Victron_MESS.Manual.SoC_Min_Perc: null");
Result = false;
}
SoC_Min_Bottom_Perc = Math.min(15, SoC_Min_Manual_Perc);

Grid_Setpoint_Manual_W = global.get("Victron_MESS.Manual.Grid_Setpoint");
if (Grid_Setpoint_Manual_W == null) {
node.warn("Victron_MESS.Manual.Grid_Setpoint: null");
Result = false;
}

Consump_Forecast_Wh = global.get("Victron_MESS.Consump.Forecast");
if (Consump_Forecast_Wh == null) {
ErrorText += "Victron_MESS.Consump.Forecast: null" + "\r";
Result = false;
}

Solar_Forecast = global.get("Victron_MESS.Solar.Forecast.All");
if (Solar_Forecast == null) {
ErrorText += "Victron_MESS.Solar.Forecast.All: null" + "\r";
Result = false;
}

Solar_Forecast_Modified = global.get("Victron_MESS.Solar.Forecast.Modified");
if (Solar_Forecast_Modified == null) {
ErrorText += "Victron_MESS.Solar.Forecast.Modified: null" + "\r";
Result = false;
}

Solar_Sunrise = global.get("Victron_MESS.Solar.Sunrise");
if (Solar_Sunrise == null) {
ErrorText += "Victron_MESS.Solar.Sunrise: null" + "\r";
Result = false;
}

Solar_Sunset = global.get("Victron_MESS.Solar.Sunset");
if (Solar_Sunset == null) {
ErrorText += "Victron_MESS.Solar.Sunset: null" + "\r";
Result = false;
}

DayAhead_PriceArray_Ct = global.get("Victron_MESS.DayAhead.PriceArray_Ct");
if (DayAhead_PriceArray_Ct == null) {
ErrorText += "Victron_MESS.DayAhead.PriceArray_Ct: null" + "\r";
Result = false;
}

if (ErrorText > "")
{ node.warn("GlobalGetAll: " + ErrorText);
}

return Result;

} // GlobalGetAll

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function SolarSetSunriseAndSunsetHours()
{
// Find Sunrise and Sunset
if (TraceOn) node.warn("SolarSetSunriseAndSunsetHours...");

Sunrise_Hour = Number(Solar_Sunrise.substring(0,2)) + 1;

Sunset_Hour = Number(Solar_Sunset.substring(0,2));

if ( (context.Last_Solar_Sunrise != Solar_Sunrise)
|| (context.Last_Solar_Sunset != Solar_Sunset) )
{
AddDebug("Daylight: "
+ Solar_Sunrise + " - " + Solar_Sunset + "\r");
}
context.Last_Solar_Sunrise = Solar_Sunrise;
context.Last_Solar_Sunset = Solar_Sunset;

} // SolarSetSunriseAndSunsetHours

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function SolarMakeForecasts()
{
// Calculate Solar Forecast per Hour
if (TraceOn) node.warn("SolarMakeForecasts...");

for (let H = 0; H <= 23; H++) {
Solar_Today_Wh += Solar_Forecast_Modified[H];
}

// Add Remainder of current Hour
Solar_Remain_Wh +=
Round(Solar_Forecast_Modified[NowHour] * MinutesRemainFactor());

// Startvalue(s)
if ( IsZeroPrice(NowHour) ) {
Solar_Remain_Zero_Price_Wh = Solar_Remain_Wh;
}
else if ( IsVeryLowPrice(NowHour) ) {
Solar_Remain_VeryLow_Price_Wh = Solar_Remain_Wh;
}
else if ( IsAnyLowPrice(NowHour) ) {
Solar_Remain_Low_Price_Wh = Solar_Remain_Wh;
}
else {
Solar_Remain_Rest_Price_Wh = Solar_Remain_Wh;
}

for (let H = NowHour+1; H <= 23; H++) {
Solar_Remain_Wh += Solar_Forecast_Modified[H];
// what part of Solar has low price
if ( IsZeroPrice(H) ) {
Solar_Remain_Zero_Price_Wh += Solar_Forecast_Modified[H];
}
else if ( IsVeryLowPrice(H) ) {
Solar_Remain_VeryLow_Price_Wh += Solar_Forecast_Modified[H];
}
else if ( IsAnyLowPrice(H) ) {
Solar_Remain_Low_Price_Wh += Solar_Forecast_Modified[H];
}
else {
Solar_Remain_Rest_Price_Wh += Solar_Forecast_Modified[H];
}
} // Next Hour until midnight

if ( (Round(context.Last_Solar_Today_Wh / 100)
!= Round(Solar_Today_Wh / 100))
|| (Round(context.Last_Solar_Remain_Wh / 100)
!= Round(Solar_Remain_Wh / 100) ) )
{

AddDebug("Solar:");
if (Solar_Remain_Zero_Price_Wh > 0)
{
AddDebug(" zero: ");
AddDebug(WhAskWhText(Solar_Remain_Zero_Price_Wh));
}
if (Solar_Remain_VeryLow_Price_Wh > 0)
{
AddDebug(" very_low: ");
AddDebug(WhAskWhText(Solar_Remain_VeryLow_Price_Wh));
}
if (Solar_Remain_Low_Price_Wh > 0)
{
AddDebug(" low: ");
AddDebug(WhAskWhText(Solar_Remain_Low_Price_Wh));
}
if (Solar_Remain_Rest_Price_Wh > 0)
{
AddDebug(" rest: ");
AddDebug(WhAskWhText(Solar_Remain_Rest_Price_Wh));
}
AddDebug(" / ");
AddDebug(WhAskWhText(Solar_Today_Wh) + "\r");

context.Last_Solar_Today_Wh = Solar_Today_Wh;
context.Last_Solar_Remain_Wh = Solar_Remain_Wh;
}

for (let H = 24; H < Solar_Forecast.length; H++)
{
Solar_Tomorrow_Wh += Solar_Forecast_Modified[H];
}

if ( Round(context.Last_Solar_Tomorrow_Wh / 100)
!= Round(Solar_Tomorrow_Wh / 100) )
{
AddDebug("Solar Tomorrow: ");
AddDebug(WhAskWhText(Solar_Tomorrow_Wh));
AddDebug("\r");

context.Last_Solar_Tomorrow_Wh = Solar_Tomorrow_Wh;
}

} // SolarMakeForecasts

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function BatteryCalculateLevels()
{
// Calculate Expected Battery energy-levels
// without charging from Grid
if (TraceOn) node.warn("BatteryCalculateLevels...");

Batt_Full_Wh =
Round(Batt_Capacity_Wh * SoC_Full_Perc / 100);

Batt_Now_Wh =
Round(Batt_Capacity_Wh * context.SoC_Now_Perc / 100);

Batt_Free_Wh = Batt_Full_Wh - Batt_Now_Wh;

Batt_Min_Manual_Wh =
Round(Batt_Capacity_Wh * SoC_Min_Manual_Perc / 100);

Batt_Min_Bottom_Wh =
Round(Batt_Capacity_Wh * SoC_Min_Bottom_Perc / 100);

for (let H = 0; H < Solar_Forecast_Modified.length; H++) {
Batt_Forecast_Wh[H] = 0; // initialise
}

Batt_Forecast_Wh[NowHour] =
Math.min(Batt_Full_Wh, // full is full
Round( Batt_Now_Wh
+ Solar_Forecast_Modified[NowHour] * MinutesRemainFactor()
- Consump_Forecast_Wh[NowHour] * MinutesRemainFactor()
)
);

for (let H = NowHour+1; H < Solar_Forecast_Modified.length; H++)
{
if (H <= 23) { // today, modified Solar
Batt_Forecast_Wh[H] =
Math.min(Batt_Full_Wh, // full is full
Round(Batt_Forecast_Wh[H-1]
+ Solar_Forecast_Modified[H]
- Consump_Forecast_Wh[H] ) );
}
else { // tomorrow, forecast Solar
Batt_Forecast_Wh[H] =
Math.min(Batt_Full_Wh, // full is full
Round(Batt_Forecast_Wh[H-1]
+ Solar_Forecast[H]
- Consump_Forecast_Wh[H] ) );
}
} // calculate Battery Forecast per Hour

global.set("Victron_MESS.Battery.Forecast", Batt_Forecast_Wh);

let Batt_Low_Value = Batt_Forecast_Wh[NowHour];
let Batt_Low_Hour = Batt_Forecast_Wh.length;
let Batt_Low_Hour_Text = "";
let Batt_High_Value = Batt_Forecast_Wh[NowHour];
let Batt_High_Hour = Batt_Forecast_Wh.length;
let Batt_High_Hour_Text = "";

for (let H = Batt_Forecast_Wh.length; H >= NowHour; H--)
{
if (Batt_Forecast_Wh[H] <= Batt_Low_Value)
{
Batt_Low_Value = Batt_Forecast_Wh[H];
Batt_Low_Hour = H; // end of this hour
}
if (Batt_Forecast_Wh[H] >= Batt_High_Value)
{
Batt_High_Value = Batt_Forecast_Wh[H];
Batt_High_Hour = H; // end of this hour
}
}

Batt_Low_Hour_Text = HourAsText(Batt_Low_Hour) + ":59: ";
Batt_High_Hour_Text = HourAsText(Batt_High_Hour) + ":59: ";

if (context.Last_SoC_Min_Manual_Perc != SoC_Min_Manual_Perc)
{
AddDebug("Batt_Capacity: ");
AddDebug(WhAskWhText(Batt_Capacity_Wh) + "\r");

AddDebug("SoC_Perc_Min_Manual: ");
AddDebug(SoC_Min_Manual_Perc + "% (");
AddDebug(WhAskWhText(Batt_Min_Manual_Wh) + ")\r");

AddDebug("SoC_Perc_Min_Bottom: ");
AddDebug(SoC_Min_Bottom_Perc + "% (");
AddDebug(WhAskWhText(Batt_Min_Bottom_Wh) + ")\r");

context.Last_SoC_Min_Manual_Perc = SoC_Min_Manual_Perc;
}

if ( context.Last_SoC_Now_Perc != context.SoC_Now_Perc )
{
AddDebug("SoC_Perc_Now: ");
AddDebug(context.SoC_Now_Perc + "% (");
AddDebug(WhAskWhText(Batt_Now_Wh) + ")\r");

context.Last_SoC_Now_Perc = context.SoC_Now_Perc;
}

if ( Round(context.Last_Batt_Low_Value / 100)
!= Round(Batt_Low_Value / 100) )
{
AddDebug("Forecast_Low at ");
AddDebug(Batt_Low_Hour_Text);
AddDebug(WhAskWhText(Batt_Low_Value) + " (");
AddDebug(Round(Batt_Low_Value / Batt_Capacity_Wh * 100) + "%)" + "\r");

context.Last_Batt_Low_Value = Batt_Low_Value;
}

if ( Round(context.Last_Batt_High_Value / 100)
!= Round(Batt_High_Value / 100))
{
AddDebug("Forecast_High at ");
AddDebug(Batt_High_Hour_Text);
AddDebug(WhAskWhText(Batt_High_Value) + " (");
AddDebug(Round(Batt_High_Value / Batt_Capacity_Wh * 100) + "%)" + "\r");

context.Last_Batt_High_Value = Batt_High_Value;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - -

// Find Hour 'Horizon' that Battery can stay above
// Minimum without charging from Grid
// This is the last Hour that can be used to find
// High and Low Hours

for (let H = NowHour; H < Solar_Forecast_Modified.length; H++)
{
if (Batt_Forecast_Wh[H] < Batt_Min_Bottom_Wh)
{ break}
Forecast_Horizon_Hour = H;
}

if (context.Last_Forecast_Horizon_Hour != Forecast_Horizon_Hour)
{
AddDebug("Forecast_Horizon: ");
AddDebug(HourAsText(Forecast_Horizon_Hour));
AddDebug(" hr \r");

context.Last_Forecast_Horizon_Hour = Forecast_Horizon_Hour;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - -

} // BatteryCalculateLevels

// - - - - - - - - - - - - - - - - - - - - - - - - - -

function CalculateEnergyToFullSoC()
{
// What energy is needed from Grid to full SoC
// today at Sunset or possibly tomorrow
// this is usefull during daytime until sunset
if (TraceOn) node.warn("CalculateEnergyToFullSoC...");

let Energy_from_Grid_to_Full_at_Sunset_Today_Wh = 0;
let Energy_from_Grid_to_Full_at_Sunset_Tomorrow_Wh = 0;
let Energy_from_Grid_to_Stay_Above_Minimum_Wh = 0;

// See what charging is minimal needed to stay above minimum SoC
// This is probably usefull during the night, until sunrise.

let Batt_Level_Wh = Batt_Now_Wh;

Batt_Level_Wh =
Math.min(Batt_Level_Wh,
Round(Batt_Now_Wh
+ Solar_Forecast_Modified[NowHour] * MinutesRemainFactor()
- Consump_Forecast_Wh[NowHour] * MinutesRemainFactor()
)
);

let Next_Minimum_Hour = 0

for (Next_Minimum_Hour = NowHour+1;
Next_Minimum_Hour <= Forecast_Horizon_Hour;
Next_Minimum_Hour++)
{
Batt_Level_Wh =
Math.min(Batt_Level_Wh,
Round(Batt_Level_Wh
+ Solar_Forecast_Modified[Next_Minimum_Hour]
- Consump_Forecast_Wh[Next_Minimum_Hour]
)
);
if (Solar_Forecast_Modified[Next_Minimum_Hour]
> Consump_Forecast_Wh[Next_Minimum_Hour])
{
break;
}
}


Energy_from_Grid_to_Stay_Above_Minimum_Wh =
Math.max(0, Batt_Min_Manual_Wh - Batt_Level_Wh);

if ( Round(context.Last_Energy_from_Grid_to_Stay_Above_Minimum_Wh / 100)
!= Round(Energy_from_Grid_to_Stay_Above_Minimum_Wh / 100) )
{
AddDebug("Grid: Stay Above Minimum: "
+ WhAskWhText(Energy_from_Grid_to_Stay_Above_Minimum_Wh) + "\r");
context.Last_Energy_from_Grid_to_Stay_Above_Minimum_Wh
= Energy_from_Grid_to_Stay_Above_Minimum_Wh;
}

Energy_from_Grid_to_Full_at_Sunset_Today_Wh = Batt_Full_Wh;

for (let H = NowHour; H < Sunset_Hour; H++) {
Energy_from_Grid_to_Full_at_Sunset_Today_Wh =
Math.min(Energy_from_Grid_to_Full_at_Sunset_Today_Wh,
Batt_Full_Wh - Batt_Forecast_Wh[H]);
}

Energy_from_Grid_to_Full_at_Sunset_Today_Wh =
Math.max(0, Energy_from_Grid_to_Full_at_Sunset_Today_Wh);

if (context.Last_Energy_from_Grid_to_Full_at_Sunset_Today_Wh
!= Energy_from_Grid_to_Full_at_Sunset_Today_Wh)
{
AddDebug("Grid: Full at Sunset Today: "
+ WhAskWhText(Energy_from_Grid_to_Full_at_Sunset_Today_Wh) + "\r");

context.Last_Energy_from_Grid_to_Full_at_Sunset_Today_Wh =
Energy_from_Grid_to_Full_at_Sunset_Today_Wh;
}


Energy_from_Grid_to_Full_at_Sunset_Tomorrow_Wh = Batt_Full_Wh;

for (let H = NowHour; H < Sunset_Hour+24; H++) {
Energy_from_Grid_to_Full_at_Sunset_Tomorrow_Wh =
Math.min(Energy_from_Grid_to_Full_at_Sunset_Tomorrow_Wh,
Batt_Full_Wh - Batt_Forecast_Wh[H]);
}

Energy_from_Grid_to_Full_at_Sunset_Tomorrow_Wh =
Math.max(0, Energy_from_Grid_to_Full_at_Sunset_Tomorrow_Wh);

if (context.Last_Energy_from_Grid_to_Full_at_Sunset_Tomorrow_Wh
!= Energy_from_Grid_to_Full_at_Sunset_Tomorrow_Wh)
{
AddDebug("Grid: Full at Sunset Tomorrow: "
+ WhAskWhText(Energy_from_Grid_to_Full_at_Sunset_Tomorrow_Wh) + "\r");

context.Last_Energy_from_Grid_to_Full_at_Sunset_Tomorrow_Wh =
Energy_from_Grid_to_Full_at_Sunset_Tomorrow_Wh;
}

Energy_from_Grid_Needed_Wh =
Math.max(Energy_from_Grid_to_Stay_Above_Minimum_Wh,
Energy_from_Grid_to_Full_at_Sunset_Today_Wh,
Energy_from_Grid_to_Full_at_Sunset_Tomorrow_Wh);

} // CalculateEnergyToFullSoC

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function IsNegativePrice(p_Hour)
{
return ( IsInArrayOfPairs(context.NegativePriceArray, [p_Hour]) )

} // IsNegativePrice

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function IsZeroPrice(p_Hour)
{
return ( IsInArrayOfPairs(context.ZeroPriceArray, [p_Hour]) )

} // IsZeroPrice

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function IsVeryLowPrice(p_Hour)
{
return ( IsInArrayOfPairs(context.VeryLowPriceArray, [p_Hour]) )

} // IsVeryLowPrice

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function IsAnyLowPrice(p_Hour)
{
let Result = false;

if (IsVeryLowPrice(p_Hour) )
{ Result = true }
else if (IsZeroPrice(p_Hour) )
{ Result = true }
else if (IsNegativePrice(p_Hour) )
{ Result = true }
else if ( IsInArrayOfPairs(context.LowPriceArray, [p_Hour]) )
{ Result = true }

return Result;

} // IsAnyLowPrice

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function IsHighPrice(p_Hour)
{
return ( IsInArrayOfPairs(context.HighPriceArray, [p_Hour]) )

} // IsHighPrice

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function NegativePriceHourByIdx(p_Idx)
{
let Result = -1;
if (context.NegativePriceArray[p_Idx] != null)
{
Result = context.NegativePriceArray[p_Idx][0];
}
return Result;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function NegativePriceIdxByHour(p_Hour)
{
let Result = -1;
for (let H = 0; H < context.NegativePriceArray.length; H++)
{
if (context.NegativePriceArray[H][0] == p_Hour)
{
Result = H;
break;
}
}
return Result;
} // NegativePriceIdxByHour

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function ZeroPriceHourByIdx(p_Idx)
{
let Result = -1;
if (context.ZeroPriceArray[p_Idx] != null)
{
Result = context.ZeroPriceArray[p_Idx][0];
}
return Result;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function ZeroPriceIdxByHour(p_Hour)
{
let Result = -1;
for (let H = 0; H < context.ZeroPriceArray.length; H++)
{
if (context.ZeroPriceArray[H][0] == p_Hour)
{
Result = H;
break;
}
}
return Result;

} // ZeroPriceIdxByHour

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function LowPriceHourByIdx(p_Idx)
{
let Result = -1;
if (context.LowPriceArray[p_Idx] != null)
{
Result = context.LowPriceArray[p_Idx][0];
}
return Result;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function LowPriceIdxByHour(p_Hour)
{
let Result = -1;
for (let H = 0; H < context.LowPriceArray.length; H++)
{
if (context.LowPriceArray[H][0] == p_Hour)
{
Result = H;
break;
}
}
return Result;

} // LowPriceIdxByHour

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function VeryLowPriceHourByIdx(p_Idx)
{
let Result = -1;
if (context.VeryLowPriceArray[p_Idx] != null)
{
Result = context.VeryLowPriceArray[p_Idx][0];
}
return Result;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function VeryLowPriceIdxByHour(p_Hour)
{
let Result = -1;
for (let H = 0; H < context.VeryLowPriceArray.length; H++)
{
if (context.VeryLowPriceArray[H][0] == p_Hour)
{
Result = H;
break;
}
}
return Result;

} // VeryLowPriceIdxByHour

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function FindCheapChargeHour()
{

if (TraceOn) node.warn("FindCheapChargeHour...");


for (let H = NowHour; H <= Forecast_Horizon_Hour; H++)
{

if ( IsNegativePrice(H) )
{
Next_Cheap_Charge_Hour = H;
Next_Cheap_Charge_Price_Ct =
DayAhead_PriceArray_Ct[Next_Cheap_Charge_Hour];
break;
}

else if ( IsZeroPrice(H) )
{
Next_Cheap_Charge_Hour = H;
Next_Cheap_Charge_Price_Ct =
DayAhead_PriceArray_Ct[Next_Cheap_Charge_Hour];
break;
}

else if ( IsVeryLowPrice(H) )
{
Next_Cheap_Charge_Hour = H;
Next_Cheap_Charge_Price_Ct =
DayAhead_PriceArray_Ct[Next_Cheap_Charge_Hour];
break;
}

else if (DayAhead_PriceArray_Ct[H]
< Next_Cheap_Charge_Price_Ct)
{
Next_Cheap_Charge_Hour = H;
Next_Cheap_Charge_Price_Ct =
DayAhead_PriceArray_Ct[Next_Cheap_Charge_Hour];
}

} // try all hours

// See if two, resp. three or more hours are needed to charge
// possible start one hour earlier with charging

if ( (context.SoC_Now_Perc < 70)
&& (Next_Cheap_Charge_Hour > NowHour)
&& (context.NegativePriceArray.length <= 1)
&& (context.ZeroPriceArray.length <= 1)
&& (context.VeryLowPriceArray.length <= 1)
&& (IsAnyLowPrice(Next_Cheap_Charge_Hour-1) )
)
{
Next_Cheap_Charge_Hour -= 1; // one hour earlier
Next_Cheap_Charge_Price_Ct =
DayAhead_PriceArray_Ct[Next_Cheap_Charge_Hour];
}

if ( (context.SoC_Now_Perc < 40)
&& (Next_Cheap_Charge_Hour > NowHour)
&& (context.NegativePriceArray.length <= 2)
&& (context.ZeroPriceArray.length <= 2)
&& (context.VeryLowPriceArray.length <= 2)
&& (IsAnyLowPrice(Next_Cheap_Charge_Hour-1) )
)
{
Next_Cheap_Charge_Hour -= 1; // another hour earlier
Next_Cheap_Charge_Price_Ct =
DayAhead_PriceArray_Ct[Next_Cheap_Charge_Hour];
}

if (context.Last_Next_Cheap_Charge_Hour != Next_Cheap_Charge_Hour)
{
AddDebug("Next_Cheap_Charge_Hour: ");
AddDebug(HourAsText(Next_Cheap_Charge_Hour));
AddDebug(" = ");
AddDebug(Next_Cheap_Charge_Price_Ct + " ct");
AddDebug("\r");

context.Last_Next_Cheap_Charge_Hour = Next_Cheap_Charge_Hour;
}

} // FindCheapChargeHour

// - - - - - - - - - - - - - - - - - - - - - - - - - -

function FindHighSellHour()
{
if (TraceOn) node.warn("FindHighSellHour...");

for (let H = NowHour; H < Next_Cheap_Charge_Hour; H++)
{

if (DayAhead_PriceArray_Ct[H] * Conversion_Loss_Factor >
( Next_Cheap_Charge_Price_Ct / Conversion_Loss_Factor
+ Sell_Price_Limit_Ct)
)
{
if (DayAhead_PriceArray_Ct[H] > Next_High_Sell_Price_Ct)
{
Next_High_Sell_Hour = H;
Next_High_Sell_Price_Ct = DayAhead_PriceArray_Ct[H];
}
}
}

if (context.Last_Next_High_Sell_Hour != Next_High_Sell_Hour)
{
AddDebug("Next_High_Sell_Hour: ");
AddDebug(HourAsText(Next_High_Sell_Hour) + " hr = ");
AddDebug(Next_High_Sell_Price_Ct);
AddDebug(" ct\r");
context.Last_Next_High_Sell_Hour = Next_High_Sell_Hour;
}


} // FindHighSellHour

// - - - - - - - - - - - - - - - - - - - - - - - - - -

function FindEnergyToSell()
{
// best, discharge at high prices, but not to much
if (TraceOn) node.warn("FindEnergyToSell...");

// This is now available
Energy_to_Sell_Wh =
Batt_Capacity_Wh * context.SoC_Now_Perc / 100;

// a minimum should remain
Energy_to_Sell_Wh -=
Math.floor(Batt_Full_Wh * SoC_Min_Manual_Perc / 100);

// Minus Consumption plus Solar
if (Next_Cheap_Charge_Hour > NowHour)
{
// This hour proportionally
Energy_to_Sell_Wh -=
Consump_Forecast_Wh[NowHour] * MinutesRemainFactor()

// Changes in the next hours
for (let H = NowHour+1; H < Next_Cheap_Charge_Hour; H++)
{
Energy_to_Sell_Wh =
Math.min(Energy_to_Sell_Wh,
Energy_to_Sell_Wh
- Consump_Forecast_Wh[H]
+ Solar_Forecast_Modified[H]);
}

// before Solar is ready, consumption comes first
Energy_to_Sell_Wh -= Consump_Forecast_Wh[Next_Cheap_Charge_Hour];

// Value can't be negative
Energy_to_Sell_Wh =
Math.max(0, Energy_to_Sell_Wh);

// node.warn(Energy_to_Sell_Wh);

if (context.Last_Energy_to_Sell_Wh != Energy_to_Sell_Wh)
{
AddDebug("Energy Available to Sell: ");
AddDebug(WhAskWhText(Energy_to_Sell_Wh) + "\r");

context.Last_Energy_to_Sell_Wh = Energy_to_Sell_Wh;
}

} // if there comes a Low Charge Hour

} // FindEnergyToSell

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function NegativePriceComes()
{
if (TraceOn) node.warn("NegativePriceComes...");

let Result = false;

if ( IsNegativePrice(NowHour) )
{
Result = false;
}
else if (IsInArrayOfPairs(context.NegativePriceArray,
[ NowHour+1, NowHour+2, NowHour+3, NowHour+4,
NowHour+5, NowHour+6, NowHour+7, NowHour+8,
NowHour+9, NowHour+10, NowHour+11, NowHour+12] )
)
{
Result = true;
}
return Result

} // NegativePriceComes

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function ZeroPriceComes()
{
if (TraceOn) node.warn("ZeroPriceComes...");

let Result = false;

if ( IsZeroPrice(NowHour) )
{
Result = false;
}
else if (IsInArrayOfPairs(context.ZeroPriceArray,
[ NowHour+1, NowHour+2, NowHour+3, NowHour+4,
NowHour+5, NowHour+6, NowHour+7, NowHour+8,
NowHour+9, NowHour+10, NowHour+11, NowHour+12] )
)
{
Result = true;
}
return Result

} // ZeroPriceComes

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function VeryLowPriceComes()
{
if (TraceOn) node.warn("VeryLowPriceComes...");

let Result = false;

if ( IsVeryLowPrice(NowHour) )
{
Result = false;
}
else if (IsInArrayOfPairs(context.VeryLowPriceArray,
[ NowHour+1, NowHour+2, NowHour+3, NowHour+4,
NowHour+5, NowHour+6, NowHour+7, NowHour+8,
NowHour+9, NowHour+10, NowHour+11, NowHour+12] )
)
{
Result = true;
}
return Result

} // VeryLowPriceComes

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

function SetSolarAvailable()
{
if (TraceOn) node.warn("SetSolarAvailable...");

let Result = false;

DayAheadRule = "\rSolar";

if (context.Energy_from_Solar_Now_W > 0)
{
Solar_To_Batt_Perc = Solar_Charge_Perc; // max perc to use for charging

if (Solar_Remain_Zero_Price_Wh
+ Solar_Remain_VeryLow_Price_Wh
+ Solar_Remain_Low_Price_Wh > Batt_Free_Wh)
{ // Low Price Solar is enough for charging
if ( Not(IsAnyLowPrice(NowHour)) )
{ // Now is NOT a low price
Solar_To_Batt_Perc = 2; // use only x% of Solar
}
} // if Solar Low Price is enough

DayAheadRule += ", use " + Solar_To_Batt_Perc + "%";

Result = true;

} // if Solar available

Batt_Max_Current_Solar_A =
Round( Solar_To_Batt_Perc / 100
* context.Energy_from_Solar_Now_W
/ context.Max_Charge_Voltage_V);

DayAheadRule += " Remain: " + WhAskWhText(Solar_Remain_Wh);
DayAheadRule += ", +:" + WhAskWhText(Solar_Tomorrow_Wh);
DayAheadRule += ".";

if (TraceOn) node.warn("SetSolarAvailable: " + Result);

return Result;

} // SetSolarAvailable

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function TryRule_DESS()
{
if (TraceOn) node.warn("TryRule_DESS...");

let Result = false;

if (context.DESS_Mode != 0)
{
DayAheadRule += " DESS Mode " + context.DESS_Mode + ".";

SoC_Min_Target_Perc = SoC_Min_Manual_Perc;
// Batt_Max_Current_Grid_A = -1;
// Batt_Max_Current_Solar_A = 0;

Result = true;
}

if (TraceOn) node.warn("TryRule_DESS: " + Result);

return Result;

} // TryRule_DESS

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function TryRule_FinishLowSoCCharging()
{
if (TraceOn) node.warn("TryRule_FinishLowSoCCharging...");

let Result = false;

if (NowInSeconds < context.Batt_Grid_End_Time)
{
DayAheadRule += " Finish Low SoC Charging.";

SoC_Min_Target_Perc = SoC_Min_Bottom_Perc;

Result = true;

} // Finish Low SoC Charging

if (TraceOn) node.warn("TryRule_FinishLowSoCCharging: " + Result);

return Result;

} // TryRule_FinishLowSoCCharging

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function TryRule_SoCBelowBottom()
{
if (TraceOn) node.warn("TryRule_SoCBelowBottom...");

let Result = false;

// SoC is below ultimate minimum
if (context.SoC_Now_Perc < SoC_Min_Bottom_Perc)
{
DayAheadRule += " SoC much to low ("
+ context.SoC_Now_Perc + "<" + SoC_Min_Bottom_Perc + ").";

SoC_Min_Target_Perc = SoC_Min_Bottom_Perc;
// Charge at least 5 minutes
context.Batt_Grid_End_Time = NowInSeconds + 300;

DayAheadRule += " Charge from Grid.";

Result = true;

} // SoC is below ultimate minimum

if (TraceOn) node.warn("TryRule_SoCBelowBottom: " + Result);

return Result;

} // TryRule_SoCBelowBottom

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function TryRule_SellHighPrice()
{
if (TraceOn) node.warn("TryRule_SellHighPrice...");

let Result = false;

// possible discharge for high price
// and charge later for low price
if ( (NowHour == Next_High_Sell_Hour)
&& (Energy_to_Sell_Wh > 0)
)
{
DayAheadRule += " High Sell Hour.";

// do not charge
Batt_Max_Current_Solar_A = 0;
Batt_Max_Current_Grid_A = 0;
Batt_Charger_Only_Target = false;

DayAheadRule += " Sell ";
DayAheadRule += WhAskWhText(Energy_to_Sell_Wh);
DayAheadRule += " at ";
DayAheadRule += Round(DayAhead_PriceArray_Ct[NowHour], 1);
DayAheadRule += " ct. ";

DayAheadRule += " Re-charge after ";
DayAheadRule += HourAsText(Next_Cheap_Charge_Hour);
DayAheadRule += " hr. ";
DayAheadRule += " at ";
DayAheadRule += Round(Next_Cheap_Charge_Price_Ct, 1);
DayAheadRule += " ct. ";

Grid_Setpoint_Unload_W =
-Math.max(200,
Math.min(Grid_Setpoint_W_Limit,
Round(Energy_to_Sell_Wh / MinutesRemainFactor() )
)
); // note the minus-sign: negative value

Result = true;

} // Discharge at High price for later Charge at Low price

if (TraceOn) node.warn("TryRule_SellHighPrice: " + Result);

return Result;

} // TryRule_SellHighPrice

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

[ Voor 220% gewijzigd door MJ de Bruijn op 14-05-2024 14:26 ]


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
Deel 4 van de Flow "Targets"

In verband met de gelimiteerde ruimte in een post is de JavaScript tekst van functie "Calculate Targets" hier separaat opgenomen. Deel een staat in de voorgaande post.

Dit is het tweede deel.
Kopieer deze tekst en plak die in de functie "Calculate Targets"in de Node Red Flow.
function TryRule_NegativePriceWait()
{
if (TraceOn) node.warn("TryRule_NegativePriceWait...");

let Result = false;

// Wait for negative price
if ( NegativePriceComes() )
{
if (context.NegativePriceArray.length >= 3)
{
DayAheadRule += " Wait for negative-negative-negative price.";
Result = true;
}

else if ( (context.NegativePriceArray.length == 2)
&& (context.SoC_Now_Perc >= 40)
)
{
DayAheadRule += " Wait for negative-negative price.";
Result = true;
}

else if ( (context.NegativePriceArray.length == 2)
&& (NegativePriceHourByIdx(0) < ZeroPriceHourByIdx(0) )
&& (NegativePriceHourByIdx(1) < ZeroPriceHourByIdx(0) )
)
{
DayAheadRule += " Wait for negative-negative-zero price.";
Result = true;
}

else if ( (context.NegativePriceArray.length == 2)
&& (NegativePriceHourByIdx(0) > ZeroPriceHourByIdx(0) )
&& (NegativePriceHourByIdx(1) > ZeroPriceHourByIdx(0) )
)
{
DayAheadRule += " Wait for zero-negative-negative price.";
Result = true;
}

else if ( (context.NegativePriceArray.length == 2)
&& (NegativePriceHourByIdx(0) < VeryLowPriceHourByIdx(0) )
&& (NegativePriceHourByIdx(1) < VeryLowPriceHourByIdx(0) )
)
{
DayAheadRule += " Wait for negative-negative-very_low price.";
Result = true;
}

else if ( (context.NegativePriceArray.length == 2)
&& (NegativePriceHourByIdx(0) < LowPriceHourByIdx(0) )
&& (NegativePriceHourByIdx(1) < LowPriceHourByIdx(0) )
)
{
DayAheadRule += " Wait for negative-negative-low price.";
Result = true;
}

else if ( (context.NegativePriceArray.length == 1)
&& (context.SoC_Now_Perc >= 70)
)
{
DayAheadRule += " Wait for negative price.";
Result = true;
}

else if ( (context.NegativePriceArray.length == 1)
&& (NegativePriceHourByIdx(0) < ZeroPriceHourByIdx(0) )
&& (NegativePriceHourByIdx(0) < ZeroPriceHourByIdx(1) )
)
{
DayAheadRule += " Wait for negative-zero-zero price.";
Result = true;
}

else if ( (context.NegativePriceArray.length == 1)
&& (NegativePriceHourByIdx(0) < VeryLowPriceHourByIdx(0) )
&& (NegativePriceHourByIdx(0) < VeryLowPriceHourByIdx(1) )
)
{
DayAheadRule += " Wait for negative-very_low-very_low price.";
Result = true;
}

else if ( (context.NegativePriceArray.length == 1)
&& (NegativePriceHourByIdx(0) < LowPriceHourByIdx(0) )
&& (NegativePriceHourByIdx(0) < LowPriceHourByIdx(1) )
)
{
DayAheadRule += " Wait for negative-low-low price.";
Result = true;
}

else if ( (context.NegativePriceArray.length == 1)
&& (NegativePriceHourByIdx(0) < ZeroPriceHourByIdx(0) )
&& (NegativePriceHourByIdx(0) < VeryLowPriceHourByIdx(0) )
)
{
DayAheadRule += " Wait for negative-zero-very_low price.";
Result = true;
}

else if ( (context.NegativePriceArray.length == 1)
&& (NegativePriceHourByIdx(0) < ZeroPriceHourByIdx(0) )
&& (NegativePriceHourByIdx(0) < LowPriceHourByIdx(0) )
)
{
DayAheadRule += " Wait for negative-zero-low price.";
Result = true;
}

else if ( (context.NegativePriceArray.length == 1)
&& (NegativePriceHourByIdx(0) < VeryLowPriceHourByIdx(0) )
&& (NegativePriceHourByIdx(0) < LowPriceHourByIdx(0) )
)
{
DayAheadRule += " Wait for negative-very_low-low price.";
Result = true;
}

else
{
DayAheadRule += " Negative prices alone are not enough.";
}


if (Result)
{
Batt_Max_Current_Grid_A = 0;
Batt_Max_Current_Solar_A = 0;
}
} // Wait for negative prices

if (TraceOn) node.warn("TryRule_NegativePriceWait: " + Result);

return Result;

} // TryRule_NegativePriceWait

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function TryRule_NegativePriceNow()
{
if (TraceOn) node.warn("TryRule_NegativePriceNow...");

let Result = false

if ( IsNegativePrice(NowHour) )
{
SoC_Min_Target_Perc = SoC_Full_Perc;

// Charge only in the hours with most negative prices
// for the highest revenues

if (NegativePriceIdxByHour(NowHour) == 0)
{ // Lowest hour, always charge
DayAheadRule += " Most Negative Price: Charge.";
}

else if ( (NegativePriceIdxByHour(NowHour) == 1)
&& (context.SoC_Now_Perc < 70)
)
{ // need at least two charge-hours, so start
DayAheadRule += " Second Negative Price: Charge.";
}

else if ( (NegativePriceIdxByHour(NowHour) == 2)
&& (context.SoC_Now_Perc < 40)
)
{ // need at least two charge-hours, so start
DayAheadRule += " Third Negative Price: Charge.";
}

else if ( Not(IsInArrayOfPairs(context.NegativePriceArray,
[NowHour+1, NowHour+2, NowHour+3] ) )
)
{
DayAheadRule += " Any Negative Price: Charge.";
}

else
{
DayAheadRule += " Negative Price: Wait for lower price.";
Batt_Max_Current_Grid_A = 0;
Batt_Max_Current_Solar_A = 0;
}

Batt_Charger_Only_Target = true;
Solar_Charger_Target = 4; // OFF
Relay_2_Target = 1; // Relay ON = PV Off

Result = true;

} // Price is Negative now

if (TraceOn) node.warn("TryRule_NegativePriceNow: " + Result);

return Result;

} // TryRule_NegativePriceNow

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function TryRule_ZeroPriceWait()
{
if (TraceOn) node.warn("TryRule_ZeroPriceWait...");

let Result = false;

if ( ZeroPriceComes() )
{
if (Solar_Remain_Zero_Price_Wh > Batt_Free_Wh)
{
DayAheadRule += " Wait for Solar at zero price.";
Result = true;
}

else if (context.ZeroPriceArray.length >= 3)
{
DayAheadRule += " Wait for zero-zero-zero price.";
Result = true;
}

else if ( (context.ZeroPriceArray.length == 2)
&& (context.SoC_Now_Perc >= 40)
)
{
DayAheadRule += " Wait for zero-zero price.";
Result = true;
}

else if ( (context.ZeroPriceArray.length == 2)
&& (ZeroPriceHourByIdx(0) < VeryLowPriceHourByIdx(0) )
&& (ZeroPriceHourByIdx(1) < VeryLowPriceHourByIdx(0) )
)
{
DayAheadRule += " Wait for zero-zero-very_low price.";
Result = true;
}

else if ( (context.ZeroPriceArray.length == 2)
&& (ZeroPriceHourByIdx(0) < LowPriceHourByIdx(0) )
&& (ZeroPriceHourByIdx(1) < LowPriceHourByIdx(0) )
)
{
DayAheadRule += " Wait for zero-zero-low price.";
Result = true;
}

else if ( (context.ZeroPriceArray.length == 1)
&& (context.SoC_Now_Perc >= 70) )
{
DayAheadRule += " Wait for zero price.";
Result = true;
}


else if ( (context.ZeroPriceArray.length == 1)
&& (ZeroPriceHourByIdx(0) < VeryLowPriceHourByIdx(0) )
&& (ZeroPriceHourByIdx(0) < VeryLowPriceHourByIdx(1) )
)
{
DayAheadRule += " Wait for zero-very_low-very_low price.";
Result = true;
}


else if ( (context.ZeroPriceArray.length == 1)
&& (ZeroPriceHourByIdx(0) < LowPriceHourByIdx(0) )
&& (ZeroPriceHourByIdx(0) < LowPriceHourByIdx(1) )
)
{
DayAheadRule += " Wait for zero-low-low price.";
Result = true;
}

else if ( (context.ZeroPriceArray.length == 1)
&& (ZeroPriceHourByIdx(0) < VeryLowPriceHourByIdx(0) )
&& (ZeroPriceHourByIdx(0) < LowPriceHourByIdx(0) )
)
{
DayAheadRule += " Wait for zero-very_low-low price.";
Result = true;
}

else
{
DayAheadRule += " Zero prices are not enough.";
}

if (Result)
{
Batt_Max_Current_Grid_A = 0;
Batt_Max_Current_Solar_A = 0;
Batt_Charger_Only_Target = false;
}

} // Wait for zero prices

if (TraceOn) node.warn("TryRule_ZeroPriceWait: " + Result);

return Result;

} // TryRule_ZeroPriceWait

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function TryRule_ZeroPriceNow()
{
if (TraceOn) node.warn("TryRule_ZeroPriceNow...");

let Result = false;

if ( IsZeroPrice(NowHour) )
{

if (ZeroPriceIdxByHour(NowHour) == 0)
{
DayAheadRule += " Zero Price.";
SoC_Min_Target_Perc = SoC_Full_Perc;
Result = true;
}

else if (Solar_Remain_Zero_Price_Wh > Batt_Free_Wh)
{
DayAheadRule += " Zero price: Solar is enough.";
Batt_Max_Current_Grid_A = 0;
Result = true;
}

else if ( (ZeroPriceIdxByHour(NowHour) == 1)
&& (context.SoC_Now_Perc < 70)
)
{ // need at least two charge-hours, so start
DayAheadRule += " Zero-zero Price: Solar not enough.";
SoC_Min_Target_Perc = SoC_Full_Perc;
Result = true;
}

else if ( (ZeroPriceIdxByHour(NowHour) == 2)
&& (context.SoC_Now_Perc < 40)
)
{ // need at least two charge-hours, so start
DayAheadRule += " Zero-zero-zero Price: Solar not enough.";
SoC_Min_Target_Perc = SoC_Full_Perc;
Result = true;
}

else if ( Not(IsInArrayOfPairs(context.ZeroPriceArray,
[NowHour+1, NowHour+2, NowHour+3] ) )
)
{
DayAheadRule += " Charge: Any Zero Price.";
SoC_Min_Target_Perc = SoC_Full_Perc;
Result = true;
}

else
{
DayAheadRule += " Wait for other zero prices.";
Batt_Max_Current_Grid_A = 0;
Batt_Max_Current_Solar_A = 0;
Result = true;
}

Batt_Charger_Only_Target = true;

} // Zero price now

if (TraceOn) node.warn("TryRule_ZeroPriceNow: " + Result);

return Result;

} // TryRule_ZeroPriceNow

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function TryRule_VeryLowPriceWait()
{
if (TraceOn) node.warn("TryRule_VeryLowPriceWait...");

let Result = false;

if ( VeryLowPriceComes() )
{

if (Solar_Remain_Zero_Price_Wh
+ Solar_Remain_VeryLow_Price_Wh > Batt_Free_Wh)
{
DayAheadRule += " Wait for Solar at very_low price.";
Result = true;
}

if (context.VeryLowPriceArray.length >= 3)
{
DayAheadRule += " Wait for very_low-very_low-very_low price.";
Result = true;
}

if ( (context.VeryLowPriceArray.length == 2)
&& (context.SoC_Now_Perc >= 40)
)
{
DayAheadRule += " Wait for very_low-very_low price.";
Result = true;
}

if ( (context.VeryLowPriceArray.length == 2)
&& (VeryLowPriceHourByIdx(0) < LowPriceHourByIdx(0) )
&& (VeryLowPriceHourByIdx(1) < LowPriceHourByIdx(0) )
)
{
DayAheadRule += " Wait for very_low-very_low-low price.";
Result = true;
}

if ( (context.VeryLowPriceArray.length == 1)
&& (context.SoC_Now_Perc >= 70)
)
{
DayAheadRule += " Wait for very_low price.";
Result = true;
}

if ( (context.VeryLowPriceArray.length == 1)
&& (VeryLowPriceHourByIdx(0) < LowPriceHourByIdx(0) )
&& (VeryLowPriceHourByIdx(0) < LowPriceHourByIdx(1) )
)
{
DayAheadRule += " Wait for very_low-low-low price.";
Result = true;
}

if (Result)
{
Batt_Max_Current_Grid_A = 0;
Batt_Max_Current_Solar_A = 0;
}

} // Wait for very_low price

if (TraceOn) node.warn("TryRule_VeryLowPriceWait: " + Result);

return Result;

} // TryRule_VeryLowPriceWait

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function TryRule_VeryLowPriceNow()
{
if (TraceOn) node.warn("TryRule_VeryLowPriceNow...");

let Result = false;

if ( IsVeryLowPrice(NowHour) )
{
DayAheadRule += " Price is very_low.";

if (VeryLowPriceIdxByHour(NowHour) == 0)
{
if (Solar_Remain_Zero_Price_Wh
+ Solar_Remain_VeryLow_Price_Wh > Batt_Free_Wh)
{
DayAheadRule += " Very_low price: Solar is enough.";
Batt_Max_Current_Grid_A = 0;
}
else
{
DayAheadRule += " Very_low Price: Solar not enough.";
SoC_Min_Target_Perc = SoC_Full_Perc;
}
Result = true;
}

else if ( (VeryLowPriceIdxByHour(NowHour) == 1)
&& (context.SoC_Now_Perc < 70)
)
{ // need at least two charge-hours, so start
if (Solar_Remain_Zero_Price_Wh
+ Solar_Remain_VeryLow_Price_Wh > Batt_Free_Wh)
{
DayAheadRule += " Very_low-Very_low price: Solar is enough.";
Batt_Max_Current_Grid_A = 0;
}
else
{
DayAheadRule += " Very_low-Very_low Price: Solar not enough.";
SoC_Min_Target_Perc = SoC_Full_Perc;
}
Result = true;
}

else if ( (VeryLowPriceIdxByHour(NowHour) == 2)
&& (context.SoC_Now_Perc < 40)
)
{ // need at least two charge-hours, so start
if (Solar_Remain_Zero_Price_Wh
+ Solar_Remain_VeryLow_Price_Wh > Batt_Free_Wh)
{
DayAheadRule += " Very_low-Very_low-Very_low price: Solar is enough.";
Batt_Max_Current_Grid_A = 0;
}
else
{
DayAheadRule += " Very_low-Very_low-Very_low Price: Solar not enough.";
SoC_Min_Target_Perc = SoC_Full_Perc;
}
Result = true;
}

else if ( Not(IsInArrayOfPairs(context.VeryLowPriceArray,
[NowHour+1, NowHour+2, NowHour+3] ) )
)
{
DayAheadRule += " Charge: Any Very_low Price.";
SoC_Min_Target_Perc = SoC_Full_Perc;
Result = true;
}

else

{
DayAheadRule += " Wait for other Very_Low prices.";
Batt_Max_Current_Grid_A = 0;
Batt_Max_Current_Solar_A = 0;
Result = true;
}

} // very_low price now

if (TraceOn) node.warn("TryRule_VeryLowPriceNow: " + Result);

return Result;

} // TryRule_VeryLowPriceNow

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function TryRule_LowPriceNow()
{
if (TraceOn) node.warn("TryRule_LowPriceNow...");

let Result = false;

Batt_Max_Current_Grid_A =
Math.max(4,
Round(Energy_from_Grid_Needed_Wh / context.Max_Charge_Voltage_V)
);


if ( IsAnyLowPrice(NowHour) )
{
if (LowPriceIdxByHour(NowHour) == 0)
{
if (Solar_Remain_Zero_Price_Wh
+ Solar_Remain_VeryLow_Price_Wh
+ Solar_Remain_Low_Price_Wh
> Batt_Free_Wh)
{
DayAheadRule += " Low price: Solar is enough.";
Batt_Max_Current_Grid_A = 0;
}
else
{
DayAheadRule += " Low Price: Solar not enough.";
SoC_Min_Target_Perc = SoC_Full_Perc;
}
Result = true;
}

else if ( (LowPriceIdxByHour(NowHour) == 1)
&& (context.SoC_Now_Perc < 70)
)
{ // need at least two charge-hours, so start
if (Solar_Remain_Zero_Price_Wh
+ Solar_Remain_VeryLow_Price_Wh
+ Solar_Remain_Low_Price_Wh
> Batt_Free_Wh)
{
DayAheadRule += " Low-Low price: Solar is enough.";
Batt_Max_Current_Grid_A = 0;
}
else
{
DayAheadRule += " Low-Low Price: Solar not enough.";
SoC_Min_Target_Perc = SoC_Full_Perc;
}
Result = true;
}

else if ( (LowPriceIdxByHour(NowHour) == 2)
&& (context.SoC_Now_Perc < 40)
)
{ // need at least two charge-hours, so start
if (Solar_Remain_Zero_Price_Wh
+ Solar_Remain_VeryLow_Price_Wh
+ Solar_Remain_Low_Price_Wh
> Batt_Free_Wh)
{
DayAheadRule += "Low-Low-Low price: Solar is enough.";
Batt_Max_Current_Grid_A = 0;
}
else
{
DayAheadRule += " Low-Low-Low Price: Solar not enough.";
SoC_Min_Target_Perc = SoC_Full_Perc;
}
Result = true;
}

else if ( Not(IsInArrayOfPairs(context.LowPriceArray,
[NowHour+1, NowHour+2, NowHour+3] ) )
)
{
DayAheadRule += " Charge: Any Low Price.";
SoC_Min_Target_Perc = SoC_Full_Perc;
Result = true;
}

else

{
DayAheadRule += " Low Price: Wait for other Low prices.";
Batt_Max_Current_Grid_A = 0;
Batt_Max_Current_Solar_A = 0;
Result = true;
}

} // Low price now

if (TraceOn) node.warn("TryRule_LowPriceNow: " + Result);

return Result;

} // TryRule_LowPriceNow

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function TryRule_HighPriceNow()
{
if (TraceOn) node.warn("TryRule_HighPriceNow...");

let Result = false;

if ( IsHighPrice(NowHour) )
{
DayAheadRule += " High Price.";

Batt_Max_Current_Grid_A = 0;

Result = true;

} // Price is High

if (TraceOn) node.warn("TryRule_HighPriceNow: " + Result);

return Result;

} // TryRule_HighPriceNow

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function TryRule_SoCBelowMinimum()
{
if (TraceOn) node.warn("TryRule_SoCBelowMinimum...");

let Result = false;

if (context.SoC_Now_Perc < SoC_Min_Manual_Perc)
{ // SoC is below Manual minimum
DayAheadRule += " SoC to low ("
+ context.SoC_Now_Perc + "<" + SoC_Min_Manual_Perc + "),";

SoC_Min_Target_Perc = SoC_Min_Manual_Perc;
context.Batt_Grid_End_Time = NowInSeconds + 300;

Result = true;

} // SoC is below minimum

if (TraceOn) node.warn("TryRule_SoCBelowMinimum: " + Result);

return Result;

} // TryRule_SoCBelowMinimum

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// function TryRule_SolarIsEnough()
// {
// let Result = false;

// Solar is Enough, use balance
// if (Energy_from_Grid_to_Full_at_Sunset_Today_Wh <= 0)
// {
// DayAheadRule += " Solar to Sunset is enough.";

// Batt_Max_Current_Grid_A = 0;

// Result = true;

// } // Solar is Enough

// if (TraceOn) node.warn("TryRule_SolarIsEnough: " + Result);

// return Result;

// } // TryRule_SolarIsEnough

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function TryRule_BetterPriceLater()
{
if (TraceOn) node.warn("TryRule_BetterPriceLater...");

let Result = false;

// if low prices come (not negetive, zero or very_low)
// and current price is (relatively) high
if ( (NowHour < Next_Cheap_Charge_Hour)
&& (DayAhead_PriceArray_Ct[NowHour]
> Next_Cheap_Charge_Price_Ct + Sell_Price_Limit_Ct)
&& (Energy_from_Grid_Needed_Wh == 0)
)
{
DayAheadRule += " Charge at or after "
+ HourAsText(Next_Cheap_Charge_Hour)
+ " hr.";
Batt_Max_Current_Grid_A = 0;

Result = true;

}

if (TraceOn) node.warn("TryRule_BetterPriceLater: " + Result);

return Result;

} // TryRule_BetterPriceLater

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function TryRule_NoSpecialPrice()
{
if (TraceOn) node.warn("TryRule_NoSpecialPrice...");

let Result = false;

// Default = no special price = Always True

{ // default keep minimal SoC
DayAheadRule += " No special Price.";

// Not higher than SoC_Min_Manual_Perc
// But lower is allowed when Solar energy is enough
// But not lower then SoC_Min_Bottom_Perc
SoC_Min_Target_Perc = SoC_Min_Manual_Perc;
Batt_Max_Current_Grid_A = 0; // but not from Grid

Result = true;

} // No special price

if (TraceOn) node.warn("TryRule_NoSpecialPrice: " + Result);

return Result;

} // TryRule_NoSpecialPrice

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function TryAllRules()
{
// Set Default Values

if (TraceOn) node.warn("TryAllRules: Start");

SoC_Min_Target_Perc =
Math.min(SoC_Min_Manual_Perc,
Math.max(context.SoC_Now_Perc, SoC_Min_Bottom_Perc)
);

// Default is max charge
Batt_Max_Current_Grid_A = context.Grid_to_Batt_Limit_A;

if ( TryRule_DESS() ) {}

else if ( TryRule_FinishLowSoCCharging() ) {}

else if ( TryRule_SoCBelowBottom() ) {}

else if ( TryRule_SellHighPrice() ) {}

else if ( TryRule_NegativePriceWait() ) {}

else if ( TryRule_NegativePriceNow() ) {}

else if ( TryRule_ZeroPriceWait() ) {}

else if ( TryRule_ZeroPriceNow() ) {}

else if ( TryRule_VeryLowPriceWait() ) {}

else if ( TryRule_VeryLowPriceNow() ) {}

else if ( TryRule_LowPriceNow() ) {}

else if ( TryRule_HighPriceNow() ) {}

else if ( TryRule_SoCBelowMinimum() ) {}

// else if ( TryRule_SolarIsEnough() ) {}

else if ( TryRule_BetterPriceLater() ) {}

else if ( TryRule_NoSpecialPrice() ) {}

// Now all Rules are evaluated

if (TraceOn) node.warn("Batt_Max_Current_Grid_A: " + Batt_Max_Current_Grid_A);
if (TraceOn) node.warn("Batt_Max_Current_Solar_A: " + Batt_Max_Current_Solar_A);


if ( Batt_Max_Current_Grid_A == 0 )
{
DayAheadRule += " No Grid charge.";
}
if ( Batt_Max_Current_Solar_A == 0 )
{
DayAheadRule += " No Solar charge.";
}

// Maximum speed to charge battery
context.Batt_Max_Current_Target_A =
Math.min(context.Grid_to_Batt_Limit_A,
Batt_Max_Current_Grid_A + Batt_Max_Current_Solar_A);


if (context.Max_Inverter_Power_W != null)
{
Max_Inverter_Power_Target_W = context.Max_Inverter_Power_W;
}


if (Grid_Setpoint_Unload_W == 0)
{ Grid_Setpoint_Target_W = Grid_Setpoint_Manual_W;
}
else
{ Grid_Setpoint_Target_W =
Grid_Setpoint_Unload_W + context.Energy_from_Solar_Now_W;
}

DayAheadRule = DayAheadRule.trim(); // remove spaces


if (context.Last_SoC_Min_Target_Perc != SoC_Min_Target_Perc)
{
AddDebug("SoC_Min_Target_Perc: " + SoC_Min_Target_Perc + "%\r");
context.Last_SoC_Min_Target_Perc = SoC_Min_Target_Perc;
}

if (context.Last_Grid_Setpoint_Target_W != Grid_Setpoint_Target_W)
{
AddDebug("Grid_Setpoint_Target_W: " + Grid_Setpoint_Target_W + " W\r");
context.Last_Grid_Setpoint_Target_W = Grid_Setpoint_Target_W;
}

if (context.Last_Max_Inverter_Power_Target_W != Max_Inverter_Power_Target_W)
{
AddDebug("Max_Inverter_Power_Target_W: " + Max_Inverter_Power_Target_W + " W\r");
context.Last_Max_Inverter_Power_Target_W = Max_Inverter_Power_Target_W;
}

if (context.Last_DayAheadRule != DayAheadRule)
{
AddDebug(DayAheadRule + "\r");
context.Last_DayAheadRule = DayAheadRule;
}

if (context.Last_Batt_Max_Current_Grid_A != Batt_Max_Current_Grid_A)
{
AddDebug("Batt_Max_Current_Grid_A: ");
AddDebug(Batt_Max_Current_Grid_A + " A\r");
context.Last_Batt_Max_Current_Grid_A = Batt_Max_Current_Grid_A;
}

if (context.Last_Batt_Max_Current_Solar_A != Batt_Max_Current_Solar_A)
{
AddDebug("Batt_Max_Current_Solar_A: ");
AddDebug(Batt_Max_Current_Solar_A + " A\r");
context.Last_Batt_Max_Current_Solar_A = Batt_Max_Current_Solar_A;
}

if (context.Last_Batt_Max_Current_Target_A != context.Batt_Max_Current_Target_A)
{
AddDebug("Batt_Max_Current_Target_A: ");
AddDebug(context.Batt_Max_Current_Target_A + " A\r");

context.Last_Batt_Max_Current_Target_A = context.Batt_Max_Current_Target_A;
}

if (context.Last_Charge_Disable_Target != Charge_Disable_Target)
{
// AddDebug("Charge Disable: ");
// AddDebug(Charge_Disable_Target + "\r");

context.Last_Charge_Disable_Target = Charge_Disable_Target;
}

if (context.Last_Batt_Charger_Only_Target != Batt_Charger_Only_Target)
{
AddDebug("Batt_Charger_Only_Target: ");
AddDebug(Batt_Charger_Only_Target + "\r");

context.Last_Batt_Charger_Only_Target = Batt_Charger_Only_Target;
}

if (context.Last_Solar_Charger_Target != Solar_Charger_Target)
{
AddDebug("Solar Charger: ");
if (Solar_Charger_Target == 1) {
AddDebug("On\r");
}
else {
AddDebug("Off\r");
}
context.Last_Solar_Charger_Target = Solar_Charger_Target;
}

if (context.Last_Relay_2_Target != Relay_2_Target)
{
AddDebug("Relay 2: ");
if (Relay_2_Target == 1) {
AddDebug("Relay On = Solar Off\r");
}
else {
AddDebug("Relay Off = Solar On\r");
}
context.Last_Relay_2_Target = Relay_2_Target;
}


} // TryAllRules

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function CreateReturnMessages()
{
msg_DayAhead_Rule =
{ topic: "DayAhead_Rule", payload: DayAheadRule };

if (context.Batt_Max_Current_Target_A != context.Max_Charge_Current_A) {
msg_Batt_Max_Current_Target_A =
{ topic: "Batt_Max_Current_Target_A",
payload: context.Batt_Max_Current_Target_A };
}

if (SoC_Min_Target_Perc != context.SoC_Min_Venus) {
msg_SoC_Min_Target_Perc =
{ topic: "SoC_Min_Target_Perc", payload: SoC_Min_Target_Perc };
}

if (Grid_Setpoint_Target_W != context.Grid_Setpoint) {
msg_Grid_Setpoint_Target_W =
{ topic: "Grid_Setpoint_Target_W", payload: Grid_Setpoint_Target_W };
}

if (Max_Inverter_Power_Target_W != context.Max_Inverter_Power_W) {
msg_Max_Inverter_Power_Target_W =
{ topic: "Max_Inverter_Power_Target_W",
payload: Max_Inverter_Power_Target_W };
}

if (Charge_Disable_Target != context.Charge_Disable) {
// msg_Charge_Disable_Target =
// { topic: "Charge_Disable_Target", payload: Charge_Disable_Target };
}


if (Solar_Charger_Target != context.Solar_Charger) {
msg_Solar_Charger_Target =
{ topic: "Solar_Charger_Target", payload: Solar_Charger_Target };
}

if (Relay_2_Target != context.Relay_2) {
msg_Relay_2_Target =
{ topic: "Relay_2_Target", payload: Relay_2_Target };
}

} // CreateReturnMessages

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

// S T A R T O F T H E P R O G R A M

// = = = = = = = = = = = = = = = = = = = = = = = = = = = =

switch (msg.topic) {

case "RunNow":
context.RunNow = true;
msg = null;
break;

case "DESS Mode":
context.DESS_Mode = msg.payload;
msg = null;
break;

case "Energy_from_Solar_Now_W":
// Energy Surplus = Solar minus consumption
context.Energy_from_Solar_Now_W = msg.payload;
msg = null;
break;

case "NegativePriceArray":
context.NegativePriceArray = msg.payload;
msg = null;
break;

case "ZeroPriceArray":
context.ZeroPriceArray = msg.payload;
msg = null;
break;

case "VeryLowPriceArray":
// near zero
context.VeryLowPriceArray = msg.payload;
msg = null;
break;

case "LowPriceArray":
context.LowPriceArray = msg.payload;
msg = null;
break;

case "HighPriceArray":
context.HighPriceArray = msg.payload;
// node.warn("High Price: " + context.HighPriceArray);
msg = null;
break;

case "DayAhead_Hours":
// ignore;
msg = null;
break;

case "Price_State":
// ignore;
msg = null;
break;

case "SoC_Now":
context.SoC_Now_Perc = msg.payload;
msg = null;
break;

case "SoC_Min_Venus":
context.SoC_Min_Venus = msg.payload;
msg = null;
break;

case "Max_Charge_Current_A":
context.Max_Charge_Current_A = msg.payload;
msg = null;
break;

case "Max_Charge_Voltage_V":
context.Max_Charge_Voltage_V = msg.payload;
msg = null;
break;

case "Grid_Setpoint_W":
context.Grid_Setpoint_W = msg.payload;
msg = null;
break;

case "Charge_Disabled":
context.Charge_Disable = msg.payload;
msg = null;
break;

case "Solar_Charger":
context.Solar_Charger = msg.payload;
msg = null;
break;

case "Max_Inverter_Power_W":
context.Max_Inverter_Power_W = msg.payload;
msg = null;
break;

case "Relay_2":
context.Relay_2 = msg.payload;
msg = null;
break;

case "Grid_to_Batt_Limit":
// From EV Charger Monitor
context.Grid_to_Batt_Limit_A = msg.payload;
msg = null;
break;

case "Debug":
context.DebugOn = Not(context.DebugOn);
node.warn("DebugOn: " + context.DebugOn);
context.RunNow = true;
ClearContext();

msg = null;
break;

default:
node.error("Unknown Topic: " + msg.topic
+ " Payload: " + msg.payload);
context.RunNow = true;

} // switch for input all variables

// - - - - - - - - - - - - - - - - - - - - - - - - - -

let External_Data_OK = false;

if (context.RunNow == true)
{
if (Verify_External_Data() == true)
{
External_Data_OK = true;
// node.warn("Verify_External_Data") ;
}
}


if ( (External_Data_OK) && (context.RunNow == true) )
{
context.RunNow = false; // don't repeat until next launch

// - - - - - - - - - - - - - - - - - - - - - - - - - -

if (context.Last_DESS_Mode != context.DESS_Mode)
{
AddDebug(
"DESS_Mode: " + context.DESS_Mode + ".\r");
context.Last_DESS_Mode = context.DESS_Mode;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - -

if ( Not(GlobalGetAll() ) )
{
node.warn("GlobalGetAll: Fatal Error")
return;
}

SolarSetSunriseAndSunsetHours();

SolarMakeForecasts();

BatteryCalculateLevels();

CalculateEnergyToFullSoC();

FindCheapChargeHour();

FindHighSellHour();

FindEnergyToSell();

SetSolarAvailable();

TryAllRules();

CreateReturnMessages();

if (ErrorText > "") {
node.warn(ErrorText);
}

if ( context.DebugOn && (DebugText > "") )
{ node.warn(DebugText);
}

node.status({fill:"green",shape:"dot",
text: "@ " + NowTime + ": "
+ "Min.SoC: " + SoC_Min_Target_Perc + "%; "
+ Batt_Max_Current_Grid_A + " + "
+ Batt_Max_Current_Solar_A + " Amp ("
+ Solar_To_Batt_Perc + "%)"}
);


node.send(msg_DayAhead_Rule);
node.send(msg_Batt_Max_Current_Target_A);
node.send(msg_SoC_Min_Target_Perc);
node.send(msg_Grid_Setpoint_Target_W);
node.send(msg_Max_Inverter_Power_Target_W);
node.send(msg_Charge_Disable_Target);
node.send(msg_Solar_Charger_Target);
node.send(msg_Relay_2_Target);
node.done();

/*
return [ msg_DayAhead_Rule,
msg_Batt_Max_Current_Target_A,
msg_SoC_Min_Target_Perc,
msg_Grid_Setpoint_Target_W,
msg_Max_Inverter_Power_Target_W,
msg_Charge_Disable_Target,
msg_Solar_Charger_Target,
msg_Relay_2_Target ];
*/

} // if context.RunNow

[ Voor 220% gewijzigd door MJ de Bruijn op 14-05-2024 14:27 ]


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
Flow "Auto Laadstation"

Dit is de definitie van de Flow "Auto Laadstation". Kopieer de onderstaande tekst en kies in Nod Red "Import Nodes". Plak dan deze tekst en kies voor import.
[
{
"id": "53e88ae831d5e7b7",
"type": "tab",
"label": "Auto Laadstation",
"disabled": false,
"info": "",
"env": []
},
{
"id": "577a3ad05f3e60ec",
"type": "function",
"z": "53e88ae831d5e7b7",
"name": "Calc EV Charge Settings",
"func": "// zie seperate post voor JavaScript tekst

",
"outputs": 1,
"timeout": "4",
"noerr": 0,
"initialize": "// Code added here will be run once\n// whenever the node is started.\n\ncontext = context || {};\n\nif (context.TraceOn == null) {\n context.TraceOn = false;\n}\nif (context.Init_OK == null) {\n context.Init_OK = false;\n}\nif (context.Last_High_Amp == null) {\n context.Last_High_Amp = 16;\n}\nif (context.Decrease_High_Amp_Secs == null) {\n context.Decrease_High_Amp_Secs = 0;\n}\nif (context.Grid_to_Batt_Limit_A == null) {\n context.Grid_to_Batt_Limit_A = 20;\n}\nif (context.EV_Charger_Mode_Manual == null) {\n context.EV_Charger_Mode_Manual = 5; // do not use\n}\nif (context.EV_Charger_Start_Hour == null) {\n context.EV_Charger_Start_Hour = 0; // do not use\n}\nif (context.EV_Charger_Last_Hour == null) {\n context.EV_Charger_Last_Hour = 0; // do not use\n}\n",
"finalize": "",
"libs": [],
"x": 498,
"y": 512,
"wires": [
[
"8f73b7a7763e4bb2",
"d49191f2050d5542",
"cbf014efb00447eb",
"18c9911d47af1b56",
"c409eb6b1bdadb34"
]
]
},
{
"id": "6bbec36f65e5700c",
"type": "victron-output-evcharger",
"z": "53e88ae831d5e7b7",
"service": "com.victronenergy.evcharger/40",
"path": "/StartStop",
"serviceObj": {
"service": "com.victronenergy.evcharger/40",
"name": "Auto Laadstation"
},
"pathObj": {
"path": "/StartStop",
"type": "enum",
"name": "Start/stop charging (manual mode)",
"enum": {
"0": "Stop",
"1": "Start"
},
"writable": true
},
"initial": "",
"name": "Start/Stop Laden",
"onlyChanges": false,
"x": 826,
"y": 688,
"wires": []
},
{
"id": "8fa055112801a40b",
"type": "inject",
"z": "53e88ae831d5e7b7",
"name": "0 = Stop",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "Start_Stop",
"payload": "0",
"payloadType": "num",
"x": 92,
"y": 688,
"wires": [
[
"d49191f2050d5542"
]
]
},
{
"id": "882e0491f3b10850",
"type": "inject",
"z": "53e88ae831d5e7b7",
"name": "1 = Start",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "Start_Stop",
"payload": "1",
"payloadType": "num",
"x": 92,
"y": 720,
"wires": [
[
"d49191f2050d5542"
]
]
},
{
"id": "2efea18fdcdb179f",
"type": "victron-output-evcharger",
"z": "53e88ae831d5e7b7",
"service": "com.victronenergy.evcharger/40",
"path": "/SetCurrent",
"serviceObj": {
"service": "com.victronenergy.evcharger/40",
"name": "Auto Laadstation"
},
"pathObj": {
"path": "/SetCurrent",
"type": "float",
"name": "Set charge current (manual mode) (A)",
"writable": true
},
"name": "Set Max Charge (Manual)",
"onlyChanges": false,
"x": 858,
"y": 848,
"wires": []
},
{
"id": "f76cdf144fb4fd56",
"type": "victron-output-evcharger",
"z": "53e88ae831d5e7b7",
"service": "com.victronenergy.evcharger/40",
"path": "/Mode",
"serviceObj": {
"service": "com.victronenergy.evcharger/40",
"name": "Auto Laadstation"
},
"pathObj": {
"path": "/Mode",
"type": "enum",
"name": "Mode",
"enum": {
"0": "Manual",
"1": "Auto",
"2": "Schedule"
},
"writable": true
},
"initial": "",
"name": "",
"onlyChanges": false,
"x": 850,
"y": 624,
"wires": []
},
{
"id": "df8284662e2f66d7",
"type": "inject",
"z": "53e88ae831d5e7b7",
"name": "0 = Manual",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "EV_Mode",
"payload": "0",
"payloadType": "num",
"x": 92,
"y": 576,
"wires": [
[
"8f73b7a7763e4bb2"
]
]
},
{
"id": "924499edb3111f7c",
"type": "inject",
"z": "53e88ae831d5e7b7",
"name": "1 = Auto",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "EV_Mode",
"payload": "1",
"payloadType": "num",
"x": 92,
"y": 608,
"wires": [
[
"8f73b7a7763e4bb2"
]
]
},
{
"id": "48ed1dcd2e8567f5",
"type": "inject",
"z": "53e88ae831d5e7b7",
"name": "2 = Scheduled",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "EV_Mode",
"payload": "2",
"payloadType": "num",
"x": 102,
"y": 640,
"wires": [
[
"8f73b7a7763e4bb2"
]
]
},
{
"id": "70a90cf4cbb79aaa",
"type": "victron-input-evcharger",
"z": "53e88ae831d5e7b7",
"service": "com.victronenergy.evcharger/40",
"path": "/Mode",
"serviceObj": {
"service": "com.victronenergy.evcharger/40",
"name": "Auto Laadstation"
},
"pathObj": {
"path": "/Mode",
"type": "enum",
"name": "Mode",
"enum": {
"0": "Manual",
"1": "Auto",
"2": "Schedule"
}
},
"initial": "",
"name": "EV_Mode",
"onlyChanges": true,
"x": 92,
"y": 272,
"wires": [
[
"1891f28aeacc992f",
"577a3ad05f3e60ec"
]
]
},
{
"id": "16ff0c8b09b22777",
"type": "victron-output-evcharger",
"z": "53e88ae831d5e7b7",
"service": "com.victronenergy.evcharger/40",
"path": "/MaxCurrent",
"serviceObj": {
"service": "com.victronenergy.evcharger/40",
"name": "Auto Laadstation"
},
"pathObj": {
"path": "/MaxCurrent",
"type": "float",
"name": "Maximum charge current (A)",
"writable": true
},
"name": "Set Max Charge",
"onlyChanges": false,
"x": 828,
"y": 800,
"wires": []
},
{
"id": "9f1298dbce082bdd",
"type": "victron-input-evcharger",
"z": "53e88ae831d5e7b7",
"service": "com.victronenergy.evcharger/40",
"path": "/MaxCurrent",
"serviceObj": {
"service": "com.victronenergy.evcharger/40",
"name": "Auto Laadstation"
},
"pathObj": {
"path": "/MaxCurrent",
"type": "float",
"name": "Maximum charge current (A)"
},
"name": "EV_Max_Charge_Auto_A",
"onlyChanges": true,
"roundValues": "no",
"x": 142,
"y": 176,
"wires": [
[
"577a3ad05f3e60ec",
"9b417a3f76ef2489"
]
]
},
{
"id": "466113809d167f66",
"type": "victron-input-evcharger",
"z": "53e88ae831d5e7b7",
"service": "com.victronenergy.evcharger/40",
"path": "/SetCurrent",
"serviceObj": {
"service": "com.victronenergy.evcharger/40",
"name": "Auto Laadstation"
},
"pathObj": {
"path": "/SetCurrent",
"type": "float",
"name": "Set charge current (manual mode) (A)"
},
"name": "EV_Max_Charge_Manual_A",
"onlyChanges": true,
"roundValues": "no",
"x": 152,
"y": 224,
"wires": [
[
"577a3ad05f3e60ec",
"ecadfdf5a6f0632c"
]
]
},
{
"id": "1b252e016235d67f",
"type": "victron-input-evcharger",
"z": "53e88ae831d5e7b7",
"service": "com.victronenergy.evcharger/40",
"path": "/StartStop",
"serviceObj": {
"service": "com.victronenergy.evcharger/40",
"name": "Auto Laadstation"
},
"pathObj": {
"path": "/StartStop",
"type": "enum",
"name": "Start/stop charging (manual mode)",
"enum": {
"0": "Stop",
"1": "Start"
}
},
"initial": "",
"name": "EV_Start_Stop",
"onlyChanges": true,
"x": 112,
"y": 320,
"wires": [
[
"52d61f34b1f5c90a",
"577a3ad05f3e60ec"
]
]
},
{
"id": "52d61f34b1f5c90a",
"type": "change",
"z": "53e88ae831d5e7b7",
"name": "",
"rules": [
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "0",
"fromt": "num",
"to": "EV Start-Stop 0: Stop",
"tot": "str"
},
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "1",
"fromt": "num",
"to": "EV Start-Stop 1: Start",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 336,
"y": 320,
"wires": [
[
"2ea293265c6f5b1b"
]
]
},
{
"id": "1891f28aeacc992f",
"type": "change",
"z": "53e88ae831d5e7b7",
"name": "",
"rules": [
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "0",
"fromt": "num",
"to": "EV Mode 0: Manual",
"tot": "str"
},
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "1",
"fromt": "num",
"to": "EV Mode 1: Auto",
"tot": "str"
},
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "2",
"fromt": "num",
"to": "EV Mode 2: Scheduled",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 336,
"y": 272,
"wires": [
[
"f3b2fbf9e1a8af84"
]
]
},
{
"id": "f344b378ace11420",
"type": "victron-input-evcharger",
"z": "53e88ae831d5e7b7",
"service": "com.victronenergy.evcharger/40",
"path": "/Status",
"serviceObj": {
"service": "com.victronenergy.evcharger/40",
"name": "Auto Laadstation"
},
"pathObj": {
"path": "/Status",
"type": "enum",
"name": "Status",
"enum": {
"0": "Disconnected",
"1": "Connected",
"2": "Charging",
"3": "Charged",
"4": "Waiting for sun",
"5": "Waiting for RFID",
"6": "Waiting for start",
"7": "Low SOC",
"8": "Ground fault",
"9": "Welded contacts",
"10": "CP Input shorted",
"11": "Residual current detected",
"12": "Under voltage detected",
"13": "Overvoltage detected",
"14": "Overheating detected"
}
},
"initial": "",
"name": "EV_Status",
"onlyChanges": true,
"x": 92,
"y": 368,
"wires": [
[
"ccd379b940f13ae5",
"577a3ad05f3e60ec"
]
]
},
{
"id": "ccd379b940f13ae5",
"type": "change",
"z": "53e88ae831d5e7b7",
"name": "",
"rules": [
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "EV_State_Text",
"tot": "str"
},
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "0",
"fromt": "num",
"to": "EV State 0: Disconnected",
"tot": "str"
},
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "1",
"fromt": "num",
"to": "EV State 1: Connected",
"tot": "str"
},
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "2",
"fromt": "num",
"to": "EV State 2: Charging",
"tot": "str"
},
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "3",
"fromt": "num",
"to": "EV State 3: Charged",
"tot": "str"
},
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "4",
"fromt": "num",
"to": "EV State 4: Waiting for Sun",
"tot": "str"
},
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "5",
"fromt": "num",
"to": "EV State 5: Waiting for RFID",
"tot": "str"
},
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "6",
"fromt": "num",
"to": "EV State 6: Waiting for Start",
"tot": "str"
},
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "7",
"fromt": "num",
"to": "EV State 7: Low SoC",
"tot": "str"
},
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "8",
"fromt": "num",
"to": "EV State 8: Ground Fault",
"tot": "str"
},
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "9",
"fromt": "num",
"to": "EV State 9: Welded Contacts",
"tot": "str"
},
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "10",
"fromt": "num",
"to": "EV State 10: CP input Shorted",
"tot": "str"
},
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "11",
"fromt": "num",
"to": "EV State 11 Residual Current Detected",
"tot": "str"
},
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "12",
"fromt": "num",
"to": "EV State 12: Under Voltage Detected",
"tot": "str"
},
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "13",
"fromt": "num",
"to": "EV State 13: Overvoltage Detected",
"tot": "str"
},
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "14",
"fromt": "num",
"to": "EV State 14: Overheating Detected",
"tot": "str"
},
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "21",
"fromt": "num",
"to": "EV State 21: Start Charge",
"tot": "str"
},
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "22",
"fromt": "num",
"to": "EV State 22: Switch to 3 Phase",
"tot": "str"
},
{
"t": "change",
"p": "payload",
"pt": "msg",
"from": "23",
"fromt": "num",
"to": "EV State 23: Switch to 1 Phase",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 336,
"y": 368,
"wires": [
[
"27c233afc2021866",
"577a3ad05f3e60ec"
]
]
},
{
"id": "9ca9154dc9817c55",
"type": "api-call-service",
"z": "53e88ae831d5e7b7",
"name": "Send Auto Laadstation Log",
"server": "289362b880855228",
"version": 5,
"debugenabled": false,
"domain": "input_text",
"service": "set_value",
"areaId": [],
"deviceId": [],
"entityId": [
"input_text.helper_auto_laadstation_log"
],
"data": "{\"value\":\"{{payload}}\"}",
"dataType": "json",
"mergeContext": "",
"mustacheAltTags": false,
"outputProperties": [],
"queue": "all",
"x": 808,
"y": 432,
"wires": [
[]
]
},
{
"id": "f616608bfad32014",
"type": "delay",
"z": "53e88ae831d5e7b7",
"name": "1 sec",
"pauseType": "delay",
"timeout": "1",
"timeoutUnits": "seconds",
"rate": "1",
"nbRateUnits": "1",
"rateUnits": "second",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": false,
"allowrate": false,
"outputs": 1,
"x": 850,
"y": 320,
"wires": [
[
"d11094c1fd76e0b7"
]
]
},
{
"id": "11e3d6aeacb051e3",
"type": "delay",
"z": "53e88ae831d5e7b7",
"name": "2 sec",
"pauseType": "delay",
"timeout": "2",
"timeoutUnits": "seconds",
"rate": "1",
"nbRateUnits": "1",
"rateUnits": "second",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": false,
"allowrate": false,
"outputs": 1,
"x": 850,
"y": 368,
"wires": [
[
"d11094c1fd76e0b7"
]
]
},
{
"id": "51591f5a7beac8c0",
"type": "inject",
"z": "53e88ae831d5e7b7",
"name": "Trace",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "Trace",
"payload": "",
"payloadType": "date",
"x": 82,
"y": 432,
"wires": [
[
"577a3ad05f3e60ec"
]
]
},
{
"id": "d12076100afdf928",
"type": "inject",
"z": "53e88ae831d5e7b7",
"name": "Every 1 sec",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "1",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "CountDown",
"payload": "",
"payloadType": "date",
"x": 102,
"y": 480,
"wires": [
[
"577a3ad05f3e60ec"
]
]
},
{
"id": "959570f4906ad682",
"type": "victron-input-gridmeter",
"z": "53e88ae831d5e7b7",
"service": "com.victronenergy.grid/33",
"path": "/Ac/L1/Current",
"serviceObj": {
"service": "com.victronenergy.grid/33",
"name": "ET340 (Grid)"
},
"pathObj": {
"path": "/Ac/L1/Current",
"type": "float",
"name": "L1 Current (A)"
},
"name": "ET340_L1_A",
"onlyChanges": true,
"roundValues": "1",
"x": 102,
"y": 32,
"wires": [
[
"577a3ad05f3e60ec"
]
]
},
{
"id": "c75fa29f74af606f",
"type": "victron-input-gridmeter",
"z": "53e88ae831d5e7b7",
"service": "com.victronenergy.grid/33",
"path": "/Ac/L2/Current",
"serviceObj": {
"service": "com.victronenergy.grid/33",
"name": "ET340 (Grid)"
},
"pathObj": {
"path": "/Ac/L2/Current",
"type": "float",
"name": "L2 Current (A)"
},
"name": "ET340_L2_A",
"onlyChanges": true,
"roundValues": "1",
"x": 102,
"y": 80,
"wires": [
[
"577a3ad05f3e60ec"
]
]
},
{
"id": "895d44ae8ca812e6",
"type": "victron-input-gridmeter",
"z": "53e88ae831d5e7b7",
"service": "com.victronenergy.grid/33",
"path": "/Ac/L3/Current",
"serviceObj": {
"service": "com.victronenergy.grid/33",
"name": "ET340 (Grid)"
},
"pathObj": {
"path": "/Ac/L3/Current",
"type": "float",
"name": "L3 Current (A)"
},
"name": "ET340_L3_A",
"onlyChanges": true,
"roundValues": "1",
"x": 102,
"y": 128,
"wires": [
[
"577a3ad05f3e60ec"
]
]
},
{
"id": "9b417a3f76ef2489",
"type": "function",
"z": "53e88ae831d5e7b7",
"name": "Text: Max Charge Auto",
"func": "\nconst Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n + (\"0\" + Now.getSeconds()).slice(-2)\n ;\n\nmsg.payload = \"Max Charge Auto: \" + msg.payload;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n text: \"@ \" + NowTime + \": \"\n + msg.payload }\n );\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 580,
"y": 176,
"wires": [
[
"d11094c1fd76e0b7"
]
]
},
{
"id": "ecadfdf5a6f0632c",
"type": "function",
"z": "53e88ae831d5e7b7",
"name": "Text: Max Charge Manual",
"func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n + (\"0\" + Now.getSeconds()).slice(-2)\n ;\n\nmsg.payload = \"Max Charge Manual: \" + msg.payload;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n text: \"@ \" + NowTime + \": \"\n + msg.payload }\n );\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 590,
"y": 224,
"wires": [
[
"d11094c1fd76e0b7"
]
]
},
{
"id": "77b005bfc989bf6d",
"type": "delay",
"z": "53e88ae831d5e7b7",
"name": "1 / 5 sec",
"pauseType": "rate",
"timeout": "5",
"timeoutUnits": "seconds",
"rate": "1",
"nbRateUnits": "5",
"rateUnits": "second",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": true,
"allowrate": false,
"outputs": 1,
"x": 420,
"y": 800,
"wires": [
[
"852ef9770db62f68"
]
]
},
{
"id": "68dcdba7ce8b755b",
"type": "delay",
"z": "53e88ae831d5e7b7",
"name": "1 / 5 sec",
"pauseType": "rate",
"timeout": "5",
"timeoutUnits": "seconds",
"rate": "1",
"nbRateUnits": "5",
"rateUnits": "second",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": true,
"allowrate": false,
"outputs": 1,
"x": 420,
"y": 848,
"wires": [
[
"b01f74db127b197b"
]
]
},
{
"id": "130361d2a781f4ce",
"type": "delay",
"z": "53e88ae831d5e7b7",
"name": "1 / 5 sec",
"pauseType": "rate",
"timeout": "5",
"timeoutUnits": "seconds",
"rate": "1",
"nbRateUnits": "5",
"rateUnits": "second",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": true,
"allowrate": false,
"outputs": 1,
"x": 532,
"y": 752,
"wires": [
[
"d20d66cd01b4b089"
]
]
},
{
"id": "2c2910b4aa31179b",
"type": "switch",
"z": "53e88ae831d5e7b7",
"name": "0: Top; 1: Bottom",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "0",
"vt": "num"
},
{
"t": "eq",
"v": "1",
"vt": "num"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 346,
"y": 736,
"wires": [
[
"d20d66cd01b4b089"
],
[
"130361d2a781f4ce"
]
]
},
{
"id": "26931988f2071217",
"type": "link in",
"z": "53e88ae831d5e7b7",
"name": "Input Main Function",
"links": [
"8ad9f2f536daf1e6",
"17276b34e9455aa1",
"fc96c666b23d0d8f",
"68c5e868d4e6b72b",
"b89c0cd495228bba",
"e60f001cc00b9d26",
"b6ab4d914b409b9d",
"f1fd0325f7053e8b",
"d927837c63bfe828",
"0378ceeb26afb426",
"e6816c6deacdaee7",
"3377df15d16d89ed"
],
"x": 225,
"y": 512,
"wires": [
[
"577a3ad05f3e60ec"
]
]
},
{
"id": "245e3ce4e79461c7",
"type": "link out",
"z": "53e88ae831d5e7b7",
"name": "Batt Overload Limit",
"mode": "link",
"links": [
"8803291847335701"
],
"x": 763,
"y": 896,
"wires": []
},
{
"id": "35920f4deb1cd5a7",
"type": "delay",
"z": "53e88ae831d5e7b7",
"name": "1 / 5 sec",
"pauseType": "rate",
"timeout": "5",
"timeoutUnits": "seconds",
"rate": "1",
"nbRateUnits": "5",
"rateUnits": "second",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": true,
"allowrate": false,
"outputs": 1,
"x": 420,
"y": 896,
"wires": [
[
"031389518085793c"
]
]
},
{
"id": "47172df1ed606009",
"type": "delay",
"z": "53e88ae831d5e7b7",
"name": "1 / 5 sec",
"pauseType": "rate",
"timeout": "5",
"timeoutUnits": "seconds",
"rate": "1",
"nbRateUnits": "5",
"rateUnits": "second",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": true,
"allowrate": false,
"outputs": 1,
"x": 324,
"y": 624,
"wires": [
[
"937ebb098c3fdc93"
]
]
},
{
"id": "8f73b7a7763e4bb2",
"type": "switch",
"z": "53e88ae831d5e7b7",
"name": "EV_Mode",
"property": "topic",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "EV_Mode",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 316,
"y": 576,
"wires": [
[
"47172df1ed606009"
]
]
},
{
"id": "d49191f2050d5542",
"type": "switch",
"z": "53e88ae831d5e7b7",
"name": "Start_Stop",
"property": "topic",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "Start_Stop",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 326,
"y": 688,
"wires": [
[
"2c2910b4aa31179b"
]
]
},
{
"id": "cbf014efb00447eb",
"type": "switch",
"z": "53e88ae831d5e7b7",
"name": "Max_Charge",
"property": "topic",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "Max_Charge",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 178,
"y": 800,
"wires": [
[
"77b005bfc989bf6d"
]
]
},
{
"id": "c409eb6b1bdadb34",
"type": "switch",
"z": "53e88ae831d5e7b7",
"name": "Grid_to_Batt_Limit",
"property": "topic",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "Grid_to_Batt_Limit",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 198,
"y": 896,
"wires": [
[
"35920f4deb1cd5a7"
]
]
},
{
"id": "18c9911d47af1b56",
"type": "switch",
"z": "53e88ae831d5e7b7",
"name": "Max_Charge_Manual",
"property": "topic",
"propertyType": "msg",
"rules": [
{
"t": "eq",
"v": "Max_Charge_Manual",
"vt": "str"
}
],
"checkall": "true",
"repair": false,
"outputs": 1,
"x": 208,
"y": 848,
"wires": [
[
"68dcdba7ce8b755b"
]
]
},
{
"id": "f3b2fbf9e1a8af84",
"type": "function",
"z": "53e88ae831d5e7b7",
"name": "Show Payload",
"func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n + (\"0\" + Now.getSeconds()).slice(-2)\n ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n text: \"@ \" + NowTime + \": \"\n + msg.payload }\n );\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 560,
"y": 272,
"wires": [
[
"d11094c1fd76e0b7"
]
]
},
{
"id": "2ea293265c6f5b1b",
"type": "function",
"z": "53e88ae831d5e7b7",
"name": "Show Payload",
"func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n + (\"0\" + Now.getSeconds()).slice(-2)\n ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n text: \"@ \" + NowTime + \": \"\n + msg.payload }\n );\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 560,
"y": 320,
"wires": [
[
"f616608bfad32014"
]
]
},
{
"id": "27c233afc2021866",
"type": "function",
"z": "53e88ae831d5e7b7",
"name": "Show Payload",
"func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n + (\"0\" + Now.getSeconds()).slice(-2)\n ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n text: \"@ \" + NowTime + \": \"\n + msg.payload }\n );\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 560,
"y": 368,
"wires": [
[
"11e3d6aeacb051e3"
]
]
},
{
"id": "937ebb098c3fdc93",
"type": "function",
"z": "53e88ae831d5e7b7",
"name": "Show Payload",
"func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n + (\"0\" + Now.getSeconds()).slice(-2)\n ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n text: \"@ \" + NowTime + \": \"\n + msg.topic + \": \" + msg.payload }\n );\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 608,
"y": 624,
"wires": [
[
"f76cdf144fb4fd56"
]
]
},
{
"id": "d20d66cd01b4b089",
"type": "function",
"z": "53e88ae831d5e7b7",
"name": "Show Payload",
"func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n + (\"0\" + Now.getSeconds()).slice(-2)\n ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n text: \"@ \" + NowTime + \": \"\n + msg.topic + \": \" + msg.payload }\n );\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 608,
"y": 688,
"wires": [
[
"6bbec36f65e5700c"
]
]
},
{
"id": "852ef9770db62f68",
"type": "function",
"z": "53e88ae831d5e7b7",
"name": "Show Payload",
"func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n + (\"0\" + Now.getSeconds()).slice(-2)\n ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n text: \"@ \" + NowTime + \": \"\n + msg.topic + \": \" + msg.payload }\n );\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 604,
"y": 800,
"wires": [
[
"16ff0c8b09b22777"
]
]
},
{
"id": "b01f74db127b197b",
"type": "function",
"z": "53e88ae831d5e7b7",
"name": "Show Payload",
"func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n + (\"0\" + Now.getSeconds()).slice(-2)\n ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n text: \"@ \" + NowTime + \": \"\n + msg.topic + \": \" + msg.payload }\n );\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 604,
"y": 848,
"wires": [
[
"2efea18fdcdb179f"
]
]
},
{
"id": "031389518085793c",
"type": "function",
"z": "53e88ae831d5e7b7",
"name": "Show Payload",
"func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n + (\"0\" + Now.getSeconds()).slice(-2)\n ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n text: \"@ \" + NowTime + \": \"\n + msg.topic + \": \" + msg.payload }\n );\n\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 604,
"y": 896,
"wires": [
[
"245e3ce4e79461c7"
]
]
},
{
"id": "d11094c1fd76e0b7",
"type": "rbe",
"z": "53e88ae831d5e7b7",
"name": "Real Changes",
"func": "rbei",
"gap": "",
"start": "",
"inout": "out",
"septopics": true,
"property": "payload",
"topi": "topic",
"x": 560,
"y": 432,
"wires": [
[
"9ca9154dc9817c55"
]
]
},
{
"id": "289362b880855228",
"type": "server",
"name": "Home Assistant Api",
"version": 5,
"addon": false,
"rejectUnauthorizedCerts": true,
"ha_boolean": "y|yes|true|on|home|open",
"connectionDelay": true,
"cacheJson": true,
"heartbeat": false,
"heartbeatInterval": "30",
"areaSelector": "friendlyName",
"deviceSelector": "friendlyName",
"entitySelector": "friendlyName",
"statusSeparator": ": ",
"statusYear": "hidden",
"statusMonth": "short",
"statusDay": "numeric",
"statusHourCycle": "default",
"statusTimeFormat": "h:m",
"enableGlobalContextStore": false
}
]
Afbeeldingslocatie: https://tweakers.net/i/foex9tHgafIrFGmCU2VH0UnQGlw=/full-fit-in/4920x3264/filters:max_bytes(3145728):no_upscale():strip_icc():fill(white):strip_exif()/f/image/sQJLwyb0ouZjqCTkJcNUnoxi.jpg?f=user_large

[ Voor 214% gewijzigd door MJ de Bruijn op 14-05-2024 15:12 ]


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
JavaScript van de functie "Calc EV Charge Settings" in Flow "Auto Laadstation"

Wanneer de Flow "Auto Laadstation" is gemaakt (zie hierboven), open dan functie "Calc EV Charge Settings". Plak de onderstaande tekst.
// This function monitors the Currents throught the
// ET340 electricity meter.
// These are the Critical and non-critical currents
// plus the Battery charge currents.

// At start, there is a "Dummy" Value of 20 Amps.
// This will decrease slowly to allow the real values
// of three phases L1, L2 and L3 to settle, so
// there is no risk of missing a high value.

// Depending on the used power, represented by this
// High Value, the available resting Power (or Amps)
// for the EV will be calculated.

const Now = new Date()
let NowHour = Now.getHours();
let NowTime = ("0" + Now.getHours()).slice(-2) + ":"
+ ("0" + Now.getMinutes()).slice(-2) + ":"
+ ("0" + Now.getSeconds()).slice(-2)
;


var CurTimeInSecs = Math.floor(Date.now() / 1000);
var New_High_Amp = 0;
var Max_Amp_Allowed = 25;

var EV_Is_Ready = false;
var Power_Limit = 3 * 6 * 230;
var Power_Available = 0;
// the available power must be more than 3 phase, 6 Amp, 230 Volt
// = 4140 Watt for several minutes

if (context.Decrease_High_Amp_Secs == 0) {
// start countdown in 3 secs
context.Decrease_High_Amp_Secs = CurTimeInSecs + 3;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

switch (msg.topic) {

case "Trace":
context.TraceOn = !context.TraceOn;
node.warn("Trace: " + context.TraceOn);
msg = null;
break;
case "CountDown":
// run at least every 1 second
msg = null;
break;
case "EV_Mode":
context.EV_Mode = msg.payload;
msg = null;
break;
case "EV_Status":
context.EV_Status = msg.payload;
msg = null;
break;
case "EV_State_Text":
context.EV_Status_Text = msg.payload;
msg = null;
break;
case "ET340_L1_A":
context.ET340_L1_A = msg.payload;
msg = null;
break;
case "ET340_L2_A":
context.ET340_L2_A = msg.payload;
msg = null;
break;
case "ET340_L3_A":
context.ET340_L3_A = msg.payload;
msg = null;3
break;
case "EV_Start_Stop":
context.EV_Start_Stop = msg.payload;
if (context.EV_Start_Stop_Targ == null) {
context.EV_Start_Stop_Targ = context.EV_Start_Stop;
}
msg = null;
break;
case "EV_Max_Charge_Auto_A":
context.EV_Max_Charge_Auto_A = msg.payload;
if (context.EV_Max_Charge_Auto_Targ_A == null) {
context.EV_Max_Charge_Auto_Targ_A = context.EV_Max_Charge_Auto_A;
}
msg = null;
break;
case "EV_Max_Charge_Manual_A":
context.EV_Max_Charge_Manual_A = msg.payload;
if (context.EV_Max_Charge_Manual_Targ_A == null) {
context.EV_Max_Charge_Manual_Targ_A = context.EV_Max_Charge_Manual_A;
}
msg = null;
break;
case "Price_State":
context.Price_State = msg.payload;
msg = null;3
break;
default:
node.warn("Unknown Topic: " + msg.topic + " Payload: " + msg.payload);
msg = null;
break;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

if ( (context.ET340_L1_A != null)
&& (context.ET340_L2_A != null)
&& (context.ET340_L3_A != null)
&& (context.EV_Mode != null)
&& (context.EV_Status != null)
&& (context.EV_Status_Text != null)
&& (context.EV_Start_Stop != null)
&& (context.EV_Start_Stop_Targ != null)
&& (context.EV_Max_Charge_Auto_A != null)
&& (context.EV_Max_Charge_Auto_Targ_A != null)
&& (context.EV_Max_Charge_Manual_A != null)
&& (context.EV_Max_Charge_Manual_Targ_A != null)
&& (context.Price_State != null)
)
{
context.Init_OK = true;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

if (context.Init_OK) {

if (context.TraceOn) { node.warn("EV_Status: " + context.EV_Status) }

context.EV_Charger_Mode_Manual =
global.get("Victron_MESS.EV_Charger.Target_Mode").substring(0, 1);

if (context.TraceOn)
{
node.warn("Charger_Mode_Manual: " + context.EV_Charger_Mode_Manual)
}

context.EV_Charger_Start_Hour =
global.get("Victron_MESS.EV_Charger.Start_Hour");
if (context.TraceOn)
{
node.warn("Charger_Start_Hour: " + context.EV_Charger_Start_Hour)
}

context.EV_Charger_Last_Hour =
global.get("Victron_MESS.EV_Charger.Last_Hour");
if (context.TraceOn)
{
node.warn("Charger_Last_Hour: " + context.EV_Charger_Last_Hour)
}

if (context.EV_Charger_Mode_Manual == "0") // manual
{ context.EV_Mode_Target = 0 }
else if (context.EV_Charger_Mode_Manual == "1") // auto
{ context.EV_Mode_Target = 1 }
else if (context.EV_Charger_Mode_Manual == "2") // scheduled
{ context.EV_Mode_Target = 2 }
else if (context.EV_Charger_Mode_Manual == "3") // Scheduler (HA)
{ context.EV_Mode_Target = 0 }
else if (context.EV_Charger_Mode_Manual == "4") // Optimized (Node Red)
{ context.EV_Mode_Target = 0 }
else if (context.EV_Charger_Mode_Manual == "5") // Do not use manual.
{ context.EV_Mode_Target = context.EV_Mode }

EV_Is_Ready = ( (context.EV_Status == 1) // connected
|| (context.EV_Status == 2) // charging
|| (context.EV_Status == 3) // charged
|| (context.EV_Status == 4) // waiting for sun
|| (context.EV_Status == 5) // waiting for RFID
|| (context.EV_Status == 6) // waiting for start
|| (context.EV_Status == 21) // starting
);
if (context.TraceOn) { node.warn("EV_Is_Ready: " + EV_Is_Ready) }


New_High_Amp = Math.max(Math.max(context.ET340_L1_A, 0),
Math.max(context.ET340_L2_A, 0),
Math.max(context.ET340_L3_A, 0),
context.Last_High_Amp);

// don't show normal countdown, only later changes
if (New_High_Amp != context.Last_High_Amp) {
if (context.TraceOn) {
node.warn("High_Amp: " + New_High_Amp);
}
context.Last_High_Amp =
Math.max(context.Last_High_Amp, New_High_Amp);
if (context.TraceOn) {
node.warn("Last_High_Amp: " + context.Last_High_Amp);
}
}

// after every x seconds
// decrease Last_High_Amp with 0,1 Amp.
// slowly, to avoid sudden power peaks
if (CurTimeInSecs > context.Decrease_High_Amp_Secs)
{
context.Last_High_Amp =
Math.round(10*context.Last_High_Amp - 1) / 10;
context.Decrease_High_Amp_Secs = CurTimeInSecs;
}

if (context.EV_Mode == 0 )
{ // Manual
context.EV_Max_Charge_Auto_Targ_A = 16;
context.EV_Max_Charge_Manual_Targ_A = 16;
}

// In all cases, avoid High currents
context.EV_Max_Charge_Auto_Targ_A =
Math.max(
Math.min(16,
Math.trunc(Max_Amp_Allowed - Math.ceil(context.Last_High_Amp))
// ,
// context.EV_Max_Charge_Auto_Targ_A
)
, 0);

context.EV_Max_Charge_Manual_Targ_A =
Math.max(
Math.min(16,
Math.round(Max_Amp_Allowed - context.Last_High_Amp)
)
, 0);


if ( (context.EV_Status == 2) // charging
|| (context.EV_Status == 6) // waiting for start
|| (context.EV_Status == 21) // starting
)
{ // charging
if (New_High_Amp < 8) {
context.Grid_to_Batt_Limit_A = 120;
}
else if (New_High_Amp < 9) {
if ( (context.Grid_to_Batt_Limit_A < 100)
|| (context.Grid_to_Batt_Limit_A > 120) )
{ context.Grid_to_Batt_Limit_A = 110 }
}
else if (New_High_Amp < 10) {
if ( (context.Grid_to_Batt_Limit_A < 90)
|| (context.Grid_to_Batt_Limit_A > 110) )
{ context.Grid_to_Batt_Limit_A = 100 }
}
else if (New_High_Amp < 11) {
if ( (context.Grid_to_Batt_Limit_A < 80)
|| (context.Grid_to_Batt_Limit_A > 100) )
{ context.Grid_to_Batt_Limit_A = 90 }
}
else if (New_High_Amp < 12) {
if ( (context.Grid_to_Batt_Limit_A < 70)
|| (context.Grid_to_Batt_Limit_A > 90) )
{ context.Grid_to_Batt_Limit_A = 80 }
}
else if (New_High_Amp < 13) {
if ( (context.Grid_to_Batt_Limit_A < 60)
|| (context.Grid_to_Batt_Limit_A > 80) )
{ context.Grid_to_Batt_Limit_A = 70 }
}
else if (New_High_Amp < 14) {
if ( (context.Grid_to_Batt_Limit_A < 50)
|| (context.Grid_to_Batt_Limit_A > 70) )
{ context.Grid_to_Batt_Limit_A = 60 }
}
else if (New_High_Amp < 15) {
if ( (context.Grid_to_Batt_Limit_A < 40)
|| (context.Grid_to_Batt_Limit_A > 60) )
{ context.Grid_to_Batt_Limit_A = 50 }
}
else if (New_High_Amp < 16) {
if ( (context.Grid_to_Batt_Limit_A < 30)
|| (context.Grid_to_Batt_Limit_A > 50) )
{ context.Grid_to_Batt_Limit_A = 40 }
}
else {
if ( (context.Grid_to_Batt_Limit_A < 20)
|| (context.Grid_to_Batt_Limit_A > 40) )
{ context.Grid_to_Batt_Limit_A = 30 }
}
} // if charging


if (context.TraceOn) {
node.warn("EV_Max_Charge_Manual_Targ_A: " + context.EV_Max_Charge_Manual_Targ_A);
node.warn("EV_Max_Charge_Auto_Targ_A: " + context.EV_Max_Charge_Auto_Targ_A);
node.warn("Grid_to_Batt_Limit_A: " + context.Grid_to_Batt_Limit_A);
}

if (context.EV_Max_Charge_Manual_Targ_A < 6) {
context.EV_Start_Stop_Targ = 0;
context.EV_Max_Charge_Auto_Targ_A = 10; // minimum allowed value
context.EV_Max_Charge_Manual_Targ_A = 6; // minimum allowed value
}

else if (context.EV_Max_Charge_Auto_Targ_A < 6) {
context.EV_Start_Stop_Targ = 0;
context.EV_Max_Charge_Auto_Targ_A = 10; // minimum allowed value
context.EV_Max_Charge_Manual_Targ_A = 6; // minimum allowed value
}


else if ( (EV_Is_Ready)
&& (context.EV_Charger_Mode_Manual == "3") ) // HA Schedule
{
if ( (NowHour >= context.EV_Charger_Start_Hour)
&& (NowHour <= context.EV_Charger_Last_Hour)
)
{ // turn on
context.EV_Start_Stop_Targ = 1;
}
else
{ // turn off
context.EV_Start_Stop_Targ = 0;
}

if (context.TraceOn)
{ node.warn("Start_Stop: " + context.EV_Start_Stop_Targ)
}

}
else if ( (EV_Is_Ready)
&& (context.EV_Charger_Mode_Manual == "4") ) // Optimized
{
if ( (context.Price_State == "Negative")
|| (context.Price_State == "Zero")
|| (context.Price_State == "Very Low")
|| (context.Price_State == "Low")
)
{ // turn on
context.EV_Start_Stop_Targ = 1;
}
else
{ // turn off
context.EV_Start_Stop_Targ = 0;
}
if (context.TraceOn)
{ node.warn("Price_State: " + context.Price_State
+ "; Start_Stop: " + context.EV_Start_Stop_Targ)
}

}

else
{
context.EV_Start_Stop_Targ = context.EV_Start_Stop; // unchanged
}


// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

if (context.EV_Mode_Target != context.EV_Mode) {
msg_EV_Mode =
{ topic: "EV_Mode",
payload: context.EV_Mode_Target};
}
else {
msg_EV_Mode = null;
}

if (context.EV_Start_Stop_Targ !=
context.EV_Start_Stop) {
msg_Start_Stop =
{ topic: "Start_Stop",
payload: context.EV_Start_Stop_Targ};
}
else {
// node.warn("context.EV_Start_Stop_Targ: null" );
msg_Start_Stop = null;
}

if (context.EV_Max_Charge_Auto_Targ_A !=
context.EV_Max_Charge_Auto_A) {
msg_Max_Charge =
{ topic: "Max_Charge",
payload: context.EV_Max_Charge_Auto_Targ_A };
}
else {
msg_Max_Charge = null;
}

if (context.EV_Max_Charge_Manual_Targ_A !=
context.EV_Max_Charge_Manual_A) {
msg_Max_Charge_Manual =
{ topic: "Max_Charge_Manual",
payload: context.EV_Max_Charge_Manual_Targ_A};
}
else {
msg_Max_Charge_Manual = null;
}

msg_Grid_to_Batt_Limit =
{ topic: "Grid_to_Batt_Limit",
payload: context.Grid_to_Batt_Limit_A};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

node.status( {fill:"green", shape:"dot",
text: "@ " + NowTime + ": "
+ context.EV_Status_Text + "; "
+ "High: " + context.Last_High_Amp + " Amp, "
+ "Charge: " + context.EV_Max_Charge_Auto_Targ_A + " Amp"}
);

node.send(msg_EV_Mode);
node.send(msg_Start_Stop);
node.send(msg_Max_Charge);
node.send(msg_Max_Charge_Manual);
node.send(msg_Grid_to_Batt_Limit);
node.done();

// if (EV_Is_Ready) {
// return [ msg_EV_Mode,
// msg_Start_Stop,
// msg_Max_Charge,
// msg_Max_Charge_Manual,
// msg_Grid_to_Batt_Limit ]
// }

} // if Init_OK

[ Voor 255% gewijzigd door MJ de Bruijn op 14-05-2024 14:32 ]


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
Dit is de volledige Flow "Home Assistant"

Dit is de definitie van de Flow "Home Assistant". Kopieer de onderstaande tekst en kies in Nod Red "Import Nodes". Plak dan deze tekst en kies voor import.
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
[
    {
        "id": "6ffb7df5f8d96f1a",
        "type": "tab",
        "label": "Home Assistant",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "b9e555505db0c195",
        "type": "victron-input-custom",
        "z": "6ffb7df5f8d96f1a",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Mode",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Mode",
            "name": "/Settings/DynamicEss/Mode",
            "type": "number"
        },
        "name": "DESS Mode",
        "onlyChanges": true,
        "roundValues": "0",
        "x": 198,
        "y": 32,
        "wires": [
            [
                "82f3a5ff3ee9a153"
            ]
        ]
    },
    {
        "id": "871a1fa47acdfeca",
        "type": "api-call-service",
        "z": "6ffb7df5f8d96f1a",
        "name": "Send DESS Mode",
        "server": "289362b880855228",
        "version": 5,
        "debugenabled": false,
        "domain": "input_number",
        "service": "set_value",
        "areaId": [],
        "deviceId": [],
        "entityId": [
            "input_number.helper_dess_mode"
        ],
        "data": "{\"value\":\"{{payload}}\"}",
        "dataType": "json",
        "mergeContext": "",
        "mustacheAltTags": false,
        "outputProperties": [],
        "queue": "none",
        "x": 662,
        "y": 80,
        "wires": [
            [
                "844c7311f8f421fb"
            ]
        ]
    },
    {
        "id": "844c7311f8f421fb",
        "type": "debug",
        "z": "6ffb7df5f8d96f1a",
        "name": "DESS Mode",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 874,
        "y": 80,
        "wires": []
    },
    {
        "id": "25d11d67fe68d5cb",
        "type": "api-current-state",
        "z": "6ffb7df5f8d96f1a",
        "name": "Get Target DESS Mode",
        "server": "289362b880855228",
        "version": 3,
        "outputs": 1,
        "halt_if": "",
        "halt_if_type": "str",
        "halt_if_compare": "is",
        "entity_id": "input_select.helper_set_target_dess_mode",
        "state_type": "str",
        "blockInputOverrides": false,
        "outputProperties": [
            {
                "property": "payload",
                "propertyType": "msg",
                "value": "",
                "valueType": "entityState"
            },
            {
                "property": "topic",
                "propertyType": "msg",
                "value": "DESS_Mode",
                "valueType": "str"
            }
        ],
        "for": "0",
        "forType": "num",
        "forUnits": "minutes",
        "override_topic": false,
        "state_location": "payload",
        "override_payload": "msg",
        "entity_location": "data",
        "override_data": "msg",
        "x": 458,
        "y": 160,
        "wires": [
            [
                "7c2d18692aa1bed1"
            ]
        ]
    },
    {
        "id": "9c1423e0dd521756",
        "type": "inject",
        "z": "6ffb7df5f8d96f1a",
        "name": "Every 8 secs",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "8",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 204,
        "y": 160,
        "wires": [
            [
                "3ece2abbca2d3163",
                "25d11d67fe68d5cb"
            ]
        ]
    },
    {
        "id": "b7bb20f11fc2f861",
        "type": "victron-output-custom",
        "z": "6ffb7df5f8d96f1a",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Mode",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Mode",
            "name": "/Settings/DynamicEss/Mode",
            "type": "number"
        },
        "name": "Set DESS Mode",
        "onlyChanges": false,
        "x": 720,
        "y": 304,
        "wires": []
    },
    {
        "id": "ddfbf3bee58c04f8",
        "type": "rbe",
        "z": "6ffb7df5f8d96f1a",
        "name": "Only Changes",
        "func": "rbe",
        "gap": "",
        "start": "",
        "inout": "out",
        "septopics": true,
        "property": "payload",
        "topi": "topic",
        "x": 432,
        "y": 256,
        "wires": [
            [
                "b0920db8c9633aee",
                "1eec9131727da66b"
            ]
        ]
    },
    {
        "id": "b0920db8c9633aee",
        "type": "change",
        "z": "6ffb7df5f8d96f1a",
        "name": "",
        "rules": [
            {
                "t": "change",
                "p": "payload",
                "pt": "msg",
                "from": "0: Off",
                "fromt": "str",
                "to": "0",
                "tot": "num"
            },
            {
                "t": "change",
                "p": "payload",
                "pt": "msg",
                "from": "1: Auto",
                "fromt": "str",
                "to": "1",
                "tot": "num"
            },
            {
                "t": "change",
                "p": "payload",
                "pt": "msg",
                "from": "2: Buy",
                "fromt": "str",
                "to": "2",
                "tot": "num"
            },
            {
                "t": "change",
                "p": "payload",
                "pt": "msg",
                "from": "3: Sell",
                "fromt": "str",
                "to": "3",
                "tot": "num"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 432,
        "y": 304,
        "wires": [
            [
                "b7bb20f11fc2f861"
            ]
        ]
    },
    {
        "id": "2a3f322e14d84e39",
        "type": "api-current-state",
        "z": "6ffb7df5f8d96f1a",
        "name": "Get SoC Min Manual",
        "server": "289362b880855228",
        "version": 3,
        "outputs": 1,
        "halt_if": "",
        "halt_if_type": "str",
        "halt_if_compare": "is",
        "entity_id": "input_select.helper_set_soc_min_manual",
        "state_type": "num",
        "blockInputOverrides": false,
        "outputProperties": [
            {
                "property": "payload",
                "propertyType": "msg",
                "value": "",
                "valueType": "entityState"
            },
            {
                "property": "topic",
                "propertyType": "msg",
                "value": "SoC_Min_Manual",
                "valueType": "str"
            }
        ],
        "for": "0",
        "forType": "num",
        "forUnits": "minutes",
        "override_topic": false,
        "state_location": "payload",
        "override_payload": "msg",
        "entity_location": "data",
        "override_data": "msg",
        "x": 452,
        "y": 352,
        "wires": [
            [
                "5b62222534168e1d"
            ]
        ]
    },
    {
        "id": "74751d57aa8416a0",
        "type": "api-current-state",
        "z": "6ffb7df5f8d96f1a",
        "name": "Get Grid Setpoint Manual",
        "server": "289362b880855228",
        "version": 3,
        "outputs": 1,
        "halt_if": "",
        "halt_if_type": "str",
        "halt_if_compare": "is",
        "entity_id": "input_select.helper_set_grid_setpoint_manual",
        "state_type": "num",
        "blockInputOverrides": false,
        "outputProperties": [
            {
                "property": "payload",
                "propertyType": "msg",
                "value": "",
                "valueType": "entityState"
            },
            {
                "property": "topic",
                "propertyType": "msg",
                "value": "Grid_Setpoint_Manual",
                "valueType": "str"
            }
        ],
        "for": "0",
        "forType": "num",
        "forUnits": "minutes",
        "override_topic": false,
        "state_location": "payload",
        "override_payload": "msg",
        "entity_location": "data",
        "override_data": "msg",
        "x": 462,
        "y": 496,
        "wires": [
            [
                "2480c449d9716e60"
            ]
        ]
    },
    {
        "id": "f260543bfbdde534",
        "type": "delay",
        "z": "6ffb7df5f8d96f1a",
        "name": "",
        "pauseType": "delay",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 188,
        "y": 496,
        "wires": [
            [
                "74751d57aa8416a0",
                "a7e0db73832a5cbb"
            ]
        ]
    },
    {
        "id": "3ece2abbca2d3163",
        "type": "delay",
        "z": "6ffb7df5f8d96f1a",
        "name": "",
        "pauseType": "delay",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 188,
        "y": 352,
        "wires": [
            [
                "2a3f322e14d84e39",
                "f260543bfbdde534"
            ]
        ]
    },
    {
        "id": "1eec9131727da66b",
        "type": "debug",
        "z": "6ffb7df5f8d96f1a",
        "name": "Dess Mode",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 714,
        "y": 256,
        "wires": []
    },
    {
        "id": "82f3a5ff3ee9a153",
        "type": "delay",
        "z": "6ffb7df5f8d96f1a",
        "name": "",
        "pauseType": "delay",
        "timeout": "10",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 192,
        "y": 80,
        "wires": [
            [
                "e3ab7c99eb67e29b"
            ]
        ]
    },
    {
        "id": "80c48da61dd9a224",
        "type": "rbe",
        "z": "6ffb7df5f8d96f1a",
        "name": "Only Changes",
        "func": "rbe",
        "gap": "",
        "start": "",
        "inout": "out",
        "septopics": true,
        "property": "payload",
        "topi": "topic",
        "x": 724,
        "y": 544,
        "wires": [
            [
                "5350a5134998b105"
            ]
        ]
    },
    {
        "id": "f49255091c284006",
        "type": "rbe",
        "z": "6ffb7df5f8d96f1a",
        "name": "Only Changes",
        "func": "rbe",
        "gap": "",
        "start": "",
        "inout": "out",
        "septopics": true,
        "property": "payload",
        "topi": "topic",
        "x": 724,
        "y": 400,
        "wires": [
            [
                "8b3c1e441c7c9f5b"
            ]
        ]
    },
    {
        "id": "30ee8cecfcfc54ae",
        "type": "catch",
        "z": "6ffb7df5f8d96f1a",
        "d": true,
        "name": "",
        "scope": null,
        "uncaught": false,
        "x": 148,
        "y": 1104,
        "wires": [
            [
                "c68dbf16e11e1f59"
            ]
        ]
    },
    {
        "id": "c68dbf16e11e1f59",
        "type": "debug",
        "z": "6ffb7df5f8d96f1a",
        "name": "Catch All",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 156,
        "y": 1168,
        "wires": []
    },
    {
        "id": "5b62222534168e1d",
        "type": "function",
        "z": "6ffb7df5f8d96f1a",
        "name": "Show Payload",
        "func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n            + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n            + (\"0\" + Now.getSeconds()).slice(-2)\n            ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n              text: \"@ \" + NowTime + \": \"\n              + msg.topic + \": \" + msg.payload }\n           );\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 432,
        "y": 400,
        "wires": [
            [
                "f49255091c284006"
            ]
        ]
    },
    {
        "id": "2480c449d9716e60",
        "type": "function",
        "z": "6ffb7df5f8d96f1a",
        "name": "Show Payload",
        "func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n            + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n            + (\"0\" + Now.getSeconds()).slice(-2)\n            ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n              text: \"@ \" + NowTime + \": \"\n              + msg.topic + \": \" + msg.payload }\n           );\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 432,
        "y": 544,
        "wires": [
            [
                "80c48da61dd9a224"
            ]
        ]
    },
    {
        "id": "7c2d18692aa1bed1",
        "type": "function",
        "z": "6ffb7df5f8d96f1a",
        "name": "Show Payload",
        "func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n            + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n            + (\"0\" + Now.getSeconds()).slice(-2)\n            ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n              text: \"@ \" + NowTime + \": \"\n              + msg.topic + \": \" + msg.payload }\n           );\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 432,
        "y": 208,
        "wires": [
            [
                "ddfbf3bee58c04f8"
            ]
        ]
    },
    {
        "id": "e3ab7c99eb67e29b",
        "type": "function",
        "z": "6ffb7df5f8d96f1a",
        "name": "Show Payload",
        "func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n            + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n            + (\"0\" + Now.getSeconds()).slice(-2)\n            ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n              text: \"@ \" + NowTime + \": \"\n              + msg.topic + \": \" + msg.payload }\n           );\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 428,
        "y": 80,
        "wires": [
            [
                "871a1fa47acdfeca"
            ]
        ]
    },
    {
        "id": "7fd873ed597be391",
        "type": "api-current-state",
        "z": "6ffb7df5f8d96f1a",
        "name": "Get EV Charger Mode",
        "server": "289362b880855228",
        "version": 3,
        "outputs": 1,
        "halt_if": "",
        "halt_if_type": "str",
        "halt_if_compare": "is",
        "entity_id": "input_select.helper_set_ev_charger_mode",
        "state_type": "str",
        "blockInputOverrides": false,
        "outputProperties": [
            {
                "property": "payload",
                "propertyType": "msg",
                "value": "",
                "valueType": "entityState"
            },
            {
                "property": "topic",
                "propertyType": "msg",
                "value": "EV_Charger_Mode",
                "valueType": "str"
            }
        ],
        "for": "0",
        "forType": "num",
        "forUnits": "minutes",
        "override_topic": false,
        "state_location": "payload",
        "override_payload": "msg",
        "entity_location": "data",
        "override_data": "msg",
        "x": 452,
        "y": 640,
        "wires": [
            [
                "64bbb78228dcf475"
            ]
        ]
    },
    {
        "id": "a7e0db73832a5cbb",
        "type": "delay",
        "z": "6ffb7df5f8d96f1a",
        "name": "",
        "pauseType": "delay",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 188,
        "y": 640,
        "wires": [
            [
                "7fd873ed597be391",
                "e8dad13d5830f775"
            ]
        ]
    },
    {
        "id": "64bbb78228dcf475",
        "type": "function",
        "z": "6ffb7df5f8d96f1a",
        "name": "Show Payload",
        "func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n            + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n            + (\"0\" + Now.getSeconds()).slice(-2)\n            ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n              text: \"@ \" + NowTime + \": \"\n              + msg.topic + \": \" + msg.payload }\n           );\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 432,
        "y": 688,
        "wires": [
            [
                "f0d3787a7042911b"
            ]
        ]
    },
    {
        "id": "f0d3787a7042911b",
        "type": "rbe",
        "z": "6ffb7df5f8d96f1a",
        "name": "Only Changes",
        "func": "rbe",
        "gap": "",
        "start": "",
        "inout": "out",
        "septopics": true,
        "property": "payload",
        "topi": "topic",
        "x": 724,
        "y": 688,
        "wires": [
            [
                "70c9defc6dd57e24"
            ]
        ]
    },
    {
        "id": "70c9defc6dd57e24",
        "type": "change",
        "z": "6ffb7df5f8d96f1a",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "Victron_MESS.EV_Charger.Target_Mode",
                "pt": "global",
                "to": "payload",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 542,
        "y": 736,
        "wires": [
            []
        ]
    },
    {
        "id": "5350a5134998b105",
        "type": "change",
        "z": "6ffb7df5f8d96f1a",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "Victron_MESS.Manual.Grid_Setpoint",
                "pt": "global",
                "to": "payload",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 532,
        "y": 592,
        "wires": [
            []
        ]
    },
    {
        "id": "8b3c1e441c7c9f5b",
        "type": "change",
        "z": "6ffb7df5f8d96f1a",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "Victron_MESS.Manual.SoC_Min_Perc",
                "pt": "global",
                "to": "payload",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 532,
        "y": 448,
        "wires": [
            []
        ]
    },
    {
        "id": "517fd4c22e8b1a48",
        "type": "api-current-state",
        "z": "6ffb7df5f8d96f1a",
        "name": "Get EV Charger Start_Hour",
        "server": "289362b880855228",
        "version": 3,
        "outputs": 1,
        "halt_if": "",
        "halt_if_type": "str",
        "halt_if_compare": "is",
        "entity_id": "input_number.helper_set_ev_charger_start_hour",
        "state_type": "num",
        "blockInputOverrides": false,
        "outputProperties": [
            {
                "property": "payload",
                "propertyType": "msg",
                "value": "",
                "valueType": "entityState"
            },
            {
                "property": "topic",
                "propertyType": "msg",
                "value": "EV_Charger_Start_Hour",
                "valueType": "str"
            }
        ],
        "for": "0",
        "forType": "num",
        "forUnits": "minutes",
        "override_topic": false,
        "state_location": "payload",
        "override_payload": "msg",
        "entity_location": "data",
        "override_data": "msg",
        "x": 472,
        "y": 784,
        "wires": [
            [
                "63d5058a1ebccda0"
            ]
        ]
    },
    {
        "id": "e8dad13d5830f775",
        "type": "delay",
        "z": "6ffb7df5f8d96f1a",
        "name": "",
        "pauseType": "delay",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 188,
        "y": 784,
        "wires": [
            [
                "517fd4c22e8b1a48",
                "aac958992a766015"
            ]
        ]
    },
    {
        "id": "63d5058a1ebccda0",
        "type": "function",
        "z": "6ffb7df5f8d96f1a",
        "name": "Show Payload",
        "func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n            + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n            + (\"0\" + Now.getSeconds()).slice(-2)\n            ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n              text: \"@ \" + NowTime + \": \"\n              + msg.topic + \": \" + msg.payload }\n           );\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 432,
        "y": 832,
        "wires": [
            [
                "7c2880b04d0d97d3"
            ]
        ]
    },
    {
        "id": "7c2880b04d0d97d3",
        "type": "rbe",
        "z": "6ffb7df5f8d96f1a",
        "name": "Only Changes",
        "func": "rbe",
        "gap": "",
        "start": "",
        "inout": "out",
        "septopics": true,
        "property": "payload",
        "topi": "topic",
        "x": 724,
        "y": 832,
        "wires": [
            [
                "4084d4ad2462fca4"
            ]
        ]
    },
    {
        "id": "4084d4ad2462fca4",
        "type": "change",
        "z": "6ffb7df5f8d96f1a",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "Victron_MESS.EV_Charger.Start_Hour",
                "pt": "global",
                "to": "payload",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 542,
        "y": 880,
        "wires": [
            []
        ]
    },
    {
        "id": "1613f20b17d1c741",
        "type": "api-current-state",
        "z": "6ffb7df5f8d96f1a",
        "name": "Get EV Charger Last_Hour",
        "server": "289362b880855228",
        "version": 3,
        "outputs": 1,
        "halt_if": "",
        "halt_if_type": "str",
        "halt_if_compare": "is",
        "entity_id": "input_number.helper_set_ev_charger_last_hour",
        "state_type": "num",
        "blockInputOverrides": false,
        "outputProperties": [
            {
                "property": "payload",
                "propertyType": "msg",
                "value": "",
                "valueType": "entityState"
            },
            {
                "property": "topic",
                "propertyType": "msg",
                "value": "EV_Charger_Last_Hour",
                "valueType": "str"
            }
        ],
        "for": "0",
        "forType": "num",
        "forUnits": "minutes",
        "override_topic": false,
        "state_location": "payload",
        "override_payload": "msg",
        "entity_location": "data",
        "override_data": "msg",
        "x": 472,
        "y": 928,
        "wires": [
            [
                "9ef7aaf7c9be5cbb"
            ]
        ]
    },
    {
        "id": "aac958992a766015",
        "type": "delay",
        "z": "6ffb7df5f8d96f1a",
        "name": "",
        "pauseType": "delay",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 188,
        "y": 928,
        "wires": [
            [
                "1613f20b17d1c741"
            ]
        ]
    },
    {
        "id": "9ef7aaf7c9be5cbb",
        "type": "function",
        "z": "6ffb7df5f8d96f1a",
        "name": "Show Payload",
        "func": "const Now = new Date()\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n            + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n            + (\"0\" + Now.getSeconds()).slice(-2)\n            ;\n\nnode.status( {fill:\"green\", shape:\"dot\",\n              text: \"@ \" + NowTime + \": \"\n              + msg.topic + \": \" + msg.payload }\n           );\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 432,
        "y": 976,
        "wires": [
            [
                "52a3c1d8cd94a5f0"
            ]
        ]
    },
    {
        "id": "52a3c1d8cd94a5f0",
        "type": "rbe",
        "z": "6ffb7df5f8d96f1a",
        "name": "Only Changes",
        "func": "rbe",
        "gap": "",
        "start": "",
        "inout": "out",
        "septopics": true,
        "property": "payload",
        "topi": "topic",
        "x": 724,
        "y": 976,
        "wires": [
            [
                "65e1b17b04fe96a7"
            ]
        ]
    },
    {
        "id": "65e1b17b04fe96a7",
        "type": "change",
        "z": "6ffb7df5f8d96f1a",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "Victron_MESS.EV_Charger.Last_Hour",
                "pt": "global",
                "to": "payload",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 532,
        "y": 1024,
        "wires": [
            []
        ]
    },
    {
        "id": "289362b880855228",
        "type": "server",
        "name": "Home Assistant Api",
        "version": 5,
        "addon": false,
        "rejectUnauthorizedCerts": true,
        "ha_boolean": "y|yes|true|on|home|open",
        "connectionDelay": true,
        "cacheJson": true,
        "heartbeat": false,
        "heartbeatInterval": "30",
        "areaSelector": "friendlyName",
        "deviceSelector": "friendlyName",
        "entitySelector": "friendlyName",
        "statusSeparator": ": ",
        "statusYear": "hidden",
        "statusMonth": "short",
        "statusDay": "numeric",
        "statusHourCycle": "default",
        "statusTimeFormat": "h:m",
        "enableGlobalContextStore": false
    }
]
Afbeeldingslocatie: https://tweakers.net/i/WHwiY-tTj-z_5mBjcR7oeHTw28k=/full-fit-in/4920x3264/filters:max_bytes(3145728):no_upscale():strip_icc():fill(white):strip_exif()/f/image/LnDDmR9xBaAgyKCelfmKNv9E.jpg?f=user_large

[ Voor 214% gewijzigd door MJ de Bruijn op 14-05-2024 15:13 ]


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
Gereserveerd.

[ Voor 183% gewijzigd door MJ de Bruijn op 14-05-2024 14:51 ]


  • V_ger
  • Registratie: September 2009
  • Laatst online: 14:22
Zo te zien mooi stukje huisvlijt. Ik ben benieuwd naar het verschil in gedrag tov van DESS. Ik gebruik zelf DESS ook niet, met name om rare laad/ontlaad beslissingen (toch iig in het begin) en de extreme focus op de SOC ipv de prijs.

Hoe gaat jouw systeem om met een niet ondersteunde EV die ik inplug of een warmtepomp die WW gaat maken?

Ik regel het nu semi automatisch.
- Kale prijs < 0 PV beperken om invloeden te vermijden (ik kan niet altijd al mijn vermogen in de accus kwijt)
- Prijs inclusief belasting <0 PV uit en inkopen.
- EV eigenlijk altijd laden vanuit het net. Ik maak zelf een schedule aan voor de goedkope uren op de dagen dat ik wil laden. Als EV charger actief dan Victron gris set-point gelijk aan laadpaal vermogen.
- Ik tune zelf een beetje mijn prijs waarboven ik verkoop zodat ik nu sochtends een lege accu heb. In de winter vaak andersom. Maar het is wel elke dag werk om te kijken hoeveel uur ik kan ontladen. Dus niet ideaal.

Ivm geluid beperk ik savonds de max inverter power en laadstroom.

[ Voor 50% gewijzigd door V_ger op 14-05-2024 16:33 ]

ZP, Gasloos sinds 2017, Nibe F1155, 12.4kWp 30° O/W + 4.4kWp 0°, 3x Victron MP2-5000 + 60 kWh


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
V_ger schreef op dinsdag 14 mei 2024 @ 16:27:
Zo te zien mooi stukje huisvlijt. Ik ben benieuwd naar het verschil in gedrag tov van DESS.
Het belangrijkste verschil is dat het MESS iedere tien seconden opnieuw een strategie berekent. Daarmee wordt direct ingespeeld op verschillen met zonne-prognose en consumptie-prognose.
Het systeem kan, afhankelijk van de actuele SoC, eerder of juist later beginnen met laden van het Grid of ontladen voor verkoop. Als een strategie reeds begonnen is kan deze worden aangepast als het weer verandert.

Bij ontladen voor verkoop houdt het systeem rekening met te verwachten eigen gebruik. Als dit - in de loop van een nacht - meevalt zal een eventueel surplus bij een eerstvolgende gelegenheid alsnog worden verkocht.

Bij laden bij negatieve prijzen of nul-prijzen zal het systeem wachten tot de meest negatieve uren. Eventuele zon in de ochtend wordt eerst nog aan het grid geleverd tegen vergoeding. Maar, als de zonne-opbrengst tegenvalt ten opzichte van de prognose zal het laden worden aangepast.
Hoe gaat jouw systeem om met een niet ondersteunde EV die ik inplug
Voor mij is de samenwerking met de EV lader heel belangrijk om EV en Victron gelijktijdig optimaal te kunnen laden. Ik zie regelmatig 3 x 24 Amp op de meter. Dat is het ingestelde maximum.
In principe zou het zo met een ander systeem kunnen samenwerken, mits dat systeem maar real-time kan worden aangestuurd om met een lager maximum te laden.
of een warmtepomp die WW gaat maken?
Naar mijn idee zou hier hetzelfde gelden. Wel moeten de verbruiks-gegevens real-time kunnen worden ingelezen. Afhankelijk van de warmtepomp kun je óf dat systeem bijsturen óf het Victron systeem bijsturen, of beide.

  • V_ger
  • Registratie: September 2009
  • Laatst online: 14:22
Je draait het op de cerbo? Hoe is de cpu belasting? Die is hier namelijk al best hoog met 3x serial battery...

ZP, Gasloos sinds 2017, Nibe F1155, 12.4kWp 30° O/W + 4.4kWp 0°, 3x Victron MP2-5000 + 60 kWh


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
V_ger schreef op dinsdag 14 mei 2024 @ 16:44:
Je draait het op de cerbo? Hoe is de cpu belasting? Die is hier namelijk al best hoog met 3x serial battery...
Ik heb daar niet eerder naar gekeken. Waar lees jij dat af?

  • V_ger
  • Registratie: September 2009
  • Laatst online: 14:22
Ik merk het vooral aan de melding in VRM dat realtime updates zijn uitgeschakeld ivm hoge load. Anders kan je het via de command line op de cerbo inzien via "top". Of "htop". Die laatste zal je wel nog moeten installeren.

ZP, Gasloos sinds 2017, Nibe F1155, 12.4kWp 30° O/W + 4.4kWp 0°, 3x Victron MP2-5000 + 60 kWh


  • Samoerai
  • Registratie: Maart 2000
  • Laatst online: 26-11 13:41
Misschien een domme vraag. Maar wat is het doel van jouw implementatie? Zoveel mogelijk geld proberen te verdienen, zoveel mogelijk 0 op de meter of ?

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
V_ger schreef op dinsdag 14 mei 2024 @ 17:06:
Ik merk het vooral aan de melding in VRM dat realtime updates zijn uitgeschakeld ivm hoge load. Anders kan je het via de command line op de cerbo inzien via "top". Of "htop". Die laatste zal je wel nog moeten installeren.
Ik vond het een interessante vraag.
Ik heb in Node Red een nieuwe bibliotheek geïnstalleerd: "node-red-contrib-os (node)".
Deze geeft prima informatie.

Je vraag:
CPU belasting: 4.42% (1 minuut gemiddelde); 4.24% (5 minuten gemiddelde); 3.7% (15 minuten gemiddelde).
Geheugengebruik: 39,8%.

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
Samoerai schreef op dinsdag 14 mei 2024 @ 17:07:
Misschien een domme vraag. Maar wat is het doel van jouw implementatie? Zoveel mogelijk geld proberen te verdienen, zoveel mogelijk 0 op de meter of ?
Geen domme vraag, ik krijg deze zeer regelmatig.
Mijn doel is zo "optimaal" mogelijk gebruik maken van mijn batterij-systeem.
Prioriteit 1: Backup systeem bij Grid uitval.
Prioriteit 2: Mijn zonne-energie zo efficiënt mogelijk gebruiken. Goedkoop laden, bij hoge prijzen (eerst) aan het net leveren.
Prioriteit 3: Nul op de meter.
Prioriteit 4: Handelen met energie.

Geld verdienen is niet aan de orde. Het is een dure hobby en qua tijdsbesteding zijn er veel activiteiten waar je meer verdient dan met deze dingetjes.

  • RikHa
  • Registratie: September 2022
  • Laatst online: 14-12 12:32
Ik ben onder de indruk! Petje af hoor.

23 kWp, 80 kWh, 11 kW laden, 17 kW ontladen. Victron VRM


  • Activate
  • Registratie: November 2007
  • Laatst online: 12:02
Poeh knap stukje werk!

Ben helemaal niet thuis in Node Red heb het gedownload in HA maar nooit gebruikt.

Is het ook aan te passen als je geen EV charger hebt?

Kunnen de prioriteiten ook anders ingesteld worden?

Zijn het betaalde abonnementen bij Forecast en Day ahead?

[ Voor 12% gewijzigd door Activate op 16-05-2024 15:56 ]


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
Dank. Dat vind ik leuk om te horen.
Ben helemaal niet thuis in Node Red heb het gedownload in HA maar nooit gebruikt.
Als je al bekend bent met 'iets' van programmeren is Node Red slechts een andere variant. Het is voornamelijk invoer en uitvoer voor de 'echte' logica die je in JavaScript functies plaatst.
Is het ook aan te passen als je geen EV charger hebt?
Jazeker. Als de auto niet is aangesloten doet deze 'flow' ook niets.
Kunnen de prioriteiten ook anders ingesteld worden?
Ja, dat kan door wijzigingen in het 'brein' programma. Dit is niet zo maar instelbaar omdat ik een bepaald doel voor ogen had.
Maar, bijvoorbeeld de grens wanneer tarieven 'laag' zijn of 'hoog' zijn is simpel in de stellen. Ook het prijsverschil tussen verkoop en inkoop om te handelen is in te stellen.
Zijn het betaalde abonnementen bij Forecast en Day ahead?
Die zijn kosteloos.

  • Activate
  • Registratie: November 2007
  • Laatst online: 12:02
Klinkt interessant.

Ik heb dus ook een deel op Oost West en 8 panelen op bijna zuid...

Ik zie er nog tegenop om het allemaal te integreren, maar het is zeker interessant!
Hier alles op 1 Fase met een SEH 5000 en een Cerbo MkII en een Multiplus 5000 en een accu van 15 Kwh

Ik zou de prioriteiten voor mij iets anders zetten , heb de afgelopen 2 jaar nog geen geen griduitval gehad, dus voor mij iets minder interessant, en 3 en 4 zou ik willen omdraaien.

[ Voor 46% gewijzigd door Activate op 16-05-2024 23:37 ]


  • antoine sellies
  • Registratie: September 2022
  • Laatst online: 04-05 14:01
Goedenavond,

Iemand en goedkope website voor een zelfbouw set

Alle info is welkom

  • SHIFTER [NL]
  • Registratie: November 2010
  • Laatst online: 13:05
Heel gaaf om te zien wat je gemaakt hebt!! _/-\o_

Ben druk bezig met het bouwen (hardware kant) aan mij eigen ESS systeem. Dit zal zeker als inspiratie dienen zodra ik met de software kant bezig ga.

3x Victron MP2-5000 | 32 kWh thuisaccu | 7.230 Wp Zonnepanelen | Gasloos | WP Boiler | Lucht-Lucht WP


  • Niek_
  • Registratie: Februari 2002
  • Laatst online: 10:19
Ik ga mij hier toch maar eens in verdiepen. Enige onafhankelijkheid van bijvoorbeeld het laden van energieprijzen vanuit 1 bron of aansturing van DESS is wellicht handig gezien het functioneren van de afgelopen dagen :)
Dan gelijk aan de slag met een andere ergernis, de laadpaal die geen vermogen krijgt als de Multiplussen maximaal aan het laden zijn omdat loadbalancing dan aangeeft dat er onvoldoende ruimte op de hoofdzekering is. Als ik in de Cerbo kan aangeven dat de laadpaal ingeplugd is en vermogen vraagt dan kan die de Multiplussen wat minder hard laten laden zodat er voldoende ruimte op de hoofdzekeringen is om het laden van de auto te starten. Dat moet als ik dit zo allemaal lees ook in Node Red kunnen.

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
Leuk als je er iets aan zou hebben.
Ik heb inmiddels nog wel aanpassingen gedaan, dus als je een specifiek element aanpakt, laat het even weten. Dan plaats ik een actuele stand.
Ook het MESS is afhankelijk van externe bronnen voor Solar en prijzen. Ik heb wel een e-mail waarschuwing toegevoegd als deze niet op tijd worden ontvangen.
Voor de prijzen kun je dan de volgende dag met de hand invullen of (iets makkelijker) uit een menu van Home Assistant kiezen.
Voor Solar werk ik aan een back-up via een Australische website. Dat is nog niet af.

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
Voor wat betreft de aansturing van het laden van je auto... dit onderdeel kun je waarschijnlijk ook apart implementeren. Ik zie geen reden waarom dit niet ook met DESS zou kunnen samenwerken.
Je moet wel het verbruik van je laadstation kunnen uitlezen én instellen.

  • Edwin de Jonge
  • Registratie: Februari 2007
  • Laatst online: 19-02 15:37
Leuk dat je kennis en kunde wil delen. Ik zit zelf ook met een solaredge installatie en een 3 fase aansluiting. Hoe heb je de ET340 aangesloten. Kan je deze direct via de RS485 Modbus-poort aansluiten op de omvormer? Zou je het zignee gedeelte ook kunnen gebruiken indien de omvormer ook zigbee heeft.

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
Edwin de Jonge schreef op woensdag 18 september 2024 @ 10:29:
Leuk dat je kennis en kunde wil delen. Ik zit zelf ook met een solaredge installatie en een 3 fase aansluiting. Hoe heb je de ET340 aangesloten.
Ik heb de kabel van de SolarEdge aangesloten op de ingang van de ET340 en de uitgang van de ET340 naar de verdeler in de groepenkast.
Is dat wat je bedoelt?
Kan je deze direct via de RS485 Modbus-poort aansluiten op de omvormer?
Ik heb de RS485 kabel van de ET340 aangesloten op een Cerbo. Ik heb geen koppeling tussen SolarEdge en de Cerbo.
Zou je het zigbee gedeelte ook kunnen gebruiken indien de omvormer ook zigbee heeft.
Ik weet wat Zigbee is, maar ik heb geen kennis over zo'n koppeling.
Ik zou zelf nooit voor een "extra" protocol kiezen of voor een andere draadloze oplossing.

  • Eric_van_Rijn
  • Registratie: December 2024
  • Laatst online: 22-12-2024
Heel knap stukje werk. Ik heb met een simpele DESS aan de gang gekregen.
Ben me nu langzaam in de NoteRed wereld aan het verdiepen, leer veel val je omschrijvingen
:)

  • Kecin
  • Registratie: Juli 2004
  • Niet online

Kecin

Je keek.

Super opzet. Ik zie in de TS staan dat er minimaal 3 batterijen gebruikt moeten worden. Mijn samenstelling zou een systeem van 3 Fase Multiplussen zijn en 2 batterijen voor een totaal van 32,2 kWh. Gezien die allemaal op dezelfde dc rail zitten ga ik ervanuit dat het enige probleem waar ik tegen aan zou lopen het maximale opladen en ontladen zal zijn?

I'm not a number, I'm a free man! Geld over? Check m'n V&A


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
Kecin schreef op zaterdag 21 december 2024 @ 08:33:
Super opzet. Ik zie in de TS staan dat er minimaal 3 batterijen gebruikt moeten worden. Mijn samenstelling zou een systeem van 3 Fase Multiplussen zijn en 2 batterijen voor een totaal van 32,2 kWh. Gezien die allemaal op dezelfde dc rail zitten ga ik ervanuit dat het enige probleem waar ik tegen aan zou lopen het maximale opladen en ontladen zal zijn?
Zo te zien vraag je me of ik het met je eens ben.
Daarvoor geef je veel te weinig informatie.
Welk type multiplus? Wat voor dc rail? Lynx? Wat voor batterijen? Twee stuks voor 32 kWh? Dat is knap.
Stel je vraag beter in "het grote Victron ontwerp topic".

  • Cereal
  • Registratie: Maart 2001
  • Laatst online: 12:57
Hey @MJ de Bruijn , ik ben m'n eerste stapjes op NodeRed gebied aan het zetten, heb ook geen andere programmeer ervaring.

Ik wil je EV implementatie overnemen, ik heb NodeRed op Cerbo GX draaien, doel is om de auto altijd vanuit het net te laten laden. Op HA is m'n Victron plugin kapot gegaan, zou het graag op de cerbo draaien.
Victron EV charger zit op AC Out, net als de rest van m'n huis.

Ik krijg een foutmelding bij het copy-pasten van je NodeRed flow:
Afbeeldingslocatie: https://tweakers.net/i/QQHfDmkGn8BmDnFJBmT0VsY8n2o=/800x/filters:strip_exif()/f/image/Ko9vIrhocStBmo17uKFVGHqW.png?f=fotoalbum_large

Dus ik loop gelijk vast :), help..

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
Hallo Cereal,

Leuk dat je dit ontwerp wilt nabouwen / kopiëren,

Helaas voegt de browser opmaak-codes tussen om de tekst leesbaar te maken. In de originele JSON zijn veel minder regel-eindes aanwezig.

De fout die je aantreft komt mogelijk doordat ik de Javascript-tekst van de functie "Calc EV-Charge Settings" separaat heb geplaatst. Dit was nodig door de maximale toegestane lengte van items in het forum.

In je scherm-afdruk zie je de tekst "// zie separate post voor JavaScript tekst" en dan een blanco regel.
Verwijder die blanco regel en zorg dat de afsluitende dubbele quote direct achter het woord "tekst" komt te staan. Probeer het dan nog een keer.

Als dit werkt moet je de functie-node openen en de tekst van het bijbehorende item plakken.

Helaas kunnen opmaak-codes van de website of de browser dit soms moeilijk maken.

Je zult ook tegen hobbels aanlopen dat entities bij jou verschillen van die bij mij.
Je moet dus meerdere nodes aanpassen.

Succes! Ik ben benieuwd...

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
Nog iets...
Je schrijft dat je nieuw bent met Node Red en geen programmeer-ervaring hebt.
Dan is "deze" toepassing best een kluif (understatement).

Om meer te leren van programmeren in Node Red zou je de volgende bijdrage kunnen volgen:
Victron, Monitoring, Waarschuwingen en Alarmen

Dat is iets anders dan je nu wilt bereiken, maar diverse technieken komen daar aan de orde.

  • Cereal
  • Registratie: Maart 2001
  • Laatst online: 12:57
Dank, met ChatGPT heb ik al het e.e.a. aan de praat kunnen krijgen, een basis "If status is charging, then AC Powerpoint 11kw" moet me wel lukken een dezer dagen :)

Mooi topic ook over monitoring/waarschuwingen/alarmen, ik ga er de komende tijd eens lekker voor zitten.

  • blokl
  • Registratie: April 2009
  • Laatst online: 27-10 16:02
Beste @MJ de Bruijn ,

Zo te zien is het een mooi stukje werk. Ik heb ongeveer dezelfde Victron configuratie en ben ook voorstander van het zelf aansturen van de Dess omgeving.


Ik kom er alleen niet achter hoe de Homeassistant koppelingen werken. Misschien dat je dat nog eens kan uitleggen?

Met vriendelijke groet,

Leo

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
Ik heb de ontwikkelingen van die koppelingen niet exact gevolgd. Wel weet ik dat er "gerommeld" is met versies, forks of zo iets.
In Node Red, kies rechtsboven het "hamburger-menu". Dat zijn de drie liggende streepjes boven elkaar.
Kies dan de optie "Manage Palette".
Als je bij tabblad "Nodes" al iets van Victron hebt geïnstalleerd, verwijder dat dan eerst.
Voorts kies je bij tabblad "Install" --> zoek Victron.
Selecteer: @victronenergy/node-red-contrib-victron
Ik heb (nu) versie 1.6.11.
Kies voor install.
Als dit succesvol was zul je links de Victron Nodes aantreffen.

  • blokl
  • Registratie: April 2009
  • Laatst online: 27-10 16:02
Ik heb ze opnieuw geïnstalleerd, maar kom nog niet echt verder.
Ik zie een "victron energie client" maar voor de rest krijg ik "Unable to access the service endpoint:/endpoint/victron/services/?????"

Dus ik denk dat ik nog wat mis?

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
blokl schreef op maandag 17 maart 2025 @ 16:38:
Ik heb ze opnieuw geïnstalleerd, maar kom nog niet echt verder.
Ik zie een "victron energie client" maar voor de rest krijg ik "Unable to access the service endpoint:/endpoint/victron/services/?????"

Dus ik denk dat ik nog wat mis?
Geef svp aan waar je de "Victron Energie Client" ziet. Ik herken dit niet.
Maak een schermafdruk.

Ook de "Unable to access the service endpoint:/endpoint/victron/services/?????"
herken ik niet van eigen ervaring.

Beschrijf stap voor stap wat je voorafgaand doet en maak weer schermafdrukken.

  • blokl
  • Registratie: April 2009
  • Laatst online: 27-10 16:02
I intalled the node red module you mentioned. If this is the only one I needed then is only the next screen view important.

You don’t make a connection from home assistant to th Victron with a modbus or something or the module from hac?

Regards,

Leo

Afbeeldingslocatie: https://tweakers.net/i/v0nmgcCDNiqZOZESUHDCy8zzn6g=/800x/filters:strip_icc():strip_exif()/f/image/05bw6pDQqmwoly4fqSYEwofe.jpg?f=fotoalbum_large

[ Voor 234% gewijzigd door teacher op 13-10-2025 13:26 ]


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
blokl schreef op dinsdag 18 maart 2025 @ 21:56:
I intalled the node red module you mentioned. If this is the only one I needed then is only the next screen view important.
I don't have a DC source available, so I can't reproduce your Node.
Please try.
In Node Red, open the config tab in the right upper corner. The steering-wheel symbol.
Under 'On All Flows', do you (still) see the 'Victron Energy Client' Config-Node?
Are both options activated?
You don’t make a connection from home assistant to th Victron with a modbus or something or the module from hac?
I don't use Modbus, however, this might be working somewhere on the background.

You say, the only Node you need is the DC source Node. Perhaps, this is not available and/or configured in your Victron setup.

Please try (for example) the VE.Bus System Node for reading.
Select any relevant option of you choosing. Send the output to a debug Node.
If this works, Node Red does communicate. Perhaps you have to experiment with your DC configuration in Victron.
If this doesn't work, then I don't know either.

  • blokl
  • Registratie: April 2009
  • Laatst online: 27-10 16:02
Can you publish you node red tab from home assistant?
I think it wil help me a lot


Thx

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
Perhaps I do understand now what your situation is.
Are you working within Home Assistant with Node Red?
I assumed you were working with Node Red from within the Victron environment itself.

From Home Assistant you cannot reach the Victron system.
You have to install Node Red in the Cerbo of your Victron.

Is this your situation?

  • John245
  • Registratie: Januari 2020
  • Laatst online: 13:02
MJ de Bruijn schreef op woensdag 19 maart 2025 @ 15:55:
Perhaps I do understand now what your situation is.
Are you working within Home Assistant with Node Red?
I assumed you were working with Node Red from within the Victron environment itself.

From Home Assistant you cannot reach the Victron system.
You have to install Node Red in the Cerbo of your Victron.

Is this your situation?
@blokl In case you don't want to use the preferred method you have to follow the manual install.

https://flows.nodered.org.../node-red-contrib-victron

Tibber; 3-fase Victron ESS, 38,4 kWh opslag; gasloos sinds 2013; Nibe F1245-10PC; SAP; HomeSeer4; Proxmox 8


  • blokl
  • Registratie: April 2009
  • Laatst online: 27-10 16:02
I have installed Node Red in the Cerbo and in HomeAssistant.
I asumed your setup is to send and receive data from the Cerbo to Node Red in HomeAssistant.

So now I untherstand your Node Red dashboard is also on the Cerbo?

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
blokl schreef op woensdag 19 maart 2025 @ 17:02:
I have installed Node Red in the Cerbo and in HomeAssistant.
I asumed your setup is to send and receive data from the Cerbo to Node Red in HomeAssistant.

So now I untherstand your Node Red dashboard is also on the Cerbo?
Yes indeed!
A lot of misunderstanding, but, in the end, the matter became clear.
I hope you will be more succesful when you try working in Node Red on the Cerbo.
Good luck.

  • sennevb
  • Registratie: Januari 2011
  • Laatst online: 16-12 12:25
Ik ben bezig met mijn installatie ook samen te stellen.
Ik wou DESS gaan gebruiken, heeft jou MESS een beter rendement omdat hij beter inspeelt op veranderingen?

Zoja, wat is je geschatte rendementswinst?
Ik wil er wel mee gaan grasduinen, maar als het teveel geklooi is voor maar 1% meer rendement ga ik dat natuurlijk niet op mijn nek halen

Alvast dank voor je reactie @MJ de Bruijn

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
sennevb schreef op maandag 7 april 2025 @ 19:29:
Zoja, wat is je geschatte rendementswinst?
Ik wil er wel mee gaan grasduinen, maar als het teveel geklooi is voor maar 1% meer rendement ga ik dat natuurlijk niet op mijn nek halen
Ik heb geen idee over het verschil in rendement.
Er is wel een verschil in uitgangspunt.
DESS zoekt (voor zover ik weet) het hoogste financiële rendement, ook als de batterij leeg raakt of leeg blijft.
MESS probeert iedere dag de batterij volledig vol te laden, liefst door zon, anders door Grid, per saldo zo goedkoop mogelijk. Daarnaast zoekt MESS naar opties om iets te verdienen.

En over "op je nek halen": het DESS is klaar en doet alles.
Mijn MESS is een eigen bouw en kan alleen maar als inspiratie dienen voor iemand die ook zelf iets wil bouwen.

  • sennevb
  • Registratie: Januari 2011
  • Laatst online: 16-12 12:25
thanks voor de toelichting! ik wil eigenlijk een combi van de 2: in de zomer is er overschot, deze dan verkopen aan de hoogst mogelijke prijs. in de winter is er tekort of geen zon, dan laden aan de laagst mogelijke prijs.

Ik bekijk het even!

  • luigi87
  • Registratie: Juni 2009
  • Laatst online: 08:50

luigi87

Domotica Fanaticus

Je hebt van je beschrijving wel werk gemaakt.
Top _/-\o_

Ik ga hier eens de tijd voor nemen, jouw uitgangpunten zijn gelijk aan die van mij.
Dus ga veel van je overnemen en leren ;)

- leeg -


  • SHIFTER [NL]
  • Registratie: November 2010
  • Laatst online: 13:05
Heel interessant dit! Wil hier ook eens mee gaan stoeien.

Zou je de laatste stand JSON van jouw Node-Red kunnen delen?

3x Victron MP2-5000 | 32 kWh thuisaccu | 7.230 Wp Zonnepanelen | Gasloos | WP Boiler | Lucht-Lucht WP


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
SHIFTER [NL] schreef op maandag 13 oktober 2025 @ 13:09:
Zou je de laatste stand JSON van jouw Node-Red kunnen delen?
Dat gaat niet heel gemakkelijk.
Het geheel bestaat uit meerdere tabbladen. Ieder tabblad bevat de logica voor een bepaald aspect, initialisaties, entiteit-statussen, solar, dayahead-prijzen, de 'planning', auto-laadstation met overbelasting-bescherming etc.
Een JSON van een tabblad is veel te groot om in een tweakers-post te passen. Daarom heb ik eerder alles in stukjes gebroken. En verder ligt alles nu 'overhoop' omdat ik aan het aanpassen ben van uur-prijzen naar kwartier-prijzen.
Kortom, zomaar even delen is niet aan de orde.
Maar, als je aangeeft welk aspect je wilt aanpakken, dan kan ik zien wat ik daarvoor geschikt kan maken.

  • gerwin16
  • Registratie: November 2004
  • Laatst online: 09:23
Dank voor het delen van jouw Victron MESS setup.
Ik ben bezig met mijn eigen MESS implementatie met inspiratie bron jouw Victron MESS setup. Ik heb het nu draaiende, maar was benieuwd wat jij ondertussen nog aangepast hebt aan de setup. Zou je dat kunnen delen?

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
gerwin16 schreef op donderdag 27 november 2025 @ 21:55:
Ik ben bezig met mijn eigen MESS implementatie met inspiratie bron jouw Victron MESS setup.
Leuk dat je iets hebt aan mijn beschrijving van Mijn ESS (MESS).
Er is inmiddels een heleboel aan gewijzigd en ik ben daar nog niet klaar mee.
Het belangrijkste is de overschakeling van uur-prijzen naar kwartier-prijzen. Dit vergt een heel andere benadering hoe je naar (prijzen in) de toekomst kijkt.
Als ik dit gereed heb, ben ik voornemens om een beschrijving te maken van de nieuwe methodieken.
Nog even geduld graag.

  • SHIFTER [NL]
  • Registratie: November 2010
  • Laatst online: 13:05
Ben heel benieuwd! Ben ook nog bezig met het opzetten van een systeem aan de hand van jouw voorbeeld :D

3x Victron MP2-5000 | 32 kWh thuisaccu | 7.230 Wp Zonnepanelen | Gasloos | WP Boiler | Lucht-Lucht WP


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57

Kwartierprijzen

Sinds 1 oktober 2025 berekenen dynamische energie-leveranciers hun prijzen per kwartier in plaats van per uur. Dit heeft grote invloed op de planning van laden, ontladen, actief verkopen, extra inkopen etc.
In principe zijn er voor het instellen van de Vistron vier opties:
1. Gebruik maken van de balans van zonne-energie.
2. Batterij laten slapen.
3. Actief/geforceerd verkopen.
4. Laden/inkopen van Grid.

1. Gebruik maken van de balans van zonne-energie.
Dit is het standaard gedrag van ESS. De batterij wordt opgeladen als er overschot aan zonne-energie is en de energie uit de batterij wordt gebruikt als er niet voldoende zonne-energie is, typisch 's nachts. Dit zal typisch in de zomer van toepassing zijn. De SoC (State-of-charge) van de batterij zal fluctueren tussen vol en (bijna) leeg.

2. Batterij laten slapen.
Als de elektriciteitsprijs langer achtereen (ongeveer) gelijk is, is het niet zinvol om de batterij te gebruiken. De conversie-verliezen van laden en ontladen zullen dan groter zijn dan de kleine verschillen tussen prijzen in opvolgende perioden. In zo'n geval is het beter om de batterij te laten 'slapen' en het Grid rechtstreeks te gebruiken.

3. Actief/geforceerd verkopen.
Indien de capaciteit van de batterij het toelaat kun je een overschot aan energie terugleveren aan het stroomnet. Bij een dynamisch contract krijg je dan in principe de actuele stroomprijs terugbetaald, vooralsnog inclusief belasting. Wanneer je dan later de batterij weer oplaadt met een lager tarief kun je aldus enige winst behalen. Hierbij moet je wel rekening houden met conversie verliezen van circa 20 tot 30%.

4. Laden/inkopen van Grid.
Als een periode van hoog tarief nadert en de batterij is (nog) niet vol kun je de batterij extra vullen vanaf het Grid. Dit kan zinvol zijn als het aankomende verkoop-tarief hoog genoeg is en de batterij ruimte heeft om extra voorraad op te slaan. Ook hier is relevant dat het verschil tussen nu inkopen en later verkopen groot genoeg is om, rekening houdend met conversie-verliezen, nog enige winst te halen.
Een andere reden om (extra) van het Grid te laden is om (typisch in de winterperiode) de batterij dagelijks tot 100% te laden.

Bij uurtarieven was het relatief eenvoudig om een keuze te maken uit deze opties. Je hoefde slechts het tarief van het eerstvolgende uur of daarop volgende uur te bezien om een keuze te maken uit deze opties.

Bij kwartiertarieven moet je - in aantallen - veel meer opties bezien. Ieder kwartier krijgt een eigen keuze van laden/ontladen/slapen etc. Om vast te stellen of een optie rendabel is moet je een groot aantal kwartieren in de toekomst in beschouwing nemen. Een verkoop-kwartier (hoge prijs) wordt daarbij gekoppeld aan een compensatie-kwartier (lage prijs). Daarbij moet je opletten dat je een 'compensatie-kwartier' niet dubbel toekent aan twee of meer verkoop-kwartieren.


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57

Tibber en de Tibber Api


Net zoals ENTSO-E biedt Tibber een optie om de tarieven automatisch op te halen. Dit gaat met een API, een Application Program Interface.

In Node Red van Victron kun je een uitbreiding installeren.
• Klik rechtsboven op het 'hamburger' menu, zo worden de drie liggende streepjes genoemd.
• Kies de optie Manage Palette. Alternatief, toets <alt> + <p>.
• Er verschijnt een scherm User Settings. Kies het tabblad Install en zoek naar Tibber.
• Selecteer node-red-contrib-tibber-api en klik op de keuze Install.

In het palette-menu links zijn nu de Tibber API Nodes beschikbaar.

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

Ik gebruik alleen de Tibber - data node. Wanneer je deze kiest moet je eerst je toegang tot Tibber instellen.
Twee zaken zijn van belang: Je Home ID en een Access Token. Deze kun je ophalen met je Tibber account.

Lees allereerst https://developer.tibber.com/docs/guides/calling-api

Daar staat een verwijzing naar de site voor het aanvragen van een token.

Wanneer je inlogt wordt jouw persoonlijke Access Token getoond. Kopieer deze en bewaar deze op een geschikte (veilige!) plaats zodat je deze later kunt vinden en kopiëren.

Afbeeldingslocatie: https://tweakers.net/i/dHG5tML9oV9UAkLwBDE5w7EEo6g=/800x/filters:strip_icc():strip_exif()/f/image/hD2qhSCsDTNG70OxVEAwEond.jpg?f=fotoalbum_large

Je Home ID kun je eveneens online opzoeken via https://developer.tibber.com/explorer

Afbeeldingslocatie: https://tweakers.net/i/eoKpizDynCKO5XMazTtI0G8ELhA=/800x/filters:strip_icc():strip_exif()/f/image/TwYhrFRH6rB2tqckfqZOLl2A.jpg?f=fotoalbum_large

1. Ga naar de genoemde website en login met je Tibber account-gegevens.
2. Bij de optie Load an example kies
3. Homes
4. In het query-voorbeeld links, voeg een regel toe met de identifier id
5. Klik op de paarse knop
6. In het resultaat verschijnt de Home id.

Kopieer deze en bewaar deze op een geschikte (veilige) plaats.

Je kunt nu een eenvoudige test-flow maken:

Afbeeldingslocatie: https://tweakers.net/i/hmVcT3HYMbHXmU-YzARUhXuDMEo=/800x/filters:strip_exif()/f/image/SjSKHtDASwheNOHkw2PZiMmn.png?f=fotoalbum_large

Ik gebruik hiervoor een tibber - data node:

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

Via deze node moet je de configuratie node instellen:

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

Als dit succesvol is gelukt kun je via de Trigger node de API aanroepen.
Als het goed is verschijnt dan in het debug-scherm rechts je eerste resultaat.

Afbeeldingslocatie: https://tweakers.net/i/QaJx4Vp5vbCXMmtmQ3R9xC3yhIg=/x800/filters:strip_icc():strip_exif()/f/image/a6a7OroQ1039yVU6Onz3iY7f.jpg?f=fotoalbum_large

In een volgende bijdrage zal ik beschrijven hoe je de gegevens dagelijks kunt ophalen.


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57

Tibber - Dagelijks Ophalen Kwartierprijzen

Ik ben overgestapt naar een dynamisch elektriciteits-contract bij Tibber. Daarmee heb ik ook toegang tot de API met de dagelijkse prijzen van Tibber. Zie: Tibber en de Tibber Api

Alle gegevens van het MESS programma, en dus ook de Tibber data, worden bewaard in een geheugengebied Context Data --> Global
Afbeeldingslocatie: https://tweakers.net/i/HJ8MbxIeymuS12KOUZlLaQ0ZRVw=/x800/filters:strip_icc():strip_exif()/f/image/VjlA6BB7yEImosuOrHlZVKIc.jpg?f=fotoalbum_large

Om de gegevens van Tibber periodiek op te halen heb ik een Node Red Flow gemaakt.
Afbeeldingslocatie: https://tweakers.net/i/afhDKSNA3bsVFP--ZWetga-8YUQ=/x800/filters:strip_icc():strip_exif()/f/image/D9RHerghVVUWbkaIY6uHa37I.jpg?f=fotoalbum_large

Omdat het aantal nodes vrij groot is zal ik niet van iedere node een schermafdruk plaatsen maar alleen de tekstuele inhoud vermelden. Ik volg de Flow-afbeelding van boven naar onder.

Change NodeDelete All TibberIedere nacht om tien minuten na middernacht verwijder ik alle aanwezige gegevens. De tien minuten 'wachttijd' zijn bedoeld om eventuele drukte bij Tibber te laten passeren om de kans op timeouts te verminderen. Ik gebruik hiervoor een cron-job, maar ook een Inject node zou volstaan.
Delete
global.Victron_MESS.LastRun.Tibber
Delete
global.Victron_MESS.Tibber
Set
global.Victron_MESS.LastRun.Tibber.Delete
to the value J:_expression
$moment().format("YYYY-MM-DD HH:mm:ss")

Switch NodeIf Tibber.Today_QH is NULL or notDe afkorting QH staat voor Quarter Hour, ofwel kwartier. Deze afkorting wordt in veel namen gebruikt.
... Propertyglobal.Victron_MESS.Tibber.Today_QH
is Null--> 1Voor een toelichting op het gebruik van de Watchdog zie: Waakhond in "Het grote Victron Node Red topic"
otherwise--> 2

SubflowShow Topiczie Show Topic en Show Payload in "Het grote Victron Node Red topic"

Tibber-data NodeGet Tibber Today-QH
API EndpointTibber Config Node
NameGet Tibber Today_QH
QuerygetTodaysEnergyPrices(himeID)
Home IDje privé Home IDVoor het verkrijgen van de juiste Home ID, zie: Tibber en de Tibber Api
Energy ResolutionQUARTER_HOURLY
Last Count10

Change NodeSet Tibber Today_QHDe opgehaalde gegevens worden bewaard in de context Global geheugenruimte
Set
msg.topic
to the value J:_expression
"Today_QH: " & $string($count($.payload)) & " items"
Set
global.Victron_MESS.Tibber.Today_QH
to the value
msg.payload
V Deep copy value
Set
global.Victron_MESS.LastRun.Tibber.Today_QH
to the value J:_expression
$moment().format("YYYY-MM-DD HH:mm:ss") & ": " & $string($count($.payload)) & " items"

Switch NodeIf Tibber.Today has responseMessageIndien er bij Tibber iets fout gaat bevat het retour-bericht een item 'responseMessage'
... Propertyglobal.Victron_MESS.Tibber.Today_QH.responseMessageDe controle vindt plaats op de opgeslagen kopie van het retour-bericht.
is not Null--> 1Een fout wordt in de pauze stand gezet omdat een volgende poging mogelijk wel succesvol is. Voor een toelichting op het gebruik van de Watchdog zie: Waakhond in "Het grote Victron Node Red topic"
otherwise--> 2

SubflowShow Topiczie Show Topic en Show Payload in "Het grote Victron Node Red topic"

Switch NodeCount Today_QH 96 or notIn principe stuurt Tibber een lijst met 96 items terug. 24 uur met ieder 4 kwartier-prijzen. Echter, bij wisseling van zomertijd naar wintertijd en omgekeerd kan het aantal items ook 92 of 100 zijn.
... Property$count($.payload)
is between
92Bij een correct aantal elementen zal de verwerking verder gaan. Indien het aantal elementen niet juist is wordt een foutmelding-procedure gestart.
and
100 --> 1
otherwise--> 2

Change NodeDelete Tibber.Tomorrow_QHIndien de gegevens van de actuele dag correct zijn opgehaald worden de (eventueel nog aanwezige) gegevens van de volgende dag verwijderd.
Delete
global.Victron_MESS.Tibber.Tomorrow_QH

Change NodeDelete DayAhead AllOok de tabellen met alle planningen gebaseerd op DayAhead-tarieven moeten worden verwijderd.
Set
msg.topic
to the value
Delete DayAhead ALL
Delete
global.Victron_MESS.DayAHead
Delete
global.Victron_MESS.LastRun.DayAhead
Set
global.Victron_MESS.LastRun.DayAhead
to the value J:_expression
$moment().format("YYYY-MM-DD HH:mm:ss")

SubflowShow Topiczie Show Topic en Show Payload in "Het grote Victron Node Red topic"

Switch Node15:00-23:00 / NOTOmdat de gegevens van de volgende dag pas na 15:00 uur beschikbaar zijn moeten deze ook pas na 15:00 worden opgehaald. Als de tijd eerder is vervolgt de verwerking zonder het ophalen van de volgende dag gegevens.
JSONata exp J:(
$nowHour := $moment().format("HHmm");
$nowHour >= "1500" and $nowHour <= "2301"
)
--> 1
otherwise--> 2

Change NodeRead Global Tomorrow_QHAllereerst wordt bezien of de gegevens van de volgende dag al beschikbaar zijn. Zo ja, dan worden deze in de payload geplaatst voor verdere verwerking.
Set
msg.payload
to the value
global.Victron_MESS.Tibber.Tomorrow_QH
V Deep copy value

Switch NodeIf NULL or notAls de gegevens van de volgende dag er niet zijn (NULL) gaat de verwerking naar het ophalen. Als de gegevens er wel zijn (otherwise) wordt gecontroleerd op de gegevens bruikbaar zijn.
... Propertyglobal.Victron_MESS.Tibber.Tomorrow_QH
is Null--> 1Voor een toelichting op het gebruik van de Watchdog zie: Waakhond in "Het grote Victron Node Red topic"
otherwise--> 2

Switch NodeIf has responseMessageIndien er bij Tibber iets fout gaat bevat het retour-bericht een item 'responseMessage'
... Propertyglobal.Victron_MESS.Tibber.Tomorrow_QH.responseMessageDe controle vindt plaats op de opgeslagen kopie van het retour-bericht.
is not Null--> 1Een fout wordt in de pauze stand gezet omdat een volgende poging mogelijk wel succesvol is. Voor een toelichting op het gebruik van de Watchdog zie: Waakhond in "Het grote Victron Node Red topic"
otherwise--> 2

Switch NodeCount 96 or notIn principe stuurt Tibber een lijst met 96 items terug. 24 uur met ieder 4 kwartier-prijzen. Echter, bij wisseling van zomertijd naar wintertijd en omgekeerd kan het aantal items ook 92 of 100 zijn.
... Property$count($.payload)
is between
92Bij een correct aantal elementen zal de verwerking verder gaan. Indien het aantal elementen niet juist is wordt een foutmelding-procedure gestart.
and
100 --> 1
otherwise--> 2

SubflowShow Topiczie Show Topic en Show Payload in "Het grote Victron Node Red topic"

Tibber-data NodeGet Tibber Tomorrow-QH
API EndpointTibber Config Node
NameGet Tibber Tomorrow_QH
QuerygetTodaysEnergyPrices(himeID)
Home IDje privé Home IDVoor het verkrijgen van de juiste Home ID, zie: Tibber en de Tibber Api
Energy ResolutionQUARTER_HOURLY
Last Count10

Change NodeSet Tibber Tomorrow_QHDe opgehaalde gegevens worden bewaard in de context Global geheugenruimte
Set
msg.topic
to the value J:_expression
"Tomorrow_QH: " & $string($count($.payload)) & " items"
Set
global.Victron_MESS.Tibber.Tomorrow_QH
to the value
msg.payload
V Deep copy value
Set
global.Victron_MESS.LastRun.Tibber.Tomorrow_QH
to the value J:_expression
$moment().format("YYYY-MM-DD HH:mm:ss") & ": " & $string($count($.payload)) & " items"

SubflowShow Topiczie Show Topic en Show Payload in "Het grote Victron Node Red topic"

Switch NodeIf Tibber.Tomorrow has responseMessageIndien er bij Tibber iets fout gaat bevat het retour-bericht een item 'responseMessage'
... Propertyglobal.Victron_MESS.Tibber.Tomorrow_QH.responseMessageDe controle vindt plaats op de opgeslagen kopie van het retour-bericht.
is not Null--> 1Een fout wordt in de pauze stand gezet omdat een volgende poging mogelijk wel succesvol is. Voor een toelichting op het gebruik van de Watchdog zie: Waakhond in "Het grote Victron Node Red topic"
otherwise--> 2

Switch NodeCount Tomorrow_QH 96 or notIn principe stuurt Tibber een lijst met 96 items terug. 24 uur met ieder 4 kwartier-prijzen. Echter, bij wisseling van zomertijd naar wintertijd en omgekeerd kan het aantal items ook 92 of 100 zijn.
... Property$count($.payload)
is between
92Bij een correct aantal elementen zal de verwerking verder gaan. Indien het aantal elementen niet juist is wordt een foutmelding-procedure gestart.
and
100 --> 1
otherwise--> 2

Change NodeDelete DayAhead AllDe tabellen met alle planningen gebaseerd op DayAhead-tarieven moeten worden verwijderd. Deze moeten opnieuw worden gemaakt op basis van de nu aanwezige gegevens van de actuele en de volgende dag.
Delete
global.Victron_MESS.DayAHead
Delete
global.Victron_MESS.LastRun.DayAhead
Set
global.Victron_MESS.LastRun.DayAhead
to the value J:_expression
$moment().format("YYYY-MM-DD HH:mm:ss")

SubflowShow Topiczie Show Topic en Show Payload in "Het grote Victron Node Red topic"

Switch Nodeif DayAHead.QH_CT is NULLDe controle of de DayAhead gegevens er (al) zijn wordt niet alleen uitgevoerd na het ophalen van Tibber data maar ook iedere minuut.
Als de DayAhead Tabellen er niet zijn gaat de verwerking verder met de functie Generate QH_ct.

Als de DayAhead gegevens meer dan een uur niet beschiklbaar zijn zal de Watchdog dit rapporteren.
... Propertyglobal.Victron_MESS.DayAhead.QH_Ct
is null--> 1
otherwise--> 2

SubflowShow Topiczie Show Topic en Show Payload in "Het grote Victron Node Red topic"

Inject NodeDebug OnDe Inject Nodes Debug On en Debug Off kunnen gebruikt worden voor debug-doeleinden.
msg.debugtrue
Inject NodeDebug Off
msg.debugfalse

functieGenerate QH_Ct
Deze functie leest de Tibber data van de actuele dag en de volgende dag (indien beschikbaar) uit de Context Global geheugenruimte.
Er wordt een tabel gemaakt (QH_Ct) met 192 waarden 'prijs per kwartier'. Ook wordt een tabel (QH_Level) gemaakt met de beoordeling (door Tibber) of de prijs low, normal, expensive of very expensive is.
Ook deze tabellen worden bewaard in de context Global geheugenruimte onder de groep 'DayAhead'.

Zie de programma-code onderaan.

SubflowTopic to LastRunDe werking van deze functie zal ik t.z.t. beschrijven in "het Grote Victron Node Red topic".
JavaScript:
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
// Generate tibber Array

context = context || {};

GetNowTime = global.get("Victron_MESS.Functions.GetNowTime");
GetTomorrow = global.get("Victron_MESS.Functions.GetTomorrow");
let NowTime = GetNowTime("NowTime"); 
let NowHour = GetNowTime("NowHour"); 
let NowQH = GetNowTime("NowQH"); 
let Nowyyyymmdd = GetNowTime("yyyymmdd");
let Tomorrowyyyymmdd = GetTomorrow("yyyymmdd");

if (context.Debug == null)
{
    context.Debug = false;
}

if (msg.debug != null)
{
    context.Debug = msg.debug;
    node.warn("Debug: " + context.Debug);
    
    node.status( {fill: "blue",
              shape:"dot",
              text: "@ " + NowTime + "; Debug "
                + context.Debug }
        );
    
    return null;
}

Round = global.get("Victron_MESS.Functions.Round");

TimestampTo_QH = 
   global.get("Victron_MESS.Functions.TimestampTo_QH");

if (context.Last_QH_Ct == null)
{
    context.Last_QH_Ct = [];
}

var QH_Ct= [];
var QH_Level= [];

let msg_QH_Ct = null;

var StatusColor = 'red';

var TibberPriceArray = "";

for (D=0; D<2; D++) // two days
{
    if (D == 0)
    {
        TibberPriceArray = global.get( "Victron_MESS.Tibber.Today_QH");
        
        if (context.Debug) { node.warn(TibberPriceArray) }
        
        if ( (TibberPriceArray == "")
          || (TibberPriceArray == null) )
        {
            node.warn("Today_QH is NULL")
        }

        for (let i = 0; i < 96; i++)
        {
            QH_Ct[i] = -99;
            QH_Level[i] = "unknown";
        }

    }
    else
    {
        TibberPriceArray = global.get( "Victron_MESS.Tibber.Tomorrow_QH");

        if (context.Debug) { node.warn(TibberPriceArray) }

        if ( (TibberPriceArray == "")
          || (TibberPriceArray == null) )
        {
            if (NowHour >= 15)
            {
                node.warn("Tomorrow_QH is NULL")
            }
        }
        
        for (let i = 96; i<192; i++)
        {
            QH_Ct[i] = -99;
            QH_Level[i] = "unknown";
        }
        
    }

    if ( (TibberPriceArray != "")
      && (TibberPriceArray != null) )
    {
        if (context.Debug)
        {
            node.warn("Process: Day: " + D
            + " Length: " + TibberPriceArray.length);
        }
        
        for (let i=0; i<TibberPriceArray.length; i++)
        {
            let total = TibberPriceArray[i].total;
            let energy = TibberPriceArray[i].energy;
            let tax = TibberPriceArray[i].tax;
            let level = (TibberPriceArray[i].level).toLowerCase();
            
            let EnergyCt = 
              Math.round( energy*1000 + 0.5 ) / 10;
            
            let startsAt = TibberPriceArray[i].startsAt;
            // node.warn(startsAt);
            let PriceTimeStamp = new Date(startsAt);
            
            let Priceyyyymmdd = PriceTimeStamp.getFullYear()
                 + ("0" + (PriceTimeStamp.getMonth() + 1)).slice(-2)
                 + ("0" + PriceTimeStamp.getDate()).slice(-2) ;       

            // node.warn(Priceyyyymmdd);

            let Price_QH = TimestampTo_QH(startsAt);
            // node.warn(Price_QH);

            if (Priceyyyymmdd == Nowyyyymmdd)
            {
                QH_Ct[Price_QH] = EnergyCt;
                QH_Level[Price_QH] = level;
            }
            else if (Priceyyyymmdd == Tomorrowyyyymmdd)
            {
                QH_Ct[Price_QH + 96] = EnergyCt;
                QH_Level[Price_QH + 96] = level;
            }
            else
            {
                node.warn("Unknown Date: " + Priceyyyymmdd);
            }
            
        } // for TibberPriceArray length
        
        StatusColor = 'green';
        
    }// if TibberPriceArray exists
} // for two days

if (context.Debug) { node.warn(QH_Ct) }

global.set( "Victron_MESS.DayAhead.QH_Ct"); // remove
global.set( "Victron_MESS.DayAhead.QH_Ct",  QH_Ct);

global.set( "Victron_MESS.DayAhead.QH_Level"); // remove
global.set( "Victron_MESS.DayAhead.QH_Level",  QH_Level);

msg_QH_Ct = 
  { topic: "DayAhead.QH_Ct", 
    payload: QH_Ct }

node.status( {fill:StatusColor,
              shape:"dot",
              text: "@ " + NowTime + ": "
                + QH_Ct[Number(NowQH)] + " ct; "  
                + QH_Ct.length + " elements." }
        );

return  msg_QH_Ct;

[ Voor 255% gewijzigd door MJ de Bruijn op 30-11-2025 15:04 ]


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
Dit is de Flow "Tibber - Dagelijks gegevens ophalen":
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
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
[
{
"id": "733c6535992c1d6d",
"type": "subflow",
"name": "Show Topic",
"info": "",
"category": "",
"in": [
{
"x": 44,
"y": 48,
"wires": [
{
"id": "25113dfa4aa29d6a"
}
]
}
],
"out": [
{
"x": 300,
"y": 48,
"wires": [
{
"id": "25113dfa4aa29d6a",
"port": 0
}
]
}
],
"env": [],
"meta": {},
"color": "#DDAA99",
"status": {
"x": 300,
"y": 96,
"wires": [
{
"id": "25113dfa4aa29d6a",
"port": 1
}
]
}
},
{
"id": "25113dfa4aa29d6a",
"type": "function",
"z": "733c6535992c1d6d",
"name": "Show Topic",
"func": "const Now = new Date();\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n                + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n                + (\"0\" + Now.getSeconds()).slice(-2) ;\n\n\nmsgState = { topic:'state', \n             payload: \"@ \" + NowTime + \": \"+ msg.topic,\n             shape: \"dot\",\n             color: \"grey\"\n};\n\nnode.send( [msg, msgState] );\n\nnode.done;",
"outputs": 2,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 166,
"y": 64,
"wires": [
[],
[]
]
},
{
"id": "9cde34175a911c36",
"type": "subflow",
"name": "Topic to LastRun",
"info": "",
"category": "",
"in": [
{
"x": 68,
"y": 48,
"wires": [
{
"id": "6af0ad5c06a89f8b"
}
]
}
],
"out": [
{
"x": 420,
"y": 32,
"wires": [
{
"id": "6af0ad5c06a89f8b",
"port": 0
}
]
}
],
"env": [],
"meta": {},
"color": "#DDAA99",
"status": {
"x": 420,
"y": 96,
"wires": [
{
"id": "6af0ad5c06a89f8b",
"port": 1
}
]
}
},
{
"id": "6af0ad5c06a89f8b",
"type": "function",
"z": "9cde34175a911c36",
"name": "Set LastRun for Topic",
"func": "// Function LastRun\nconst Now = new Date();\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n                + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n                + (\"0\" + Now.getSeconds()).slice(-2) ;\n\nlet CurTopc = 'Unknown'\n\nif (msg.topic == null)\n{\n    CurTopic = 'NULL';\n    node.warn(\"msg.topic is NULL; payload: \" + msg.payload )\n}\nelse if (msg.topic == undefined)\n{\n    CurTopic = 'Undefined';\n    node.warn(\"msg.topic is Undefined; payload: \" + msg.payload )\n}\nelse\n{\n    CurTopic = msg.topic;\n    CurTopic = CurTopic.replaceAll(\" \", \"-\");\n\n    global.set (\"Victron_MESS.LastRun.\" + CurTopic) // remove\n    global.set (\"Victron_MESS.LastRun.\" + CurTopic, NowTime)\n\n}\n\n\nmsgState = { topic:'state', \n             payload: NowTime + \": \" + CurTopic,\n             shape: \"dot\",\n             color: \"grey\"\n            };\n\nnode.send( [msg, msgState] );\n\nnode.done;\n",
"outputs": 2,
"timeout": "2",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 236,
"y": 48,
"wires": [
[],
[]
]
},
{
"id": "4eeabf29980b7298",
"type": "tab",
"label": "Tibber",
"disabled": false,
"info": "",
"env": []
},
{
"id": "882789e0e67415a7",
"type": "switch",
"z": "4eeabf29980b7298",
"name": "If Tibber.Today_QH is NULL or not",
"property": "Victron_MESS.Tibber.Today_QH",
"propertyType": "global",
"rules": [
{
"t": "null"
},
{
"t": "else"
}
],
"checkall": "false",
"repair": false,
"outputs": 2,
"x": 396,
"y": 96,
"wires": [
[
"e93de54cbd0ac9c3"
],
[
"a151b437ad35e16e",
"e9b4d5c6f2cbfeb9",
"1032e8031a43541c"
]
]
},
{
"id": "aa313e63a8bb1c6a",
"type": "cronplus",
"z": "4eeabf29980b7298",
"name": "at 00:00:10",
"outputField": "payload",
"timeZone": "",
"storeName": "",
"commandResponseMsgOutput": "output1",
"defaultLocation": "",
"defaultLocationType": "default",
"outputs": 1,
"options": [
{
"name": "schedule1",
"topic": "Every Day",
"payloadType": "default",
"payload": "",
"expressionType": "cron",
"expression": "10 0 0 * * *",
"location": "",
"offset": "0",
"solarType": "all",
"solarEvents": "sunrise,sunset"
}
],
"x": 106,
"y": 32,
"wires": [
[
"f9a338d40380b241"
]
]
},
{
"id": "570681eea8f4b93e",
"type": "inject",
"z": "4eeabf29980b7298",
"name": "1 min",
"props": [
{
"p": "topic",
"vt": "str"
},
{
"p": "payload"
}
],
"repeat": "60",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "Check Tibber (1 min)",
"payload": "( $moment().format(\"YYYY-MM-DD HH:mm:ss\") )",
"payloadType": "jsonata",
"x": 86,
"y": 96,
"wires": [
[
"882789e0e67415a7"
]
]
},
{
"id": "895fc654aef9f73f",
"type": "link out",
"z": "4eeabf29980b7298",
"name": "link to Process QuarterHours",
"mode": "link",
"links": [
"babb971fbf7f5d5b",
"cf8ea5bc65a7f867"
],
"x": 505,
"y": 1664,
"wires": []
},
{
"id": "f9a338d40380b241",
"type": "change",
"z": "4eeabf29980b7298",
"name": "Delete All Tibber",
"rules": [
{
"t": "delete",
"p": "Victron_MESS.LastRun.Tibber",
"pt": "global"
},
{
"t": "delete",
"p": "Victron_MESS.Tibber",
"pt": "global"
},
{
"t": "set",
"p": "Victron_MESS.LastRun.Tibber.Delete",
"pt": "global",
"to": "$moment().format(\"YYYY-MM-DD HH:mm:ss\")",
"tot": "jsonata"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 336,
"y": 32,
"wires": [
[
"882789e0e67415a7"
]
]
},
{
"id": "f8a97df91041a3ef",
"type": "link out",
"z": "4eeabf29980b7298",
"name": "To Watchdog",
"mode": "link",
"links": [
"19d1f71e8a5c0f9e",
"72aba6706bd5614f"
],
"x": 849,
"y": 144,
"wires": []
},
{
"id": "e9b4d5c6f2cbfeb9",
"type": "trigger",
"z": "4eeabf29980b7298",
"name": "Watchdog 65 min",
"op1": "",
"op2": "Tibber Today_QH is NULL",
"op1type": "nul",
"op2type": "str",
"duration": "65",
"extend": false,
"overrideDelay": false,
"units": "min",
"reset": "",
"bytopic": "all",
"topic": "topic",
"outputs": 2,
"x": 714,
"y": 144,
"wires": [
[],
[
"f8a97df91041a3ef"
]
]
},
{
"id": "a151b437ad35e16e",
"type": "change",
"z": "4eeabf29980b7298",
"name": "",
"rules": [
{
"t": "set",
"p": "reset",
"pt": "msg",
"to": "true",
"tot": "bool"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 704,
"y": 96,
"wires": [
[
"e9b4d5c6f2cbfeb9"
]
]
},
{
"id": "2032dc89208a2634",
"type": "tibber-data",
"z": "4eeabf29980b7298",
"name": "Get Tibber Today_QH",
"active": true,
"apiEndpointRef": "980a5e303861268d",
"queryName": "getTodaysEnergyPrices",
"homeId": "04abbeae-a220-4c3b-854d-2452f154a1e6",
"energyResolution": "QUARTER_HOURLY",
"lastCount": 10,
"x": 356,
"y": 192,
"wires": [
[
"a7ed06b1f43a7564"
]
]
},
{
"id": "01bf41a25fa7a075",
"type": "function",
"z": "4eeabf29980b7298",
"name": "Generate QH_Ct",
"func": "// Generate tibber Array\n\ncontext = context || {};\n\nGetNowTime = global.get(\"Victron_MESS.Functions.GetNowTime\");\nGetTomorrow = global.get(\"Victron_MESS.Functions.GetTomorrow\");\nlet NowTime = GetNowTime(\"NowTime\"); \nlet NowHour = GetNowTime(\"NowHour\"); \nlet NowQH = GetNowTime(\"NowQH\"); \nlet Nowyyyymmdd = GetNowTime(\"yyyymmdd\");\nlet Tomorrowyyyymmdd = GetTomorrow(\"yyyymmdd\");\n\nif (context.Debug == null)\n{\n    context.Debug = false;\n}\n\nif (msg.debug != null)\n{\n    context.Debug = msg.debug;\n    node.warn(\"Debug: \" + context.Debug);\n    \n    node.status( {fill: \"blue\",\n              shape:\"dot\",\n              text: \"@ \" + NowTime + \"; Debug \"\n                + context.Debug }\n        );\n    \n    return null;\n}\n\nRound = global.get(\"Victron_MESS.Functions.Round\");\n\nTimestampTo_QH = \n   global.get(\"Victron_MESS.Functions.TimestampTo_QH\");\n\nif (context.Last_QH_Ct == null)\n{\n    context.Last_QH_Ct = [];\n}\n\nvar QH_Ct= [];\nvar QH_Level= [];\n\nlet msg_QH_Ct = null;\n\nvar StatusColor = 'red';\n\nvar TibberPriceArray = \"\";\n\nfor (D=0; D<2; D++) // two days\n{\n    if (D == 0)\n    {\n        TibberPriceArray = global.get( \"Victron_MESS.Tibber.Today_QH\");\n        \n        if (context.Debug) { node.warn(TibberPriceArray) }\n        \n        if ( (TibberPriceArray == \"\")\n          || (TibberPriceArray == null) )\n        {\n            node.warn(\"Today_QH is NULL\")\n        }\n\n        for (let i = 0; i < 96; i++)\n        {\n            QH_Ct[i] = -99;\n            QH_Level[i] = \"unknown\";\n        }\n\n    }\n    else\n    {\n        TibberPriceArray = global.get( \"Victron_MESS.Tibber.Tomorrow_QH\");\n\n        if (context.Debug) { node.warn(TibberPriceArray) }\n\n        if ( (TibberPriceArray == \"\")\n          || (TibberPriceArray == null) )\n        {\n            if (NowHour >= 15)\n            {\n                node.warn(\"Tomorrow_QH is NULL\")\n            }\n        }\n        \n        for (let i = 96; i<192; i++)\n        {\n            QH_Ct[i] = -99;\n            QH_Level[i] = \"unknown\";\n        }\n        \n    }\n\n    if ( (TibberPriceArray != \"\")\n      && (TibberPriceArray != null) )\n    {\n        if (context.Debug)\n        {\n            node.warn(\"Process: Day: \" + D\n            + \" Length: \" + TibberPriceArray.length);\n        }\n        \n        for (let i=0; i<TibberPriceArray.length; i++)\n        {\n            let total = TibberPriceArray[i].total;\n            let energy = TibberPriceArray[i].energy;\n            let tax = TibberPriceArray[i].tax;\n            let level = (TibberPriceArray[i].level).toLowerCase();\n            \n            let EnergyCt = \n              Math.round( energy*1000 + 0.5 ) / 10;\n            \n            let startsAt = TibberPriceArray[i].startsAt;\n            // node.warn(startsAt);\n            let PriceTimeStamp = new Date(startsAt);\n            \n            let Priceyyyymmdd = PriceTimeStamp.getFullYear()\n                 + (\"0\" + (PriceTimeStamp.getMonth() + 1)).slice(-2)\n                 + (\"0\" + PriceTimeStamp.getDate()).slice(-2) ;       \n\n            // node.warn(Priceyyyymmdd);\n\n            let Price_QH = TimestampTo_QH(startsAt);\n            // node.warn(Price_QH);\n\n            if (Priceyyyymmdd == Nowyyyymmdd)\n            {\n                QH_Ct[Price_QH] = EnergyCt;\n                QH_Level[Price_QH] = level;\n            }\n            else if (Priceyyyymmdd == Tomorrowyyyymmdd)\n            {\n                QH_Ct[Price_QH + 96] = EnergyCt;\n                QH_Level[Price_QH + 96] = level;\n            }\n            else\n            {\n                node.warn(\"Unknown Date: \" + Priceyyyymmdd);\n            }\n            \n        } // for TibberPriceArray length\n        \n        StatusColor = 'green';\n        \n    }// if TibberPriceArray exists\n} // for two days\n\nif (context.Debug) { node.warn(QH_Ct) }\n\nglobal.set( \"Victron_MESS.DayAhead.QH_Ct\"); // remove\nglobal.set( \"Victron_MESS.DayAhead.QH_Ct\",  QH_Ct);\n\nglobal.set( \"Victron_MESS.DayAhead.QH_Level\"); // remove\nglobal.set( \"Victron_MESS.DayAhead.QH_Level\",  QH_Level);\n\nmsg_QH_Ct = \n  { topic: \"DayAhead.QH_Ct\", \n    payload: QH_Ct }\n\nnode.status( {fill:StatusColor,\n              shape:\"dot\",\n              text: \"@ \" + NowTime + \": \"\n                + QH_Ct[Number(NowQH)] + \" ct; \"  \n                + QH_Ct.length + \" elements.\" }\n        );\n\nreturn  msg_QH_Ct;\n",
"outputs": 1,
"timeout": "4",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 342,
"y": 1536,
"wires": [
[
"81ef4a7809c69874",
"ebbd7666a79ace40"
]
]
},
{
"id": "a7ed06b1f43a7564",
"type": "change",
"z": "4eeabf29980b7298",
"name": "Set Tibber Today_QH",
"rules": [
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "\"Today_QH: \" & $string($count($.payload)) & \" items\"",
"tot": "jsonata"
},
{
"t": "set",
"p": "Victron_MESS.Tibber.Today_QH",
"pt": "global",
"to": "payload",
"tot": "msg",
"dc": true
},
{
"t": "set",
"p": "Victron_MESS.LastRun.Tibber.Today_QH",
"pt": "global",
"to": "$moment().format(\"YYYY-MM-DD HH:mm:ss\")\t& \": \" &\t$string($count($.payload)) & \" items\"",
"tot": "jsonata"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 356,
"y": 240,
"wires": [
[
"471087d1994f0123"
]
]
},
{
"id": "b17cd1af2c80472a",
"type": "tibber-data",
"z": "4eeabf29980b7298",
"name": "Get Tibber Tomorrow_QH",
"active": true,
"apiEndpointRef": "980a5e303861268d",
"queryName": "getTomorrowsEnergyPrices",
"homeId": "04abbeae-a220-4c3b-854d-2452f154a1e6",
"energyResolution": "QUARTER_HOURLY",
"lastCount": 10,
"x": 362,
"y": 944,
"wires": [
[
"0b0b11736c0a9612"
]
]
},
{
"id": "0b0b11736c0a9612",
"type": "change",
"z": "4eeabf29980b7298",
"name": "Set Tibber Tomorrow_QH",
"rules": [
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "\"Tomorrow_QH: \" & $string($count($.payload)) & \" items\"",
"tot": "jsonata"
},
{
"t": "set",
"p": "Victron_MESS.Tibber.Tomorrow_QH",
"pt": "global",
"to": "payload",
"tot": "msg",
"dc": true
},
{
"t": "set",
"p": "Victron_MESS.LastRun.Tibber.Tomorrow_QH",
"pt": "global",
"to": "$moment().format(\"YYYY-MM-DD HH:mm:ss\")\t& \": \" &\t$string($count($.payload)) & \" items\"",
"tot": "jsonata"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 362,
"y": 992,
"wires": [
[
"6f7c6c90fd379093"
]
]
},
{
"id": "2bef3fcceac875e2",
"type": "switch",
"z": "4eeabf29980b7298",
"name": "If NULL or not",
"property": "Victron_MESS.Tibber.Tomorrow_QH",
"propertyType": "global",
"rules": [
{
"t": "null"
},
{
"t": "else"
}
],
"checkall": "false",
"repair": false,
"outputs": 2,
"x": 332,
"y": 752,
"wires": [
[
"44a56aa4c537a4d7",
"26acc274e13a9f15"
],
[
"342ae1217d089260",
"7b8edf7a95288622"
]
]
},
{
"id": "ebbd7666a79ace40",
"type": "subflow:9cde34175a911c36",
"z": "4eeabf29980b7298",
"name": "",
"x": 342,
"y": 1664,
"wires": [
[
"895fc654aef9f73f"
]
]
},
{
"id": "9b9c846297f10c84",
"type": "switch",
"z": "4eeabf29980b7298",
"name": "If DayAhead.QH_Ct is NULL",
"property": "Victron_MESS.DayAhead.QH_Ct",
"propertyType": "global",
"rules": [
{
"t": "null"
},
{
"t": "else"
}
],
"checkall": "false",
"repair": false,
"outputs": 2,
"x": 372,
"y": 1408,
"wires": [
[
"01bf41a25fa7a075",
"dcd0590bd6a69b39"
],
[
"45cf9791b1c3a3e2"
]
]
},
{
"id": "01c5c4a8be8ba8d1",
"type": "change",
"z": "4eeabf29980b7298",
"name": "Delete Tibber.Tomorrow_QH",
"rules": [
{
"t": "delete",
"p": "Victron_MESS.Tibber.Tomorrow_QH",
"pt": "global"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 376,
"y": 496,
"wires": [
[
"306325a6966a9fac"
]
]
},
{
"id": "b3f69f3daddce9db",
"type": "link out",
"z": "4eeabf29980b7298",
"name": "To Watchdog",
"mode": "link",
"links": [
"19d1f71e8a5c0f9e",
"72aba6706bd5614f"
],
"x": 813,
"y": 736,
"wires": []
},
{
"id": "44a56aa4c537a4d7",
"type": "trigger",
"z": "4eeabf29980b7298",
"name": "Watchdog",
"op1": "",
"op2": "Tibber Tomorrow_QH is NULL",
"op1type": "nul",
"op2type": "str",
"duration": "65",
"extend": false,
"overrideDelay": false,
"units": "min",
"reset": "",
"bytopic": "all",
"topic": "topic",
"outputs": 2,
"x": 680,
"y": 736,
"wires": [
[],
[
"b3f69f3daddce9db"
]
]
},
{
"id": "342ae1217d089260",
"type": "change",
"z": "4eeabf29980b7298",
"name": "",
"rules": [
{
"t": "set",
"p": "reset",
"pt": "msg",
"to": "true",
"tot": "bool"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 704,
"y": 784,
"wires": [
[
"44a56aa4c537a4d7"
]
]
},
{
"id": "81ef4a7809c69874",
"type": "change",
"z": "4eeabf29980b7298",
"name": "",
"rules": [
{
"t": "set",
"p": "reset",
"pt": "msg",
"to": "true",
"tot": "bool"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 700,
"y": 1536,
"wires": [
[
"dcd0590bd6a69b39"
]
]
},
{
"id": "dcd0590bd6a69b39",
"type": "trigger",
"z": "4eeabf29980b7298",
"name": "Watchdog 65 min",
"op1": "",
"op2": "DayAhead.QH_Ct is NULL",
"op1type": "nul",
"op2type": "str",
"duration": "65",
"extend": false,
"overrideDelay": false,
"units": "min",
"reset": "",
"bytopic": "all",
"topic": "topic",
"outputs": 2,
"x": 710,
"y": 1408,
"wires": [
[],
[
"90f3ae770c6e9a1f"
]
]
},
{
"id": "90f3ae770c6e9a1f",
"type": "link out",
"z": "4eeabf29980b7298",
"name": "From Watchdog",
"mode": "link",
"links": [
"19d1f71e8a5c0f9e",
"72aba6706bd5614f"
],
"x": 843,
"y": 1408,
"wires": []
},
{
"id": "0bfcb2ce29848aa1",
"type": "switch",
"z": "4eeabf29980b7298",
"name": "15:00-23:00 / NOT",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "jsonata_exp",
"v": "(\t    $nowHour := $moment().format(\"HHmm\");\t    $nowHour >= \"1500\" and $nowHour <= \"2301\"\t)\t\t",
"vt": "jsonata"
},
{
"t": "else"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 346,
"y": 640,
"wires": [
[
"095ba271fd6ed19a"
],
[
"c8a06571d125db68"
]
]
},
{
"id": "947ce53e1485061d",
"type": "inject",
"z": "4eeabf29980b7298",
"name": "On Demand",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 102,
"y": 1536,
"wires": [
[
"01bf41a25fa7a075"
]
]
},
{
"id": "e4cc29cd6d84f9a2",
"type": "change",
"z": "4eeabf29980b7298",
"name": "Delete DayAhead ALL",
"rules": [
{
"t": "delete",
"p": "Victron_MESS.DayAhead",
"pt": "global"
},
{
"t": "delete",
"p": "Victron_MESS.LastRun.DayAhead",
"pt": "global"
},
{
"t": "set",
"p": "Victron_MESS.LastRun.DayAhead.Delete",
"pt": "global",
"to": "$moment().format(\"YYYY-MM-DD HH:mm:ss\")",
"tot": "jsonata"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 352,
"y": 1296,
"wires": [
[
"92dbdc9c9d56234e"
]
]
},
{
"id": "306325a6966a9fac",
"type": "change",
"z": "4eeabf29980b7298",
"name": "Delete DayAhead ALL",
"rules": [
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "Delete DayAhead ALL",
"tot": "str"
},
{
"t": "delete",
"p": "Victron_MESS.DayAhead",
"pt": "global"
},
{
"t": "delete",
"p": "Victron_MESS.LastRun.DayAhead",
"pt": "global"
},
{
"t": "set",
"p": "Victron_MESS.LastRun.DayAhead.Delete",
"pt": "global",
"to": "$moment().format(\"YYYY-MM-DD HH:mm:ss\")",
"tot": "jsonata"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 356,
"y": 544,
"wires": [
[
"1032e8031a43541c"
]
]
},
{
"id": "f9bb822d9b35bacc",
"type": "inject",
"z": "4eeabf29980b7298",
"name": "Debug On",
"props": [
{
"p": "debug",
"v": "true",
"vt": "bool"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"x": 92,
"y": 1584,
"wires": [
[
"01bf41a25fa7a075"
]
]
},
{
"id": "2876f88efeca52e5",
"type": "inject",
"z": "4eeabf29980b7298",
"name": "Debug Off",
"props": [
{
"p": "debug",
"v": "false",
"vt": "bool"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"x": 92,
"y": 1632,
"wires": [
[
"01bf41a25fa7a075"
]
]
},
{
"id": "1346cd2a2f1abadc",
"type": "inject",
"z": "4eeabf29980b7298",
"name": "On Demand",
"props": [
{
"p": "topic",
"vt": "str"
},
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "Get Tibber Tomorrow",
"payload": "( $moment().format(\"YYYY-MM-DD HH:mm:ss\") )",
"payloadType": "jsonata",
"x": 102,
"y": 944,
"wires": [
[
"b17cd1af2c80472a"
]
]
},
{
"id": "6454b17d9435f71c",
"type": "inject",
"z": "4eeabf29980b7298",
"name": "On Demand",
"props": [
{
"p": "topic",
"vt": "str"
},
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "Get Tibber Today",
"payload": "( $moment().format(\"YYYY-MM-DD HH:mm:ss\") )",
"payloadType": "jsonata",
"x": 106,
"y": 192,
"wires": [
[
"2032dc89208a2634"
]
]
},
{
"id": "7b8edf7a95288622",
"type": "switch",
"z": "4eeabf29980b7298",
"name": "If has responseMessage",
"property": "Victron_MESS.Tibber.Tomorrow_QH.responseMesage",
"propertyType": "global",
"rules": [
{
"t": "nnull"
},
{
"t": "else"
}
],
"checkall": "false",
"repair": false,
"outputs": 2,
"x": 366,
"y": 800,
"wires": [
[
"26acc274e13a9f15"
],
[
"9b240a9689b3908e"
]
]
},
{
"id": "4cdb4f8f6bb00b6c",
"type": "inject",
"z": "4eeabf29980b7298",
"name": "On Demand",
"props": [
{
"p": "topic",
"vt": "str"
},
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "Check Tibber Tomorrow",
"payload": "( $moment().format(\"YYYY-MM-DD HH:mm:ss\") )",
"payloadType": "jsonata",
"x": 106,
"y": 704,
"wires": [
[
"095ba271fd6ed19a"
]
]
},
{
"id": "4d2ffc6fbac3b794",
"type": "switch",
"z": "4eeabf29980b7298",
"name": "If Tibber.Today has responseMessage",
"property": "Victron_MESS.Tibber.Today_QH.responseMessage",
"propertyType": "global",
"rules": [
{
"t": "nnull"
},
{
"t": "else"
}
],
"checkall": "false",
"repair": false,
"outputs": 2,
"x": 406,
"y": 336,
"wires": [
[
"587d7d592c01f5c9"
],
[
"ca5cc930e17f8210",
"e9e1c719db699afa"
]
]
},
{
"id": "13fc4fde7f9865f2",
"type": "switch",
"z": "4eeabf29980b7298",
"name": "If Tibber.Tomorrow has responseMessage",
"property": "Victron_MESS.Tibber.Tomorrow_QH.responseMessage",
"propertyType": "global",
"rules": [
{
"t": "nnull"
},
{
"t": "else"
}
],
"checkall": "false",
"repair": false,
"outputs": 2,
"x": 412,
"y": 1088,
"wires": [
[
"87a46ddd408c4ba5"
],
[
"4ae5faf9a9b35658",
"ce9238882f0c4127"
]
]
},
{
"id": "587d7d592c01f5c9",
"type": "change",
"z": "4eeabf29980b7298",
"name": "copy Error",
"rules": [
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "Tibber Today has responseMesage",
"tot": "str"
},
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "Victron_MESS.Tibber.Today_QH.responseMessage",
"tot": "global",
"dc": true
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 698,
"y": 288,
"wires": [
[
"f602df00e35b3ec5"
]
]
},
{
"id": "f602df00e35b3ec5",
"type": "trigger",
"z": "4eeabf29980b7298",
"name": "Watchdog 65 min",
"op1": "",
"op2": "",
"op1type": "nul",
"op2type": "pay",
"duration": "65",
"extend": false,
"overrideDelay": false,
"units": "min",
"reset": "",
"bytopic": "all",
"topic": "topic",
"outputs": 1,
"x": 714,
"y": 384,
"wires": [
[
"307623ed66c90c89"
]
]
},
{
"id": "307623ed66c90c89",
"type": "link out",
"z": "4eeabf29980b7298",
"name": "To Watchdog",
"mode": "link",
"links": [
"19d1f71e8a5c0f9e",
"72aba6706bd5614f"
],
"x": 847,
"y": 384,
"wires": []
},
{
"id": "ca5cc930e17f8210",
"type": "change",
"z": "4eeabf29980b7298",
"name": "",
"rules": [
{
"t": "set",
"p": "reset",
"pt": "msg",
"to": "true",
"tot": "bool"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 704,
"y": 336,
"wires": [
[
"f602df00e35b3ec5"
]
]
},
{
"id": "87a46ddd408c4ba5",
"type": "change",
"z": "4eeabf29980b7298",
"name": "copy Error",
"rules": [
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "Tibber Tomorrow has responseMessage",
"tot": "str"
},
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "Victron_MESS.Tibber.Tomorrow_QH.responseMessage",
"tot": "global",
"dc": true
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 694,
"y": 1056,
"wires": [
[
"5ae02d3bde767c67"
]
]
},
{
"id": "5ae02d3bde767c67",
"type": "trigger",
"z": "4eeabf29980b7298",
"name": "Watchdog 65 min",
"op1": "",
"op2": "",
"op1type": "nul",
"op2type": "pay",
"duration": "65",
"extend": false,
"overrideDelay": false,
"units": "min",
"reset": "",
"bytopic": "all",
"topic": "topic",
"outputs": 1,
"x": 710,
"y": 1136,
"wires": [
[
"4166d4402b0b26bd"
]
]
},
{
"id": "4166d4402b0b26bd",
"type": "link out",
"z": "4eeabf29980b7298",
"name": "To Watchdog",
"mode": "link",
"links": [
"19d1f71e8a5c0f9e",
"72aba6706bd5614f"
],
"x": 843,
"y": 1136,
"wires": []
},
{
"id": "4ae5faf9a9b35658",
"type": "change",
"z": "4eeabf29980b7298",
"name": "",
"rules": [
{
"t": "set",
"p": "reset",
"pt": "msg",
"to": "true",
"tot": "bool"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 704,
"y": 1088,
"wires": [
[
"5ae02d3bde767c67"
]
]
},
{
"id": "471087d1994f0123",
"type": "subflow:733c6535992c1d6d",
"z": "4eeabf29980b7298",
"name": "",
"x": 326,
"y": 288,
"wires": [
[
"4d2ffc6fbac3b794"
]
]
},
{
"id": "61b7d0f57fb30c4b",
"type": "subflow:733c6535992c1d6d",
"z": "4eeabf29980b7298",
"name": "",
"x": 322,
"y": 1360,
"wires": [
[
"9b9c846297f10c84"
]
]
},
{
"id": "1032e8031a43541c",
"type": "subflow:733c6535992c1d6d",
"z": "4eeabf29980b7298",
"name": "",
"x": 326,
"y": 592,
"wires": [
[
"0bfcb2ce29848aa1"
]
]
},
{
"id": "26acc274e13a9f15",
"type": "subflow:733c6535992c1d6d",
"z": "4eeabf29980b7298",
"name": "",
"x": 322,
"y": 896,
"wires": [
[
"b17cd1af2c80472a"
]
]
},
{
"id": "45cf9791b1c3a3e2",
"type": "subflow:733c6535992c1d6d",
"z": "4eeabf29980b7298",
"name": "",
"x": 322,
"y": 1456,
"wires": [
[]
]
},
{
"id": "fa6755e05d414017",
"type": "switch",
"z": "4eeabf29980b7298",
"name": "Count Today_QH 96 or not",
"property": "$count($.payload)",
"propertyType": "jsonata",
"rules": [
{
"t": "btwn",
"v": "95",
"vt": "num",
"v2": "97",
"v2t": "num"
},
{
"t": "else"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 376,
"y": 448,
"wires": [
[
"01c5c4a8be8ba8d1",
"d994f23ad63208d9"
],
[
"96a35ffe4e1b0b1e"
]
]
},
{
"id": "96a35ffe4e1b0b1e",
"type": "change",
"z": "4eeabf29980b7298",
"name": "copy Error",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "topic",
"tot": "msg",
"dc": true
},
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "Tibber Today has incorrect count",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 694,
"y": 464,
"wires": [
[
"da6904aa0e6d4bd5"
]
]
},
{
"id": "da6904aa0e6d4bd5",
"type": "trigger",
"z": "4eeabf29980b7298",
"name": "Watchdog 65 min",
"op1": "",
"op2": "",
"op1type": "nul",
"op2type": "pay",
"duration": "65",
"extend": false,
"overrideDelay": false,
"units": "min",
"reset": "",
"bytopic": "all",
"topic": "topic",
"outputs": 1,
"x": 714,
"y": 512,
"wires": [
[
"d8abd9416392e907"
]
]
},
{
"id": "d8abd9416392e907",
"type": "link out",
"z": "4eeabf29980b7298",
"name": "To Watchdog",
"mode": "link",
"links": [
"19d1f71e8a5c0f9e",
"72aba6706bd5614f"
],
"x": 847,
"y": 512,
"wires": []
},
{
"id": "d994f23ad63208d9",
"type": "change",
"z": "4eeabf29980b7298",
"name": "",
"rules": [
{
"t": "set",
"p": "reset",
"pt": "msg",
"to": "true",
"tot": "bool"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 704,
"y": 432,
"wires": [
[
"da6904aa0e6d4bd5"
]
]
},
{
"id": "b758967f73168f67",
"type": "link out",
"z": "4eeabf29980b7298",
"name": "To Watchdog",
"mode": "link",
"links": [
"19d1f71e8a5c0f9e",
"72aba6706bd5614f"
],
"x": 843,
"y": 1264,
"wires": []
},
{
"id": "ce9238882f0c4127",
"type": "switch",
"z": "4eeabf29980b7298",
"name": "Count Tomorrow_QH 96 or not",
"property": "$count($.payload)",
"propertyType": "jsonata",
"rules": [
{
"t": "btwn",
"v": "95",
"vt": "num",
"v2": "97",
"v2t": "num"
},
{
"t": "else"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 382,
"y": 1216,
"wires": [
[
"e91af8f749354460",
"e4cc29cd6d84f9a2"
],
[
"139c2cd9bd530f86"
]
]
},
{
"id": "e91af8f749354460",
"type": "change",
"z": "4eeabf29980b7298",
"name": "",
"rules": [
{
"t": "set",
"p": "reset",
"pt": "msg",
"to": "true",
"tot": "bool"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 704,
"y": 1184,
"wires": [
[
"3bea6b699fd11ba7"
]
]
},
{
"id": "139c2cd9bd530f86",
"type": "change",
"z": "4eeabf29980b7298",
"name": "copy Error",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "topic",
"tot": "msg",
"dc": true
},
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "Tibber Tomorrow has incorrect count",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 694,
"y": 1216,
"wires": [
[
"3bea6b699fd11ba7"
]
]
},
{
"id": "3bea6b699fd11ba7",
"type": "trigger",
"z": "4eeabf29980b7298",
"name": "Watchdog 65 min",
"op1": "",
"op2": "",
"op1type": "nul",
"op2type": "pay",
"duration": "65",
"extend": false,
"overrideDelay": false,
"units": "min",
"reset": "",
"bytopic": "all",
"topic": "topic",
"outputs": 1,
"x": 710,
"y": 1264,
"wires": [
[
"b758967f73168f67"
]
]
},
{
"id": "8c38ca1bc423e6d4",
"type": "link in",
"z": "4eeabf29980b7298",
"name": "Link in Check QH_Ct",
"links": [
"aea4dabe57116bad",
"c8a06571d125db68",
"92dbdc9c9d56234e"
],
"x": 203,
"y": 1360,
"wires": [
[
"61b7d0f57fb30c4b"
]
]
},
{
"id": "aea4dabe57116bad",
"type": "link out",
"z": "4eeabf29980b7298",
"name": "lLink Out goto Check QH_Ct",
"mode": "link",
"links": [
"8c38ca1bc423e6d4"
],
"x": 513,
"y": 848,
"wires": []
},
{
"id": "c8a06571d125db68",
"type": "link out",
"z": "4eeabf29980b7298",
"name": "lLink Out goto Check QH_Ct",
"mode": "link",
"links": [
"8c38ca1bc423e6d4"
],
"x": 517,
"y": 640,
"wires": []
},
{
"id": "e93de54cbd0ac9c3",
"type": "subflow:733c6535992c1d6d",
"z": "4eeabf29980b7298",
"name": "",
"x": 326,
"y": 144,
"wires": [
[
"2032dc89208a2634"
]
]
},
{
"id": "6f7c6c90fd379093",
"type": "subflow:733c6535992c1d6d",
"z": "4eeabf29980b7298",
"name": "",
"x": 322,
"y": 1040,
"wires": [
[
"13fc4fde7f9865f2"
]
]
},
{
"id": "92dbdc9c9d56234e",
"type": "link out",
"z": "4eeabf29980b7298",
"name": "lLink Out goto Check QH_Ct",
"mode": "link",
"links": [
"8c38ca1bc423e6d4"
],
"x": 523,
"y": 1296,
"wires": []
},
{
"id": "e9e1c719db699afa",
"type": "subflow:733c6535992c1d6d",
"z": "4eeabf29980b7298",
"name": "",
"x": 326,
"y": 384,
"wires": [
[
"fa6755e05d414017"
]
]
},
{
"id": "9b240a9689b3908e",
"type": "switch",
"z": "4eeabf29980b7298",
"name": "Count 96 or not",
"property": "$count($.payload)",
"propertyType": "jsonata",
"rules": [
{
"t": "btwn",
"v": "95",
"vt": "num",
"v2": "97",
"v2t": "num"
},
{
"t": "else"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 336,
"y": 848,
"wires": [
[
"aea4dabe57116bad"
],
[
"26acc274e13a9f15"
]
]
},
{
"id": "095ba271fd6ed19a",
"type": "change",
"z": "4eeabf29980b7298",
"name": "Read Global Tomorrow_QH",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "Victron_MESS.Tibber.Tomorrow_QH",
"tot": "global",
"dc": true
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 376,
"y": 704,
"wires": [
[
"2bef3fcceac875e2"
]
]
},
{
"id": "980a5e303861268d",
"type": "tibber-api-endpoint",
"queryUrl": "https://api.tibber.com/v1-beta/gql",
"feedConnectionTimeout": "30",
"feedTimeout": "60",
"queryRequestTimeout": "30",
"name": "Tibber Config Node"
}
]

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57

Over de Context Global Geheugenruimte



MESS is inmiddels behoorlijk uitgebreid. Er zijn meer dan 10 tabbladen waartussen gegevens worden uitgewisseld en gedeeld.
Ik probeer zoveel mogelijk om een bepaalde functionaliteit slechts één keer in het totaal op te nemen. Voor mensen die bekend zijn met programmeren hoef ik dit niet nader toe te lichten.

Bij de start van het programma, bijvoorbeeld na een reboot, is het Context Global geheugengebied nog leeg. Daarom wordt het wegschrijven van veel gegevens met een 'At Deploy' Injectie Node geactiveerd. Voorbeelden zijn vaste waarden (parameters) zoals prijs-grenzen, batterij-capaciteit, API-sleutels, GPS-locatie etc.

Afbeeldingslocatie: https://tweakers.net/i/bV1Wlsa3aktvEFueW3X2AJfiFjE=/x800/filters:strip_icc():strip_exif()/f/image/tgBmeQyYB0GE1fKIQxrYJBK1.jpg?f=fotoalbum_large

Zie ook Flow Parameters - Diverse Parameters

Ook veel gebruikte functies worden aldus aangemaakt en in de Context Global Geheugenruimte geplaatst.

Afbeeldingslocatie: https://tweakers.net/i/TgpHMfl03o8Je_VTEYVxyCoU98A=/x800/filters:strip_icc():strip_exif()/f/image/pjkRGk4dJPK2PP1elrvek2XZ.jpg?f=fotoalbum_large

Flow Parameters - SetGlobalFunctions

Ook de State van een heleboel Victron Entiteiten 'verzamel' ik in de Context Global Geheugenruimte. In de diverse flows waar deze States gebruikt worden hoef ik deze dan alleen uit te lezen en hoef ik geen aandacht te geven aan het opvangen en verzamelen van wijzigingen.

Afbeeldingslocatie: https://tweakers.net/i/5ddPsnjUh5YQVN9ad4n_9kB1wMY=/x800/filters:strip_icc():strip_exif()/f/image/IjiS2CJZnM4I0uV0jJ08uCPX.jpg?f=fotoalbum_large

Omdat de werking van deze Flows redelijk triviaal is zal ik de stappen niet uitgebreid beschrijven.
Voor de volledigheid neem ik hierna wel de actuele stand van de Flows over om te kunnen kopiëren en Plakken.

[ Voor 6% gewijzigd door MJ de Bruijn op 01-12-2025 14:22 ]


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57

Flow Parameters - Make Consump_Forecast_Wh



Omdat een forum bijdrage een maximum aantal tekens toestaat heb ik de 'Flow Parameters' in stukjes moeten opbreken. Dit is deel 1.

Afbeeldingslocatie: https://tweakers.net/i/SohY5kg8z8_y9r4H-Sv1hO2OQIQ=/800x/filters:strip_icc():strip_exif()/f/image/ZiZEFKgpI9g4sqthaUSK2v6u.jpg?f=fotoalbum_large
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
[
    {
        "id": "b90ba71de262da79",
        "type": "function",
        "z": "1fc0ea28e0170585",
        "name": "Make Consump_Forecast_Wh",
        "func": "// Make Consumption Forecast Array\n\nconst Now = new Date();\nlet NowMonth = Now.getMonth();\nlet NowTime = Now.getFullYear() + \"-\"\n            + (\"0\" + (Now.getMonth() + 1)).slice(-2) + \"-\"\n            + (\"0\" + Now.getDate()).slice(-2) + \" \"\n            + (\"0\" + Now.getHours()).slice(-2) + \":\"\n            + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n            + (\"0\" + Now.getSeconds()).slice(-2) ;\n\n\n\nlet MonthFactor = 0;\nswitch(NowMonth) {\n    case 0:  MonthFactor = 1.25;\n             break;\n    case 1:  MonthFactor = 1.2;\n             break;\n    case 2:  MonthFactor = 1.15;\n             break;\n    case 3:  MonthFactor = 1.1;\n             break;\n    case 4:  MonthFactor = 1.05;\n             break;\n    case 5:  MonthFactor = 1;\n             break;\n    case 6:  MonthFactor = 1;\n             break;\n    case 7:  MonthFactor = 1.05;\n             break;\n    case 8:  MonthFactor = 1.1;\n             break;\n    case 9:  MonthFactor = 1.15;\n             break;\n    case 10: MonthFactor = 1.2;\n             break;\n    case 11: MonthFactor = 1.25;\n             break;\n    default: MonthFactor = 1;\n             break;\n}\n\nlet Consump_Forecast_Wh = new Array(192);\n\nfor (let i=0; i<192; i++)\n{\n    Consump_Forecast_Wh[i] = 0;\n}\n\nfor (let i=0; i<=7*4+3; i++)\n{\n    Consump_Forecast_Wh[i] = Math.round(MonthFactor * 120);\n}\nfor (let i=8*4; i<=8*4+3; i++)\n{\n    Consump_Forecast_Wh[i] = Math.round(MonthFactor * 200);\n}\n\nfor (let i=9*4; i<=11*4+3; i++)\n{\n    Consump_Forecast_Wh[i] = Math.round(MonthFactor * 150);\n}\n\nfor (let i=12*4; i<=13*4+3; i++)\n{\n    Consump_Forecast_Wh[i] = Math.round(MonthFactor * 200);\n}\n\nfor (let i=14*4; i<=16*4+3; i++)\n{\n    Consump_Forecast_Wh[i] = Math.round(MonthFactor * 150);\n}\n\nfor (let i=17*4; i<=17*4+3; i++)\n{\n    Consump_Forecast_Wh[i] = Math.round(MonthFactor * 200);\n}\n\nfor (let i=18*4; i<=19*4+3; i++)\n{\n    Consump_Forecast_Wh[i] = Math.round(MonthFactor * 250);\n}\n\nfor (let i=20*4; i<=20*4+3; i++)\n{\n    Consump_Forecast_Wh[i] = Math.round(MonthFactor * 300);\n}\n\nfor (let i=21*4; i<=21*4+3; i++)\n{\n    Consump_Forecast_Wh[i] = Math.round(MonthFactor * 250);\n}\n\nfor (let i=22*4; i<=23*4+3; i++)\n{\n    Consump_Forecast_Wh[i] = Math.round(MonthFactor * 200);\n}\n\nlet Consump_Per_Day_Wh = 0; // estimate Wh\n\nfor (let i = 0; i <= 95; i++) // next day \n{\n    Consump_Per_Day_Wh +=  Consump_Forecast_Wh[i];\n    Consump_Forecast_Wh[i+96] = Consump_Forecast_Wh[i];\n}\n\nglobal.set(\"Victron_MESS.Consump.Forecast\", Consump_Forecast_Wh);\n\nglobal.set(\"Victron_MESS.LastRun.Deploy.Consump_Forecast\"); // remove\nglobal.set(\"Victron_MESS.LastRun.Deploy.Consump_Forecast\", NowTime);\n\nnode.status( \n  { fill:\"green\", \n    shape:\"dot\",\n    text: \"@ \" + NowTime\n  } );\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 390,
        "y": 336,
        "wires": [
            []
        ]
    },
    {
        "id": "80a676ec44349edb",
        "type": "change",
        "z": "1fc0ea28e0170585",
        "name": "",
        "rules": [
            {
                "t": "delete",
                "p": "Victron_MESS.Consump",
                "pt": "global"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 410,
        "y": 240,
        "wires": [
            [
                "f6759a39081a82c7"
            ]
        ]
    },
    {
        "id": "21b119640c6d16db",
        "type": "cronplus",
        "z": "1fc0ea28e0170585",
        "name": "every month",
        "outputField": "payload",
        "timeZone": "",
        "storeName": "",
        "commandResponseMsgOutput": "output1",
        "defaultLocation": "",
        "defaultLocationType": "default",
        "outputs": 1,
        "options": [
            {
                "name": "schedule1",
                "topic": "topic1",
                "payloadType": "default",
                "payload": "",
                "expressionType": "cron",
                "expression": "1 * * 1 * *",
                "location": "",
                "offset": "0",
                "solarType": "all",
                "solarEvents": "sunrise,sunset"
            }
        ],
        "x": 122,
        "y": 192,
        "wires": [
            [
                "80a676ec44349edb"
            ]
        ]
    },
    {
        "id": "f6759a39081a82c7",
        "type": "switch",
        "z": "1fc0ea28e0170585",
        "name": "Consump.Forecast is NULL",
        "property": "Victron_MESS.Consump",
        "propertyType": "global",
        "rules": [
            {
                "t": "null"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 380,
        "y": 288,
        "wires": [
            [
                "b90ba71de262da79"
            ]
        ]
    },
    {
        "id": "4cdd8409aa184b83",
        "type": "inject",
        "z": "1fc0ea28e0170585",
        "name": "1 min",
        "props": [
            {
                "p": "topic",
                "vt": "str"
            },
            {
                "p": "payload"
            }
        ],
        "repeat": "60",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "Check Consump Forecast",
        "payload": "( $moment().format(\"YYYY-MM-DD HH:mm:ss\") )",
        "payloadType": "jsonata",
        "x": 102,
        "y": 288,
        "wires": [
            [
                "f6759a39081a82c7"
            ]
        ]
    },
    {
        "id": "e5fb79aeb0bd81e2",
        "type": "inject",
        "z": "1fc0ea28e0170585",
        "name": "At Deploy",
        "props": [
            {
                "p": "topic",
                "vt": "str"
            },
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": 0.1,
        "topic": "Delete Consump Forecast",
        "payload": "( $moment().format(\"YYYY-MM-DD HH:mm:ss\") )",
        "payloadType": "jsonata",
        "x": 112,
        "y": 240,
        "wires": [
            [
                "80a676ec44349edb"
            ]
        ]
    }
]

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57

Flow Parameters - SetGlobalFunctions



Heel veel functie-Nodes in MESS gebruiken dezelfde berekeningen en gegevens (datums, afronden, formatteren, sorteren). Ik heb deze functionaliteiten vastgelegd in algemene 'Globale' functies. Deze Flow maakt de betreffende functies aan en plaatst deze in de Context Global Geheugenruimte.

Afbeeldingslocatie: https://tweakers.net/i/8kX0RBUXed5aAR8H8GZByO7SeII=/800x/filters:strip_icc():strip_exif()/f/image/ZvwLNCq2WvL8eZkeK1bZen0w.jpg?f=fotoalbum_large
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
[
    {
        "id": "ac3e701902b41060",
        "type": "function",
        "z": "1fc0ea28e0170585",
        "name": "SetGlobalFunctions",
        "func": "// Prepare Javascript functions for multiple nodes\n\nlet DebugStep = \"Start\";\nlet NewLine = \"\\n\\r\";\n    \ntry {\n\n    DebugStep = \"GetNowTime\";\n\n    const GetNowTime = (p_format) =>\n    {\n        const Now = new Date()\n        \n        if (p_format == \"NowHour\")\n        {\n            // return (\"0\" + Now.getHours()).slice(-2);\n            return Now.getHours();\n        }\n        else if ( (p_format == \"NowQuarter\")\n               || (p_format == \"NowQuarterHour\")\n               || (p_format == \"Now_QH\")\n               || (p_format == \"NowQH\")\n               )\n        {\n            return (4 * Now.getHours()\n                 + Math.floor(Now.getMinutes()/15) );\n    \n        }\n        else if (p_format == \"NowMinutes\")\n        {\n            // return (\"0\" + Now.getMinutes()).slice(-2);\n            return Now.getMinutes();\n        }\n        else if (p_format == \"NowInSeconds\")\n        {\n            return Math.floor(Now.getTime() / 1000);\n        }\n        if ( (p_format == \"uu:mm\")\n          || (p_format == \"UU:MM\") )\n        {\n            return (\"0\" + Now.getHours()).slice(-2)\n                    + \":\"\n                    + (\"0\" + Now.getMinutes() ).slice(-2);\n        }\n        else if ( (p_format == \"yyyymmdd\")\n               || (p_format == \"YYYYMMDD\") )\n        {\n            return Now.getFullYear()\n                + (\"0\" + (Now.getMonth() + 1)).slice(-2)\n                + (\"0\" + Now.getDate()).slice(-2);\n        }\n        else if ( (p_format == \"yyyy-mm-dd\")\n               || (p_format == \"YYYY-MM-DD\") )\n        {\n            return Now.getFullYear()\n                + \"-\" + (\"0\" + (Now.getMonth() + 1)).slice(-2)\n                + \"-\" + (\"0\" + Now.getDate()).slice(-2);\n        }\n        else if ( (p_format == \"uu:mm:ss\")\n               || (p_format == \"UU:MM:SS\") )\n        {\n            return (\"0\" + Now.getHours()).slice(-2) + \":\"\n                + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n                + (\"0\" + Now.getSeconds()).slice(-2) ;\n        }\n        else\n        {\n            return Now.getFullYear() + \"-\"\n                + (\"0\" + (Now.getMonth() + 1)).slice(-2) + \"-\"\n                + (\"0\" + Now.getDate()).slice(-2) + \" \"\n                + (\"0\" + Now.getHours()).slice(-2) + \":\"\n                + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n                + (\"0\" + Now.getSeconds()).slice(-2) ;\n        }\n    }\n    \n    global.set(\"Victron_MESS.Functions.GetNowTime\", GetNowTime);\n    \n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    \n    DebugStep = \"GetTomorrow\";\n\n    const GetTomorrow = (p_format) =>\n    {\n        let Tomorrow = new Date();\n        Tomorrow.setDate(Tomorrow.getDate() + 1);\n    \n        if (p_format == \"yyyymmdd\")\n        {\n            return Tomorrow.getFullYear()\n                 + (\"0\" + (Tomorrow.getMonth() + 1)).slice(-2)\n                 + (\"0\" + Tomorrow.getDate()).slice(-2) ;\n        }\n        else\n        {\n            return Tomorrow.getFullYear() + \"-\"\n                 + (\"0\" + (Tomorrow.getMonth() + 1)).slice(-2) + \"-\"\n                 + (\"0\" + Tomorrow.getDate()).slice(-2) ;\n        }\n    }\n    \n    global.set(\"Victron_MESS.Functions.GetTomorrow\", GetTomorrow);\n    \n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    \n    DebugStep = \"GetYesterday\";\n\n    const GetYesterday = (p_format) =>\n    {\n        let Yesterday = new Date();\n        Yesterday.setDate(Yesterday.getDate() - 1);\n    \n        if (p_format == \"yyyymmdd\")\n        {\n            return Yesterday.getFullYear()\n                 + (\"0\" + (Yesterday.getMonth() + 1)).slice(-2)\n                 + (\"0\" + Yesterday.getDate()).slice(-2) ;\n        }\n        else\n        {\n            return Yesterday.getFullYear() + \"-\"\n                 + (\"0\" + (Yesterday.getMonth() + 1)).slice(-2) + \"-\"\n                 + (\"0\" + Yesterday.getDate()).slice(-2) ;\n        }\n    }\n    \n    global.set(\"Victron_MESS.Functions.GetYesterday\", GetYesterday);\n    \n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    \n    DebugStep = \"TimestampTo_HH_MM_SS\";\n\n    const TimestampTo_HH_MM_SS = (p_date) =>\n    {\n        const curdate = new Date(null);\n    \n        curdate.setSeconds(p_date)\n    \n        return ( (\"0\" + curdate.getHours()).slice(-2) + \":\"\n                + (\"0\" + curdate.getMinutes()).slice(-2) + \":\"\n                + (\"0\" + curdate.getSeconds()).slice(-2) \n                );\n                \n    }\n    \n    global.set(\"Victron_MESS.Functions.TimestampTo_HH_MM_SS\", \n               TimestampTo_HH_MM_SS);\n    \n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    \n    DebugStep = \"TimestampTo_QH\";\n\n    const TimestampTo_QH = (p_date) =>\n    {\n        try\n        {\n            var TempDate = new Date(p_date);\n    \n            return (4 * TempDate.getHours()\n                 + Math.floor(TempDate.getMinutes()/15) );\n        }\n        catch (error)\n        {\n            node.warn(\"Error TimestampTo_QH: \" + error);\n            return (\"\");\n        }\n    }\n    \n    global.set(\"Victron_MESS.Functions.TimestampTo_QH\", \n               TimestampTo_QH);\n    \n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    \n    DebugStep = \"QHToText\";\n\n    const QHToText = (p_QH) =>\n    {\n        const QH_Symbol = [\":00\", \":15\", \":30\", \":45\"];\n        \n        let curHour = Math.floor(p_QH/4);\n        let curQH = p_QH - 4 * curHour;\n        let Result = \"\";\n        \n        if (curHour < 24) {\n            Result += curHour.toString()\n            + QH_Symbol[curQH];\n        }\n        else {\n            Result += \"+\" + (curHour-24).toString()\n            + QH_Symbol[curQH];\n        }\n        \n        return Result;\n        \n    }\n    \n    global.set(\"Victron_MESS.Functions.QHToText\", \n               QHToText);\n    \n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    \n    DebugStep = \"Not\";\n\n    const Not = (p_Boolean) =>\n    {\n        return !p_Boolean;\n    }\n    \n    global.set(\"Victron_MESS.Functions.Not\", Not);\n    \n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    \n    DebugStep = \"CtToText\";\n\n    const CtToText = (C) =>\n    {\n        let C1= Round(C, 1);\n        return Number(C1).toFixed(1) + \"ct\";\n    } // CtToText\n    \n    global.set(\"Victron_MESS.Functions.CtToText\", CtToText);\n    \n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    \n    DebugStep = \"HourToText\";\n\n    const HourToText = (H) =>\n    {\n        if (Number(H) <= 23) {\n            return Number(H).toString();\n        }\n        else {\n            return \"+\" + (Number(H)-24).toString();\n        }\n    } // HourToText\n    \n    global.set(\"Victron_MESS.Functions.HourToText\", HourToText);\n    \n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    \n    DebugStep = \"WhToText\";\n\n    const WhToText = (p_Wh, dec) =>\n    {\n        let WhSign = Math.sign(p_Wh);\n        let WhAbs = Math.abs(p_Wh);\n        if (WhAbs >= 1000) {\n            if (dec == null) // 1 decimal\n                { return ((WhSign * Round(WhAbs/100)/10)).toFixed(1) + \"kWh\" }\n            else if (dec == 0)\n                { return ((WhSign * Round(WhAbs/1000))).toFixed(dec) + \"kWh\" }\n            else if (dec == 1)\n                { return ((WhSign * Round(WhAbs/100)/10)).toFixed(dec) + \"kWh\" }\n            else if (dec == 2)\n                { return ((WhSign * Round(WhAbs/10)/100)).toFixed(dec) + \"kWh\" }\n                \n        }\n        else {\n            return (WhSign * Round(WhAbs)).toString() + \"Wh\";\n        }\n    } // WhToText\n    \n    global.set(\"Victron_MESS.Functions.WhToText\", WhToText);\n    \n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    \n    DebugStep = \"kWhToWh\";\n\n    const kWhToWh = (x) =>\n    {\n        return Round(x * 1000);\n        \n    } // kWhToWh\n    \n    global.set(\"Victron_MESS.Functions.kWhToWh\", kWhToWh);\n    \n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    \n    DebugStep = \"Round\";\n\n    const Round = (x, dec) =>\n    {\n        let curSign = Math.sign(x);\n        let x_abs = Math.abs(x);\n        \n        if (dec == null)\n            { return curSign * Math.round(x_abs) }\n        else if (dec == 0)\n            { return curSign * Math.round(x_abs) }\n        else if (dec == 1)\n            { return curSign * Math.round(x_abs*10) / 10 }\n        else if (dec == 2)\n            { return curSign * Math.round(x_abs*100) / 100 }\n        else if (dec == 3)\n            { return curSign * Math.round(x_abs*1000) / 1000 }\n        else if (dec == -1)\n            { return curSign * Math.round(x_abs/10) * 10 }\n        else if (dec == -2)\n            { return curSign * Math.round(x_abs/100) * 100 }\n        else if (dec == -3)\n            { return curSign * Math.round(x_abs/1000) * 1000 }\n        else\n            { return curSign * Math.round(x_abs) }\n    } // Round\n    \n    global.set(\"Victron_MESS.Functions.Round\", Round);\n    \n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    \n    DebugStep = \"Price_incl_Cost\";\n\n    const Price_incl_Cost = (Net_Price, CallerName) =>\n    {\n        var DebugStep = \"Start\";\n        var NewLine = \"\\n\\r\";\n\n    try\n        {\n            DebugStep = \"Test Undefined\";\n            \n            // node.warn(typeof Net_Price);\n            if (typeof Net_Price == 'undefined')\n            {\n                node.warn(\"CallerName: \" + CallerName + NewLine\n                    + \"Price_incl_Cost; Net_Price is: \" + Net_Price);\n        \n            }\n        \n            DebugStep = \"Get Globals\";\n        \n        \tif (context.kWh_TaxCt == null) {\n        \t\tcontext.kWh_TaxCt = \n        \t\t  global.get(\"Victron_MESS.Param.kWh_TaxCt\");\n        \t\t// node.warn(\"kWh_TaxCt: \" + context.kWh_TaxCt);\n        \t\tif (context.kWh_TaxCt == null) {\n        \t\t\tnode.warn(\"Victron_MESS.Param.kWh_TaxCt: null\");\n        \t\t}\n        \t} // if context.kWh_TaxCt == null\n        \t\n        \tif (context.kWh_CostCt == null) {\n        \t\tcontext.kWh_CostCt = \n        \t\t  global.get(\"Victron_MESS.Param.kWh_CostCt\");\n        \t\t// node.warn(\"kWh_CostCt: \" + context.kWh_CostCt);\n        \t\tif (context.kWh_CostCt == null) {\n        \t\t\tnode.warn(\"Victron_MESS.Param.kWh_CostCt: null\");\n        \t\t}\n        \t} // if context.kWh_CostCt == null\n        \t\n        \tif (context.BTW_Perc == null) {\n        \t\tcontext.BTW_Perc = \n        \t\t  global.get(\"Victron_MESS.Param.BTW_Perc\");\n        \t\t// node.warn(\"BTW_Perc: \" + context.BTW_Perc);\n        \t\tif (context.BTW_Perc == null) {\n        \t\t\tnode.warn(\"Victron_MESS.Param.BTW_Perc: null\");\n        \t\t}\n        \t} // if context.BTW_Perc == null\n        \t\n        \tDebugStep = \"Calculate Result\";\n        \t\n            result = (Number(Net_Price) \n        \t          + Number(context.kWh_TaxCt)\n        \t\t\t  + Number(context.kWh_CostCt)\n        \t\t\t )\n        \t\t\t * (1 + Number(context.BTW_Perc) / 100);\n        \t\t\t \n        }\n        catch (error)\n        {\n            node.warn(\"Error in \" + \"Price_incl_Cost\" + \":\" + NewLine\n                + \"CallerName: \" + CallerName + NewLine\n                + \"Step: \" + DebugStep + NewLine\n                + \"Error: \" + error)\n        }\n    \n        return result;\n    }\n    \n    global.set(\"Victron_MESS.Functions.Price_incl_Cost\", Price_incl_Cost);\n    \n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    \n    DebugStep = \"PosInArrayOfSets\";\n    \n    const PosInArrayOfSets = (p_ArrayOfSets, p_SearchQH) =>\n    \n    {\n        let Result = -1;\n        \n        for ( let i = 0; i < p_ArrayOfSets.length; i++)\n        {\n            if ( p_ArrayOfSets[i][0] == p_SearchQH ) \n            {\n                Result = i;\n                break;\n            } // Item was found\n        } // for i\n        \n        return Result;\n        \n    } // PosInArrayOfSets\n    \n    global.set(\"Victron_MESS.Functions.PosInArrayOfSets\", PosInArrayOfSets);\n    \n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    \n    DebugStep = \"IsInArrayOfSets\";\n    \n    const IsInArrayOfSets = (p_ArrayOfSets, p_SearchQHs) =>\n    \n    { // See if any of the items within array p_SearchQHs\n      // are in array p_ArrayOfSets{hour, value]\n        let Result = false;\n        \n        for (let i = 0; i < p_SearchQHs.length; i++) {\n            for ( let j = 0; j < p_ArrayOfSets.length; j++)\n            {\n                if ( p_ArrayOfSets[j][0] == p_SearchQHs[i] ) \n                {\n                    Result = true;\n                    break;\n                } // Item was found\n            } // for j\n            if (Result) break;\n        } // all items\n        \n        return Result; // not found\n        \n    } // IsInArrayOfSets\n    \n    global.set(\"Victron_MESS.Functions.IsInArrayOfSets\", IsInArrayOfSets);\n    \n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    \n    DebugStep = \"ArrayOfSetsGetPriceByHour\";\n    \n    const ArrayOfSetsGetPriceByHour = (p_ArrayOfSets, p_SearchQH) =>\n    \n    { // Find the price of the specified hour \n      // is in array p_ArrayOfSets{hour, value]\n        let Result = null;\n        \n        for (let i = 0; i < p_ArrayOfSets.length; i++) {\n            if ( p_ArrayOfSets[i][0] == p_SearchQH ) \n            {\n                Result = p_ArrayOfSets[i][1];\n                break;\n            } // Item was found\n        } // all items\n        \n        return Result;\n        \n    } // ArrayOfSetsGetPriceByHour\n    \n    global.set(\"Victron_MESS.Functions.ArrayOfSetsGetPriceByHour\", \n                  ArrayOfSetsGetPriceByHour);\n    \n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    \n    DebugStep = \"ArrayOfSets_SortByPrice_Asc\";\n    \n    const ArrayOfSets_SortByPrice_Asc = (pair_1, pair_2) =>\n    {\n        // each param a, b contains [ hour, price ]\n        // So, argument [0] is hour, argument [1] is price\n        if ( ( (pair_1[0] <= 23) && (pair_2[0] <= 23) )\n          || ( (pair_1[0] > 23)  && (pair_2[0] > 23)  )\n           ) // same day\n        {\n          if (pair_1[1] < pair_2[1])\n          { return -1 }\n          else { return 1 }\n            \n        } \n        else if ( (pair_1[0] <= 23) && (pair_2[0] > 23) )\n        { return -1 } \n        else { return 1 } \n        \n    } // ArrayOfSets_SortByPriceFn\n    \n    global.set(\"Victron_MESS.Functions.ArrayOfSets_SortByPrice_Asc\", \n                 ArrayOfSets_SortByPrice_Asc);\n    \n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    \n    DebugStep = \"ArrayOfSets_SortByPrice_Desc\";\n    \n    const ArrayOfSets_SortByPrice_Desc = (pair_1, pair_2) =>\n    {\n        // each param a, b contains [ hour, price ]\n        // So, argument [0] is hour, argument [1] is price\n        if ( ( (pair_1[0] <= 23) && (pair_2[0] <= 23) )\n          || ( (pair_1[0] > 23)  && (pair_2[0] > 23)  )\n           ) // same day\n        {\n          if (pair_2[1] < pair_1[1])\n          { return -1 }\n          else { return 1 }\n            \n        } \n        else if ( (pair_1[0] <= 23) && (pair_2[0] > 23) )\n        { return -1 } \n        else { return 1 } \n        \n    } // ArrayOfSets_SortByPrice_Desc\n    \n    global.set(\"Victron_MESS.Functions.ArrayOfSets_SortByPrice_Desc\", \n                 ArrayOfSets_SortByPrice_Desc);\n    \n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    \n    DebugStep = \"ArrayOfSets_SortOnlyByPrice_Asc\";\n    \n    const ArrayOfSets_SortOnlyByPrice_Asc = (pair_1, pair_2) =>\n    {\n        // each param a, b contains [ hour, price ]\n        // So, argument [0] is hour, argument [1] is price\n        if (pair_1[1] < pair_2[1])\n        { return -1 }\n        else\n        { return 1 }\n    \n    } // ArrayOfSets_SortOnlyByPrice_Asc\n    \n    global.set(\"Victron_MESS.Functions.ArrayOfSets_SortOnlyByPrice_Asc\", \n                 ArrayOfSets_SortOnlyByPrice_Asc);\n    \n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n    DebugStep = \"Add_Trace\";\n    \n    const Add_Trace = (p_ID, NewText) =>\n    {\n        \n        // node.warn(NewText);\n        \n        if (p_ID == null)\n        {\n            node.warn(p_ID)\n        }\n        \n        let curTraceText = global.get(\"Trace.\" + p_ID);\n\n        if (curTraceText == null)\n        {\n            curTraceText = [\"\"];\n        }\n\n        let curTraceTextIndex = curTraceText.length - 1;\n\n        if (NewText == \"ClearTrace\")\n        {\n            curTraceText = [\"\"];\n            curTraceTextIndex = 0;\n\n        }\n\n        else if ( (curTraceText[curTraceTextIndex].length + NewText.length) > 1000)\n        {\n            curTraceTextIndex++;\n            curTraceText[curTraceTextIndex] += NewLine;\n            curTraceText[curTraceTextIndex] = NewText;\n        }\n        else\n        {\n            curTraceText[curTraceTextIndex] += NewText;\n        }\n\n        global.set(\"Trace.\" + p_ID , curTraceText);\n\n        return null;\n    \n    } // Add_Trace\n\n    global.set(\"Victron_MESS.Functions.Add_Trace\", \n                 Add_Trace);\n    \n    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n    \n    DebugStep = \"Finish\";\n    \n    // And use this one:\n    let NowTime = GetNowTime();\n    \n    global.set(\"Victron_MESS.LastRun.Deploy.Functions\"); // remove\n    global.set(\"Victron_MESS.LastRun.Deploy.Functions\", NowTime);\n    \n    node.status( \n      { fill:\"green\", \n        shape:\"dot\",\n        text: \"@ \" + NowTime\n      } );\n\n}\ncatch (error)\n{\n    node.warn(\"Error in \" + \"SetGlobalFunctions\" + \":\" + NewLine\n    + \"Step: \" + DebugStep + NewLine\n    + \"Error: \" + error)\n\n    node.status( \n    { fill:\"red\", \n        shape:\"dot\",\n        text: \"@ \" + NowTime + \" \"\n        + \"Step: \" + DebugStep + \"; Error: \" + error }\n    );\n    \n}\n\nreturn msg;\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 346,
        "y": 512,
        "wires": [
            []
        ]
    },
    {
        "id": "1617e6b7dce07a36",
        "type": "change",
        "z": "1fc0ea28e0170585",
        "name": "",
        "rules": [
            {
                "t": "delete",
                "p": "Victron_MESS.Functions",
                "pt": "global"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 410,
        "y": 416,
        "wires": [
            [
                "700472570d90bc88"
            ]
        ]
    },
    {
        "id": "700472570d90bc88",
        "type": "switch",
        "z": "1fc0ea28e0170585",
        "name": "Functions is NULL",
        "property": "Victron_MESS.Functions",
        "propertyType": "global",
        "rules": [
            {
                "t": "null"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 350,
        "y": 464,
        "wires": [
            [
                "ac3e701902b41060"
            ]
        ]
    },
    {
        "id": "a1dc31488e292602",
        "type": "inject",
        "z": "1fc0ea28e0170585",
        "name": "1 min",
        "props": [
            {
                "p": "topic",
                "vt": "str"
            },
            {
                "p": "payload"
            }
        ],
        "repeat": "60",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "Check Functions",
        "payload": "( $moment().format(\"YYYY-MM-DD HH:mm:ss\") )",
        "payloadType": "jsonata",
        "x": 102,
        "y": 464,
        "wires": [
            [
                "700472570d90bc88"
            ]
        ]
    },
    {
        "id": "ede884d4c779e08d",
        "type": "inject",
        "z": "1fc0ea28e0170585",
        "name": "On Demand",
        "props": [
            {
                "p": "topic",
                "vt": "str"
            },
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "Delete Functions",
        "payload": "( $moment().format(\"YYYY-MM-DD HH:mm:ss\") )",
        "payloadType": "jsonata",
        "x": 122,
        "y": 416,
        "wires": [
            [
                "1617e6b7dce07a36"
            ]
        ]
    }
]

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57

Flow Parameters - Diverse parameters



Omdat de instelling van parameters per setup zal verschillen geef ik hierna slechts een selectie van mijn gegevens als voorbeeld.
(Een bijkomende reden is dat de lengte van een Forum bijdrage de lengte van het totaal niet toestaat).

Deel 3.

Afbeeldingslocatie: https://tweakers.net/i/ShuROjKeA6MvJimZxyPGIupoWF4=/800x/filters:strip_icc():strip_exif()/f/image/sVWNtBdHOmxRxIsEFNsNkdYB.jpg?f=fotoalbum_large
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
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
[
    {
        "id": "f0950da7d6ff8217",
        "type": "change",
        "z": "1fc0ea28e0170585",
        "name": "Conversion_Loss_Factor (0.9)",
        "rules": [
            {
                "t": "set",
                "p": "Victron_MESS.Param.Conversion_Loss_Factor",
                "pt": "global",
                "to": "0.9",
                "tot": "num"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 394,
        "y": 1216,
        "wires": [
            []
        ]
    },
    {
        "id": "537a960b51cab901",
        "type": "change",
        "z": "1fc0ea28e0170585",
        "name": "Buy_Price_Difference_LimitCt (10)",
        "rules": [
            {
                "t": "set",
                "p": "Victron_MESS.Param.Buy_Price_Difference_LimitCt",
                "pt": "global",
                "to": "10",
                "tot": "num"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 404,
        "y": 976,
        "wires": [
            []
        ]
    },
    {
        "id": "47cdf21f872f0a11",
        "type": "change",
        "z": "1fc0ea28e0170585",
        "name": "PV_Turn_Off_Price_LimitCt (-15)",
        "rules": [
            {
                "t": "set",
                "p": "Victron_MESS.Param.PV_Turn_Off_Price_LimitCt",
                "pt": "global",
                "to": "-15",
                "tot": "num"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 404,
        "y": 1168,
        "wires": [
            []
        ]
    },
    {
        "id": "70a50f4186c1168a",
        "type": "change",
        "z": "1fc0ea28e0170585",
        "name": "VeryLowPriceThresholdCt (2)",
        "rules": [
            {
                "t": "set",
                "p": "Victron_MESS.Param.VeryLowPriceThresholdCt",
                "pt": "global",
                "to": "2",
                "tot": "num"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 380,
        "y": 880,
        "wires": [
            []
        ]
    },
    {
        "id": "7479116b1db6bdf3",
        "type": "change",
        "z": "1fc0ea28e0170585",
        "name": "ZeroPriceThresholdCt (0.1)",
        "rules": [
            {
                "t": "set",
                "p": "Victron_MESS.Param.ZeroPriceThresholdCt",
                "pt": "global",
                "to": "0.1",
                "tot": "num"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 380,
        "y": 832,
        "wires": [
            []
        ]
    },
    {
        "id": "efd628fc51655937",
        "type": "change",
        "z": "1fc0ea28e0170585",
        "name": "NegativePriceThresholdCt (-0.5)",
        "rules": [
            {
                "t": "set",
                "p": "Victron_MESS.Param.NegativePriceThresholdCt",
                "pt": "global",
                "to": "-0.5",
                "tot": "num"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 390,
        "y": 784,
        "wires": [
            []
        ]
    },
    {
        "id": "2946ee12ca65a101",
        "type": "change",
        "z": "1fc0ea28e0170585",
        "name": "Energy_to_Sell_Buffer_Wh (10)",
        "rules": [
            {
                "t": "set",
                "p": "Victron_MESS.Param.Energy_to_Sell_Buffer_Wh",
                "pt": "global",
                "to": "10",
                "tot": "num"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 390,
        "y": 736,
        "wires": [
            []
        ]
    },
    {
        "id": "8ee10c9a91ed118c",
        "type": "delay",
        "z": "1fc0ea28e0170585",
        "name": "",
        "pauseType": "delayv",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 116,
        "y": 1216,
        "wires": [
            [
                "f0950da7d6ff8217",
                "21aee576d83d6976"
            ]
        ]
    },
    {
        "id": "752db992978083ee",
        "type": "delay",
        "z": "1fc0ea28e0170585",
        "name": "",
        "pauseType": "delayv",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 116,
        "y": 976,
        "wires": [
            [
                "537a960b51cab901",
                "dea649f4022b8e08"
            ]
        ]
    },
    {
        "id": "f259ba3bc3206215",
        "type": "delay",
        "z": "1fc0ea28e0170585",
        "name": "",
        "pauseType": "delayv",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 116,
        "y": 1168,
        "wires": [
            [
                "47cdf21f872f0a11",
                "8ee10c9a91ed118c"
            ]
        ]
    },
    {
        "id": "36b4d70a38d7013d",
        "type": "delay",
        "z": "1fc0ea28e0170585",
        "name": "",
        "pauseType": "delayv",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 112,
        "y": 880,
        "wires": [
            [
                "70a50f4186c1168a",
                "3ca86c5202dddb8a"
            ]
        ]
    },
    {
        "id": "a3f8adbd2d3d5bd1",
        "type": "delay",
        "z": "1fc0ea28e0170585",
        "name": "",
        "pauseType": "delayv",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 112,
        "y": 832,
        "wires": [
            [
                "7479116b1db6bdf3",
                "36b4d70a38d7013d"
            ]
        ]
    },
    {
        "id": "bb14770781e92314",
        "type": "delay",
        "z": "1fc0ea28e0170585",
        "name": "",
        "pauseType": "delayv",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 112,
        "y": 784,
        "wires": [
            [
                "efd628fc51655937",
                "a3f8adbd2d3d5bd1"
            ]
        ]
    },
    {
        "id": "21aee576d83d6976",
        "type": "delay",
        "z": "1fc0ea28e0170585",
        "name": "",
        "pauseType": "delayv",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 116,
        "y": 1264,
        "wires": [
            [
                "e89af4aee2f66aac",
                "808e7401f1e5a657"
            ]
        ]
    },
    {
        "id": "808e7401f1e5a657",
        "type": "change",
        "z": "1fc0ea28e0170585",
        "name": "Batt_Capacity_Wh (3 x 5.12 x 1000)",
        "rules": [
            {
                "t": "set",
                "p": "Victron_MESS.Param.Batt_Capacity_Wh",
                "pt": "global",
                "to": "15360",
                "tot": "num"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 414,
        "y": 1264,
        "wires": [
            []
        ]
    },
    {
        "id": "d311a4d4a273c069",
        "type": "change",
        "z": "1fc0ea28e0170585",
        "name": "kWh_TaxCt (10.15)",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "kWh_TaxCt",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "10.15",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "Victron_MESS.Param.kWh_TaxCt",
                "pt": "global",
                "to": "payload",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 354,
        "y": 1024,
        "wires": [
            [
                "3d55964532332309"
            ]
        ]
    },
    {
        "id": "dda3f009a9085fa7",
        "type": "change",
        "z": "1fc0ea28e0170585",
        "name": "kWh_CostCt (2.05)",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "kWh_CostCt",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "2.05",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "Victron_MESS.Param.kWh_CostCt",
                "pt": "global",
                "to": "payload",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 354,
        "y": 1072,
        "wires": [
            [
                "3d55964532332309"
            ]
        ]
    },
    {
        "id": "e62627e031e3d5dd",
        "type": "change",
        "z": "1fc0ea28e0170585",
        "name": "BTW_Perc (21)",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "BTW_Perc",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "21",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "Victron_MESS.Param.BTW_Perc",
                "pt": "global",
                "to": "payload",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 344,
        "y": 1120,
        "wires": [
            [
                "3d55964532332309"
            ]
        ]
    },
    {
        "id": "dea649f4022b8e08",
        "type": "delay",
        "z": "1fc0ea28e0170585",
        "name": "",
        "pauseType": "delayv",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 116,
        "y": 1024,
        "wires": [
            [
                "6545a2764919e29b",
                "d311a4d4a273c069"
            ]
        ]
    },
    {
        "id": "6545a2764919e29b",
        "type": "delay",
        "z": "1fc0ea28e0170585",
        "name": "",
        "pauseType": "delayv",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 116,
        "y": 1072,
        "wires": [
            [
                "c455bcedceb07a20",
                "dda3f009a9085fa7"
            ]
        ]
    },
    {
        "id": "c455bcedceb07a20",
        "type": "delay",
        "z": "1fc0ea28e0170585",
        "name": "",
        "pauseType": "delayv",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 116,
        "y": 1120,
        "wires": [
            [
                "e62627e031e3d5dd",
                "f259ba3bc3206215"
            ]
        ]
    },
    {
        "id": "3d55964532332309",
        "type": "function",
        "z": "1fc0ea28e0170585",
        "name": "Calc All Cost",
        "func": "context = context || {};\n\nGetNowTime = global.get(\"Victron_MESS.Functions.GetNowTime\");\nlet NowTime = GetNowTime();\n\nRound = global.get(\"Victron_MESS.Functions.Round\");\n\nif (msg.topic == \"kWh_TaxCt\")\n{\n    context.kWh_TaxCt = Number(msg.payload);\n    // node.warn(\"kWh_TaxCt: \" + context.kWh_TaxCt);\n    return;\n}\nelse if (msg.topic == \"kWh_CostCt\")\n{\n    context.kWh_CostCt = Number(msg.payload);\n    // node.warn(\"kWh_CostCt: \" + context.kWh_CostCt);\n    return;\n}\nelse if (msg.topic == \"BTW_Perc\")\n{\n    context.BTW_Perc = Number(msg.payload);\n    // node.warn(\"BTW_Perc: \" + context.BTW_Perc);\n}\n\nif ((context.kWh_TaxCt != null)\n&&  (context.kWh_CostCt != null)\n&&  (context.BTW_Perc != null) )\n{\n    All_CostCt = \n    Round( (context.kWh_TaxCt + context.kWh_CostCt)\n     * (1 + context.BTW_Perc/100), 2 ).toString();\n\n\n    msg.type = \"call_service\";\n    msg.domain = \"input_text\";\n    msg.service = \"set_value\";\n    msg.entity_id = \"input_number.dayahead_all_costct\";\n\n    msg.topic = \"All_CostCt\";\n    msg.payload = All_CostCt;\n    \n    global.set (\"Victron_MESS.Param.All_CostCt\") // remove\n    global.set (\"Victron_MESS.Param.All_CostCt\", All_CostCt)\n\n    node.status( \n      { fill: \"green\", \n        shape: \"dot\",\n        text: \"@ \" + NowTime + \", All Cost: \"+ msg.payload\n      } );\n    \n}\nelse\n{\n    node.status( \n      { fill: \"red\", \n        shape: \"dot\",\n        text: \"@ \" + NowTime + \"Error\"\n      } );\n      \n    msg = null;\n}\n\nreturn msg;\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 558,
        "y": 1120,
        "wires": [
            [
                "7f03e8f188d4ad1c"
            ]
        ]
    },
    {
        "id": "7f03e8f188d4ad1c",
        "type": "link out",
        "z": "1fc0ea28e0170585",
        "name": "Link AllCost to Home Assistant Api",
        "mode": "link",
        "links": [
            "30a7afbf79ee7ae6"
        ],
        "x": 661,
        "y": 1120,
        "wires": []
    },
    {
        "id": "b7d518dd3146316f",
        "type": "change",
        "z": "1fc0ea28e0170585",
        "name": "Delay 50",
        "rules": [
            {
                "t": "set",
                "p": "delay",
                "pt": "msg",
                "to": "50",
                "tot": "num"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 112,
        "y": 688,
        "wires": [
            [
                "5e39357b0d66a4bd"
            ]
        ]
    },
    {
        "id": "3259667b30c1f855",
        "type": "inject",
        "z": "1fc0ea28e0170585",
        "name": "At Deploy",
        "props": [
            {
                "p": "topic",
                "vt": "str"
            },
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": 0.1,
        "topic": "Deploy Params",
        "payload": "( $moment().format(\"YYYY-MM-DD HH:mm:ss\") )",
        "payloadType": "jsonata",
        "x": 112,
        "y": 640,
        "wires": [
            [
                "b7d518dd3146316f"
            ]
        ]
    },
    {
        "id": "5e39357b0d66a4bd",
        "type": "delay",
        "z": "1fc0ea28e0170585",
        "name": "",
        "pauseType": "delayv",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 112,
        "y": 736,
        "wires": [
            [
                "bb14770781e92314",
                "2946ee12ca65a101"
            ]
        ]
    },
    {
        "id": "c981900724b44000",
        "type": "cronplus",
        "z": "1fc0ea28e0170585",
        "name": "at 00:00:00",
        "outputField": "payload",
        "timeZone": "",
        "storeName": "",
        "commandResponseMsgOutput": "output1",
        "defaultLocation": "",
        "defaultLocationType": "default",
        "outputs": 1,
        "options": [
            {
                "name": "schedule1",
                "topic": "topic1",
                "payloadType": "default",
                "payload": "",
                "expressionType": "cron",
                "expression": "0 0 0 * * * * ",
                "location": "",
                "offset": "0",
                "solarType": "all",
                "solarEvents": "sunrise,sunset"
            }
        ],
        "x": 122,
        "y": 592,
        "wires": [
            [
                "b7d518dd3146316f"
            ]
        ]
    },
    {
        "id": "3e071cd9382fde27",
        "type": "change",
        "z": "1fc0ea28e0170585",
        "name": "Sell_Price_Difference_LimitCt (5)",
        "rules": [
            {
                "t": "set",
                "p": "Victron_MESS.Param.Sell_Price_Difference_LimitCt",
                "pt": "global",
                "to": "5",
                "tot": "num"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 404,
        "y": 928,
        "wires": [
            []
        ]
    },
    {
        "id": "3ca86c5202dddb8a",
        "type": "delay",
        "z": "1fc0ea28e0170585",
        "name": "",
        "pauseType": "delayv",
        "timeout": "1",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 116,
        "y": 928,
        "wires": [
            [
                "752db992978083ee",
                "3e071cd9382fde27"
            ]
        ]
    }
]

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57

Flow States - Calc Solar Surplus Now



Dit gedeelte monitort voortdurend of er meer zonne-energie wordt opgewekt dan verbruikt (door 'het huis').
De uitkomst wordt gebruikt bij het kiezen tussen wel of niet rechtstreeks gebruiken van het Grid.

Afbeeldingslocatie: https://tweakers.net/i/XOw8nq5sHvt7v4qxE_lFePCUgvY=/800x/filters:strip_icc():strip_exif()/f/image/kxUDQJeTKYTfxRdecsuv5T6X.jpg?f=fotoalbum_large
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
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
[
    {
        "id": "c07eb22635b761ff",
        "type": "victron-input-vebus",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.vebus/276",
        "path": "/Ac/Out/L1/P",
        "serviceObj": {
            "service": "com.victronenergy.vebus/276",
            "name": "MultiPlus-II 48/3000/35-32"
        },
        "pathObj": {
            "path": "/Ac/Out/L1/P",
            "type": "float",
            "name": "Output power phase 1 (W)"
        },
        "name": "AC_OUT_L1_W",
        "onlyChanges": false,
        "x": 140,
        "y": 192,
        "wires": [
            [
                "fc6b981413443dfd"
            ]
        ]
    },
    {
        "id": "3f861b9b58be2427",
        "type": "victron-input-vebus",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.vebus/276",
        "path": "/Ac/Out/L2/P",
        "serviceObj": {
            "service": "com.victronenergy.vebus/276",
            "name": "MultiPlus-II 48/3000/35-32"
        },
        "pathObj": {
            "path": "/Ac/Out/L2/P",
            "type": "float",
            "name": "Output power phase 2 (W)"
        },
        "name": "AC_OUT_L2_W",
        "onlyChanges": false,
        "x": 140,
        "y": 240,
        "wires": [
            [
                "fc6b981413443dfd"
            ]
        ]
    },
    {
        "id": "24c7e101fbb5eab5",
        "type": "victron-input-vebus",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.vebus/276",
        "path": "/Ac/Out/L3/P",
        "serviceObj": {
            "service": "com.victronenergy.vebus/276",
            "name": "MultiPlus-II 48/3000/35-32"
        },
        "pathObj": {
            "path": "/Ac/Out/L3/P",
            "type": "float",
            "name": "Output power phase 3 (W)"
        },
        "name": "AC_OUT_L3_W",
        "onlyChanges": false,
        "x": 140,
        "y": 288,
        "wires": [
            [
                "fc6b981413443dfd"
            ]
        ]
    },
    {
        "id": "cd32c1cf5b4f93db",
        "type": "victron-input-vebus",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.vebus/276",
        "path": "/Ac/ActiveIn/L1/P",
        "serviceObj": {
            "service": "com.victronenergy.vebus/276",
            "name": "MultiPlus-II 48/3000/35-32"
        },
        "pathObj": {
            "path": "/Ac/ActiveIn/L1/P",
            "type": "float",
            "name": "Input power phase 1 (W)"
        },
        "name": "AC_IN_L1_W",
        "onlyChanges": false,
        "roundValues": "0",
        "x": 130,
        "y": 384,
        "wires": [
            [
                "fc6b981413443dfd"
            ]
        ]
    },
    {
        "id": "a74a1fe15478954b",
        "type": "victron-input-vebus",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.vebus/276",
        "path": "/Ac/ActiveIn/L2/P",
        "serviceObj": {
            "service": "com.victronenergy.vebus/276",
            "name": "MultiPlus-II 48/3000/35-32"
        },
        "pathObj": {
            "path": "/Ac/ActiveIn/L2/P",
            "type": "float",
            "name": "Input power phase 2 (W)"
        },
        "name": "AC_IN_L2_W",
        "onlyChanges": false,
        "roundValues": "0",
        "x": 130,
        "y": 432,
        "wires": [
            [
                "fc6b981413443dfd"
            ]
        ]
    },
    {
        "id": "d14de3d297bde2bc",
        "type": "victron-input-vebus",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.vebus/276",
        "path": "/Ac/ActiveIn/L3/P",
        "serviceObj": {
            "service": "com.victronenergy.vebus/276",
            "name": "MultiPlus-II 48/3000/35-32"
        },
        "pathObj": {
            "path": "/Ac/ActiveIn/L3/P",
            "type": "float",
            "name": "Input power phase 3 (W)"
        },
        "name": "AC_IN_L3_W",
        "onlyChanges": false,
        "roundValues": "0",
        "x": 130,
        "y": 480,
        "wires": [
            [
                "fc6b981413443dfd"
            ]
        ]
    },
    {
        "id": "bbeb63cd759c1b2b",
        "type": "victron-input-solarcharger",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.solarcharger/279",
        "path": "/Yield/Power",
        "serviceObj": {
            "service": "com.victronenergy.solarcharger/279",
            "name": "SmartSolar Charger MPPT 100/20 48V"
        },
        "pathObj": {
            "path": "/Yield/Power",
            "type": "float",
            "name": "PV Power (W)"
        },
        "name": "Solar_Charger_W",
        "onlyChanges": false,
        "roundValues": "0",
        "x": 150,
        "y": 336,
        "wires": [
            [
                "fc6b981413443dfd"
            ]
        ]
    },
    {
        "id": "fc6b981413443dfd",
        "type": "function",
        "z": "873bf4cb2fbee905",
        "name": "Calc Solar Surplus Now",
        "func": "const ProgName = \"Calc Solar Surplus Now\";\n\ncontext = context || {};\n\nlet Now = new Date();\nStartTime = Math.floor(Now.getTime() / 1000);\n\nlet NewLine = \"\\n\\r\";\n\nif (context.First_StartTime == null)\n{\n    context.First_StartTime = StartTime;\n}\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\nif ( !(Get_Functions() ) )\n{   \n    return null;\n}\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\nfunction Get_Functions() {\n\nlet NewLine = \"\\n\\r\";\nlet FuncCount = 0;\nlet FuncNull = 0;\nlet ErrText = \"\";\nlet Result = true;\n\ntry {\n\n    FuncCount++;\n    GetNowTime = global.get(\"Victron_MESS.Functions.GetNowTime\");\n    if (GetNowTime  == null)\n    {   ErrText += \"Victron_MESS.Functions.GetNowTime\" + NewLine;\n        FuncNull += 1;\n        Result = false;\n    }\n\n    FuncCount++;\n    Not = global.get(\"Victron_MESS.Functions.Not\");\n    if (Not  == null)\n    {   ErrText += \"Victron_MESS.Functions.Not\" + NewLine;\n        FuncNull += 1;\n        Result = false;\n    }\n    \n    FuncCount++;\n    Round = global.get(\"Victron_MESS.Functions.Round\");\n    if (Round  == null)\n    {   ErrText += \"Victron_MESS.Functions.Round\" + NewLine;\n        FuncNull += 1;\n        Result = false;\n    }\n\n    if ( !(Result) )\n    {\n        node.status(\n            {fill:\"black\",\n            shape:\"dot\",\n            text: \"Wait for Get_Functions \"\n                + \"(\" + FuncNull + \"/\" + FuncCount + \")...\"\n            }\n        );\n    }\n    \n    if (ErrText > \"\")\n    {   \n        if (context.Next_Error_Time == null)\n        {\n            context.Next_Error_Time = StartTime;\n        }\n        if (StartTime >= context.Next_Error_Time)\n        {\n            node.warn(\"Error Get_Functions:\" + NewLine + ErrText);\n            context.Next_Error_Time = StartTime + 15;\n        }\n\n        node.status({fill:\"red\", shape:\"dot\",\n                     text: ProgName + \": Error Get_Functions\" }\n                   );\n    }\n    \n} // try\ncatch (error)\n{\n    Result = false;\n    \n    node.error(\"Program: \" + ProgName + NewLine\n    + \"Step: Get_Functions\" + NewLine\n    + \"Error: \" + error)\n    \n} // catch\n\nreturn Result;\n    \n} // Get_Functions\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\nlet NowTime = GetNowTime(\"uu:mm:ss\");\nlet StartInSeconds = GetNowTime(\"NowInSeconds\");\n\nvar Energy_from_Solar_Now_W = 0;\nvar Energy_to_Non_Crit_W = 0;\nvar Energy_to_Critical_W = 0;\n\nif (context.Energy_from_Solar_Total_W == null) {\n    context.Energy_from_Solar_Total_W = 0;\n}\nif (context.MsgCount == null) {\n    context.MsgCount = 0;\n}\nif (context.NextOutputTime == null) {\n    context.NextOutputTime = StartInSeconds;\n}\n\nswitch (msg.topic) {\n    \n  case \"RunNow\":\n    context.RunNow = true;\n    msg = null;\n    break;\n    \n  case \"AC_IN_L1_W\":\n    context.AC_IN_L1_W = msg.payload;  \n    msg = null;\n    break;\n    \n  case \"AC_IN_L2_W\":\n    context.AC_IN_L2_W = msg.payload;  \n    msg = null;\n    break;\n    \n  case \"AC_IN_L3_W\":\n    context.AC_IN_L3_W = msg.payload;  \n    msg = null;\n    break;\n    \n  case \"AC_OUT_L1_W\":\n    context.AC_OUT_L1_W = msg.payload;  \n    msg = null;\n    break;\n    \n  case \"AC_OUT_L2_W\":\n    context.AC_OUT_L2_W = msg.payload;  \n    msg = null;\n    break;\n    \n  case \"AC_OUT_L3_W\":\n    context.AC_OUT_L3_W = msg.payload;  \n    msg = null;\n    break;\n\n  case \"Solar_Charger_W\":\n    context.Solar_Charger_W = msg.payload;\n    msg = null;\n    break;\n    \n  case \"ET340_L1_W\":\n    context.ET340_L1_W = msg.payload;  \n    msg = null;\n    break;\n    \n  case \"ET340_L2_W\":\n    context.ET340_L2_W = msg.payload;  \n    msg = null;\n    break;\n    \n  case \"ET340_L3_W\":\n    context.ET340_L3_W = msg.payload;  \n    msg = null;\n    break;\n     \n  case \"SolarEdge_L1_W\":\n    context.SolarEdge_L1_W = msg.payload;  \n    msg = null;\n    break;\n     \n  case \"SolarEdge_L2_W\":\n    context.SolarEdge_L2_W = msg.payload;  \n    msg = null;\n    break;\n     \n  case \"SolarEdge_L3_W\":\n    context.SolarEdge_L3_W = msg.payload;  \n    msg = null;\n    break;\n     \n  default: \n      ErrorText += \"Unknown Topic: \" + msg.topic \n      + \" Payload: \" + msg.payload\n      + \"\\r\";\n      context.RunNow = true;\n      \n} // switch for input all variables\n\nif   (  (context.AC_IN_L1_W != null)\n    && (context.AC_IN_L2_W != null)\n    && (context.AC_IN_L3_W != null)\n    && (context.AC_OUT_L1_W != null)\n    && (context.AC_OUT_L2_W != null)\n    && (context.AC_OUT_L3_W != null)\n    && (context.Solar_Charger_W != null)\n    && (context.ET340_L1_W != null)\n    && (context.ET340_L2_W != null)\n    && (context.ET340_L3_W != null)\n    && (context.SolarEdge_L1_W != null)\n    && (context.SolarEdge_L2_W != null)\n    && (context.SolarEdge_L3_W != null)\n    )\n{\n\n    // if AC-OUT is positive, this is consumption,\n    // if AC-OUT is negative then PV available.\n    // Change sign to calculate with positive value\n    Energy_from_Solar_Now_W = - context.AC_OUT_L1_W\n                          - context.AC_OUT_L2_W \n                          - context.AC_OUT_L3_W;\n\n    Energy_from_Solar_Now_W += context.Solar_Charger_W;\n\n    // NOTE: SOLAREDGE VALUES ARE NEGATIVE\n\n    // Calculate Usage by critical devices\n    Energy_to_Critical_W = context.AC_OUT_L1_W \n                         + context.AC_OUT_L2_W \n                         + context.AC_OUT_L3_W\n                         + Math.abs(context.SolarEdge_L1_W) \n                         + Math.abs(context.SolarEdge_L2_W)\n                         + Math.abs(context.SolarEdge_L3_W);\n                         \n\n    Energy_to_Critical_W = Math.max(0, Energy_to_Critical_W);\n\n    // Calculate Usage by non-critical devices\n    Energy_to_Non_Crit_W = context.ET340_L1_W \n                         + context.ET340_L2_W \n                         + context.ET340_L3_W\n                         - context.AC_IN_L1_W \n                         - context.AC_IN_L2_W \n                         - context.AC_IN_L3_W;\n                         \n    Energy_to_Non_Crit_W = Math.max(0, Energy_to_Non_Crit_W);\n\n    Energy_from_Solar_Now_W -= Energy_to_Non_Crit_W;\n\n    Energy_from_Solar_Now_W = Math.max(Energy_from_Solar_Now_W, 0);\n    \n    // Summarize for later Average\n    context.Energy_from_Solar_Total_W += Energy_from_Solar_Now_W;\n    context.MsgCount += 1;\n    \n    if (StartInSeconds > context.NextOutputTime) {\n        \n        let Average = \n          Round(context.Energy_from_Solar_Total_W / \n                       Math.max(context.MsgCount, 1), -1 );\n                       \n        // reset\n        context.Energy_from_Solar_Total_W = null;\n        context.MsgCount = null;\n        context.NextOutputTime = null;\n\n        if (Average > 0) {\n            node.status( {fill:\"green\",shape:\"dot\",\n                          text: \"@ \" +  NowTime + \": \" + Average + \" W; \"\n                        + \"Critical: \" + Energy_to_Critical_W + \"; \"\n                        + \"Non-Crit: \" + Energy_to_Non_Crit_W\n            } );\n        }\n        else {\n            node.status( {fill:\"grey\",shape:\"dot\",\n                          text: \"@ \" +  NowTime + \": No Surplus; \"\n                        + \"Critical: \" + Energy_to_Critical_W + \"; \"\n                        + \"Non-Crit: \" + Energy_to_Non_Crit_W\n                          } );\n        }\n\n        // if (context.Last_Average != Average)\n        // {\n        //     context.Last_Average = Average;\n            \n            return { topic: \"Solar_Surplus\", payload: Average }\n        // }\n        \n    } // output only every few seconds\n\n} // if init OK\n\n",
        "outputs": 1,
        "timeout": "4",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 434,
        "y": 384,
        "wires": [
            [
                "1ab0e39b023e2eca"
            ]
        ]
    },
    {
        "id": "ce89f82494c11431",
        "type": "victron-input-gridmeter",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.grid/33",
        "path": "/Ac/L1/Power",
        "serviceObj": {
            "service": "com.victronenergy.grid/33",
            "name": "ET340 (Grid)"
        },
        "pathObj": {
            "path": "/Ac/L1/Power",
            "type": "float",
            "name": "L1 Power (W)"
        },
        "name": "ET340_L1_W",
        "onlyChanges": false,
        "roundValues": "0",
        "x": 130,
        "y": 528,
        "wires": [
            [
                "fc6b981413443dfd"
            ]
        ]
    },
    {
        "id": "3a379b3413cb039f",
        "type": "victron-input-gridmeter",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.grid/33",
        "path": "/Ac/L2/Power",
        "serviceObj": {
            "service": "com.victronenergy.grid/33",
            "name": "ET340 (Grid)"
        },
        "pathObj": {
            "path": "/Ac/L2/Power",
            "type": "float",
            "name": "L2 Power (W)"
        },
        "name": "ET340_L2_W",
        "onlyChanges": false,
        "roundValues": "0",
        "x": 130,
        "y": 576,
        "wires": [
            [
                "fc6b981413443dfd"
            ]
        ]
    },
    {
        "id": "3b17fee7be762407",
        "type": "victron-input-gridmeter",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.grid/33",
        "path": "/Ac/L3/Power",
        "serviceObj": {
            "service": "com.victronenergy.grid/33",
            "name": "ET340 (Grid)"
        },
        "pathObj": {
            "path": "/Ac/L3/Power",
            "type": "float",
            "name": "L3 Power (W)"
        },
        "name": "ET340_L3_W",
        "onlyChanges": false,
        "roundValues": "0",
        "x": 130,
        "y": 624,
        "wires": [
            [
                "fc6b981413443dfd"
            ]
        ]
    },
    {
        "id": "1ab0e39b023e2eca",
        "type": "link out",
        "z": "873bf4cb2fbee905",
        "name": "To Set State",
        "mode": "link",
        "links": [
            "2880c73ffcc9698f"
        ],
        "x": 611,
        "y": 384,
        "wires": []
    },
    {
        "id": "2f03491c31ecd365",
        "type": "victron-input-pvinverter",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.pvinverter/31",
        "path": "/Ac/L1/Power",
        "serviceObj": {
            "service": "com.victronenergy.pvinverter/31",
            "name": "SolarEdge (ET340)"
        },
        "pathObj": {
            "path": "/Ac/L1/Power",
            "type": "float",
            "name": "L1 Power (W)"
        },
        "name": "SolarEdge_L1_W",
        "onlyChanges": false,
        "roundValues": "0",
        "x": 408,
        "y": 192,
        "wires": [
            [
                "fc6b981413443dfd"
            ]
        ]
    },
    {
        "id": "a9ac17341615c1cb",
        "type": "victron-input-pvinverter",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.pvinverter/31",
        "path": "/Ac/L2/Power",
        "serviceObj": {
            "service": "com.victronenergy.pvinverter/31",
            "name": "SolarEdge (ET340)"
        },
        "pathObj": {
            "path": "/Ac/L2/Power",
            "type": "float",
            "name": "L2 Power (W)"
        },
        "name": "SolarEdge_L2_W",
        "onlyChanges": false,
        "roundValues": "0",
        "x": 408,
        "y": 240,
        "wires": [
            [
                "fc6b981413443dfd"
            ]
        ]
    },
    {
        "id": "0546bb559874144a",
        "type": "victron-input-pvinverter",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.pvinverter/31",
        "path": "/Ac/L3/Power",
        "serviceObj": {
            "service": "com.victronenergy.pvinverter/31",
            "name": "SolarEdge (ET340)"
        },
        "pathObj": {
            "path": "/Ac/L3/Power",
            "type": "float",
            "name": "L3 Power (W)"
        },
        "name": "SolarEdge_L3_W",
        "onlyChanges": false,
        "roundValues": "0",
        "x": 408,
        "y": 288,
        "wires": [
            [
                "fc6b981413443dfd"
            ]
        ]
    }
]

[ Voor 3% gewijzigd door MJ de Bruijn op 01-12-2025 14:59 ]


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57

Flow States - DESS en ESS



MESS houdt ook rekening met de mogelijkheid dat DESS wordt gebruikt (in plaats van MESS).
Eventuele wijzigingen van de instellingen worden ook doorgezonden aan Home Assistant (voor monitoring doeleinden).

Afbeeldingslocatie: https://tweakers.net/i/JgFuGmp4ZZHGrrGXzjruluusU1c=/800x/filters:strip_icc():strip_exif()/f/image/ObLPUdwCUPsmAF5XtumCWofi.jpg?f=fotoalbum_large
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
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
[
    {
        "id": "e0dfb9db0c37b271",
        "type": "subflow",
        "name": "Show",
        "info": "",
        "category": "",
        "in": [
            {
                "x": 52,
                "y": 48,
                "wires": [
                    {
                        "id": "274fd352337bfbb9"
                    }
                ]
            }
        ],
        "out": [
            {
                "x": 308,
                "y": 48,
                "wires": [
                    {
                        "id": "274fd352337bfbb9",
                        "port": 0
                    }
                ]
            }
        ],
        "env": [],
        "meta": {},
        "color": "#DDAA99",
        "status": {
            "x": 308,
            "y": 96,
            "wires": [
                {
                    "id": "274fd352337bfbb9",
                    "port": 1
                }
            ]
        }
    },
    {
        "id": "274fd352337bfbb9",
        "type": "function",
        "z": "e0dfb9db0c37b271",
        "name": "Show Payload",
        "func": "const Now = new Date();\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n                + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n                + (\"0\" + Now.getSeconds()).slice(-2) ;\n\nmsgState = { topic:'state', \n             payload: \"@ \" + NowTime + \": \"+ msg.topic + \": \" + msg.payload,\n             shape: \"dot\",\n             color: \"grey\"\n};\n\nnode.send( [msg, msgState] );\n\nnode.done;",
        "outputs": 2,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 176,
        "y": 64,
        "wires": [
            [],
            []
        ]
    },
    {
        "id": "90dcbfba68b349c8",
        "type": "victron-input-custom",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.settings",
        "path": "/Settings/DynamicEss/Mode",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "com.victronenergy.settings"
        },
        "pathObj": {
            "path": "/Settings/DynamicEss/Mode",
            "name": "/Settings/DynamicEss/Mode",
            "type": "number"
        },
        "name": "DESS_Mode",
        "onlyChanges": false,
        "roundValues": "0",
        "x": 130,
        "y": 688,
        "wires": [
            [
                "8e38f3b762cd85ab",
                "568cea0f90de2435"
            ]
        ]
    },
    {
        "id": "78eb311d83ac3b59",
        "type": "victron-input-battery",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.battery/512",
        "path": "/Soc",
        "serviceObj": {
            "service": "com.victronenergy.battery/512",
            "name": "Totle Power"
        },
        "pathObj": {
            "path": "/Soc",
            "type": "float",
            "name": "State of charge (%)"
        },
        "name": "SoC_Now_Perc",
        "onlyChanges": false,
        "roundValues": "1",
        "x": 144,
        "y": 896,
        "wires": [
            [
                "72d582578c02f0cd",
                "969699a25ab68669",
                "a676366b73611e87"
            ]
        ]
    },
    {
        "id": "a9394c7d423249f6",
        "type": "victron-input-ess",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.settings",
        "path": "/Settings/CGwacs/BatteryLife/State",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "Venus settings"
        },
        "pathObj": {
            "path": "/Settings/CGwacs/BatteryLife/State",
            "type": "enum",
            "name": "ESS state",
            "enum": {
                "1": "BatteryLife enabled (GUI controlled)",
                "2": "Optimized Mode /w BatteryLife: self consumption",
                "3": "Optimized Mode /w BatteryLife: self consumption, SoC exceeds 85%",
                "4": "Optimized Mode /w BatteryLife: self consumption, SoC at 100%",
                "5": "Optimized Mode /w BatteryLife: SoC below dynamic SoC limit",
                "6": "Optimized Mode /w BatteryLife: SoC has been below SoC limit for more than 24 hours. Charging the battery (5A)",
                "7": "Optimized Mode /w BatteryLife: Inverter/Charger is in sustain mode",
                "8": "Optimized Mode /w BatteryLife: recharging, SoC dropped by 5% or more below the minimum SoC",
                "9": "'Keep batteries charged' mode is enabled",
                "10": "Optimized mode w/o BatteryLife: self consumption, SoC at or above minimum SoC",
                "11": "Optimized mode w/o BatteryLife: self consumption, SoC is below minimum SoC",
                "12": "Optimized mode w/o BatteryLife: recharging, SoC dropped by 5% or more below minimum SoC"
            }
        },
        "initial": "",
        "name": "ESS_State",
        "onlyChanges": false,
        "x": 116,
        "y": 1088,
        "wires": [
            [
                "c424324946c8d0df",
                "53700193147df894"
            ]
        ]
    },
    {
        "id": "53700193147df894",
        "type": "function",
        "z": "873bf4cb2fbee905",
        "name": "To Text: ESS_State",
        "func": "// Function To Text: ESS_State\n\ncontext = context || {};\n\nif (context.Last_Payload == null)\n{\n    context.Last_Payload = \"\";\n}\n\nconst Now = new Date();\nlet NowTime = Now.getFullYear() + \"-\"\n            + (\"0\" + (Now.getMonth() + 1)).slice(-2) + \"-\"\n            + (\"0\" + Now.getDate()).slice(-2) + \" \"\n            + (\"0\" + Now.getHours()).slice(-2) + \":\"\n            + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n            + (\"0\" + Now.getSeconds()).slice(-2) ;\n\n\nif ( msg.topic == \"ESS_State\" )\n{\n    if (msg.payload == \"1\")\n    {\n        msg.payload = msg.topic + \" \" + msg.payload + \": Batt.Life Enabled (GUI controlled)\"\n    }\n    else if (msg.payload == \"2\")\n    {\n        msg.payload = msg.topic + \" \" + msg.payload + \": Opt.Mode, /w Batt.Life, Self Cons.\"\n    }\n    else if (msg.payload == \"3\")\n    {\n        msg.payload = msg.topic + \" \" + msg.payload + \": Opt.Mode, /w Batt.Life, Self Cons, SoC > 85%\"\n    }\n    else if (msg.payload == \"4\")\n    {\n        msg.payload = msg.topic + \" \" + msg.payload + \": Opt.Mode, /w Batt.Life, Self Cons, SoC = 100%\"\n    }\n    else if (msg.payload == \"5\")\n    {\n        msg.payload = msg.topic + \" \" + msg.payload + \": Opt.Mode, /w Batt.Life, SoC < Dyn.SoC\"\n    }\n    else if (msg.payload == \"6\")\n    {\n        msg.payload = msg.topic + \" \" + msg.payload + \": Opt.Mode, /w Batt.Life, SoC < SoC limit (>24h), Charging\"\n    }\n    else if (msg.payload == \"7\")\n    {\n        msg.payload = msg.topic + \" \" + msg.payload + \": Opt.Mode, /w Batt.Life, Inverter in Sustain Mode\"\n    }\n    else if (msg.payload == \"8\")\n    {\n        msg.payload = msg.topic + \" \" + msg.payload + \": Opt.Mode, /w Batt.Life, Recharging, SoC 5% < min.SoC\"\n    }\n    else if (msg.payload == \"9\")\n    {\n        msg.payload = msg.topic + \" \" + msg.payload + \": Keep Batt.Charged\"\n    }\n    else if (msg.payload == \"10\")\n    {\n        msg.payload = msg.topic + \" \" + msg.payload + \": Opt.Mode, w/o Batt.Life, Self Consumption, SoC >= Min.SoC\"\n    }\n    else if (msg.payload == \"11\")\n    {\n        msg.payload = msg.topic + \" \" + msg.payload + \": Opt.Mode, w/o Batt.Life, SoC < Min.SoC'\"\n    }\n    else if (msg.payload == \"12\")\n    {\n        msg.payload = msg.topic + \" \" + msg.payload + \": Opt.Mode, w/o Batt.Life, Recharging, SoC < min.SoC-5%\"\n    }\n    else\n    {\n        msg.payload = msg.topic + \" \" + msg.payload + \": Unknown\"\n    }\n    msg.topic = msg.topic + \"_Text\";\n    \n\n    if (msg.payload != context.Last_Payload)\n    {\n        context.Last_Payload = msg.payload;\n        context.Last_Time = NowTime;\n        node.status( {fill:\"yellow\", shape:\"dot\",\n                      text: \"@ \" + NowTime + \": \"\n                      + msg.payload }\n                   );\n        return msg;\n    }\n    else {\n        node.status( {fill:\"green\", shape:\"dot\",\n                      text: \"@ \" + context.Last_Time + \": \"\n                      + msg.payload }\n                   );\n    }\n    \n}\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 374,
        "y": 1136,
        "wires": [
            [
                "67639ea8dca2895e",
                "9432fb1fd2aaece1"
            ]
        ]
    },
    {
        "id": "72d582578c02f0cd",
        "type": "link out",
        "z": "873bf4cb2fbee905",
        "name": "To Set State",
        "mode": "link",
        "links": [
            "2880c73ffcc9698f"
        ],
        "x": 289,
        "y": 848,
        "wires": []
    },
    {
        "id": "67639ea8dca2895e",
        "type": "link out",
        "z": "873bf4cb2fbee905",
        "name": "To Set State",
        "mode": "link",
        "links": [
            "2880c73ffcc9698f"
        ],
        "x": 583,
        "y": 1136,
        "wires": []
    },
    {
        "id": "c424324946c8d0df",
        "type": "link out",
        "z": "873bf4cb2fbee905",
        "name": "To Set State",
        "mode": "link",
        "links": [
            "2880c73ffcc9698f"
        ],
        "x": 299,
        "y": 1088,
        "wires": []
    },
    {
        "id": "568cea0f90de2435",
        "type": "rbe",
        "z": "873bf4cb2fbee905",
        "name": "only changes",
        "func": "rbe",
        "gap": "",
        "start": "",
        "inout": "out",
        "septopics": true,
        "property": "payload",
        "topi": "topic",
        "x": 358,
        "y": 688,
        "wires": [
            [
                "253b4e9f795118dc"
            ]
        ]
    },
    {
        "id": "969699a25ab68669",
        "type": "rbe",
        "z": "873bf4cb2fbee905",
        "name": "only changes",
        "func": "rbe",
        "gap": "",
        "start": "",
        "inout": "out",
        "septopics": true,
        "property": "payload",
        "topi": "topic",
        "x": 342,
        "y": 944,
        "wires": [
            []
        ]
    },
    {
        "id": "9432fb1fd2aaece1",
        "type": "rbe",
        "z": "873bf4cb2fbee905",
        "name": "only changes",
        "func": "rbe",
        "gap": "",
        "start": "",
        "inout": "out",
        "septopics": true,
        "property": "payload",
        "topi": "topic",
        "x": 354,
        "y": 1184,
        "wires": [
            [
                "0ebb7af534fecb1c"
            ]
        ]
    },
    {
        "id": "cf60cdb222be69f6",
        "type": "victron-input-ess",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.settings",
        "path": "/Settings/CGwacs/BatteryLife/MinimumSocLimit",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "Venus settings"
        },
        "pathObj": {
            "path": "/Settings/CGwacs/BatteryLife/MinimumSocLimit",
            "type": "integer",
            "name": "Minimum Discharge SOC (%)",
            "mode": "both"
        },
        "initial": "",
        "name": "Min_Discharge_SoC",
        "onlyChanges": false,
        "x": 150,
        "y": 752,
        "wires": [
            [
                "9b74cdc38a564b47"
            ]
        ]
    },
    {
        "id": "20bb0ad782fc5eec",
        "type": "link out",
        "z": "873bf4cb2fbee905",
        "name": "To Call Websocket Api",
        "mode": "link",
        "links": [
            "30a7afbf79ee7ae6"
        ],
        "x": 479,
        "y": 800,
        "wires": []
    },
    {
        "id": "7959bd2bb9a19cd4",
        "type": "change",
        "z": "873bf4cb2fbee905",
        "name": "Set API variables",
        "rules": [
            {
                "t": "set",
                "p": "type",
                "pt": "msg",
                "to": "call_service",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "domain",
                "pt": "msg",
                "to": "input_number",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "service",
                "pt": "msg",
                "to": "set_value",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "input_number.helper_mess_target_soc",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "entity_id",
                "pt": "msg",
                "to": "input_number.helper_mess_target_soc",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 550,
        "y": 752,
        "wires": [
            [
                "44ccb7601bb62ceb"
            ]
        ]
    },
    {
        "id": "44ccb7601bb62ceb",
        "type": "subflow:e0dfb9db0c37b271",
        "z": "873bf4cb2fbee905",
        "name": "",
        "x": 338,
        "y": 800,
        "wires": [
            [
                "20bb0ad782fc5eec"
            ]
        ]
    },
    {
        "id": "a676366b73611e87",
        "type": "function",
        "z": "873bf4cb2fbee905",
        "name": "Batt SoC Full Time to State",
        "func": "// Function Batt SoC Full Time to State\n\ncontext = context || {}\n\nif (context.Last_SoC == null)\n{\n    context.Last_SoC = 0;\n}\n\nlet CurSoC = Number(msg.payload);\n\nconst Now = new Date();\nlet NowTime = Now.getFullYear() + \"-\"\n            + (\"0\" + (Now.getMonth() + 1)).slice(-2) + \"-\"\n            + (\"0\" + Now.getDate()).slice(-2) + \" \"\n            + (\"0\" + Now.getHours()).slice(-2) + \":\"\n            + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n            + (\"0\" + Now.getSeconds()).slice(-2) ;\n\n\nlet Batt_SoC_Full_Time = \n    global.get(\"Victron_MESS.State.Batt_SoC_Full_Time\") || \"Unknown\";\n\nif ( (CurSoC >= 99)\n  && (CurSoC != context.Last_SoC) )\n{\n    global.set(\"Victron_MESS.State.Batt_SoC_Full_Time\",\n               NowTime);\n               \n    context.Last_SoC = CurSoC;\n\n    node.status( {fill:\"yellow\", shape:\"dot\",\n                  text: \"Batt_SoC_Full_Time: \" + NowTime }\n               );\n}\nelse \n{\n    node.status( {fill:\"green\", shape:\"dot\",\n                  text: \"Batt_SoC_Full_Time: \" + Batt_SoC_Full_Time }\n               );\n}\n\nreturn msg;\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 392,
        "y": 896,
        "wires": [
            []
        ]
    },
    {
        "id": "0ebb7af534fecb1c",
        "type": "change",
        "z": "873bf4cb2fbee905",
        "name": "Set API variables",
        "rules": [
            {
                "t": "set",
                "p": "type",
                "pt": "msg",
                "to": "call_service",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "domain",
                "pt": "msg",
                "to": "input_text",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "service",
                "pt": "msg",
                "to": "set_value",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "Send ESS_State as Text",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "entity_id",
                "pt": "msg",
                "to": "input_text.helper_ess_state_as_text",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 554,
        "y": 1184,
        "wires": [
            [
                "80d18571582bd73f"
            ]
        ]
    },
    {
        "id": "2bfe6d826ae7f142",
        "type": "link out",
        "z": "873bf4cb2fbee905",
        "name": "To Call Websocket Api",
        "mode": "link",
        "links": [
            "30a7afbf79ee7ae6"
        ],
        "x": 481,
        "y": 1232,
        "wires": []
    },
    {
        "id": "13431a85b02e802a",
        "type": "link out",
        "z": "873bf4cb2fbee905",
        "name": "To Call Websocket Api",
        "mode": "link",
        "links": [
            "30a7afbf79ee7ae6"
        ],
        "x": 673,
        "y": 688,
        "wires": []
    },
    {
        "id": "253b4e9f795118dc",
        "type": "change",
        "z": "873bf4cb2fbee905",
        "name": "Set API variables",
        "rules": [
            {
                "t": "set",
                "p": "type",
                "pt": "msg",
                "to": "call_service",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "domain",
                "pt": "msg",
                "to": "input_number",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "service",
                "pt": "msg",
                "to": "set_value",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "entity_id",
                "pt": "msg",
                "to": "input_number.helper_dess_mode",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 550,
        "y": 688,
        "wires": [
            [
                "13431a85b02e802a"
            ]
        ]
    },
    {
        "id": "9b74cdc38a564b47",
        "type": "rbe",
        "z": "873bf4cb2fbee905",
        "name": "only changes",
        "func": "rbe",
        "gap": "",
        "start": "",
        "inout": "out",
        "septopics": true,
        "property": "payload",
        "topi": "topic",
        "x": 358,
        "y": 752,
        "wires": [
            [
                "7959bd2bb9a19cd4"
            ]
        ]
    },
    {
        "id": "80d18571582bd73f",
        "type": "subflow:e0dfb9db0c37b271",
        "z": "873bf4cb2fbee905",
        "name": "",
        "x": 338,
        "y": 1232,
        "wires": [
            [
                "2bfe6d826ae7f142"
            ]
        ]
    },
    {
        "id": "8e38f3b762cd85ab",
        "type": "link out",
        "z": "873bf4cb2fbee905",
        "name": "To Set State",
        "mode": "link",
        "links": [
            "2880c73ffcc9698f"
        ],
        "x": 303,
        "y": 656,
        "wires": []
    }
]

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57

Flow States - Diverse States



Heel veel entiteiten sturen iedere vijf seconden een update. De functie 'Set Payload in global.Victron_MESS.State' verwerkt deze en schrijft alleen veranderingen weg naar het Context Global Geheugengebied.
De optie 'Only Changes' van veel Nodes voldoet hier niet omdat, bijvoorbeeld bij een gedeeltelijke Deploy of het opschonen van Context Global, de basis-stand wordt gemist.

Afbeeldingslocatie: https://tweakers.net/i/SelLk4UTj48hVw9hnfgiHt_ASMQ=/800x/filters:strip_exif()/f/image/iZAsxYJhraXJpAucsZ6qPApQ.png?f=fotoalbum_large
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
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
[
    {
        "id": "e0dfb9db0c37b271",
        "type": "subflow",
        "name": "Show",
        "info": "",
        "category": "",
        "in": [
            {
                "x": 52,
                "y": 48,
                "wires": [
                    {
                        "id": "274fd352337bfbb9"
                    }
                ]
            }
        ],
        "out": [
            {
                "x": 308,
                "y": 48,
                "wires": [
                    {
                        "id": "274fd352337bfbb9",
                        "port": 0
                    }
                ]
            }
        ],
        "env": [],
        "meta": {},
        "color": "#DDAA99",
        "status": {
            "x": 308,
            "y": 96,
            "wires": [
                {
                    "id": "274fd352337bfbb9",
                    "port": 1
                }
            ]
        }
    },
    {
        "id": "274fd352337bfbb9",
        "type": "function",
        "z": "e0dfb9db0c37b271",
        "name": "Show Payload",
        "func": "const Now = new Date();\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n                + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n                + (\"0\" + Now.getSeconds()).slice(-2) ;\n\nmsgState = { topic:'state', \n             payload: \"@ \" + NowTime + \": \"+ msg.topic + \": \" + msg.payload,\n             shape: \"dot\",\n             color: \"grey\"\n};\n\nnode.send( [msg, msgState] );\n\nnode.done;",
        "outputs": 2,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 176,
        "y": 64,
        "wires": [
            [],
            []
        ]
    },
    {
        "id": "579401fa45c450f5",
        "type": "function",
        "z": "873bf4cb2fbee905",
        "name": "Set Payload in global.Victron_MESS.State",
        "func": "const ProgName = \"Set Payload in global.Victron_MESS.State\";\n\ncontext = context || {};\n\nlet Now = new Date();\nStartTime = Math.floor(Now.getTime() / 1000);\n\nlet NewLine = \"\\n\\r\";\n\nif (context.First_StartTime == null)\n{\n    context.First_StartTime = StartTime;\n}\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\nif ( !(Get_Functions() ) )\n{   \n    return null;\n}\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\nfunction Get_Functions() {\n\nlet NewLine = \"\\n\\r\";\nlet FuncCount = 0;\nlet FuncNull = 0;\nlet ErrText = \"\";\nlet Result = true;\n\ntry {\n\n    FuncCount++;\n    GetNowTime = global.get(\"Victron_MESS.Functions.GetNowTime\");\n    if (GetNowTime  == null)\n    {   ErrText += \"Victron_MESS.Functions.GetNowTime\" + NewLine;\n        FuncNull += 1;\n        Result = false;\n    }\n\n    FuncCount++;\n    Not = global.get(\"Victron_MESS.Functions.Not\");\n    if (Not  == null)\n    {   ErrText += \"Victron_MESS.Functions.Not\" + NewLine;\n        FuncNull += 1;\n        Result = false;\n    }\n    \n    FuncCount++;\n    Round = global.get(\"Victron_MESS.Functions.Round\");\n    if (Round  == null)\n    {   ErrText += \"Victron_MESS.Functions.Round\" + NewLine;\n        FuncNull += 1;\n        Result = false;\n    }\n\n    if ( !(Result) )\n    {\n        node.status(\n            {fill:\"black\",\n            shape:\"dot\",\n            text: \"Wait for Get_Functions \"\n                + \"(\" + FuncNull + \"/\" + FuncCount + \")...\"\n            }\n        );\n    }\n    \n    if (ErrText > \"\")\n    {   \n        if (context.Next_Error_Time == null)\n        {\n            context.Next_Error_Time = StartTime;\n        }\n        if (StartTime >= context.Next_Error_Time)\n        {\n            node.warn(\"Error Get_Functions:\" + NewLine + ErrText);\n            context.Next_Error_Time = StartTime + 15;\n        }\n\n        node.status({fill:\"red\", shape:\"dot\",\n                     text: ProgName + \": Error Get_Functions\" }\n                   );\n    }\n    \n} // try\ncatch (error)\n{\n    Result = false;\n    \n    node.error(\"Program: \" + ProgName + NewLine\n    + \"Step: Get_Functions\" + NewLine\n    + \"Error: \" + error)\n    \n} // catch\n\nreturn Result;\n    \n} // Get_Functions\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\n\nif (msg.topic == \"Delete_States\") {\n    context = null;\n}\n\ncontext = context || {};\n\nif (context.Last_Payload == null)\n{\n    context.Last_Payload = [];\n}\n\nlet NowTime = GetNowTime();\n\nlet Entity_Name = \"Victron_MESS.State.\" + msg.topic;\n\nif (context.Last_Payload[msg.topic] != msg.payload)\n{\n    // Only Changes\n    // global.set(Entity_Name); // remove\n    global.set(Entity_Name, msg.payload);\n\n    // node.warn(msg.topic + \": \" + context.Last_Payload[msg.topic] + \" --> \" + msg.payload);\n    context.Last_Payload[msg.topic] = msg.payload;\n\n    node.status( {fill:\"yellow\", shape:\"dot\",\n              text: \"@ \" + NowTime + \": \"\n              + msg.topic + \": \" + msg.payload }\n           );\n\n}\nelse\n{\n    node.status( {fill:\"green\", shape:\"dot\",\n              text: \"@ \" + NowTime + \": \"\n              + msg.topic + \": \" + msg.payload }\n           );\n\n    msg = null;\n}\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "\n",
        "finalize": "",
        "libs": [],
        "x": 612,
        "y": 1600,
        "wires": [
            [
                "ad07ebb674632a2a"
            ]
        ]
    },
    {
        "id": "28021ca47b0a4870",
        "type": "victron-input-battery",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.battery/512",
        "path": "/Info/MaxChargeVoltage",
        "serviceObj": {
            "service": "com.victronenergy.battery/512",
            "name": "Totle Power"
        },
        "pathObj": {
            "path": "/Info/MaxChargeVoltage",
            "type": "float",
            "name": "CVL - Charge Voltage Limit (V)"
        },
        "name": "Batt_Max_Charge_Voltage_V",
        "onlyChanges": false,
        "roundValues": "0",
        "x": 180,
        "y": 1408,
        "wires": [
            [
                "579401fa45c450f5"
            ]
        ]
    },
    {
        "id": "c0e35d1e895caabc",
        "type": "victron-input-ess",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.settings",
        "path": "/Settings/SystemSetup/MaxChargeCurrent",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "Venus settings"
        },
        "pathObj": {
            "path": "/Settings/SystemSetup/MaxChargeCurrent",
            "type": "float",
            "name": "DVCC Charge current limit (A)",
            "mode": "both"
        },
        "name": "Batt_Max_Charge_Current_A",
        "onlyChanges": false,
        "x": 180,
        "y": 1456,
        "wires": [
            [
                "579401fa45c450f5"
            ]
        ]
    },
    {
        "id": "26de2e4dc6d12ce9",
        "type": "victron-input-ess",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.settings",
        "path": "/Settings/CGwacs/MaxDischargePower",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "Venus settings"
        },
        "pathObj": {
            "path": "/Settings/CGwacs/MaxDischargePower",
            "type": "integer",
            "name": "Max inverter power (W)",
            "mode": "both"
        },
        "initial": "",
        "name": "Max_Inverter_Power_W",
        "onlyChanges": false,
        "x": 160,
        "y": 1504,
        "wires": [
            [
                "579401fa45c450f5"
            ]
        ]
    },
    {
        "id": "dd894acabb8ba644",
        "type": "victron-input-system",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.system/0",
        "path": "/Control/ActiveSocLimit",
        "serviceObj": {
            "service": "com.victronenergy.system/0",
            "name": "Venus system"
        },
        "pathObj": {
            "path": "/Control/ActiveSocLimit",
            "type": "integer",
            "name": "ESS active SOC limit (%)"
        },
        "name": "SoC_Min_Venus",
        "onlyChanges": false,
        "x": 144,
        "y": 1632,
        "wires": [
            [
                "579401fa45c450f5"
            ]
        ]
    },
    {
        "id": "f154e93b358f56a6",
        "type": "victron-input-ess",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.settings",
        "path": "/Settings/CGwacs/AcPowerSetPoint",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "Venus settings"
        },
        "pathObj": {
            "path": "/Settings/CGwacs/AcPowerSetPoint",
            "type": "integer",
            "name": "Grid set-point (W)",
            "mode": "both"
        },
        "name": "Grid_Setpoint_W",
        "onlyChanges": false,
        "x": 144,
        "y": 1680,
        "wires": [
            [
                "579401fa45c450f5"
            ]
        ]
    },
    {
        "id": "4b33ecd392da4748",
        "type": "victron-input-ess",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.vebus/276",
        "path": "/Hub4/DisableCharge",
        "serviceObj": {
            "service": "com.victronenergy.vebus/276",
            "name": "MultiPlus-II 48/3000/35-32"
        },
        "pathObj": {
            "path": "/Hub4/DisableCharge",
            "type": "enum",
            "name": "Disable charge",
            "enum": {
                "0": "No",
                "1": "Yes"
            },
            "mode": "both"
        },
        "initial": "",
        "name": "Charge_Disable",
        "onlyChanges": false,
        "x": 144,
        "y": 1728,
        "wires": [
            [
                "579401fa45c450f5"
            ]
        ]
    },
    {
        "id": "5728d4a769c1b906",
        "type": "victron-input-solarcharger",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.solarcharger/279",
        "path": "/Mode",
        "serviceObj": {
            "service": "com.victronenergy.solarcharger/279",
            "name": "SmartSolar Charger MPPT 100/20 48V"
        },
        "pathObj": {
            "path": "/Mode",
            "type": "enum",
            "name": "Charger on/off",
            "enum": {
                "1": "On",
                "4": "Off"
            },
            "mode": "both"
        },
        "initial": "",
        "name": "Solar_Charger",
        "onlyChanges": false,
        "x": 144,
        "y": 1776,
        "wires": [
            [
                "579401fa45c450f5"
            ]
        ]
    },
    {
        "id": "4ff0d8160ce166f3",
        "type": "victron-input-relay",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.system/0",
        "path": "/Relay/0/State",
        "serviceObj": {
            "service": "com.victronenergy.system/0",
            "name": "Venus system"
        },
        "pathObj": {
            "path": "/Relay/0/State",
            "type": "enum",
            "name": "Venus relay 1 state",
            "enum": {
                "0": "Open",
                "1": "Closed"
            },
            "mode": "both",
            "disabled": false
        },
        "initial": "",
        "name": "Relay_1",
        "onlyChanges": false,
        "x": 124,
        "y": 1824,
        "wires": [
            [
                "579401fa45c450f5"
            ]
        ]
    },
    {
        "id": "0626f121c0a0ef27",
        "type": "victron-input-relay",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.system/0",
        "path": "/Relay/1/State",
        "serviceObj": {
            "service": "com.victronenergy.system/0",
            "name": "Venus system"
        },
        "pathObj": {
            "path": "/Relay/1/State",
            "type": "enum",
            "name": "Venus relay 2 state",
            "enum": {
                "0": "Open",
                "1": "Closed"
            },
            "mode": "both"
        },
        "initial": "",
        "name": "Relay_2",
        "onlyChanges": false,
        "x": 124,
        "y": 1872,
        "wires": [
            [
                "579401fa45c450f5"
            ]
        ]
    },
    {
        "id": "ea17638fe24719f5",
        "type": "victron-input-gridmeter",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.grid/33",
        "path": "/Ac/L1/Current",
        "serviceObj": {
            "service": "com.victronenergy.grid/33",
            "name": "ET340 (Grid)"
        },
        "pathObj": {
            "path": "/Ac/L1/Current",
            "type": "float",
            "name": "L1 Current (A)"
        },
        "name": "ET340_L1_A",
        "onlyChanges": false,
        "roundValues": "1",
        "x": 122,
        "y": 2016,
        "wires": [
            [
                "579401fa45c450f5"
            ]
        ]
    },
    {
        "id": "8856e7198bec0eaa",
        "type": "victron-input-gridmeter",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.grid/33",
        "path": "/Ac/L2/Current",
        "serviceObj": {
            "service": "com.victronenergy.grid/33",
            "name": "ET340 (Grid)"
        },
        "pathObj": {
            "path": "/Ac/L2/Current",
            "type": "float",
            "name": "L2 Current (A)"
        },
        "name": "ET340_L2_A",
        "onlyChanges": false,
        "roundValues": "1",
        "x": 122,
        "y": 2064,
        "wires": [
            [
                "579401fa45c450f5"
            ]
        ]
    },
    {
        "id": "f151dc78d1c58f25",
        "type": "victron-input-gridmeter",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.grid/33",
        "path": "/Ac/L3/Current",
        "serviceObj": {
            "service": "com.victronenergy.grid/33",
            "name": "ET340 (Grid)"
        },
        "pathObj": {
            "path": "/Ac/L3/Current",
            "type": "float",
            "name": "L3 Current (A)"
        },
        "name": "ET340_L3_A",
        "onlyChanges": false,
        "roundValues": "1",
        "x": 122,
        "y": 2112,
        "wires": [
            [
                "579401fa45c450f5"
            ]
        ]
    },
    {
        "id": "eb33bc765db80d10",
        "type": "victron-input-vebus",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.vebus/276",
        "path": "/VebusChargeState",
        "serviceObj": {
            "service": "com.victronenergy.vebus/276",
            "name": "MultiPlus-II 48/3000/35-32"
        },
        "pathObj": {
            "path": "/VebusChargeState",
            "type": "enum",
            "name": "Charge state",
            "enum": {
                "0": "Initialising",
                "1": "Bulk",
                "2": "Absorption",
                "3": "Float",
                "4": "Storage",
                "5": "Absorb repeat",
                "6": "Forced absorb",
                "7": "Equalise",
                "8": "Bulk stopped",
                "9": "Unknown"
            }
        },
        "initial": "",
        "name": "Batt_Charge_State",
        "onlyChanges": false,
        "x": 150,
        "y": 1312,
        "wires": [
            [
                "789c561d418cac34"
            ]
        ]
    },
    {
        "id": "789c561d418cac34",
        "type": "function",
        "z": "873bf4cb2fbee905",
        "name": "To Text: Batt_Charge_State",
        "func": "const ProgName = \"To Text: Charge_State\";\n\ncontext = context || {};\n\nconst Now = new Date();\nlet NowTime = Now.getFullYear() + \"-\"\n            + (\"0\" + (Now.getMonth() + 1)).slice(-2) + \"-\"\n            + (\"0\" + Now.getDate()).slice(-2) + \" \"\n            + (\"0\" + Now.getHours()).slice(-2) + \":\"\n            + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n            + (\"0\" + Now.getSeconds()).slice(-2) ;\n\nStartTime = Math.floor(Now.getTime() / 1000);\n\nlet NewLine = \"\\n\\r\";\n\nif (context.First_StartTime == null)\n{\n    context.First_StartTime = StartTime;\n}\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\nif ( !(Get_Functions() ) )\n{   \n    return null;\n}\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\nfunction Get_Functions() {\n\nlet NewLine = \"\\n\\r\";\nlet FuncCount = 0;\nlet FuncNull = 0;\nlet ErrText = \"\";\nlet Result = true;\n\ntry {\n\n    FuncCount++;\n    GetNowTime = global.get(\"Victron_MESS.Functions.GetNowTime\");\n    if (GetNowTime  == null)\n    {   ErrText += \"Victron_MESS.Functions.GetNowTime\" + NewLine;\n        FuncNull += 1;\n        Result = false;\n    }\n\n    FuncCount++;\n    Not = global.get(\"Victron_MESS.Functions.Not\");\n    if (Not  == null)\n    {   ErrText += \"Victron_MESS.Functions.Not\" + NewLine;\n        FuncNull += 1;\n        Result = false;\n    }\n    \n    FuncCount++;\n    Round = global.get(\"Victron_MESS.Functions.Round\");\n    if (Round  == null)\n    {   ErrText += \"Victron_MESS.Functions.Round\" + NewLine;\n        FuncNull += 1;\n        Result = false;\n    }\n\n    if ( !(Result) )\n    {\n        node.status(\n            {fill:\"black\",\n            shape:\"dot\",\n            text: \"Wait for Get_Functions \"\n                + \"(\" + FuncNull + \"/\" + FuncCount + \")...\"\n            }\n        );\n    }\n    \n    if ( (ErrText > \"\")\n      && ( StartTime > context.First_StartTime + 15 )\n       )\n    {   \n        node.warn(\"Get_Functions:\" + NewLine + ErrText);\n\n        node.status({fill:\"red\", shape:\"dot\",\n                     text: ProgName + \": Error Get_Functions\" \n                     + \"\\n\\r\" + ErrText}\n                   );\n    }\n    \n} // try\ncatch (error)\n{\n    Result = false;\n    \n    node.error(\"Program: \" + ProgName + NewLine\n    + \"Step: Get_Functions\" + NewLine\n    + \"Error: \" + error)\n    \n} // catch\n\nreturn Result;\n    \n} // Get_Functions\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\nif ( msg.topic == \"Batt_Charge_State\" )\n{\n    \n    msg.payload = msg.topic + \" \" + msg.payload + \": \" + msg.textvalue;\n/*    \n    if (msg.payload == \"0\")\n    {\n        msg.payload = msg.topic + \" \" + msg.payload + \": Initializing\"\n    }\n    else if (msg.payload == \"1\")\n    {\n        msg.payload = msg.topic + \" \" + msg.payload + \": Bulk\"\n    }\n    else if (msg.payload == \"2\")\n    {\n        msg.payload = msg.topic + \" \" + msg.payload + \": Absorption\"\n    }\n    else if (msg.payload == \"3\")\n    {\n        msg.payload = msg.topic + \" \" + msg.payload + \": Float\"\n    }\n    else if (msg.payload == \"4\")\n    {\n        msg.payload = msg.topic + \" \" + msg.payload + \": Storage\"\n    }\n    else if (msg.payload == \"5\")\n    {\n        msg.payload = msg.topic + \" \" + msg.payload + \": Absorp Repeat\"\n    }\n    else if (msg.payload == \"6\")\n    {\n        msg.payload = msg.topic + \" \" + msg.payload + \": Forced Absorb\"\n    }\n    else if (msg.payload == \"7\")\n    {\n        msg.payload = msg.topic + \" \" + msg.payload + \": Equalise\"\n    }\n    else if (msg.payload == \"8\")\n    {\n        msg.payload = msg.topic + \" \" + msg.payload + \": Bulk STopped\"\n    }\n    else if (msg.payload == \"9\")\n    {\n        msg.payload = msg.topic + \" \" + msg.payload + \": Unknown\"\n    }\n    else\n    {\n        msg.payload = msg.topic + \" \" + msg.payload + \": Unknown\"\n    }\n*/    \n    msg.topic = msg.topic + \"_Text\";\n    \n    node.status( {fill:\"green\", shape:\"dot\",\n                  text: \"@ \" + NowTime + \": \"\n                  + msg.payload }\n               );\n               \n}\nelse \n{\n    msg = null;\n}\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 220,
        "y": 1360,
        "wires": [
            [
                "29ed2bca9bf58176"
            ]
        ]
    },
    {
        "id": "01c3cbde84bf4335",
        "type": "victron-input-vebus",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.vebus/276",
        "path": "/Alarms/GridLost",
        "serviceObj": {
            "service": "com.victronenergy.vebus/276",
            "name": "MultiPlus-II 48/3000/35-32"
        },
        "pathObj": {
            "path": "/Alarms/GridLost",
            "type": "enum",
            "name": "Grid lost alarm",
            "enum": {
                "0": "Ok",
                "2": "Alarm"
            }
        },
        "initial": "",
        "name": "Grid_Lost_Alarm",
        "onlyChanges": false,
        "x": 136,
        "y": 1920,
        "wires": [
            [
                "579401fa45c450f5"
            ]
        ]
    },
    {
        "id": "2880c73ffcc9698f",
        "type": "link in",
        "z": "873bf4cb2fbee905",
        "name": "Entry Point Set State",
        "links": [
            "8e38f3b762cd85ab",
            "72d582578c02f0cd",
            "67639ea8dca2895e",
            "1ab0e39b023e2eca",
            "6dad5bca7e6496b7",
            "c424324946c8d0df",
            "32aa3d4a5c947b8e",
            "50fc62634e324a1e",
            "0029ad7a00cf0b7a",
            "4251c2214bfb5fd7",
            "b68016548bd8048a",
            "5d52d345d45747af",
            "c0dd8c60400b4654",
            "29ed2bca9bf58176",
            "31a8580e6fd4dbc3",
            "b5258fd655ebedca"
        ],
        "x": 645,
        "y": 1552,
        "wires": [
            [
                "579401fa45c450f5"
            ]
        ]
    },
    {
        "id": "29ed2bca9bf58176",
        "type": "link out",
        "z": "873bf4cb2fbee905",
        "name": "To Set State",
        "mode": "link",
        "links": [
            "2880c73ffcc9698f"
        ],
        "x": 405,
        "y": 1360,
        "wires": []
    },
    {
        "id": "84fb05a6d7387a7d",
        "type": "victron-input-temperature",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.temperature/24",
        "path": "/Temperature",
        "serviceObj": {
            "service": "com.victronenergy.temperature/24",
            "name": "Multiplus II (1) Temp"
        },
        "pathObj": {
            "path": "/Temperature",
            "type": "float",
            "name": "Temperature (°C)"
        },
        "name": "Multiplus_II_1_Temp",
        "onlyChanges": false,
        "roundValues": "0",
        "x": 142,
        "y": 2160,
        "wires": [
            [
                "579401fa45c450f5"
            ]
        ]
    },
    {
        "id": "a1b2d22e035c31ce",
        "type": "victron-input-temperature",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.temperature/25",
        "path": "/Temperature",
        "serviceObj": {
            "service": "com.victronenergy.temperature/25",
            "name": "Multiplus II (2) Temp"
        },
        "pathObj": {
            "path": "/Temperature",
            "type": "float",
            "name": "Temperature (°C)"
        },
        "name": "Multiplus_II_2_Temp",
        "onlyChanges": false,
        "roundValues": "0",
        "x": 142,
        "y": 2208,
        "wires": [
            [
                "579401fa45c450f5"
            ]
        ]
    },
    {
        "id": "4926c1874c79556a",
        "type": "victron-input-temperature",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.temperature/26",
        "path": "/Temperature",
        "serviceObj": {
            "service": "com.victronenergy.temperature/26",
            "name": "Multiplus II (3) Temp"
        },
        "pathObj": {
            "path": "/Temperature",
            "type": "float",
            "name": "Temperature (°C)"
        },
        "name": "Multiplus_II_3_Temp",
        "onlyChanges": false,
        "roundValues": "0",
        "x": 142,
        "y": 2256,
        "wires": [
            [
                "579401fa45c450f5"
            ]
        ]
    },
    {
        "id": "16e540753921a6ed",
        "type": "victron-input-digitalinput",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.digitalinput/1",
        "path": "/Alarm",
        "serviceObj": {
            "service": "com.victronenergy.digitalinput/1",
            "name": "Smoke alarm"
        },
        "pathObj": {
            "path": "/Alarm",
            "type": "enum",
            "name": "Digital input alarm",
            "enum": {
                "0": "No alarm",
                "1": "Warning",
                "2": "Alarm"
            }
        },
        "initial": "",
        "name": "Smoke_Alarm",
        "onlyChanges": false,
        "x": 122,
        "y": 1968,
        "wires": [
            [
                "579401fa45c450f5"
            ]
        ]
    },
    {
        "id": "ad07ebb674632a2a",
        "type": "subflow:e0dfb9db0c37b271",
        "z": "873bf4cb2fbee905",
        "name": "Show Changed",
        "x": 528,
        "y": 1776,
        "wires": [
            []
        ]
    },
    {
        "id": "c5de25a26b0e421a",
        "type": "comment",
        "z": "873bf4cb2fbee905",
        "name": "Green: no change \\n Yellow: changed",
        "info": "",
        "x": 542,
        "y": 1664,
        "wires": []
    },
    {
        "id": "aef8f060832f3d48",
        "type": "victron-input-settings",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.settings",
        "path": "/Settings/CGwacs/MaxDischargePercentage",
        "serviceObj": {
            "service": "com.victronenergy.settings",
            "name": "Venus settings"
        },
        "pathObj": {
            "path": "/Settings/CGwacs/MaxDischargePercentage",
            "type": "float",
            "name": "ESS max discharge current (fractional) (%)",
            "mode": "both"
        },
        "name": "Max_Discharge_Current_Perc",
        "onlyChanges": false,
        "roundValues": "0",
        "x": 184,
        "y": 1552,
        "wires": [
            [
                "579401fa45c450f5"
            ]
        ]
    }
]

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57

Flow States - EV / Autolaadstation



Ik gebruik een Victron Auto Laadstation. Het gebruik daarvan heeft invloed op de instellingen van het huis-systeem.

Afbeeldingslocatie: https://tweakers.net/i/4zBF5r2iER8y44YQ3cGVIgj1F3I=/800x/filters:strip_exif()/f/image/if2unDHNcArUtzYy1WcZ54EI.png?f=fotoalbum_large
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
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
[
    {
        "id": "e0dfb9db0c37b271",
        "type": "subflow",
        "name": "Show",
        "info": "",
        "category": "",
        "in": [
            {
                "x": 52,
                "y": 48,
                "wires": [
                    {
                        "id": "274fd352337bfbb9"
                    }
                ]
            }
        ],
        "out": [
            {
                "x": 308,
                "y": 48,
                "wires": [
                    {
                        "id": "274fd352337bfbb9",
                        "port": 0
                    }
                ]
            }
        ],
        "env": [],
        "meta": {},
        "color": "#DDAA99",
        "status": {
            "x": 308,
            "y": 96,
            "wires": [
                {
                    "id": "274fd352337bfbb9",
                    "port": 1
                }
            ]
        }
    },
    {
        "id": "274fd352337bfbb9",
        "type": "function",
        "z": "e0dfb9db0c37b271",
        "name": "Show Payload",
        "func": "const Now = new Date();\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n                + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n                + (\"0\" + Now.getSeconds()).slice(-2) ;\n\nmsgState = { topic:'state', \n             payload: \"@ \" + NowTime + \": \"+ msg.topic + \": \" + msg.payload,\n             shape: \"dot\",\n             color: \"grey\"\n};\n\nnode.send( [msg, msgState] );\n\nnode.done;",
        "outputs": 2,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 176,
        "y": 64,
        "wires": [
            [],
            []
        ]
    },
    {
        "id": "0b1e401c5678eb33",
        "type": "victron-input-evcharger",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.evcharger/40",
        "path": "/Mode",
        "serviceObj": {
            "service": "com.victronenergy.evcharger/40",
            "name": "Auto Laadstation"
        },
        "pathObj": {
            "path": "/Mode",
            "type": "enum",
            "name": "Mode",
            "enum": {
                "0": "Manual",
                "1": "Auto",
                "2": "Schedule"
            }
        },
        "initial": "",
        "name": "EV_Mode",
        "onlyChanges": false,
        "x": 112,
        "y": 2400,
        "wires": [
            [
                "6c2651c0295e7d0b",
                "0029ad7a00cf0b7a"
            ]
        ]
    },
    {
        "id": "605b02abb2d0e6f1",
        "type": "victron-input-evcharger",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.evcharger/40",
        "path": "/MaxCurrent",
        "serviceObj": {
            "service": "com.victronenergy.evcharger/40",
            "name": "Auto Laadstation"
        },
        "pathObj": {
            "path": "/MaxCurrent",
            "type": "float",
            "name": "Maximum charge current (A)"
        },
        "name": "EV_Max_Charge_Auto_A",
        "onlyChanges": false,
        "roundValues": "no",
        "x": 162,
        "y": 2592,
        "wires": [
            [
                "e05d655aba2681ce",
                "b68016548bd8048a"
            ]
        ]
    },
    {
        "id": "a80b4d274276dc17",
        "type": "victron-input-evcharger",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.evcharger/40",
        "path": "/SetCurrent",
        "serviceObj": {
            "service": "com.victronenergy.evcharger/40",
            "name": "Auto Laadstation"
        },
        "pathObj": {
            "path": "/SetCurrent",
            "type": "float",
            "name": "Set charge current (manual mode) (A)"
        },
        "name": "EV_Max_Charge_Manual_A",
        "onlyChanges": false,
        "roundValues": "no",
        "x": 172,
        "y": 2688,
        "wires": [
            [
                "0a1cb1f18e5a53ef",
                "5d52d345d45747af"
            ]
        ]
    },
    {
        "id": "2973019f7c9ef5e1",
        "type": "victron-input-evcharger",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.evcharger/40",
        "path": "/StartStop",
        "serviceObj": {
            "service": "com.victronenergy.evcharger/40",
            "name": "Auto Laadstation"
        },
        "pathObj": {
            "path": "/StartStop",
            "type": "enum",
            "name": "Start/stop charging (manual mode)",
            "enum": {
                "0": "Stop",
                "1": "Start"
            }
        },
        "initial": "",
        "name": "EV_Start_Stop",
        "onlyChanges": false,
        "x": 132,
        "y": 2496,
        "wires": [
            [
                "56d7e84e89e2ac93",
                "4251c2214bfb5fd7"
            ]
        ]
    },
    {
        "id": "3c33a3ae0c28f30d",
        "type": "victron-input-evcharger",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.evcharger/40",
        "path": "/Status",
        "serviceObj": {
            "service": "com.victronenergy.evcharger/40",
            "name": "Auto Laadstation"
        },
        "pathObj": {
            "path": "/Status",
            "type": "enum",
            "name": "Status",
            "enum": {
                "0": "Disconnected",
                "1": "Connected",
                "2": "Charging",
                "3": "Charged",
                "4": "Waiting for sun",
                "5": "Waiting for RFID",
                "6": "Waiting for start",
                "7": "Low SOC",
                "8": "Ground test error",
                "9": "Welded contacts test error",
                "10": "CP input test error (shorted)",
                "11": "Residual current detected",
                "12": "Undervoltage detected",
                "13": "Overvoltage detected",
                "14": "Overheating detected",
                "15": "Reserved",
                "16": "Reserved",
                "17": "Reserved",
                "18": "Reserved",
                "19": "Reserved",
                "20": "Charging limit",
                "21": "Start charging",
                "22": "Switching to 3 phase",
                "23": "Switching to 1 phase",
                "24": "Stop charging"
            }
        },
        "initial": "",
        "name": "EV_State",
        "onlyChanges": false,
        "x": 112,
        "y": 2304,
        "wires": [
            [
                "440497f22eeb2c4b",
                "50fc62634e324a1e"
            ]
        ]
    },
    {
        "id": "6c2651c0295e7d0b",
        "type": "function",
        "z": "873bf4cb2fbee905",
        "name": "To Text: EV_Mode",
        "func": "// Function To Text: EV_Mode\n\ncontext = context || {};\n\nconst Now = new Date();\nlet NowTime = Now.getFullYear() + \"-\"\n            + (\"0\" + (Now.getMonth() + 1)).slice(-2) + \"-\"\n            + (\"0\" + Now.getDate()).slice(-2) + \" \"\n            + (\"0\" + Now.getHours()).slice(-2) + \":\"\n            + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n            + (\"0\" + Now.getSeconds()).slice(-2) ;\n\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \n\nfunction Code_To_Text (p_code)\n{\n    if (Number(p_code) == 0) \n    {\n        Result = p_code + \": Manual\"\n    }\n    else if (Number(p_code) == 1) \n    {\n        Result = p_code + \": Auto\"\n    }\n    else if (Number(p_code) == 2) \n    {\n        Result = p_code + \": Scheduled\"\n    }\n    else\n    {\n        Result = p_code + \": Unknown\"\n    }\n    \n    return Result;\n\n} // Code_To_Text\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \n\nif ( msg.topic == \"EV_Mode\" )\n{\n    msg.payload = Code_To_Text(msg.payload);\n\n    if (context.LastPayload != msg.payload)\n    {\n        if (context.LastPayload != null)\n        {\n            global.set( \"Victron_MESS.LastRun.EV_Charger.State_EV_Mode\"); // remove\n            global.set( \"Victron_MESS.LastRun.EV_Charger.State_EV_Mode\",\n                NowTime + \"; \" +  context.LastPayload + \" --> \" + msg.payload);\n        }\n\n        context.LastPayload = msg.payload\n\n        node.status( {fill:\"yellow\", shape:\"dot\",\n                      text: \"@ \" + NowTime + \": \"\n                      + msg.payload }\n                   );\n    }\n    else\n    {\n        node.status( {fill:\"green\", shape:\"dot\",\n                      text: \"@ \" + NowTime + \": \"\n                      + msg.payload }\n                   );\n    \n        msg = null;\n    }\n               \n}\nelse {\n    msg = null;\n}\n\nreturn msg;\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 342,
        "y": 2448,
        "wires": [
            [
                "e9f4035e7b4b5c7c"
            ]
        ]
    },
    {
        "id": "e05d655aba2681ce",
        "type": "function",
        "z": "873bf4cb2fbee905",
        "name": "To Text: EV_Max_Charge_Auto_A",
        "func": "// Function To Text: EV_Max_Charge_Auto_A\n\ncontext = context || {};\n\nconst Now = new Date();\nlet NowTime = Now.getFullYear() + \"-\"\n            + (\"0\" + (Now.getMonth() + 1)).slice(-2) + \"-\"\n            + (\"0\" + Now.getDate()).slice(-2) + \" \"\n            + (\"0\" + Now.getHours()).slice(-2) + \":\"\n            + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n            + (\"0\" + Now.getSeconds()).slice(-2) ;\n\n\nif ( msg.topic == \"EV_Max_Charge_Auto_A\" )\n{\n    if (context.LastPayload != msg.payload)\n    {\n        if (context.LastPayload != null)\n        {\n            global.set( \"Victron_MESS.LastRun.EV_Charger.State_Max_Charge_Auto\"); // remove\n            global.set( \"Victron_MESS.LastRun.EV_Charger.State_Max_Charge_Auto\",\n                NowTime + \"; \" +  context.LastPayload + \" --> \" + msg.payload);\n        }\n\n        context.LastPayload = msg.payload\n        \n        msg.payload = \"Max Charge Auto: \" + msg.payload;\n\n        node.status( {fill:\"yellow\", shape:\"dot\",\n                      text: \"@ \" + NowTime + \": \"\n                      + msg.payload }\n                   );\n    }\n    else\n    {\n        node.status( {fill:\"green\", shape:\"dot\",\n                      text: \"@ \" + NowTime + \": \"\n                      + msg.payload }\n                   );\n    \n        msg = null;\n    }\n}\nelse {\n    msg = null;\n}\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 292,
        "y": 2640,
        "wires": [
            [
                "e9f4035e7b4b5c7c"
            ]
        ]
    },
    {
        "id": "0a1cb1f18e5a53ef",
        "type": "function",
        "z": "873bf4cb2fbee905",
        "name": "To Text: EV_Max_Charge_Manual_A",
        "func": "// Function To Text: EV_Max_Charge_Manual_A\n\ncontext = context || {};\n\nconst Now = new Date();\nlet NowTime = Now.getFullYear() + \"-\"\n            + (\"0\" + (Now.getMonth() + 1)).slice(-2) + \"-\"\n            + (\"0\" + Now.getDate()).slice(-2) + \" \"\n            + (\"0\" + Now.getHours()).slice(-2) + \":\"\n            + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n            + (\"0\" + Now.getSeconds()).slice(-2) ;\n\n\nif ( msg.topic == \"EV_Max_Charge_Manual_A\" )\n{\n    if (context.LastPayload != msg.payload)\n    {\n        if (context.LastPayload != null)\n        {\n            global.set( \"Victron_MESS.LastRun.EV_Charger.State_Max_Charge_Manual\"); // remove\n            global.set( \"Victron_MESS.LastRun.EV_Charger.State_Max_Charge_Manual\",\n                NowTime + \"; \" +  context.LastPayload + \" --> \" + msg.payload);\n        }\n\n        context.LastPayload = msg.payload\n\n        msg.payload = \"Max Charge Manual: \" + msg.payload;\n\n        node.status( {fill:\"yellow\", shape:\"dot\",\n                      text: \"@ \" + NowTime + \": \"\n                      + msg.payload }\n                   );\n    }\n    else\n    {\n        node.status( {fill:\"green\", shape:\"dot\",\n                      text: \"@ \" + NowTime + \": \"\n                      + msg.payload }\n                   );\n    \n        msg = null;\n    }\n\n}\nelse {\n    msg = null;\n}\n\nreturn msg;\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 282,
        "y": 2736,
        "wires": [
            [
                "e9f4035e7b4b5c7c"
            ]
        ]
    },
    {
        "id": "56d7e84e89e2ac93",
        "type": "function",
        "z": "873bf4cb2fbee905",
        "name": "To Text: EV_Start_Stop",
        "func": "// Function To Text: EV_Start_Stop\n\ncontext = context || {};\n\nconst Now = new Date();\nlet NowTime = Now.getFullYear() + \"-\"\n            + (\"0\" + (Now.getMonth() + 1)).slice(-2) + \"-\"\n            + (\"0\" + Now.getDate()).slice(-2) + \" \"\n            + (\"0\" + Now.getHours()).slice(-2) + \":\"\n            + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n            + (\"0\" + Now.getSeconds()).slice(-2) ;\n\n\nif ( msg.topic == \"EV_Start_Stop\" )\n{\n    if (context.LastPayload != msg.payload)\n    {\n        if (context.LastPayload != null)\n        {\n            if (Number(msg.payload) == 0)\n            {\n                global.set( \"Victron_MESS.LastRun.EV_Charger.State_Stop\"); // remove\n                global.set( \"Victron_MESS.LastRun.EV_Charger.State_Stop\",\n                    NowTime + \"; \" +  context.LastPayload + \" --> \" + msg.payload);\n            }\n            else\n            {\n                global.set( \"Victron_MESS.LastRun.EV_Charger.State_Start\"); // remove\n                global.set( \"Victron_MESS.LastRun.EV_Charger.State_Start\",\n                    NowTime + \"; \" +  context.LastPayload + \" --> \" + msg.payload);\n                \n            }\n        }\n        \n        context.LastPayload = msg.payload\n    \n        if (msg.payload == \"0\") \n        {\n            msg.payload = msg.topic + \" \" + msg.payload + \": Stop\"\n        }\n        else if (msg.payload == \"1\") \n        {\n            msg.payload = msg.topic + \" \" + msg.payload + \": Start\"\n        }\n        else\n        {\n            msg.payload = \"EV_Start_Stop \" + msg.payload + \": Unknown\"\n        }\n        \n        node.status( {fill:\"yellow\", shape:\"dot\",\n                      text: \"@ \" + NowTime + \": \"\n                      + msg.payload }\n                   );\n\n    }\n    else\n    {\n        node.status( {fill:\"green\", shape:\"dot\",\n                      text: \"@ \" + NowTime + \": \"\n                      + msg.payload }\n                   );\n    \n        msg = null;\n    }\n               \n}\nelse {\n    msg = null;\n}\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 322,
        "y": 2544,
        "wires": [
            [
                "e9f4035e7b4b5c7c"
            ]
        ]
    },
    {
        "id": "440497f22eeb2c4b",
        "type": "function",
        "z": "873bf4cb2fbee905",
        "name": "To Text: EV_State",
        "func": "// Function To Text: EV_State\n\ncontext = context || {};\n\nconst Now = new Date();\nlet NowTime = Now.getFullYear() + \"-\"\n            + (\"0\" + (Now.getMonth() + 1)).slice(-2) + \"-\"\n            + (\"0\" + Now.getDate()).slice(-2) + \" \"\n            + (\"0\" + Now.getHours()).slice(-2) + \":\"\n            + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n            + (\"0\" + Now.getSeconds()).slice(-2) ;\n\n\nfunction Code_To_Text (p_code)\n{\n    let Result = \"\";\n    \n    if (p_code == \"0\")\n\t{\n        Result = p_code + \": Disconnected\"\n\t}\n    else if (p_code == \"1\")\n\t{\n        Result = p_code + \": Connected\"\n\t}\n    else if (p_code == \"2\")\n\t{\n        Result = p_code + \": Charging\"\n\t}\n    else if (p_code == \"3\")\n\t{\n        Result = p_code + \": Charged\"\n\t}\n    else if (p_code == \"4\")\n\t{\n        Result = p_code + \": Waiting for Sun\"\n\t}\n    else if (p_code == \"5\")\n\t{\n        Result = p_code + \": Waiting for RFID\"\n\t}\n    else if (p_code == \"6\")\n\t{\n        Result = p_code + \": Waiting for Start\"\n\t}\n    else if (p_code == \"7\")\n\t{\n        Result = p_code + \": Low SoC\"\n\t}\n    else if (p_code == \"8\")\n\t{\n        Result = p_code + \": Ground Fault\"\n\t}\n    else if (p_code == \"9\")\n\t{\n        Result = p_code + \": Welded Contacts\"\n\t}\n    else if (p_code == \"10\")\n\t{\n        Result = p_code + \": CP input Shorted\"\n\t}\n    else if (p_code == \"11\")\n\t{\n        Result = p_code + \": Residual Current Detected\"\n\t}\n    else if (p_code == \"12\")\n\t{\n        Result = p_code + \": Under Voltage Detected\"\n\t}\n    else if (p_code == \"13\")\n\t{\n        Result = p_code + \": Overvoltage Detected\"\n\t}\n    else if (p_code == \"14\")\n\t{\n        Result = p_code + \": Overheating Detected\"\n\t}\n    else if (p_code == \"21\")\n\t{\n        Result = p_code + \": Start Charge\"\n\t}\n    else if (p_code == \"22\")\n\t{\n        Result = p_code + \": Switch to 3 Phase\"\n\t}\n    else if (p_code == \"23\")\n\t{\n        Result = p_code + \": Switch to 1 Phase\"\n\t}\n    else\n    {\n        Result = p_code + \": Unknown\"\n    }\n    \n    return Result\n    \n} // Code_To_Text\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\nif ( msg.topic == \"EV_State\" )\n{\n    msg.topic = \"EV_State_Text\";\n    \n    msg.payload = Code_To_Text(msg.payload);\n    \n    if (context.LastPayload != msg.payload)\n    {\n        if (context.LastPayload != null)\n        {\n            global.set( \"Victron_MESS.LastRun.EV_Charger.State_EV_State\"); // remove\n            global.set( \"Victron_MESS.LastRun.EV_Charger.State_EV_State\",\n                NowTime + \"; \" +  context.LastPayload + \" --> \" + msg.payload);\n        }\n\n        node.status( {fill:\"yellow\", shape:\"dot\",\n                      text: \"@ \" + NowTime + \": \"\n                      + msg.payload }\n                   );\n                   \n        context.LastPayload = msg.payload\n    }\n    else\n    {\n        node.status( {fill:\"green\", shape:\"dot\",\n                      text: \"@ \" + NowTime + \": \"\n                      + msg.payload }\n                   );\n    \n        msg = null;\n    }\n               \n}\nelse {\n    msg = null;\n}\n\nreturn msg;\n\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 342,
        "y": 2352,
        "wires": [
            [
                "50fc62634e324a1e",
                "e9f4035e7b4b5c7c"
            ]
        ]
    },
    {
        "id": "445dc615fa81dfc9",
        "type": "victron-input-evcharger",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.evcharger/40",
        "path": "/Current",
        "serviceObj": {
            "service": "com.victronenergy.evcharger/40",
            "name": "Auto Laadstation"
        },
        "pathObj": {
            "path": "/Current",
            "type": "float",
            "name": "Charge current (A)"
        },
        "name": "EV_Charge_Current_A",
        "onlyChanges": false,
        "roundValues": "0",
        "x": 152,
        "y": 2896,
        "wires": [
            [
                "c0dd8c60400b4654"
            ]
        ]
    },
    {
        "id": "50fc62634e324a1e",
        "type": "link out",
        "z": "873bf4cb2fbee905",
        "name": "To Set State",
        "mode": "link",
        "links": [
            "2880c73ffcc9698f"
        ],
        "x": 497,
        "y": 2304,
        "wires": []
    },
    {
        "id": "0029ad7a00cf0b7a",
        "type": "link out",
        "z": "873bf4cb2fbee905",
        "name": "To Set State",
        "mode": "link",
        "links": [
            "2880c73ffcc9698f"
        ],
        "x": 349,
        "y": 2400,
        "wires": []
    },
    {
        "id": "4251c2214bfb5fd7",
        "type": "link out",
        "z": "873bf4cb2fbee905",
        "name": "To Set State",
        "mode": "link",
        "links": [
            "2880c73ffcc9698f"
        ],
        "x": 349,
        "y": 2496,
        "wires": []
    },
    {
        "id": "b68016548bd8048a",
        "type": "link out",
        "z": "873bf4cb2fbee905",
        "name": "To Set State",
        "mode": "link",
        "links": [
            "2880c73ffcc9698f"
        ],
        "x": 349,
        "y": 2592,
        "wires": []
    },
    {
        "id": "5d52d345d45747af",
        "type": "link out",
        "z": "873bf4cb2fbee905",
        "name": "To Set State",
        "mode": "link",
        "links": [
            "2880c73ffcc9698f"
        ],
        "x": 349,
        "y": 2688,
        "wires": []
    },
    {
        "id": "c0dd8c60400b4654",
        "type": "link out",
        "z": "873bf4cb2fbee905",
        "name": "To Set State",
        "mode": "link",
        "links": [
            "2880c73ffcc9698f"
        ],
        "x": 413,
        "y": 2896,
        "wires": []
    },
    {
        "id": "e9f4035e7b4b5c7c",
        "type": "change",
        "z": "873bf4cb2fbee905",
        "name": "Set API variables",
        "rules": [
            {
                "t": "set",
                "p": "type",
                "pt": "msg",
                "to": "call_service",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "domain",
                "pt": "msg",
                "to": "input_text",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "service",
                "pt": "msg",
                "to": "set_value",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "entity_id",
                "pt": "msg",
                "to": "input_text.helper_auto_laadstation_log",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 578,
        "y": 2736,
        "wires": [
            [
                "584d22203201a042"
            ]
        ]
    },
    {
        "id": "ce5d8709278fd758",
        "type": "link out",
        "z": "873bf4cb2fbee905",
        "name": "To Call Websocket Api",
        "mode": "link",
        "links": [
            "30a7afbf79ee7ae6"
        ],
        "x": 539,
        "y": 2800,
        "wires": []
    },
    {
        "id": "584d22203201a042",
        "type": "delay",
        "z": "873bf4cb2fbee905",
        "name": "",
        "pauseType": "rate",
        "timeout": "5",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 294,
        "y": 2800,
        "wires": [
            [
                "133911b52fe5091d"
            ]
        ]
    },
    {
        "id": "133911b52fe5091d",
        "type": "subflow:e0dfb9db0c37b271",
        "z": "873bf4cb2fbee905",
        "name": "",
        "x": 446,
        "y": 2800,
        "wires": [
            [
                "ce5d8709278fd758"
            ]
        ]
    }
]

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57

Flow States - SolarEdge



Dan is er nog mijn zonne-energie installatie van SolarEdge.

Afbeeldingslocatie: https://tweakers.net/i/XaVmMFvOdnIbrFFMa_AkEBx0zdM=/800x/filters:strip_exif()/f/image/NMfr0k7CbBbPqCeHDtKrOc4H.png?f=fotoalbum_large
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
[
    {
        "id": "e0dfb9db0c37b271",
        "type": "subflow",
        "name": "Show",
        "info": "",
        "category": "",
        "in": [
            {
                "x": 52,
                "y": 48,
                "wires": [
                    {
                        "id": "274fd352337bfbb9"
                    }
                ]
            }
        ],
        "out": [
            {
                "x": 308,
                "y": 48,
                "wires": [
                    {
                        "id": "274fd352337bfbb9",
                        "port": 0
                    }
                ]
            }
        ],
        "env": [],
        "meta": {},
        "color": "#DDAA99",
        "status": {
            "x": 308,
            "y": 96,
            "wires": [
                {
                    "id": "274fd352337bfbb9",
                    "port": 1
                }
            ]
        }
    },
    {
        "id": "274fd352337bfbb9",
        "type": "function",
        "z": "e0dfb9db0c37b271",
        "name": "Show Payload",
        "func": "const Now = new Date();\nlet NowTime = (\"0\" + Now.getHours()).slice(-2) + \":\"\n                + (\"0\" + Now.getMinutes()).slice(-2) + \":\"\n                + (\"0\" + Now.getSeconds()).slice(-2) ;\n\nmsgState = { topic:'state', \n             payload: \"@ \" + NowTime + \": \"+ msg.topic + \": \" + msg.payload,\n             shape: \"dot\",\n             color: \"grey\"\n};\n\nnode.send( [msg, msgState] );\n\nnode.done;",
        "outputs": 2,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 176,
        "y": 64,
        "wires": [
            [],
            []
        ]
    },
    {
        "id": "321d9521a4b6084d",
        "type": "victron-input-pvinverter",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.pvinverter/31",
        "path": "/Ac/Energy/Forward",
        "serviceObj": {
            "service": "com.victronenergy.pvinverter/31",
            "name": "SolarEdge (ET340)"
        },
        "pathObj": {
            "path": "/Ac/Energy/Forward",
            "type": "float",
            "name": "Total energy (kWh)"
        },
        "name": "SolarEdge (ET340) Energy (kWh)",
        "onlyChanges": false,
        "roundValues": "1",
        "x": 186,
        "y": 3088,
        "wires": [
            [
                "3bc4ac71a41be621"
            ]
        ]
    },
    {
        "id": "d604ef190a895338",
        "type": "debug",
        "z": "873bf4cb2fbee905",
        "name": "SolarEdge_Reading_kWh",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 514,
        "y": 3152,
        "wires": []
    },
    {
        "id": "3bc4ac71a41be621",
        "type": "function",
        "z": "873bf4cb2fbee905",
        "name": "Monitor SolarEdge",
        "func": "const ProgName = \"Monitor SolarEdge\";\n\ncontext = context || {};\n\nlet Now = new Date();\nStartTime = Math.floor(Now.getTime() / 1000);\n\nlet NewLine = \"\\n\\r\";\n\nif (context.First_StartTime == null)\n{\n    context.First_StartTime = StartTime;\n}\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\nif ( !(Get_Functions() ) )\n{   \n    return null;\n}\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\nfunction Get_Functions() {\n\nlet NewLine = \"\\n\\r\";\nlet FuncCount = 0;\nlet FuncNull = 0;\nlet ErrText = \"\";\nlet Result = true;\n\ntry {\n\n    FuncCount++;\n    GetNowTime = global.get(\"Victron_MESS.Functions.GetNowTime\");\n    if (GetNowTime  == null)\n    {   ErrText += \"Victron_MESS.Functions.GetNowTime\" + NewLine;\n        FuncNull += 1;\n        Result = false;\n    }\n\n    FuncCount++;\n    Not = global.get(\"Victron_MESS.Functions.Not\");\n    if (Not  == null)\n    {   ErrText += \"Victron_MESS.Functions.Not\" + NewLine;\n        FuncNull += 1;\n        Result = false;\n    }\n    \n    FuncCount++;\n    Round = global.get(\"Victron_MESS.Functions.Round\");\n    if (Round  == null)\n    {   ErrText += \"Victron_MESS.Functions.Round\" + NewLine;\n        FuncNull += 1;\n        Result = false;\n    }\n\n    if ( !(Result) )\n    {\n        node.status(\n            {fill:\"black\",\n            shape:\"dot\",\n            text: \"Wait for Get_Functions \"\n                + \"(\" + FuncNull + \"/\" + FuncCount + \")...\"\n            }\n        );\n    }\n    \n    if (ErrText > \"\")\n    {   \n        if (context.Next_Error_Time == null)\n        {\n            context.Next_Error_Time = StartTime;\n        }\n        if (StartTime >= context.Next_Error_Time)\n        {\n            node.warn(\"Error Get_Functions:\" + NewLine + ErrText);\n            context.Next_Error_Time = StartTime + 15;\n        }\n\n        node.status({fill:\"red\", shape:\"dot\",\n                     text: ProgName + \": Error Get_Functions\" }\n                   );\n    }\n    \n} // try\ncatch (error)\n{\n    Result = false;\n    \n    node.error(\"Program: \" + ProgName + NewLine\n    + \"Step: Get_Functions\" + NewLine\n    + \"Error: \" + error)\n    \n} // catch\n\nreturn Result;\n    \n} // Get_Functions\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\nlet NowTime = GetNowTime();\nlet NowHour = GetNowTime(\"NowHour\");\nlet NowQuarterHour = GetNowTime(\"NowQuarterHour\");\n\nvar SolarEdge_Energy = 0;\n\nif (context.LastQuarterHour == null) {\n    context.LastQuarterHour = -1;\n}\nif (context.LastReading == null) {\n    context.LastReading = -1;\n}\nif (context.SolarEdge_Reading_kWh == null) {\n    context.SolarEdge_Reading_kWh = global.get(\"Victron_MESS.SolarEdge.Reading\");\n    context.LastQuarterHour = -1;\n    context.LastReading = -1;\n}\nif (context.SolarEdge_Reading_kWh == null) {\n    // e.g. after reboot\n    context.SolarEdge_Reading_kWh = new Array(96); // initialize\n    context.SolarEdge_Reading_kWh.fill(0);\n}\n\n\nswitch (msg.topic) {\n    \n  case \"SolarEdge (ET340) Energy (kWh)\":\n    SolarEdge_Energy = msg.payload;  \n    msg = null;\n    break;\n\n  default: \n    node.warn(\"Unknown Topic: \" + msg.topic \n              + \" Payload: \" + msg.payload);\n    msg = null;\n    break;\n\n} // switch for input all variables\n\nlet newMsg = null;\n\nif (SolarEdge_Energy != null) // a value was received\n{\n    if ( (NowQuarterHour != context.LastQuarterHour)\n      || (context.SolarEdge_Reading_kWh == null) )\n    { // a new quarter has started\n    \n        // node.warn(\"a new quarter has started\");\n    \n        if (NowQuarterHour == 0) \n        { //send old values\n            newMsg = { topic: \"Victron_MESS.SolarEdge.Reading\",\n                       payload: context.SolarEdge_Reading_kWh };\n        } // if new day\n        \n        if ( (NowQuarterHour == 0) || (context.LastQuarterHour == 95) )\n        { // New day, clear history\n            context.SolarEdge_Reading_kWh = new Array(96); // initialize\n            context.SolarEdge_Reading_kWh.fill(0);\n        } // if new day\n        \n        // make sure current value is processed\n        context.LastReading = -1; \n        context.LastQuarterHour = NowQuarterHour;\n    }\n\n    if (SolarEdge_Energy != context.LastReading)\n    { // the value has changed\n\n        context.SolarEdge_Reading_kWh[NowQuarterHour] = SolarEdge_Energy;\n        \n        global.set(\"Victron_MESS.SolarEdge.Reading\", context.SolarEdge_Reading_kWh);\n    \n        global.set(\"Victron_MESS.LastRun.SolarEdge.Reading\"); // remove\n        global.set(\"Victron_MESS.LastRun.SolarEdge.Reading\", NowTime);\n    \n        node.status( \n          { fill:\"green\", \n            shape:\"dot\",\n            text: \"@ \" + NowTime + \" SolarEdge Reading: \"\n                + context.SolarEdge_Reading_kWh[NowQuarterHour] + \" kWh\" } \n        );\n\n        context.LastReading = SolarEdge_Energy;\n    }\n    \n} // if there was a value received\n\nreturn newMsg;\n",
        "outputs": 1,
        "timeout": "4",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 486,
        "y": 3088,
        "wires": [
            [
                "d604ef190a895338"
            ]
        ]
    },
    {
        "id": "cfd0a6d8af878a24",
        "type": "victron-input-pvinverter",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.pvinverter/31",
        "path": "/Ac/Power",
        "serviceObj": {
            "service": "com.victronenergy.pvinverter/31",
            "name": "SolarEdge (ET340)"
        },
        "pathObj": {
            "path": "/Ac/Power",
            "type": "float",
            "name": "Total Power (W)"
        },
        "name": "",
        "onlyChanges": false,
        "roundValues": "0",
        "x": 202,
        "y": 2960,
        "wires": [
            [
                "414db56412cf3068"
            ]
        ]
    },
    {
        "id": "e8a47e95d40e124a",
        "type": "change",
        "z": "873bf4cb2fbee905",
        "name": "x -> Off",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "SolarEdge_Production",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "Off",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 644,
        "y": 2944,
        "wires": [
            [
                "32aa3d4a5c947b8e"
            ]
        ]
    },
    {
        "id": "414db56412cf3068",
        "type": "switch",
        "z": "873bf4cb2fbee905",
        "name": "<= 0 or > 0",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "lte",
                "v": "0",
                "vt": "num"
            },
            {
                "t": "else"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 466,
        "y": 2960,
        "wires": [
            [
                "e8a47e95d40e124a"
            ],
            [
                "a0e077644d6d7185"
            ]
        ]
    },
    {
        "id": "a0e077644d6d7185",
        "type": "change",
        "z": "873bf4cb2fbee905",
        "name": "x -> On",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "SolarEdge_Production",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "On",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 644,
        "y": 2976,
        "wires": [
            [
                "32aa3d4a5c947b8e"
            ]
        ]
    },
    {
        "id": "32aa3d4a5c947b8e",
        "type": "link out",
        "z": "873bf4cb2fbee905",
        "name": "To Set State",
        "mode": "link",
        "links": [
            "2880c73ffcc9698f"
        ],
        "x": 763,
        "y": 2960,
        "wires": []
    },
    {
        "id": "9eb212513d62fe57",
        "type": "victron-input-pvinverter",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.pvinverter/31",
        "path": "/ErrorCode",
        "serviceObj": {
            "service": "com.victronenergy.pvinverter/31",
            "name": "SolarEdge (ET340)"
        },
        "pathObj": {
            "path": "/ErrorCode",
            "type": "enum",
            "name": "Error",
            "enum": {
                "0": "No Error"
            }
        },
        "name": "",
        "onlyChanges": false,
        "roundValues": "0",
        "x": 162,
        "y": 3024,
        "wires": [
            [
                "e7399cf50aa5d7a1"
            ]
        ]
    },
    {
        "id": "e7399cf50aa5d7a1",
        "type": "subflow:e0dfb9db0c37b271",
        "z": "873bf4cb2fbee905",
        "name": "",
        "x": 402,
        "y": 3024,
        "wires": [
            []
        ]
    }
]

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57

Flow States - Monitoring Consumptie



Ik probeer inzicht te krijgen in mijn actuele consumptie per uur (per kwartier is niet nauwkeurig te meten). Het huisverbruik wordt geregistreerd en verder na-berekend.

Afbeeldingslocatie: https://tweakers.net/i/acw40OukSUxJT-sdreFfsYWYuf0=/800x/filters:strip_exif()/f/image/p4gxIQYz73ow65HbsrdHswDq.png?f=fotoalbum_large

Het resultaat wordt één maal per dag per e-mail aan mijzelf gestuurd zodat ik dit in een spreadsheet kan verwerken.

Afbeeldingslocatie: https://tweakers.net/i/b3OFKORRxSVVyrgP3QyV7DKmyiM=/800x/filters:strip_exif()/f/image/gjOXLXxYvNbsKK6kf1QvnYZZ.png?f=fotoalbum_large
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
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
[
    {
        "id": "76b3d3c432059f79",
        "type": "victron-input-vebus",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.vebus/276",
        "path": "/Energy/AcIn1ToAcOut",
        "serviceObj": {
            "service": "com.victronenergy.vebus/276",
            "name": "MultiPlus-II 48/3000/35-32"
        },
        "pathObj": {
            "path": "/Energy/AcIn1ToAcOut",
            "type": "float",
            "name": "Energy ACIn1 to AcOut (kWh)"
        },
        "name": "AcIn1_To_AcOut",
        "onlyChanges": false,
        "roundValues": "3",
        "x": 136,
        "y": 3296,
        "wires": [
            [
                "685dbdcbb65adb98"
            ]
        ]
    },
    {
        "id": "526006de042d3c61",
        "type": "victron-input-vebus",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.vebus/276",
        "path": "/Energy/InverterToAcOut",
        "serviceObj": {
            "service": "com.victronenergy.vebus/276",
            "name": "MultiPlus-II 48/3000/35-32"
        },
        "pathObj": {
            "path": "/Energy/InverterToAcOut",
            "type": "float",
            "name": "Inverter To AcOut (kWh)"
        },
        "name": "Inv_To_AcOut",
        "onlyChanges": false,
        "roundValues": "3",
        "x": 126,
        "y": 3392,
        "wires": [
            [
                "685dbdcbb65adb98"
            ]
        ]
    },
    {
        "id": "07446ba43def2347",
        "type": "victron-input-vebus",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.vebus/276",
        "path": "/Energy/OutToInverter",
        "serviceObj": {
            "service": "com.victronenergy.vebus/276",
            "name": "MultiPlus-II 48/3000/35-32"
        },
        "pathObj": {
            "path": "/Energy/OutToInverter",
            "type": "float",
            "name": "AcOut to Inverter (kWh)"
        },
        "name": "AcOut_To_Inv",
        "onlyChanges": false,
        "roundValues": "3",
        "x": 126,
        "y": 3440,
        "wires": [
            [
                "685dbdcbb65adb98"
            ]
        ]
    },
    {
        "id": "51f962c6b55724d1",
        "type": "victron-input-vebus",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.vebus/276",
        "path": "/Energy/AcOutToAcIn1",
        "serviceObj": {
            "service": "com.victronenergy.vebus/276",
            "name": "MultiPlus-II 48/3000/35-32"
        },
        "pathObj": {
            "path": "/Energy/AcOutToAcIn1",
            "type": "float",
            "name": "Energy AcOut to AcIn1 (kWh)"
        },
        "name": "AcOut_To_AcIn1",
        "onlyChanges": false,
        "roundValues": "3",
        "x": 136,
        "y": 3344,
        "wires": [
            [
                "685dbdcbb65adb98"
            ]
        ]
    },
    {
        "id": "853b26802516bd20",
        "type": "victron-input-gridmeter",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.grid/33",
        "path": "/Ac/Energy/Forward",
        "serviceObj": {
            "service": "com.victronenergy.grid/33",
            "name": "ET340 (Grid)"
        },
        "pathObj": {
            "path": "/Ac/Energy/Forward",
            "type": "float",
            "name": "Total Forward Energy (bought) (kWh)"
        },
        "name": "Grid_In",
        "onlyChanges": false,
        "roundValues": "3",
        "x": 106,
        "y": 3584,
        "wires": [
            [
                "685dbdcbb65adb98"
            ]
        ]
    },
    {
        "id": "eb788518dffb1489",
        "type": "victron-input-gridmeter",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.grid/33",
        "path": "/Ac/Energy/Reverse",
        "serviceObj": {
            "service": "com.victronenergy.grid/33",
            "name": "ET340 (Grid)"
        },
        "pathObj": {
            "path": "/Ac/Energy/Reverse",
            "type": "float",
            "name": "Total Reverse Energy (sold) (kWh)"
        },
        "name": "Grid_Out",
        "onlyChanges": false,
        "roundValues": "3",
        "x": 116,
        "y": 3632,
        "wires": [
            [
                "685dbdcbb65adb98"
            ]
        ]
    },
    {
        "id": "9c3105c15958fdfe",
        "type": "victron-input-vebus",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.vebus/276",
        "path": "/Energy/AcIn1ToInverter",
        "serviceObj": {
            "service": "com.victronenergy.vebus/276",
            "name": "MultiPlus-II 48/3000/35-32"
        },
        "pathObj": {
            "path": "/Energy/AcIn1ToInverter",
            "type": "float",
            "name": "Energy AcIn1 to Inverter (kWh)"
        },
        "name": "AcIn1_To_Inv",
        "onlyChanges": false,
        "roundValues": "3",
        "x": 126,
        "y": 3488,
        "wires": [
            [
                "685dbdcbb65adb98"
            ]
        ]
    },
    {
        "id": "fa57ea8d3a0c2bce",
        "type": "victron-input-vebus",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.vebus/276",
        "path": "/Energy/InverterToAcIn1",
        "serviceObj": {
            "service": "com.victronenergy.vebus/276",
            "name": "MultiPlus-II 48/3000/35-32"
        },
        "pathObj": {
            "path": "/Energy/InverterToAcIn1",
            "type": "float",
            "name": "Energy Inverter to AcIn1 (kWh)"
        },
        "name": "Inv_To_AcIn1",
        "onlyChanges": false,
        "roundValues": "3",
        "x": 126,
        "y": 3536,
        "wires": [
            [
                "685dbdcbb65adb98"
            ]
        ]
    },
    {
        "id": "685dbdcbb65adb98",
        "type": "function",
        "z": "873bf4cb2fbee905",
        "name": "Monitor Consumption",
        "func": "const ProgName = \"Monitor Consumption\";\n\ncontext = context || {};\n\nlet Now = new Date();\nStartTime = Math.floor(Now.getTime() / 1000);\n\nlet NewLine = \"\\n\\r\";\n\nif (context.First_StartTime == null)\n{\n    context.First_StartTime = StartTime;\n}\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\nif ( !(Get_Functions() ) )\n{   \n    return null;\n}\n\n// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n\nfunction Get_Functions() {\n\nlet NewLine = \"\\n\\r\";\nlet FuncCount = 0;\nlet FuncNull = 0;\nlet ErrText = \"\";\nlet Result = true;\n\ntry {\n\n    FuncCount++;\n    GetNowTime = global.get(\"Victron_MESS.Functions.GetNowTime\");\n    if (GetNowTime  == null)\n    {   ErrText += \"Victron_MESS.Functions.GetNowTime\" + NewLine;\n        FuncNull += 1;\n        Result = false;\n    }\n\n    FuncCount++;\n    GetYesterday = global.get(\"Victron_MESS.Functions.GetYesterday\");\n    if (GetYesterday  == null)\n    {   ErrText += \"Victron_MESS.Functions.GetYesterday\" + NewLine;\n        FuncNull += 1;\n        Result = false;\n    }\n\n    FuncCount++;\n    Not = global.get(\"Victron_MESS.Functions.Not\");\n    if (Not  == null)\n    {   ErrText += \"Victron_MESS.Functions.Not\" + NewLine;\n        FuncNull += 1;\n        Result = false;\n    }\n    \n    FuncCount++;\n    Round = global.get(\"Victron_MESS.Functions.Round\");\n    if (Round  == null)\n    {   ErrText += \"Victron_MESS.Functions.Round\" + NewLine;\n        FuncNull += 1;\n        Result = false;\n    }\n\n    if ( !(Result) )\n    {\n        node.status(\n            {fill:\"black\",\n            shape:\"dot\",\n            text: \"Wait for Get_Functions \"\n                + \"(\" + FuncNull + \"/\" + FuncCount + \")...\"\n            }\n        );\n    }\n    \n    if (ErrText > \"\")\n    {   \n        if (context.Next_Error_Time == null)\n        {\n            context.Next_Error_Time = StartTime;\n        }\n        if (StartTime >= context.Next_Error_Time)\n        {\n            node.warn(\"Error Get_Functions:\" + NewLine + ErrText);\n            context.Next_Error_Time = StartTime + 15;\n        }\n\n        node.status({fill:\"red\", shape:\"dot\",\n                     text: ProgName + \": Error Get_Functions\" }\n                   );\n    }\n    \n} // try\ncatch (error)\n{\n    Result = false;\n    \n    node.error(\"Program: \" + ProgName + NewLine\n    + \"Step: Get_Functions\" + NewLine\n    + \"Error: \" + error)\n    \n} // catch\n\nreturn Result;\n    \n} // Get_Functions\n\n// - - - - - - - - - - - - - - - - - - - - - - -\n\nfunction MakeEmail(p_Name)\n{\n\n    // node.warn(\"MakeEmail Started\");\n    DebugStep = \"MakeEmail Started\";\n    \n    let Export = \"\";\n    let NewLine = \"\\n\\r\";\n    let Delimiter = \"; \";\n    \n\n    DebugStep = \"MakeEmail; Fill Sensorlist\";\n    \n    for (let t=0 ; t < 3; t++) // three title lines\n    {\n        let Split_Col1 = [\"\", \"\", \"uur\"];\n        Export += Split_Col1[t];\n\n        for (let i = 0; i < SensorList.length; i++)\n        {\n            let Split_Title = (SensorList[i]+ \" _ _\").split(\"_\");\n            let Split_Delta = [\"\", \"dit\", \"uur\"];\n            Export += Delimiter + Split_Title[t]\n                    + Delimiter + Split_Delta[t];\n        }\n\n        let Consump_Critical_Title = [ \"\", \"Consump\", \"Critical\"];\n        let Consump_Non_Crit_Title = [ \"\", \"Consump\", \"Non.Crit\"];\n        let Consump_Total_Title = [ \"\", \"Consump\", \"Total\"];\n\n        Export += Delimiter + Consump_Critical_Title[t];\n        Export += Delimiter + Consump_Non_Crit_Title[t];\n        Export += Delimiter + Consump_Total_Title[t];\n        \n        Export += EndLine;\n    }\n    Export += EndLine;\n\n    DebugStep = \"MakeEmail; Fill Table\";\n    \n    for (let u = 0; u<25; u++)\n    {   // now individual rows\n        Export += u;\n\n        for (let i = 0; i < SensorList.length; i++)\n        {\n            Export += Delimiter \n                + context.ReadingTable[i][u].toLocaleString( \"nl-NL\", {useGrouping: false} );\n\n            if ( (u <= 23)\n              && (context.ReadingTable[i][u+1] != 0) )\n            {\n                let Delta_This_Hour = \n                    context.ReadingTable[i][u+1] - context.ReadingTable[i][u];\n                Export += Delimiter \n                    + Delta_This_Hour.toLocaleString( \"nl-NL\", {useGrouping: false} );\n            }\n            else\n            {\n                Export += Delimiter + \"0\";\n            }\n        }\n\n        let Consump_Critical = 0;\n        let Consump_Non_Crit = 0;\n\n        if ( (u <= 23)\n          && (context.ReadingTable[0][u+1] != 0) )\n        {\n            Consump_Critical = \n                context.ReadingTable[0][u+1] - context.ReadingTable[0][u] // AcIn1_To_AcOut\n                + context.ReadingTable[2][u+1] - context.ReadingTable[2][u] // Inv_To_AcOut\n                + context.ReadingTable[8][u+1] - context.ReadingTable[8][u] // SolarEdge\n                - context.ReadingTable[1][u+1] + context.ReadingTable[1][u] // AcOut_To_AcIn1\n                - context.ReadingTable[3][u+1] + context.ReadingTable[3][u]; // AcOut_To_Inv\n                \n            Consump_Non_Crit = \n                context.ReadingTable[6][u+1] - context.ReadingTable[6][u] // Grid_In\n                - context.ReadingTable[7][u+1] + context.ReadingTable[7][u] // Grid_Out\n                - context.ReadingTable[0][u+1] + context.ReadingTable[0][u] // AcIn1_To_AcOut\n                - context.ReadingTable[4][u+1] + context.ReadingTable[4][u] // AcIn1_To_Inv\n                + context.ReadingTable[3][u+1] - context.ReadingTable[3][u] // AcOut_To_Inv\n                + context.ReadingTable[5][u+1] - context.ReadingTable[5][u]; // Inv_To_AcIn1\n        }    \n        \n        let Consump_Total = Consump_Critical + Consump_Non_Crit;\n        \n        Export += Delimiter \n            + Consump_Critical.toLocaleString( \"nl-NL\", {useGrouping: false} );\n        \n        Export += Delimiter \n            + Consump_Non_Crit.toLocaleString( \"nl-NL\", {useGrouping: false} );\n\n        Export += Delimiter \n            + Consump_Total.toLocaleString( \"nl-NL\", {useGrouping: false} );\n\n        Export += EndLine;\n    }\n    \n    DebugStep = \"MakeEmail; Make msgEmail\";\n    \n    msgEmail = { \n        topic: \"All Reading \" + p_Name,\n        payload: Export};\n    \n} // MakeEmail\n\n// - - - - - - - - - - - - - - - - - - - - - - -\n\ntry\n{\n\n    var DebugStep = \"Start\";\n\n    var NowTime = GetNowTime();\n    var NowHour = GetNowTime(\"NowHour\");\n    var Yesterday = GetYesterday(\"YYYYMMDD\");\n    Yesterday = Yesterday.replaceAll(\"-\", \"\").substring(4);\n\n    var EndLine = \"\\n\";\n    var msgEmail = null;\n    var Explain = \"\";\n    var Make_Snapshot = false;\n    var Make_Email_Now = false;\n\n// - - - - - - - - - - - - - - - - - - - - - - -\n\n    DebugStep = \"Clear Context\";\n\n    if (msg.clearcontext) \n    {\n        context = null;\n    \n        node.status({fill:\"blue\",\n            shape:\"dot\",\n            text: \"@ \" + NowTime + \" - Clear Context\"}\n         );\n    \n        return null;\n    }\n\n    DebugStep = \"Prepare Lists\";\n    \n    var SensorList = [\"AcIn1_To_AcOut\",\n            \"AcOut_To_AcIn1\",\n            \"Inv_To_AcOut\",\n            \"AcOut_To_Inv\",\n            \"AcIn1_To_Inv\",\n            \"Inv_To_AcIn1\",\n            \"Grid_In\",\n            \"Grid_Out\",\n            \"SolarEdge\"\n        ]\n    \n    if (context.ReadingTable == null) {\n        context.ReadingTable = [];\n    }\n    \n    if (context.LastReadingValue == undefined) {\n        context.LastReadingValue = new Array(SensorList.length);\n        context.LastReadingValue.fill(0);\n    }\n\n    DebugStep = \"Input Msg\";\n    \n    if (msg.topic == \"Snapshot\")\n    {\n        Make_Snapshot = true;\n    }\n    else if (msg.topic == \"EmailNow\")\n    {\n        Make_Email_Now = true;\n    }\n    else if (SensorList.indexOf(msg.topic) >= 0)\n    {\n        context.LastReadingValue[SensorList.indexOf(msg.topic)] = msg.payload;\n    }\n    else\n    {\n        node.warn(\"Unknown topic: \" + msg.topic);\n    }\n\n    DebugStep = \"Prepare Reading Table\";\n    \n    for (let i = 0; i < SensorList.length; i++)\n    {\n        if (context.ReadingTable[i] == null) {\n            // node.warn(\"Read from Global: \" + SensorList[i]+ \"...\")\n            context.ReadingTable[i] = \n                global.get(\"Victron_MESS.Reading.Today.\" + SensorList[i]);\n            if (context.ReadingTable[i] == null) \n            {   // e.g. after reboot\n                // node.warn(\"Initialise: \" + SensorList[i]+ \"...\")\n                context.ReadingTable[i] = new Array(25); // initialize\n                for (let j = 0; j<25; j++)\n                {\n                    context.ReadingTable[i][j] = 0;\n                }\n            }\n        }\n    }\n\n    if (Make_Email_Now == true)\n    {\n        \n        DebugStep = \"Monitor Consumption; Make_Email_Now == true\";\n        \n        MakeEmail(\"On_Demand\");\n\n        node.status({fill:\"yellow\",\n            shape:\"dot\",\n            text: \"@ \" + NowTime + \" - Email Now\"}\n         );\n    \n    }\n\n    else if (Make_Snapshot == true)\n    {\n        DebugStep = \"Snapshot is true\";\n\n        if (NowHour == 0)\n        {   \n            DebugStep = \"Finish previous day\";\n            \n            for (let i = 0; i < SensorList.length; i++)\n            {\n                context.ReadingTable[i][24] = context.LastReadingValue[i];\n                \n                let TempTable = [];\n                for (let j = 0; j < 25; j++)\n                {\n                    TempTable[j] = context.ReadingTable[i][j];\n                }\n                global.set(\"Victron_MESS.Reading.\" + Yesterday + \".\"\n                    + SensorList[i] ); //  delete\n                global.set(\"Victron_MESS.Reading.\" + Yesterday + \".\"\n                    + SensorList[i], TempTable );\n            }\n            \n            DebugStep = \"MakeEmail\";\n            \n            MakeEmail(Yesterday);\n\n            // start new day\n            for (let i = 0; i < SensorList.length; i++)\n            {\n                for (let j = 0; j<25; j++)\n                {\n                    context.ReadingTable[i][j] = 0;\n                }\n            } // all tables\n            \n            \n        } // if NowHour = 0\n\n        DebugStep = \"Fill current day\";\n        \n        for (let i = 0; i < SensorList.length; i++)\n        {\n            context.ReadingTable[i][NowHour] = context.LastReadingValue[i];\n            \n            global.set(\"Victron_MESS.Reading.Today.\" + SensorList[i],\n                context.ReadingTable[i]);\n                \n            global.set(\"Victron_MESS.LastRun.Reading\"); // remove\n            global.set(\"Victron_MESS.LastRun.Reading\", NowTime);\n            \n        } // write current states\n\n        DebugStep = \"Status\";\n    \n        node.status( \n            { fill:\"green\", \n                shape:\"dot\",\n                text: \"@ \" + NowTime} \n            )\n    \n    } // if snapshot\n\n} // try\ncatch (error)\n{\n    node.warn(\"Error in Monitor Consumption: \" + NewLine\n        + \"  Step = \" + DebugStep + NewLine\n        + \"  Error \" + error)\n    \n}\n\nreturn msgEmail;\n",
        "outputs": 1,
        "timeout": "4",
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 468,
        "y": 3584,
        "wires": [
            [
                "7f8af95245cd6c5f"
            ]
        ]
    },
    {
        "id": "7cf9c6eb4e888055",
        "type": "victron-input-pvinverter",
        "z": "873bf4cb2fbee905",
        "service": "com.victronenergy.pvinverter/31",
        "path": "/Ac/Energy/Forward",
        "serviceObj": {
            "service": "com.victronenergy.pvinverter/31",
            "name": "SolarEdge (ET340)"
        },
        "pathObj": {
            "path": "/Ac/Energy/Forward",
            "type": "float",
            "name": "Total energy (kWh)"
        },
        "name": "SolarEdge",
        "onlyChanges": false,
        "roundValues": "3",
        "x": 116,
        "y": 3680,
        "wires": [
            [
                "685dbdcbb65adb98"
            ]
        ]
    },
    {
        "id": "75a1fdade29288f0",
        "type": "cronplus",
        "z": "873bf4cb2fbee905",
        "name": "Every Hour",
        "outputField": "payload",
        "timeZone": "",
        "storeName": "",
        "commandResponseMsgOutput": "output1",
        "defaultLocation": "",
        "defaultLocationType": "default",
        "outputs": 1,
        "options": [
            {
                "name": "schedule1",
                "topic": "Snapshot",
                "payloadType": "default",
                "payload": "",
                "expressionType": "cron",
                "expression": "0 0 * * * * *",
                "location": "",
                "offset": "0",
                "solarType": "all",
                "solarEvents": "sunrise,sunset"
            }
        ],
        "x": 126,
        "y": 3248,
        "wires": [
            [
                "685dbdcbb65adb98"
            ]
        ]
    },
    {
        "id": "6ce833fc22cf97c9",
        "type": "change",
        "z": "873bf4cb2fbee905",
        "name": "",
        "rules": [
            {
                "t": "delete",
                "p": "Victron_MESS.Reading.Today",
                "pt": "global"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 534,
        "y": 3344,
        "wires": [
            []
        ]
    },
    {
        "id": "3453b9a19d9232d2",
        "type": "inject",
        "z": "873bf4cb2fbee905",
        "name": "On Demand",
        "props": [
            {
                "p": "topic",
                "vt": "str"
            },
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "Delete Reading Today",
        "payload": "( $moment().format(\"YYYY-MM-DD HH:mm:ss\") )",
        "payloadType": "jsonata",
        "x": 438,
        "y": 3296,
        "wires": [
            [
                "6ce833fc22cf97c9"
            ]
        ]
    },
    {
        "id": "7f8af95245cd6c5f",
        "type": "link out",
        "z": "873bf4cb2fbee905",
        "name": "Link Out Readings to Email",
        "mode": "link",
        "links": [
            "937f02372220657c"
        ],
        "x": 627,
        "y": 3568,
        "wires": []
    },
    {
        "id": "315f5c9b26681c1e",
        "type": "change",
        "z": "873bf4cb2fbee905",
        "name": "Set Clear Context",
        "rules": [
            {
                "t": "set",
                "p": "clearcontext",
                "pt": "msg",
                "to": "true",
                "tot": "bool"
            },
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "Clear Context",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 658,
        "y": 3488,
        "wires": [
            [
                "685dbdcbb65adb98"
            ]
        ]
    },
    {
        "id": "b983663d4790616b",
        "type": "inject",
        "z": "873bf4cb2fbee905",
        "name": "On Demand",
        "props": [
            {
                "p": "topic",
                "vt": "str"
            },
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "Delete Context",
        "payload": "( $moment().format(\"YYYY-MM-DD HH:mm:ss\") )",
        "payloadType": "jsonata",
        "x": 450,
        "y": 3488,
        "wires": [
            [
                "315f5c9b26681c1e"
            ]
        ]
    },
    {
        "id": "9ba440736a726214",
        "type": "inject",
        "z": "873bf4cb2fbee905",
        "name": "Email Now",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "EmailNow",
        "payload": "",
        "payloadType": "date",
        "x": 116,
        "y": 3200,
        "wires": [
            [
                "685dbdcbb65adb98"
            ]
        ]
    }
]

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57

Zoek de juiste kwartieren - hoog laag enz.



Voor lezers die zich laten inspireren door de beschrijvingen van MESS geeft ik hier een toelichting op de werkwijze van het programma dat de 'juiste' kwartieren opzoekt. Wat juist is of niet hangt af van de situatie van de dag. Voorbeeld, 20 cent (netto) voor de KiloWattUur prijs kan soms hoog zijn, maar soms ook laag zijn. Dat is afhankelijk van de andere prijzen van die dag. Er is een situatie geweest dat ik bij 60 cent per KwH heb geladen om bij 90 cent per KwH terug te leveren aan het Grid. Toch nog 'winst'.

Het programma heet Find High and Low QH en is opgenomen in een Flow DayAhead. Het gaat om de resterende kwartieren van de actuele dag en de kwartieren van de volgende dag.

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

Kies hieronder voor de JavaScript-programmatekst van de drie functies.
Programma Find High and Low QH
Programma QH_Ct to Separate Hours
Programma High and Low as Text to Home Assistant

In de Flow zie je een Inject Node Trace On. Als je deze gebruikt verschijnt er een kort verslag van de opties en beslissingen.
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
Find High and Low QH

Start of Program
SoC_Min_Manual_Perc: 20
NegativePriceThresholdCt: -0.5
ZeroPriceThresholdCt: 0.1
VeryLowPriceThresholdCt: 2
Conversion_Loss_Factor: 0.9
Sell_Price_Difference_Limit: 5.0ct
Buy_Price_Difference_Limit: 10.0ct
SoC_Now_Perc: 100

Today_HighestPrice: 15.4ct
Today_LowestPrice: 4.7ct
Today_AveragePrice: 8.7ct
Today High Threshold: 12.0ct
Today Low Threshold: 5.7ct

Tomorrow_HighestPrice: -99.0ct
Tomorrow_LowestPrice: -99.0ct
Tomorrow_AveragePrice: -99.0ct
Tomorrow High Threshold: -99.0ct
Tomorrow Low Threshold: -99.0ct

Search for high and low QH
Sort QH_High High to Low
Sort other Tables Low to High

Find Sell QH by Highest prices
Sell_Price_Difference_Limit: 5.0ct
Current SoC: 100%
MaxSellQH: 6

High Price 17:30: 15.4ct
    incl.Cost: 33.4ct
    Conv.loss: 30.1ct
  Search matching re-charge QH

High Price 17:45: 15.1ct
    incl.Cost: 33.0ct
    Conv.loss: 29.7ct
  Search matching re-charge QH

High Price 16:45: 14.9ct
    incl.Cost: 32.8ct
    Conv.loss: 29.5ct
  Search matching re-charge QH

High Price 17:15: 14.7ct
    incl.Cost: 32.5ct
    Conv.loss: 29.3ct
  Search matching re-charge QH

High Price 18:00: 14.7ct
    incl.Cost: 32.5ct
    Conv.loss: 29.3ct
  Search matching re-charge QH

High Price 16:30: 13.5ct
    incl.Cost: 31.1ct
    Conv.loss: 28.0ct
  Search matching re-charge QH

High Price 19:00: 12.9ct
    incl.Cost: 30.4ct
    Conv.loss: 27.3ct
  Search matching re-charge QH

Find earlier QH to Charge extra...
Buy_Price_Difference_Limit: 10.0ct

Write Tables to Global
Done.
Korte toelichting.
  • Het programma haalt eerst de parameters op die worden gebruikt bij de beoordeling. Ik ga ervan uit dat de namen voldoende weergeven wat de betekenis is.
  • Dan berekent het programma de grenswaarden voor de actuele dag en de volgende dag. Een waarde -99 cent betekent dat de gegevens nog niet bekend zijn.
  • De aldus gemaakte tabellen worden gesorteerd op bedrag en, afhankelijk van de berekende grenswaarden, geplaatst in tabellen 'hoog', 'laag' etc.
  • Het kan zinvol zijn om bij hoge tarieven actief naar het Grid te ontladen. Maar... alleen als er kort daarna kan worden geladen tegen een voldoende lagere prijs. Er moet voldoende energie in de batterij overblijven om dat re-charge punt te halen. Als dat er niet is vervalt de verkoop-optie.
  • Als er verkoop-kwartieren gevonden worden kan het (ook nog) interessant zijn om tevoren de batterij extra te laden. Er is dan meer om te verkopen.
Een uitgebreidere uitleg plaats ik bij de programma-tekst hierna.

Er zijn ook nog twee functies die de gevonden gegevens naar Home Assistant sturen. Ik heb daar een dashboard gemaakt. Voor de werking van MESS is dit niet essentieel.

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

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

De Prognose SoC wordt berekend op basis van het overschot van de Prognose Zonne-opbrenst (het Surplus) en het geschatte huis-verbruik (Consumptie). Hierbij is nog geen rekening gehouden met laden / ontladen van het Grid. Dat is namelijk wat MESS - op basis van deze prognose - gaat bepalen.

[ Voor 30% gewijzigd door MJ de Bruijn op 02-12-2025 14:09 ]


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57

Programma Find High and Low QH



Voor lezers die dit programma willen nabouwen - of er inspiratie uit halen - volgt hier de programma-tekst met toelichting. Vanwege de maximale lengte van een forum-bijdrage heb ik dit over meerdere bijdragen verdeeld.
JavaScript:
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
const ProgName = "Find High and Low QH";
const Prog_ID = "HighAndLow";

context = context || {};

let Now = new Date();
StartTime = Math.floor(Now.getTime() / 1000);

let NewLine = "\n\r";

if (context.First_StartTime == null)
{
    context.First_StartTime = StartTime;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

if ( !(Get_Functions() ) )
{   
    return;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function Get_Functions() {

let NewLine = "\n\r";
let FuncCount = 0;
let FuncNull = 0;
let ErrText = "";
let Result = true;

try {

    FuncCount++;
    GetNowTime = global.get("Victron_MESS.Functions.GetNowTime");
    if (GetNowTime  == null)
    {   ErrText += "Victron_MESS.Functions.GetNowTime" + NewLine;
        FuncNull += 1;
        Result = false;
    }

    FuncCount++;
    Not = global.get("Victron_MESS.Functions.Not");
    if (Not  == null)
    {   ErrText += "Victron_MESS.Functions.Not" + NewLine;
        FuncNull += 1;
        Result = false;
    }
    
    FuncCount++;
    CtToText = global.get("Victron_MESS.Functions.CtToText");
    if (Not  == null)
    {   ErrText += "Victron_MESS.Functions.Not" + NewLine;
        FuncNull += 1;
        Result = false;
    }
    
    FuncCount++;
    HourToText = global.get("Victron_MESS.Functions.HourToText");
    if (HourToText  == null)
    {   ErrText += "Victron_MESS.Functions.HourToText" + NewLine;
        FuncNull += 1;
        Result = false;
    }
    
    FuncCount++;
    QHToText = global.get("Victron_MESS.Functions.QHToText");
    if (QHToText  == null)
    {   ErrText += "Victron_MESS.Functions.QHToText" + NewLine;
        FuncNull += 1;
        Result = false;
    }

    FuncCount++;
    Round = global.get("Victron_MESS.Functions.Round");
    if (Round  == null)
    {   ErrText += "Victron_MESS.Functions.Round" + NewLine;
        FuncNull += 1;
        Result = false;
    }
    
    FuncCount++;
    Price_incl_Cost = global.get("Victron_MESS.Functions.Price_incl_Cost");
    if (Price_incl_Cost  == null)
    {   ErrText += "Victron_MESS.Functions.Price_incl_Cost" + NewLine;
        FuncNull += 1;
        Result = false;
    }
    
    FuncCount++;
    IsInArrayOfSets = global.get("Victron_MESS.Functions.IsInArrayOfSets");
    if (IsInArrayOfSets  == null)
    {   ErrText += "Victron_MESS.Functions.IsInArrayOfSets" + NewLine;
        FuncNull += 1;
        Result = false;
    }
    
    FuncCount++;
    ArrayOfSets_SortByPrice_Asc = 
        global.get("Victron_MESS.Functions.ArrayOfSets_SortByPrice_Asc");
    if (ArrayOfSets_SortByPrice_Asc  == null)
    {   ErrText += "Victron_MESS.Functions.ArrayOfSets_SortByPrice_Asc" + NewLine;
        FuncNull += 1;
        Result = false;
    }
    
    FuncCount++;
    ArrayOfSets_SortByPrice_Desc = 
        global.get("Victron_MESS.Functions.ArrayOfSets_SortByPrice_Desc");
    if (ArrayOfSets_SortByPrice_Desc  == null)
    {   ErrText += "Victron_MESS.Functions.ArrayOfSets_SortByPrice_Desc" + NewLine;
        FuncNull += 1;
        Result = false;
    }
    
    FuncCount++;
    ArrayOfSets_SortOnlyByPrice_Asc = 
        global.get("Victron_MESS.Functions.ArrayOfSets_SortOnlyByPrice_Asc");
    if (ArrayOfSets_SortOnlyByPrice_Asc  == null)
    {   ErrText += "Victron_MESS.Functions.ArrayOfSets_SortOnlyByPrice_Asc" + NewLine;
        FuncNull += 1;
        Result = false;
    }
    
    FuncCount++;
    Add_Trace = global.get("Victron_MESS.Functions.Add_Trace");
    if (Add_Trace  == null)
    {   ErrText += "Victron_MESS.Functions.Add_Trace" + NewLine;
        FuncNull += 1;
        Result = false;
    }
    else
    {
        Add_Trace(Prog_ID, "ClearTrace");
    }

    if ( !(Result) )
    {
        node.status(
            {fill:"black",
            shape:"dot",
            text: "Wait for Get_Functions "
                + "(" + FuncNull + "/" + FuncCount + ")..."
            }
        );
    }
    
    if ( (ErrText > "")
      && ( StartTime > context.First_StartTime + 15 )
       )
    {   
        node.warn("Get_Functions:" + NewLine + ErrText);

        node.status({fill:"red", shape:"dot",
                     text: ProgName + ": Error Get_Functions" 
                     + "\n\r" + ErrText}
                   );
    }
    
} // try
catch (error)
{
    Result = false;
    
    node.error("Program: " + ProgName + NewLine
    + "Step: Get_Functions" + NewLine
    + "Error: " + error)
    
} // catch

return Result;
    
} // Get_Functions

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Het programma begint met het ophalen van voor MESS gemaakte 'standaard' functies.

Voor de inhoud van deze functies zie:
Flow Parameters - SetGlobalFunctions

[ Voor 83% gewijzigd door MJ de Bruijn op 02-12-2025 13:07 ]


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
vervolg...
JavaScript:
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
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function GlobalGetAll()
{
    
let Result = true;
let ErrText = "";
let NewLine = "\n\r";
let VarCount = 0;
let VarNull = 0;

try {

    VarCount += 1;
    SoC_Min_Manual_Perc = global.get("Victron_MESS.Manual.SoC_Min_Perc");
    if (SoC_Min_Manual_Perc == null) {
        ErrText += "Victron_MESS.Manual.SoC_Min_Perc: null" + NewLine;
        VarNull += 1;
        Result = false;
    }
    else
    {
        Add_Trace(Prog_ID, "SoC_Min_Manual_Perc: " + SoC_Min_Manual_Perc + NewLine);
    }

    VarCount += 1;
    QH_Ct = 
      global.get("Victron_MESS.DayAhead.QH_Ct");
    if (QH_Ct == null) {
        ErrText += "Victron_MESS.DayAhead.QH_Ct: null" + NewLine;
        VarNull += 1;
        Result = false;
    }

    VarCount += 1;
    QH_Level = 
      global.get("Victron_MESS.DayAhead.QH_Level");
    if (QH_Level == null) {
        ErrText += "Victron_MESS.DayAhead.QH_Level: null" + NewLine;
        VarNull += 1;
        Result = false;
    }

    VarCount += 1;
    NegativePriceThresholdCt = 
      global.get("Victron_MESS.Param.NegativePriceThresholdCt");
    if (NegativePriceThresholdCt == null) {
        ErrText += "Victron_MESS.Param.NegativePriceThresholdCt: null" + NewLine;
        VarNull += 1;
        Result = false;
    }
    else
    {
        Add_Trace(Prog_ID, "NegativePriceThresholdCt: " + NegativePriceThresholdCt + NewLine);
    }

    VarCount += 1;
    ZeroPriceThresholdCt = 
      global.get("Victron_MESS.Param.ZeroPriceThresholdCt");
    if (ZeroPriceThresholdCt == null) {
        ErrText += "Victron_MESS.Param.ZeroPriceThresholdCt: null" + NewLine;
        VarNull += 1;
        Result = false;
    }
    else
    {
        Add_Trace(Prog_ID, "ZeroPriceThresholdCt: " + ZeroPriceThresholdCt + NewLine);
    }
    
    VarCount += 1;
    VeryLowPriceThresholdCt = 
      global.get("Victron_MESS.Param.VeryLowPriceThresholdCt");
    if (VeryLowPriceThresholdCt == null) {
        ErrText += "Victron_MESS.Param.VeryLowPriceThresholdCt: null" + NewLine;
        VarNull += 1;
        Result = false;
    }
    else
    {
        Add_Trace(Prog_ID, "VeryLowPriceThresholdCt: " + VeryLowPriceThresholdCt + NewLine);
    }
    
    VarCount += 1;
    Conversion_Loss_Factor = 
      global.get("Victron_MESS.Param.Conversion_Loss_Factor");
    if (Conversion_Loss_Factor == null) {
        ErrText += "Victron_MESS.Param.Conversion_Loss_Factor: null" + NewLine;
        VarNull += 1;
        Result = false;
    }
    else
    {
        Add_Trace(Prog_ID, "Conversion_Loss_Factor: " + Conversion_Loss_Factor + NewLine);
    }
    
    VarCount += 1;
    Sell_Price_Difference_LimitCt = 
      global.get("Victron_MESS.Param.Sell_Price_Difference_LimitCt");
    if (Sell_Price_Difference_LimitCt == null) {
        ErrText += "Victron_MESS.Param.Sell_Price_Difference_LimitCt: null" + "\n\r";
        VarNull += 1;
        Result = false;
    }
    else
    {
        Add_Trace(Prog_ID, "Sell_Price_Difference_Limit: " 
            + CtToText(Sell_Price_Difference_LimitCt) + NewLine);
    }
    
    VarCount += 1;
    Buy_Price_Difference_LimitCt = 
      global.get("Victron_MESS.Param.Buy_Price_Difference_LimitCt");
    if (Buy_Price_Difference_LimitCt == null) {
        ErrText += "Victron_MESS.Param.Buy_Price_Difference_LimitCt: null" + "\n\r";
        VarNull += 1;
        Result = false;
    }
    else
    {
        Add_Trace(Prog_ID, "Buy_Price_Difference_Limit: " 
            + CtToText(Buy_Price_Difference_LimitCt) + NewLine);
    }
    
    VarCount += 1;
    SoC_Now_Perc = 
      global.get("Victron_MESS.State.SoC_Now_Perc");
    if (SoC_Now_Perc == null) {
        ErrText += "Victron_MESS.State.SoC_Now_Perc: null" + "\n\r";
        VarNull += 1;
        Result = false;
    }
    else
    {
        Add_Trace(Prog_ID, "SoC_Now_Perc: " 
            + SoC_Now_Perc + NewLine);
    }

    if ( Not(Result) )
    {
        node.status(
            {fill:"black",
            shape:"dot",
            text: " Wait for Global_Get_All "
                + "(" + VarNull + "/" + VarCount + ")..."
            }
        );
    }
    
    if ( (ErrText > "")
      && ( StartTime > context.First_StartTime + 15 )
       )
    {   
        node.warn("Global_Get_All:" + NewLine + ErrText);

        node.status({fill:"red", shape:"dot",
                     text: ProgName + ": Error Global_Get_All" 
                     + "\n\r" + ErrText}
                   );
    }
    
    Add_Trace(Prog_ID, NewLine);
    
} // try
catch (error)
{
    Result = false;
    
    node.warn("Program: " + ProgName + NewLine
    + "Step: Global_Get_All" + NewLine
    + "Error: " + error)
    
} // catch

return Result;
    
} // Global_Get_All


// - - - - - - - - - - - - - - - - - - - - - - - - - -     
Op analoge wijze haalt het programma Parameters en Statussen op (GlobalGetAll).
Zie: Flow Parameters - Diverse Parameters

Ook worden twee tabellen met de kwartierprijzen opgehaald: QH_Ct en QH_Level.
Deze twee tabellen worden gemaakt door Flow Tibber - Dagelijks Ophalen Kwartierprijzen

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

De tabel QH_Level bevat de - door Tibber bepaalde - beoordeling van de hoogte van de prijzen.

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

[ Voor 6% gewijzigd door MJ de Bruijn op 02-12-2025 20:40 ]


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
vervolg...
JavaScript:
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
// - - - - - - - - - - - - - - - - - - - - - - - - - -     

var DebugStep = "Start";
var Explain = ProgName + NewLine;
var TraceOn= false;

try
{
    
    DebugStep = "All variables";

    let NowTime = GetNowTime();
    let NowHour = GetNowTime("NowHour");
    let Now_QH = GetNowTime("NowQuarter");
    let NowMinutes = GetNowTime("NowMinutes");
    let StartInSeconds = GetNowTime("NowInSeconds");
    
    if (msg.clearcontext) 
    {
        context = null;
    
        node.status({fill:"blue",
            shape:"dot",
            text: "@ " + NowTime + " - Clear Context"}
         );
    
        return [msg];
    }
    
    if (msg.traceon) 
    {
        node.status({fill:"blue",
            shape:"dot",
            text: "@ " + NowTime + " - Debug"}
         );
        
        TraceOn= true;
    }
    
    var QH_Ct = null;
    
    var QH_High = [];
    var QH_Normal = [];
    var QH_Low = [];
    var QH_VeryLow = [];
    var QH_Zero = [];
    var QH_Negative = [];
    var QH_By_Price = [];
    
    var QH_Buy = [];
    var QH_Sell = [];
    // var QH_BlocksCt = []
    
    var NegativePriceThresholdCt = 0;
    var ZeroPriceThresholdCt = 0;
    var VeryLowPriceThresholdCt = 0;
    var Conversion_Loss_Factor = 0;
    var Sell_Price_Difference_LimitCt = 0;
    var Buy_Price_Difference_LimitCt = 0;
    
    var PriceState = "Unknown";

    var Sell_QH_Count = 0;
    var Max_Sell_QH = 0;
    var Recharge_Look_Ahead_Number_of_QH = 20;

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    
    Add_Trace(Prog_ID, "Find High and Low QH" + NewLine);
    
    // - - - - - - - - - - - - - - - - - - - - - - - - - 
    
    DebugStep = "Start of Program";
    Add_Trace(Prog_ID, DebugStep + NewLine);

    if (!GlobalGetAll())
    {
         node.warn('GlobalGetAll failed.');
            
         node.status({fill:"red",shape:"dot",
              text: "GlobalGetAll failed."
             });
             
        return null;    
    }
    
    
    // discharge 12% per QH
    Max_Sell_QH = Math.floor((SoC_Now_Perc - SoC_Min_Manual_Perc) / 12);


    // find hours of Highest and Lowest prices
    let Today_HighestPrice = 
      QH_Ct.slice(0,95).reduce((a, b) => Math.max(a, b), -Infinity);
      
    let Today_LowestPrice = 
      QH_Ct.slice(0,95).reduce((a, b) => Math.min(a, b), Infinity);

    let Today_Sum = 
      QH_Ct.slice(0, 95).reduce((a, b) => a + b, 0);
    
    let Today_Count = 
      QH_Ct.slice(0, 95).length;
      
    let Today_AveragePrice = 
      Math.round(1000*Today_Sum/Today_Count)/1000;

    let Today_HighPrice_Threshold = 
      Math.round((Today_AveragePrice + (Today_HighestPrice - Today_AveragePrice) / 2) * 1000) / 1000;
      
    let Today_LowPrice_Threshold = 
      Math.round((Today_AveragePrice - (Today_AveragePrice - Today_LowestPrice) * 0.75) * 1000) / 1000;

    let Tomorrow_HighestPrice = 
      QH_Ct.slice(96).reduce((a, b) => Math.max(a, b), -Infinity);
    
    let Tomorrow_LowestPrice = 
      QH_Ct.slice(96).reduce((a, b) => Math.min(a, b), Infinity);
    
    let Tomorrow_Sum = 
      QH_Ct.slice(96).reduce((a, b) => a + b, 0);
    
    let Tomorrow_Count = 
      QH_Ct.slice(96).length;
    
    let Tomorrow_AveragePrice = 
      Math.round(1000*Tomorrow_Sum/Tomorrow_Count)/1000;

    let Tomorrow_HighPrice_Threshold = 
      Math.round((Tomorrow_AveragePrice + (Tomorrow_HighestPrice - Tomorrow_AveragePrice) / 2)* 1000) / 1000;
      
    let Tomorrow_LowPrice_Threshold = 
      Math.round((Tomorrow_AveragePrice - (Tomorrow_AveragePrice - Tomorrow_LowestPrice) / 2) * 1000) / 1000;

    Add_Trace(Prog_ID, 
    "Today_HighestPrice: " + CtToText(Today_HighestPrice) + NewLine
    + "Today_LowestPrice: " + CtToText(Today_LowestPrice)  + NewLine
    + "Today_AveragePrice: " + CtToText(Today_AveragePrice) + NewLine
    + "Today High Threshold: " + CtToText(Today_HighPrice_Threshold) + NewLine
    + "Today Low Threshold: " + CtToText(Today_LowPrice_Threshold) + NewLine
    + NewLine
    + "Tomorrow_HighestPrice: " + CtToText(Tomorrow_HighestPrice) + NewLine
    + "Tomorrow_LowestPrice: " + CtToText(Tomorrow_LowestPrice)  + NewLine
    + "Tomorrow_AveragePrice: " + CtToText(Tomorrow_AveragePrice) + NewLine
    + "Tomorrow High Threshold: " + CtToText(Tomorrow_HighPrice_Threshold) + NewLine
    + "Tomorrow Low Threshold: " + CtToText(Tomorrow_LowPrice_Threshold) + NewLine
    + NewLine )
    ;
    
    // - - - - - - - - - - - - - - - - - - - - - - - - -
Vervolgens worden variabelen aangemaakt en de te gebruiken lege tabellen.
QH_High Zal de kwartieren bevatten met de - voor dat moment - hoge prijzen.
QH_Normal Bevat de kwartieren die niet onder de andere categorieën vallen.
QH_Low De - voor dat moment - lage prijzen.
QH_VeryLow De grenswaarde voor erg lage prijzen wordt vastgelegd in VeryLowPriceThresholdCt.
QH_Zero Voor de vaststelling of een prijs negatief is wordt een heel kleine marge aangehouden,
QH_Negative
Voor de vaststelling of een prijs negatief is wordt een heel kleine marge aangehouden (ZeroPriceThresholdCt) om eventueel snel uit- en aanschakelen van zonne-omvormers te voorkomen.

QH_By_Price Deze tabel tenslotte zal alle kwartieren met prijzen gaan bevatten van laag naar hoog, vanaf het huidige kwartier tot aan de beschikbare DayAhead kwartieren. Als het programma klaar is ziet dit er zo uit:

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

Parameter [0] is het kwartier-nummer van 0 tot 191. 0 tot 95 is vandaag, 96 tot 191 is morgen,
parameter [1] is de KwH prijs van dat kwartier en
parameter [2] is de begintijd van het kwartier in leesbare vorm.
JavaScript:
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
    DebugStep = "Search for high and low QH";
    Add_Trace(Prog_ID, DebugStep + NewLine);

    for (let q = Now_QH; q < QH_Ct.length; q++) 
    {
        let curQH_AsText = QHToText(q);
        
        if (QH_Ct [  q  ] <= -98)
        {   // price unknown
            continue;
        }
        else if (QH_Level [  q  ].toLowerCase() == "very_expensive" )
        {
            QH_High.push([q, QH_Ct [  q  ], curQH_AsText ] ) 
            
        }
        // else if (QH_Level [  q  ].toLowerCase() == "expensive" )
        // {
        //     QH_High.push([q, QH_Ct [  q  ], curQH_AsText ] ) 
        // }
        else if (QH_Ct [  q  ] <= NegativePriceThresholdCt)
        { 
            QH_Negative.push([q, QH_Ct [  q  ], curQH_AsText ] )
        }
        else if (QH_Ct [  q  ] <= ZeroPriceThresholdCt)
        {
            QH_Zero.push([q, QH_Ct [  q  ] , curQH_AsText ] ) 
        }
        else if (QH_Ct [  q  ] <= VeryLowPriceThresholdCt)
        {
            QH_VeryLow.push([q, QH_Ct [  q  ] , curQH_AsText ] ) 
        }
        else if (QH_Level [  q  ].toLowerCase() == "cheap" )
        {
            QH_Low.push([q, QH_Ct [  q  ] , curQH_AsText ] ) 
            
        }
        else if ( ( (q <= 95) && (QH_Ct [  q  ] <= Today_LowPrice_Threshold) )
               || ( (q >  95) && (QH_Ct [  q  ] <= Tomorrow_LowPrice_Threshold) )
                )
        {
            QH_Low.push([q, QH_Ct [  q  ] , curQH_AsText ] ) 
        }
        else if ( ( (q <= 95) && (QH_Ct [  q  ]  >= Today_HighPrice_Threshold) )
               || ( (q >  95) && (QH_Ct [  q  ]  >= Tomorrow_HighPrice_Threshold) )
           )
        {
            QH_High.push( [ q, QH_Ct [  q  ] , curQH_AsText ] ) 
        }
        else 
        {
            QH_Normal.push( [ q, QH_Ct [  q  ] , curQH_AsText ] ) 
        }
        QH_By_Price.push([q, QH_Ct [  q  ] , curQH_AsText ] ) ;
    } // try all prices
    // - - - - - - - - - - - - - - - - - - - - - - - - - -
Het programma leest alle beschikbare kwartieren stuk voor stuk uit en zal deze - afhankelijk van de vastgestelde grenswaarden - toevoegen aan de tabellen QH_High, QH_Negative, QH_Zero, QH_VeryLow, QH_Low.

[ Voor 10% gewijzigd door MJ de Bruijn op 02-12-2025 20:41 ]


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
vervolg...
JavaScript:
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
    // - - - - - - - - - - - - - - - - - - - - - - - - - -
    
    DebugStep = "Sort QH_High High to Low";
    Add_Trace(Prog_ID, DebugStep + NewLine);

    QH_High.sort( ArrayOfSets_SortByPrice_Desc ) ;
    
    DebugStep = "Sort other Tables Low to High";
    Add_Trace(Prog_ID, DebugStep + NewLine);

    QH_By_Price.sort( ArrayOfSets_SortOnlyByPrice_Asc ) ;

    QH_Normal.sort( ArrayOfSets_SortByPrice_Asc) ;
    QH_Low.sort( ArrayOfSets_SortByPrice_Asc) ;
    QH_VeryLow.sort( ArrayOfSets_SortByPrice_Asc ) ;
    QH_Zero.sort( ArrayOfSets_SortByPrice_Asc ) ;
    QH_Negative.sort( ArrayOfSets_SortByPrice_Asc ) ;
    
    Add_Trace(Prog_ID, NewLine );

    // - - - - - - - - - - - - - - - - - - - - - - - - -

    DebugStep = "Find Sell QH by Highest prices";
    Add_Trace(Prog_ID, DebugStep + NewLine);
    
    Add_Trace(Prog_ID, "Sell_Price_Difference_Limit: " 
        + CtToText(Sell_Price_Difference_LimitCt) + NewLine);
    Add_Trace(Prog_ID, "Current SoC: " + SoC_Now_Perc + "%" + NewLine);
    Add_Trace(Prog_ID, "MaxSellQH: " + Max_Sell_QH + NewLine);
    
    Add_Trace(Prog_ID, NewLine);

    let QH_Repl_Used = [];

    for (let i = 0; i < QH_High.length; i++) // try all high prices
    {  

        if (Sell_QH_Count >= Max_Sell_QH) {break} // max number depends on SoC
    
        let Sell_Result = false;
        let Explain = "";
        
        // let idxHighPrice = QH_By_Price.length - q - 1;
        let HighPrice_QH = QH_High[i][0];
        let HighPrice = QH_High[i][1];
        let HighPrice_incl_cost = Price_incl_Cost(HighPrice, DebugStep);
        let HighPrice_Conv_Loss = HighPrice_incl_cost * Conversion_Loss_Factor;

        if (HighPrice_QH > Now_QH + 32)
        {
            Add_Trace(Prog_ID, "Skip "
            + QHToText(HighPrice_QH) + NewLine );
            continue
        }

        Add_Trace(Prog_ID, 
            "High Price "
            + QHToText(HighPrice_QH) 
            + ": " + CtToText(HighPrice) + NewLine);
        Add_Trace(Prog_ID, "    incl.Cost: " + CtToText(HighPrice_incl_cost)
            + NewLine );
        Add_Trace(Prog_ID, "    Conv.loss: " + CtToText(HighPrice_Conv_Loss)
            + NewLine );
        Add_Trace(Prog_ID, "  Search matching re-charge QH" 
            + NewLine );
Vervolgens worden de tabellen gesorteerd op bedrag. De 'High' tabel van hoog naar laag en de overige tabellen van laag naar hoog.

Om de bezien or er kwartieren zijn waarbij het zinvol is om actief aan het Grid terug te leveren zal het programma alle hoge prijzen stuk voor stuk bezien.
Er moet in de periode na het 'hoge' kwartier een moment zijn dat de verkochte energie gecompenseerd kan worden tegen een gunstige prijs.
De 'waarde' van een verkochte KwH is niet zomaar de netto prijs. Ook de belasting en leverancier-kosten kunnen (vooralsnog) worden verrekend. Maar, het ontladen van de batterij gaat gepaard met conversie-verlies. Daarom moet er een waarde-vermindering worden toegepast (Conversion_Loss_Factor). Ik heb deze op 0,9 gesteld.
JavaScript:
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
        for (let q = HighPrice_QH + 1;
            q < Math.min(QH_Ct.length, 
                        HighPrice_QH + 1 + Recharge_Look_Ahead_Number_of_QH) ; 
            q++)
        {
            let ReplPrice = QH_Ct   [   q   ]     ;

            if (ReplPrice < -98) { continue }
            
            let ReplPrice_incl_cost = Price_incl_Cost(ReplPrice, DebugStep);
            let ReplPrice_Conv_Loss = ReplPrice_incl_cost / Conversion_Loss_Factor;

            Explain = "    Try " + QHToText(q) 
                + " " + CtToText(ReplPrice) 
                + "/" + CtToText(ReplPrice_incl_cost)
                + "/" + CtToText(ReplPrice_Conv_Loss)
                + NewLine;
    
            if (QH_Repl_Used.indexOf(q) >= 0)
            {
                // Replace QH already reserved for another Sell QH.
                continue;
            }
    
            if (HighPrice_Conv_Loss 
                < ReplPrice_Conv_Loss + Sell_Price_Difference_LimitCt)
            {   // No Sell
                // Add_Trace(Prog_ID, "  Re-charge costs more than Sell" 
                //    + NewLine );
            }
            else
            {   // Sell
                Sell_Result = true;
                
                QH_Repl_Used.push(q);
                
                Add_Trace(Prog_ID, 
                    Explain
                    +"    Re-charge costs less than Sell" 
                    + NewLine );
                    
                break;
            }
        } // try all High_QH
        if (Sell_Result == true)
        {
            if (Not( IsInArrayOfSets(QH_Sell, [HighPrice_QH]) ) )
            {
                if (QH_Ct[HighPrice_QH] > 0)
                {
                    QH_Sell.push( [HighPrice_QH, QH_Ct[HighPrice_QH],
                        QHToText(HighPrice_QH) ] );
                    
                    Add_Trace(Prog_ID, "  Add: " 
                        + QHToText(HighPrice_QH)
                        + " to QH_Sell."
                        + NewLine );
                        
                    Sell_QH_Count++;
                }
            }
        } // if Sell_Result == true
    
        Add_Trace(Prog_ID, NewLine );

    }
    
    // node.warn(QH_Sell);

    Add_Trace(Prog_ID, NewLine );

    // - - - - - - - - - - - - - - - - - - - - - - - - - -
Het programma check de prijzen van de kwartieren in de eerste uren na het kandidaat-verkoop-moment.
Ook hier wordt de netto-prijs omgerekend inclusief kosten en wordt de conversie-factor toegepast maar nu om de prijs te verhogen (= delen door Conversion_Loss_Factor). Het verschil tussen verkoop-waarde (HighPrice_Conv_Loss) en terugkoop-waarde (ReplPrice_Conv_Loss) moet ook nog groter zijn dan een minimum bedrag (Sell_Price_Difference_LimitCt) anders staat het systeem maar elektronen rond te pompen zonder dat er iets verdiend wordt.
Als er geen zinvol terugkoop moment wordt gevonden wordt het kandidaat-verkoop-kwartier verworpen.

[ Voor 15% gewijzigd door MJ de Bruijn op 02-12-2025 20:41 ]


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
vervolg...
JavaScript:
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
    // - - - - - - - - - - - - - - - - - - - - - - - - - -
    
    DebugStep = "Find earlier QH to Charge extra...";
    
    Add_Trace(Prog_ID, DebugStep + NewLine);
    Add_Trace(Prog_ID, "Buy_Price_Difference_Limit: " 
        + CtToText(Buy_Price_Difference_LimitCt) + NewLine);
    

    for (let i = 0; i < QH_Sell.length; i++)
    {
        let cur_Sell_QH = QH_Sell[i][0];
        let cur_Sell_price = QH_Sell[i][1];
        let cur_Sell_price_incl_cost = Price_incl_Cost(cur_Sell_price, DebugStep);
        let cur_Sell_price_conv = cur_Sell_price_incl_cost * Conversion_Loss_Factor;

        DebugStep = "Find earlier QH. Find Match for cur_Sell_QH";

        Add_Trace(Prog_ID, 
            NewLine
            + "Find match for " 
            + QHToText(cur_Sell_QH) 
            + " (" + CtToText(cur_Sell_price_conv) + ")"
            + NewLine);
        
        // now check all earlier QH's
        for (let Cand_QH = cur_Sell_QH - 1; 
                Cand_QH >= Math.max(Now_QH, cur_Sell_QH - 12); 
                Cand_QH--)
        {

            DebugStep = "Find earlier QH. Check Candidate " + Cand_QH;

            let Cand_price = QH_Ct[Cand_QH]
            let Cand_price_incl_cost = Price_incl_Cost(Cand_price, DebugStep);
            let Cand_price_conv = Cand_price_incl_cost / Conversion_Loss_Factor;
            
            let Pred_price = QH_Ct[Cand_QH-1]
            let Pred_price_incl_cost = Price_incl_Cost(Pred_price, DebugStep);
            let Pred_price_conv = Pred_price_incl_cost / Conversion_Loss_Factor;
            
            if ( Cand_price < -98 )
            { 
                DebugStep = "Find earlier QH. Check Candidate " 
                    + Cand_QH + " undefined";
                    
                Add_Trace(Prog_ID, "  " 
                    + QHToText(Cand_QH) + ": Price undefined."
                    + NewLine);
            }
            else if ( Cand_price_conv >= cur_Sell_price_conv )
            { 
                DebugStep = "Find earlier QH. Check Candidate " 
                    + Cand_QH + " to high";

                Add_Trace(Prog_ID, "  " 
                    + QHToText(Cand_QH) 
                    + " (Buy: " + CtToText(Cand_price_conv) + ")"
                    + " to high."
                    + NewLine);
            }
            else if ( Cand_price_conv > Pred_price_conv )
            { 
                DebugStep = "Find earlier QH. Check Candidate " 
                    + Cand_QH + " higher then predecessor";

                Add_Trace(Prog_ID, "  " 
                    + QHToText(Cand_QH) 
                    + " (buy: " + CtToText(Cand_price_conv) + ")"
                    + " is higher"
                    + NewLine
                    + "    than " + QHToText(Cand_QH-1) 
                    + " (buy: " + CtToText(Pred_price_conv) + ")"
                    + "." + NewLine );
                    
                // break; // don't try further
            }
            else if ( Cand_price_conv > 
                        cur_Sell_price_conv - Buy_Price_Difference_LimitCt)
            { 
                DebugStep = "Find earlier QH. Check Candidate " 
                    + Cand_QH + " low but not enough";

                Add_Trace(Prog_ID, "  " 
                    + QHToText(Cand_QH) 
                    + " (buy: " + CtToText(Cand_price_conv) + ")"
                    + " is low."
                    + NewLine);
            }
            else 
            {

                DebugStep = "Find earlier QH. Check Candidate " 
                    + Cand_QH + " is lowest";

                Add_Trace(Prog_ID, "  " 
                    + QHToText(Cand_QH) 
                    + " (buy: " + CtToText(Cand_price_conv) + ")"
                    + " low enough."
                    + NewLine);
            
                if ( IsInArrayOfSets(QH_Buy, [Cand_QH]) )
                {   
                    Add_Trace(Prog_ID, 
                    "    Already selected for another Peak."
                    + NewLine);
                    continue
                }
                else
                {
                    Add_Trace(Prog_ID, "Add " 
                    + QHToText(Cand_QH) 
                    + " to QH_Buy array."
                    + NewLine);
                    
                    QH_Buy.push([Cand_QH, QH_Ct[Cand_QH], QHToText(Cand_QH) ] );
                    // node.warn("Buy + : " + q)
                    break
                }
            } // else
            
        } // for Cand_QH all earlier QH
        
    } // i all peaks

    Add_Trace(Prog_ID, NewLine);

    // - - - - - - - - - - - - - - - - - - - - - - - - - -
    
    if (QH_Level[Now_QH] != null)
    {
        PriceState = QH_Level[Now_QH].toLowerCase();
    }

    else if (IsInArrayOfSets(QH_Negative, [Now_QH]) ) {
        PriceState = "Negative";
    }
    else if (IsInArrayOfSets(QH_Zero, [Now_QH]) ) {
        PriceState = "Zero";
    }
    else if (IsInArrayOfSets(QH_High, [Now_QH]) ) {
        PriceState = "High";
    }
    else if (IsInArrayOfSets(QH_VeryLow, [Now_QH]) ) {
        PriceState = "Very Low";
    }
    else if (IsInArrayOfSets(QH_Low, [Now_QH]) ) {
        PriceState = "Low";
    }
    else if (IsInArrayOfSets(QH_Normal, [Now_QH]) ) {
        PriceState = "Normal";
    }
    else {
        PriceState = "Unknown";
    }

    DebugStep = "Write Tables to Global";
    
    Add_Trace(Prog_ID, DebugStep + NewLine);
    
    global.set( "Victron_MESS.DayAhead.QH_By_Price",  QH_By_Price );
    global.set( "Victron_MESS.DayAhead.QH_High",  QH_High );
    global.set( "Victron_MESS.DayAhead.QH_Normal",  QH_Normal );
    global.set( "Victron_MESS.DayAhead.QH_Low",  QH_Low );
    global.set( "Victron_MESS.DayAhead.QH_VeryLow",  QH_VeryLow );
    global.set( "Victron_MESS.DayAhead.QH_Zero",  QH_Zero );
    global.set( "Victron_MESS.DayAhead.QH_Negative",  QH_Negative );
    // global.set( "Victron_MESS.DayAhead.QH_BlocksCt", QH_BlocksCt );
    global.set( "Victron_MESS.DayAhead.QH_Buy", QH_Buy );
    global.set( "Victron_MESS.DayAhead.QH_Sell", QH_Sell );
    global.set( "Victron_MESS.DayAhead.PriceState",  PriceState );
    
    global.set( "Victron_MESS.LastRun.DayAhead.HighAndLowHours"); // remove
    global.set( "Victron_MESS.LastRun.DayAhead.HighAndLowHours",  NowTime);
    
    DebugStep = "Done.";
    
    Add_Trace(Prog_ID, DebugStep + NewLine);

    node.status({fill:"green",shape:"dot",
         text: "@ " + NowTime 
        });
}
catch (error)
{
        node.warn("Program: " + ProgName + NewLine 
        + "Error in " + "Main Program" + ":" + NewLine
        + "Step: " + DebugStep + NewLine
        + "Error: " + error)
}

if ( TraceOn )
    {  
        let curTraceText = global.get("Trace." + Prog_ID);

        for (let i = 0; i < curTraceText.length; i++ )
        {
            node.warn(curTraceText[i]);
        }
    }


if (TraceOn)
{   
    return null;
}
else
{
    return msg;
}
Indien er aldus een kwartier is gevonden waarbij het zinvol is om actief te verkopen probeert het programma nog of in de voorafgaande kwartieren misschien een lage prijs is waarbij het zinvol is om 'extra' te laden. Er kan dan meer verkocht worden. De berekening van het relevante verschil wordt analoog uitgevoerd zoals hierboven beschreven. Echter, het prijsverschil om extra in te kopen (Buy_Price_Difference_LimitCt) moet groter zijn dan bij verkoop omdat er anders slechts sprake is van verschuiving.

Tenslotte schrijft het programma de gevulde tabellen weg naar de Context Global Geheugenruimte.
Deze kunnen door het 'Brein' van MESS worden gebruikt bij de beoordeling van de gewenste instellingen, Verkopen, kopen, balans of slapen. Zie ook Kwartierprijzen
Het programma dat de beoordeling doet zal ik separaat beschrijven. Zie ook verderop.


[ Voor 5% gewijzigd door MJ de Bruijn op 02-12-2025 20:41 ]


  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57

Programma QH_Ct to Separate Hours



En, gewoon om compleet te zijn, dit is het programma dat de gegevens naar Home Assistant stuurt.
JavaScript:
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
const ProgramName = "QH_Ct to Separate Hours";

context = context || {};

let Now = new Date();
StartTime = Math.floor(Now.getTime() / 1000);

let NewLine = "\n\r";

if (context.First_StartTime == null)
{
    context.First_StartTime = StartTime;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

if ( !(Get_Functions() ) )
{   
    return;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function Get_Functions() {

let NewLine = "\n\r";
let FuncCount = 0;
let FuncNull = 0;
let ErrText = "";
let Result = true;

try {

    FuncCount++;
    GetNowTime = global.get("Victron_MESS.Functions.GetNowTime");
    if (GetNowTime  == null)
    {   ErrText += "Victron_MESS.Functions.GetNowTime" + NewLine;
        FuncNull += 1;
        Result = false;
    }

    FuncCount++;
    Not = global.get("Victron_MESS.Functions.Not");
    if (Not  == null)
    {   ErrText += "Victron_MESS.Functions.Not" + NewLine;
        FuncNull += 1;
        Result = false;
    }
    
    FuncCount++;
    Round = global.get("Victron_MESS.Functions.Round");
    if (Round  == null)
    {   ErrText += "Victron_MESS.Functions.Round" + NewLine;
        FuncNull += 1;
        Result = false;
    }

    if ( !(Result) )
    {
        node.status(
            {fill:"black",
            shape:"dot",
            text: "Wait for Get_Functions "
                + "(" + FuncNull + "/" + FuncCount + ")..."
            }
        );
    }
    
    if ( (ErrText > "")
      && ( StartTime > context.First_StartTime + 15 )
       )
    {   
        node.warn("Get_Functions:" + NewLine + ErrText);

        node.status({fill:"red", shape:"dot",
                     text: ProgName + ": Error Get_Functions" 
                     + "\n\r" + ErrText}
                   );
    }
    
} // try
catch (error)
{
    Result = false;
    
    node.error("Program: " + ProgramName + NewLine
    + "Step: Get_Functions" + NewLine
    + "Error: " + error)
    
} // catch

return Result;
    
} // Get_Functions

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

var DebugStep = "Start";
var Explain = "";

try
{
    DebugStep = "All variables";
    
    let NowTime = GetNowTime();
    let NowHour =  GetNowTime("NowHour");
    let Now_QH =  GetNowTime("NowQuarterHour");
    let StartInSeconds = GetNowTime("NowInSeconds");
    
    
    if (msg.clearcontext) 
    {
        context = null;
    
        node.status({fill:"blue",
            shape:"dot",
            text: "@ " + NowTime + " - Clear Context"}
         );
    
        return [null];
    }
    
    QH_Ct = global.get("Victron_MESS.DayAhead.QH_Ct");
    
    if (context.LastPrice == null) {
        context.LastPrice = {};
        for (let i = 0; i < 48; i++) {
            context.LastPrice[i] = null;
        }
    }
    
    let NewPrice = {};
    for (let i = 0; i < 48; i++) {
        NewPrice[i] = -99;
    }
    
    let HighValue = 0;
    let LowValue = 0;
    let MsgCount = 0;
    
    // node.warn(QH_Ct);
    
    NodeStep = "High and Low values";
    
    for (let i = 0; i < 48; i++)
    {
    
        if ( (i >= NowHour) 
          && (i < Math.floor(QH_Ct.length/4)) )
        {
            if ( (QH_Ct[i*4] == null)  
              || (QH_Ct[i*4+1] == null)  
              || (QH_Ct[i*4+2] == null)  
              || (QH_Ct[i*4+3] == null) )
            {
            }
            else if ( (isNaN(QH_Ct[i*4])) 
                   || (isNaN(QH_Ct[i*4+1])) 
                   || (isNaN(QH_Ct[i*4+2])) 
                   || (isNaN(QH_Ct[i*4+3])) )
            {
            }
            else 
            {
                if ( (QH_Ct[i*4] > -98)
                  && (QH_Ct[i*4+1] > -98)
                  && (QH_Ct[i*4+2] > -98)
                  && (QH_Ct[i*4+3] > -98) )
                {
                    NewPrice[i] = 
                        Round(10*(
                        QH_Ct[i*4]
                        + QH_Ct[i*4+1]
                        + QH_Ct[i*4+2]
                        + QH_Ct[i*4+3] )
                        , 0)/40;
                    HighValue = Math.max(HighValue, 
                          QH_Ct[i*4],
                          QH_Ct[i*4+1],
                          QH_Ct[i*4+2],
                          QH_Ct[i*4+3]
                          );
                    LowValue = Math.min(LowValue, 
                          QH_Ct[i*4],
                          QH_Ct[i*4+1],
                          QH_Ct[i*4+2],
                          QH_Ct[i*4+3]
                    );
                }
            }
            
        }
        
        DebugStep = "Create messages";
        
        msg.type = "call_service";
        msg.domain = "input_number";
        msg.service = "set_value";
        
        let CurHour = "0" + i;
        CurHour = CurHour.substring(CurHour.length - 2) 
        msg.topic = "input_number.dayahead_price_" 
          + CurHour + "_hr";
    
        msg.entity_id = msg.topic;
    
    
        if (NewPrice[i] != context.LastPrice[i]) {
            msg.payload = NewPrice[i];
            context.LastPrice[i] = NewPrice[i];
            node.status({fill:"green",
                shape:"dot",
                 text: "@ " + NowTime  + "; " + CurHour + "hr: " + msg.payload}
                 );
            MsgCount++;
            
            // node.warn("Send: " + CurHour + "hr: " + msg.payload);
            node.send( msg );
        }
    }
    
    // node.warn("High: " + HighValue);
    // node.warn("Low: " + LowValue);
    
    DebugStep = "Messages for High and Low";
    
    msg.type = "call_service";
    msg.domain = "input_number";
    msg.service = "set_value";
    
    msg.topic = "input_number.dayahead_price_lowvalue";
    msg.payload = Round(LowValue, 0); 
    msg.entity_id = "input_number.dayahead_price_lowvalue";
    msg.priority = true;
    
    if (msg.payload != context.LastLowValue) 
    {
        context.LastLowValue = msg.payload;
        node.status({fill:"green",
            shape:"dot",
             text: "@ " + NowTime  + "; LowValue: " + msg.payload}
             );
        MsgCount++;
        global.set( "Victron_MESS.DayAhead.LowValue", msg.payload);
        node.send( msg );
    }
    
    msg.topic = "input_number.dayahead_price_highvalue";
    msg.payload = Round(HighValue, 0);
    msg.entity_id = "input_number.dayahead_price_highvalue";
    msg.priority = true;
    
    if (msg.payload != context.LastHighValue) 
    {
        context.LastHighValue = msg.payload;
        node.status({fill:"green",
            shape:"dot",
             text: "@ " + NowTime  + "; HighValue: " + msg.payload}
             );
        MsgCount++;
        global.set( "Victron_MESS.DayAhead.HighValue", msg.payload);
        node.send( msg );
    }
    
    global.set( "Victron_MESS.LastRun.DayAhead.SeparateHours"); // remove
    global.set( "Victron_MESS.LastRun.DayAhead.SeparateHours",
       NowTime + "; " + MsgCount + " messages sent.");
    
    node.status({fill:"green",
        shape:"dot",
         text: "@ " + NowTime + "; " + MsgCount + " messages sent."}
         );
    
}
catch (error)
{
        node.warn("Program: " + ProgramName + NewLine 
        + "Error in " + arguments.callee.name + ":" + NewLine
        + "Step: " + DebugStep + NewLine
        + "Error: " + error)
}

node.done();

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57

Programma High and Low as Text to Home Assistant



En deze...
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
const ProgramName = "High and Low as Text to Home Assistant";

context = context || {};

let Now = new Date();
StartTime = Math.floor(Now.getTime() / 1000);

let NewLine = "\n\r";

if (context.First_StartTime == null)
{
  context.First_StartTime = StartTime;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

if ( !(Get_Functions() ) )
{   
  return;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function Get_Functions() {

let NewLine = "\n\r";
let FuncCount = 0;
let FuncNull = 0;
let ErrText = "";
let Result = true;

try {

  FuncCount++;
  GetNowTime = global.get("Victron_MESS.Functions.GetNowTime");
  if (GetNowTime  == null)
  {   ErrText += "Victron_MESS.Functions.GetNowTime" + NewLine;
    FuncNull += 1;
    Result = false;
  }

  FuncCount++;
  Not = global.get("Victron_MESS.Functions.Not");
  if (Not  == null)
  {   ErrText += "Victron_MESS.Functions.Not" + NewLine;
    FuncNull += 1;
    Result = false;
  }
  
  FuncCount++;
  Round = global.get("Victron_MESS.Functions.Round");
  if (Round  == null)
  {   ErrText += "Victron_MESS.Functions.Round" + NewLine;
    FuncNull += 1;
    Result = false;
  }

  FuncCount++;
  QHToText = global.get("Victron_MESS.Functions.QHToText");
  if (QHToText  == null)
  {   ErrText += "Victron_MESS.Functions.QHToText" + NewLine;
    FuncNull += 1;
    Result = false;
  }

  FuncCount++;
  Price_incl_Cost = global.get("Victron_MESS.Functions.Price_incl_Cost");
  if (Price_incl_Cost  == null)
  {   ErrText += "Victron_MESS.Functions.Price_incl_Cost" + NewLine;
    FuncNull += 1;
    Result = false;
  }

  FuncCount++;
  CtToText = global.get("Victron_MESS.Functions.CtToText");
  if (CtToText  == null)
  {   ErrText += "Victron_MESS.Functions.CtToText" + NewLine;
    FuncNull += 1;
    Result = false;
  }

  if ( !(Result) )
  {
    node.status(
      {fill:"black",
      shape:"dot",
      text: "Wait for Get_Functions "
        + "(" + FuncNull + "/" + FuncCount + ")..."
      }
    );
  }
  
  if ( (ErrText > "")
  && ( StartTime > context.First_StartTime + 15 )
   )
  {   
    node.warn("Get_Functions:" + NewLine + ErrText);

    node.status({fill:"red", shape:"dot",
           text: ProgName + ": Error Get_Functions" 
           + "\n\r" + ErrText}
         );
  }
  
} // try
catch (error)
{
  Result = false;
  
  node.error("Program: " + ProgramName + NewLine
  + "Step: Get_Functions" + NewLine
  + "Error: " + error)
  
} // catch

return Result;
  
} // Get_Functions

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -





var DebugStep = "Start";
var Explain = "";

// - - - - - - - - - - - - - - - - - - - - - - - - - 

function Clear_Context_Last()
{
  context.Last_QH_Negative = [];
  context.Last_QH_VeryLow = [];
  context.Last_QH_Low = []
  context.Last_QH_Normal = []
  context.Last_QH_High = [];
  context.Last_QH_Buy = [];
  context.Last_QH_Sell = [];
  context.Last_PriceState = "";
  context.Last_Current_Price = "";
  context.Last_QH_GridCharge = [];
  context.Last_QH_SolarCharge = [];
}

// - - - - - - - - - - - - - - - - - - - - - - - - - 

function sortFirstElement(a, b) {
  if (a[0] === b[0]) {
    return 0;
  }
  else {
    return (a[0] < b[0]) ? -1 : 1;
  }
}
// - - - - - - - - - - - - - - - - - - - - - - - - - 

function QH_Price_Array_To_List (ParamArrayName, ParamArray)
{
  Result = "";
  const QH_ymbol = [":00", ":15", ":30", ":45"];

  let lastElement = -2;
  let delimiter = "";
  let NextElement = -2;

  if (ParamArray.length > 0)
  {
    for (let i = 0; i < ParamArray.length; i++) {
   
      NextElement = -2;
      
      if (Number.isInteger(ParamArray[i]))
      {
        curElement = ParamArray[i];
        if (i < ParamArray.length-1)
        {
          NextElement = ParamArray[i+1];
        }
      }
      else
      {
        curElement = ParamArray[i][0];
        if (i < ParamArray.length-1)
        {
          NextElement = ParamArray[i+1][0];
        }
      }

      if (curElement == lastElement + 1)
      {   // continue series
        delimiter = "..";
        lastElement = curElement;
      }
      else
      {   // new series
        Result += delimiter;
        Result += QHToText(curElement);
        delimiter = ', ';
        lastElement = curElement;
        continue;
      }

      if (curElement != NextElement - 1)
      {   // end series
        Result += delimiter;
        Result += QHToText(curElement);
        delimiter = ', ';
        lastElement = curElement;
        continue;
      }

      if (i == ParamArray.length-1)
      {   // last
        Result += delimiter;
        Result += QHToText(curElement);
      }

    }
  } // if the input Array has length

  Result = ParamArrayName + ": [" + Result + "]";

  return Result;
  
} // QH_Price_Array_To_List

// - - - - - - - - - - - - - - - - - - - - - - - - - 
  
try {

  DebugStep = "All Variables";

  let NowTime = GetNowTime();
  let Now_QH = GetNowTime("Now_QH");
  
  let MsgCount = 0;
  
  if (msg.clearcontext) 
  {
    context = null;
  
    node.status({fill:"blue",
      shape:"dot",
      text: "@ " + NowTime + " - Clear Context"}
     );
  
    context = context || {};
  
    Clear_Context_Last();
  
    return [null];
  }
  
  // - - - - - - - - - - - - - - - - - - - - - - - - - 
  
  DebugStep = "All get";
  
  QH_High = global.get( "Victron_MESS.DayAhead.QH_High");
  QH_Normal = global.get( "Victron_MESS.DayAhead.QH_Normal");
  QH_Low = global.get( "Victron_MESS.DayAhead.QH_Low");
  QH_VeryLow = global.get( "Victron_MESS.DayAhead.QH_VeryLow" );
  QH_Zero = global.get( "Victron_MESS.DayAhead.QH_Zero" );
  QH_Negative = global.get( "Victron_MESS.DayAhead.QH_Negative" );
  QH_Buy = global.get( "Victron_MESS.DayAhead.QH_Buy" );
  QH_Sell = global.get( "Victron_MESS.DayAhead.QH_Sell" );
  PriceState = global.get( "Victron_MESS.DayAhead.PriceState" );
  QH_Ct = global.get( "Victron_MESS.DayAhead.QH_Ct" );
  
  QH_GridCharge = 
    global.get( "Victron_MESS.Targets.Grid_QH_To_Charge" );
  if (QH_GridCharge == null)
  {
    QH_GridCharge = [];
  }
  
  QH_SolarCharge = 
    global.get( "Victron_MESS.Targets.Solar_QH_To_Charge" );
  if (QH_SolarCharge == null)
  {
    QH_SolarCharge = [];
  }
  
  
  if (QH_High == null)
  {
    node.warn("QH_High is null")
  }
  else
  {
    QH_High = QH_High.sort(sortFirstElement);
  }
  
  if (QH_Normal == null)
  {
    node.warn("QH_Normal is null")
  }
  else
  {
    QH_Normal = QH_Normal.sort(sortFirstElement);
  }
  
  if (QH_Low == null)
  {
    node.warn("QH_Low is null")
  }
  else
  {
    QH_Low = QH_Low.sort(sortFirstElement);
  }
  
  if (QH_VeryLow == null)
  {
    node.warn("QH_VeryLow is null")
  }
  else
  {
    QH_VeryLow = QH_VeryLow.sort(sortFirstElement);
  }
  
  if (QH_Zero == null)
  {
    node.warn("QH_Zero is null")
  }
  else
  {
    QH_Zero = QH_Zero.sort(sortFirstElement);
  }
  
  if (QH_Negative == null)
  {
    node.warn("QH_Negative is null")
  }
  else
  {
    QH_Negative = QH_Negative.sort(sortFirstElement);
  }
  
  if (QH_Buy == null)
  {
    node.warn("QH_Buy is null")
  }
  else
  {
    QH_Buy = QH_Buy.sort(sortFirstElement);
  }
  
  if (QH_Sell == null)
  {
    node.warn("QH_Sell is null")
  }
  else
  {
    QH_Sell = QH_Sell.sort(sortFirstElement);
  }
  
  if (QH_GridCharge == null)
  {
    node.warn("QH_GridCharge is null")
  }
  else
  {
    QH_GridCharge = 
    QH_GridCharge.sort(function(a, b) {return a - b;} );
  }
  
  if (QH_SolarCharge == null)
  {
    node.warn("QH_SolarCharge is null")
  }
  else
  {
    QH_SolarCharge = 
    QH_SolarCharge.sort(function(a, b) {return a - b;} );
  }
  
  DebugStep = "Make messages";
  
  msg.type = "call_service";
  msg.domain = "input_text";
  msg.service = "set_value";
  
  if (QH_Negative != null)
  {
    msg.payload =
     QH_Price_Array_To_List("Negative", QH_Negative);
    msg.entity_id = "input_text.helper_dayahead_negative";
    msg.topic = msg.entity_id;
    if ( msg.payload != context.Last_QH_Negative )
    {
      context.Last_QH_Negative = msg.payload;
      node.send(msg);
      MsgCount++;
    }
  }
  if (QH_Zero != null)
  {
    msg.payload =
      QH_Price_Array_To_List("Zero", QH_Zero);
    msg.entity_id = "input_text.helper_dayahead_zero";
    msg.topic = msg.entity_id;
    if ( msg.payload != context.Last_QH_Zero )
    {
      context.Last_QH_Zero = msg.payload;
      node.send(msg);
      MsgCount++;
    }
  }
  if (QH_VeryLow != null)
  {
    msg.payload =
      QH_Price_Array_To_List("Very low", QH_VeryLow);
    msg.entity_id = "input_text.helper_dayahead_verylow";
    msg.topic = msg.entity_id;
    if ( msg.payload != context.Last_QH_VeryLow )
    {
      context.Last_QH_VeryLow = msg.payload;
      node.send(msg);
      MsgCount++;
    }
  }
  if (QH_Low != null)
  {
    msg.payload =
      QH_Price_Array_To_List("Low", QH_Low);
    msg.entity_id = "input_text.helper_dayahead_low";
    msg.topic = msg.entity_id;
    if ( msg.payload != context.Last_QH_Low )
    {
      context.Last_QH_Low = msg.payload;
      node.send(msg);
      MsgCount++;
    }
  }
  if (QH_Normal != null)
  {
    msg.payload =
      QH_Price_Array_To_List("Normal", QH_Normal);
    msg.entity_id = "input_text.helper_dayahead_price_normal";
    msg.topic = msg.entity_id;
    if ( msg.payload != context.Last_QH_Normal )
    {
      context.Last_QH_Normal = msg.payload;
      node.send(msg);
      MsgCount++;
    }
  }
  if (QH_High != null)
  {
    msg.payload =
      QH_Price_Array_To_List("High", QH_High);
    msg.entity_id = "input_text.helper_dayahead_high";
    msg.topic = msg.entity_id;
    if ( msg.payload != context.Last_QH_High )
    {
      context.Last_QH_High = msg.payload;
      node.send(msg);
      MsgCount++;
    }
  }
  
  if (QH_Buy != null)
  {
    msg.payload =
      QH_Price_Array_To_List("Buy", QH_Buy);
    msg.entity_id = "input_text.helper_dayahead_buy";
    msg.topic = msg.entity_id;
    if ( msg.payload != context.Last_QH_Buy )
    {
      context.Last_QH_Buy = msg.payload;
      node.send(msg);
      MsgCount++;
    }
  }
  
  if (QH_Sell != null)
  {
    msg.payload =
      QH_Price_Array_To_List("Sell", QH_Sell);
    msg.entity_id = "input_text.helper_dayahead_sell";
    msg.topic = msg.entity_id;
    if ( msg.payload != context.Last_QH_Sell )
    {
      context.Last_QH_Sell = msg.payload;
      node.send(msg);
      MsgCount++;
    }
  }
  
  if (PriceState != null)
  {
    msg.payload = PriceState;
    msg.entity_id = "input_text.helper_dayahead_price_state";
    msg.topic = msg.entity_id;
    if ( msg.payload != context.Last_PriceState )
    {
      context.Last_PriceState = msg.payload;
      node.send(msg);
      MsgCount++;
    }
  }
  
  if (QH_Ct != null)
  {
    msg.payload = 
      "Current Price: " + CtToText(QH_Ct[Now_QH])
      + "; incl.cost: " + CtToText(Price_incl_Cost(QH_Ct[Now_QH], "High and Low as Text"))
      + ".";
    msg.entity_id = "input_text.helper_dayahead_current_price";
    msg.topic = msg.entity_id;
    msg.priority = true;
    if ( msg.payload != context.Last_Current_Price )
    {
      context.Last_Current_Price = msg.payload;
      node.send(msg);
      MsgCount++;
    }
  }
  
  if (QH_GridCharge != null)
  {
    msg.payload = 
      QH_Price_Array_To_List("Grid Charge", QH_GridCharge);
    msg.entity_id = "input_text.helper_dayahead_rule_grid";
    msg.topic = msg.entity_id;
    msg.priority = true;
    if ( msg.payload != context.Last_QH_GridCharge )
    {
      context.Last_QH_GridCharge = msg.payload;
      node.send(msg);
      MsgCount++;
    }
  }
  
  if (QH_SolarCharge != null)
  {
    msg.payload = 
      QH_Price_Array_To_List("Solar Charge", QH_SolarCharge);
    msg.entity_id = "input_text.helper_dayahead_rule_solar";
    msg.topic = msg.entity_id;
    msg.priority = true;
    if ( msg.payload != context.Last_QH_SolarCharge )
    {
      context.Last_QH_SolarCharge = msg.payload;
      node.send(msg);
      MsgCount++;
    }
  }
  
  node.status({fill:"green",
    shape:"dot",
     text: "@ " + NowTime + "; " + MsgCount + " messages sent."}
     );
  
}
catch (error)
{
    node.warn("Program: " + ProgramName + NewLine 
    + "Error in " + arguments.callee.name + ":" + NewLine
    + "Step: " + DebugStep + NewLine
    + "Error: " + error)
}

node.done();
De afhandeling van de te verzenden berichten is in een separate Flow opgenomen. Hier is ook een wachtrij, een controle en een retry mechanisme in opgenomen.
Mogelijk beschrijf ik dat nog wel eens. Zoek daarvoor dan in de inhoudsopgave.

[ Voor 12% gewijzigd door MJ de Bruijn op 02-12-2025 20:44 ]


  • Tillumen
  • Registratie: November 2024
  • Laatst online: 17-12 22:23
@MJ de Bruijn Wat en werk en leuk om te lezen! Zou het een idee zijn om de config ergens op GIT ofzo te zetten zodat de laatste versie altijd ergens staat?

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 13:57
Tillumen schreef op donderdag 4 december 2025 @ 10:31:
@MJ de Bruijn Wat en werk en leuk om te lezen! Zou het een idee zijn om de config ergens op GIT ofzo te zetten zodat de laatste versie altijd ergens staat?
Leuk te horen dat je het op prijs stelt.
Ik heb erover gedacht om mijn software beschikbaar te maken. Maar het is heel erg op een specifieke situatie toegesneden, Niemand kan het 'as is' overnemen. Het kan ter inspiratie dienen voor zelfbouw of gewoon om te lezen hoe je zaken aanpakt. Dat helpt te begrijpen hoe andere systemen werken.
Ik ben ook niet meer in staat om ondersteuning te bieden. Als ik het via GIT beschikbaar zou maken zou de kwaliteit een stuk hoger moeten liggen. Die 'lat' haal ik niet.
Pagina: 1