Acties:
  • +26 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02

Victron - Mijn ESS - MESS



Dit verslag beschrijft een persoonlijke opzet van een methodiek voor het laden en ontladen van een Victron batterij-systeem.

N.B. Sinds ik deze bijdrage geschreven heb is er al weer veel veranderd.
De grote lijnen zijn gelijk gebleven, maar op detail niveau is er van alles toegevoegd of gewijzigd.
Indien je op basis van het hierna beschrevene een eigen opzet wilt maken, zoek dan even contact. Ik kan dan de actuele situatie delen.

Inhoudsopgave
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

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 05-10-2024 15:15 ]


Acties:
  • +1 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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 ]


Acties:
  • 0 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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 ]


Acties:
  • 0 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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 ]


Acties:
  • 0 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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 ]


Acties:
  • 0 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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 ]


Acties:
  • 0 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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 ]


Acties:
  • 0 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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 ]


Acties:
  • 0 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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 ]


Acties:
  • 0 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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 ]


Acties:
  • 0 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
Gereserveerd.

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


Acties:
  • 0 Henk 'm!

  • V_ger
  • Registratie: September 2009
  • Nu online
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 ]

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


Acties:
  • 0 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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.

Acties:
  • 0 Henk 'm!

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

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


Acties:
  • 0 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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?

Acties:
  • 0 Henk 'm!

  • V_ger
  • Registratie: September 2009
  • Nu online
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.

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


Acties:
  • 0 Henk 'm!

  • Samoerai
  • Registratie: Maart 2000
  • Laatst online: 15-07 12:17
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 ?

Acties:
  • +1 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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%.

Acties:
  • 0 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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.

Acties:
  • 0 Henk 'm!

  • RikHa
  • Registratie: September 2022
  • Laatst online: 02:37
Ik ben onder de indruk! Petje af hoor.

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


Acties:
  • 0 Henk 'm!

  • Activate
  • Registratie: November 2007
  • Laatst online: 11-09 00:37
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 ]


Acties:
  • 0 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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.

Acties:
  • 0 Henk 'm!

  • Activate
  • Registratie: November 2007
  • Laatst online: 11-09 00:37
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 ]


Acties:
  • 0 Henk 'm!

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

Iemand en goedkope website voor een zelfbouw set

Alle info is welkom

Acties:
  • 0 Henk 'm!

  • SHIFTER [NL]
  • Registratie: November 2010
  • Laatst online: 21-09 01:44
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


Acties:
  • 0 Henk 'm!

  • Niek_
  • Registratie: Februari 2002
  • Laatst online: 03:08
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.

Acties:
  • 0 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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.

Acties:
  • 0 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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.

Acties:
  • +1 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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.

Acties:
  • +1 Henk 'm!

  • 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
:)

Acties:
  • 0 Henk 'm!

  • 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 am not a number, I am a free man! Geld over? Check m'n V&A


Acties:
  • 0 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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".

Acties:
  • 0 Henk 'm!

  • Cereal
  • Registratie: Maart 2001
  • Laatst online: 27-08 16:25
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..

Acties:
  • +1 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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...

Acties:
  • 0 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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.

Acties:
  • 0 Henk 'm!

  • Cereal
  • Registratie: Maart 2001
  • Laatst online: 27-08 16:25
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.

Acties:
  • 0 Henk 'm!

  • blokl
  • Registratie: April 2009
  • Laatst online: 01-07 09:54
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

Acties:
  • 0 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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.

Acties:
  • 0 Henk 'm!

  • blokl
  • Registratie: April 2009
  • Laatst online: 01-07 09:54
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?

Acties:
  • 0 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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.

Acties:
  • 0 Henk 'm!

  • blokl
  • Registratie: April 2009
  • Laatst online: 01-07 09:54
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 58% gewijzigd door blokl op 18-03-2025 22:00 ]


Acties:
  • 0 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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.

Acties:
  • 0 Henk 'm!

  • blokl
  • Registratie: April 2009
  • Laatst online: 01-07 09:54
Can you publish you node red tab from home assistant?
I think it wil help me a lot


Thx

Acties:
  • 0 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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?

Acties:
  • 0 Henk 'm!

  • John245
  • Registratie: Januari 2020
  • Laatst online: 05:57
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


Acties:
  • 0 Henk 'm!

  • blokl
  • Registratie: April 2009
  • Laatst online: 01-07 09:54
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?

Acties:
  • 0 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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.

Acties:
  • 0 Henk 'm!

  • sennevb
  • Registratie: Januari 2011
  • Laatst online: 06:41
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

Acties:
  • 0 Henk 'm!

  • MJ de Bruijn
  • Registratie: November 2016
  • Laatst online: 21-09 18:02
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.

Acties:
  • 0 Henk 'm!

  • sennevb
  • Registratie: Januari 2011
  • Laatst online: 06:41
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: 19:30

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 -

Pagina: 1