Binding for the Alfen wallbox in work?

I just started to integrate the Alfen Wallbox via Modbus TCP/IP. This has also been discussed in the following thread:

But actually it has several challenges:

  • The Modbus binding doesn´t support reading Strings
  • The Modbus binding doesn´t support reading FLOAT64 data type values

Whereas I could easily live without reading the Strings, for which a certain solution is described, the reading of the FLOAT64 data is not resolved yet. And those parameters are quite relevant.

Does anybody work on developing a binding for the Alfen wallbox which allows a fast and smart integration for everyone?

I don´t have the knowledge to develop a binding by myself, but I would be happy to support the testing.

Don’t complain but implement this as openHAB rules yourself. Combining two 32bit values into one isn’t rocket science I have even seen posts on this here on the forum.
You should not expect or wait for anyone to start building a dedicated binding just for that purpose when there’s an alternative as simple as this.

I succeeded in the meanwhile to read out the Mode3 string in such a way that I can use it further in rules or apply a transformation on it.

rule "String zusammen setzen - Mode 3"
    when 
        Item WB_Status_Mode3_1 received update 
    then
        var Int1 = ((WB_Status_Mode3_1.state as DecimalType).intValue())/256
        var Int2 = ((WB_Status_Mode3_2.state as DecimalType).intValue())/256
        var Int3 = ((WB_Status_Mode3_3.state as DecimalType).intValue())/256
        var Int4 = ((WB_Status_Mode3_4.state as DecimalType).intValue())/256
        var Int5 = ((WB_Status_Mode3_5.state as DecimalType).intValue())/256

        var String Mode3_String = String::valueOf(Character::toChars(Int1))
        if (WB_Status_Mode3_2.state != 0) Mode3_String = Mode3_String + String::valueOf(Character::toChars(Int2))
        if (WB_Status_Mode3_3.state != 0) Mode3_String = Mode3_String + String::valueOf(Character::toChars(Int3))
        if (WB_Status_Mode3_4.state != 0) Mode3_String = Mode3_String + String::valueOf(Character::toChars(Int4))
        if (WB_Status_Mode3_5.state != 0) Mode3_String = Mode3_String + String::valueOf(Character::toChars(Int5))

        //logInfo("Wallbox: Mode3-String", "Mode3_String: " + Mode3_String + ", Länge Mode3_String: " + Mode3_String.length)
        
        WB_Status_Mode3.postUpdate(Mode3_String)
end 

But I have no clue, how I could continue with reading the FLOAT64 value. The only thing I found is the below linked post which allows to transfer a byte array to a FLOAT32 value. But how to adapt it for FLOAT64 with the most significant bit first (called “network byte order” in the Alfen manual)? Do you have a tip for the respective posts you indicated?

Apart from that, I hope, that other Alfen users can answer me the following point I wonder about.

By using Modscan, I could read out the following data for the FLOT64 registers from 362 on:
(Modscan works with an offset of 1 compared to the Alfen documentation)


According the Alfen documentation, I understand, that the registers are reserved or not available respectively. Can someone confirm that actually only the FLOT64 register “Real Energy Delivered Sum” with the starting register 374 is available?

Last but not least, I would still appreciate when reading FLOAT64 registers becomes possible with the Modbus binding directly. I understood from other posts, that there might be a lot of different ways how the registers are arranged, but looking to other tools, like Modscan, it is most probably most of the time reduced to two approaches.

I believe I succeeded in doing it. I posted my draft here in the hope that I found somebody to test it practially.

Unrelated to your 64-bit work (well done on that) -

There’s a bit of a race condition hidden here. When a block of data is read across Modbus, the binding updates individual linked Items. There is no guarantee which will be the first or last to be updated.
So there is a small risk in a rule like this of mixing ‘new’ and ‘old’ data, i.e. looking at an Item that hasn’t been updated yet.

I think it would be sufficient to just put a 50mS sleep at the top of your rule, to be sure everything got done before using related Item states.

This I guess will apply to your 64-bit work as well - make sure you are not splicing old and new data into a spurious value.

There is an elaborate way to “do it properly” and wait for all of a set to get updated, described elsewhere, but I doubt you need that complexity

Very good point! Thanks a lot for this tip.

Since a few weeks I have my electric car and could therefore test also the wallbox integration. As it has proven itself now for a few weeks, I´m happy to share my integration.

// Funktion zum Lesen von Input-Registern wird von der Wallbox nicht unterstützt, Register können nur als Holding-Register gelesen werden
Bridge modbus:tcp:wallbox "Alfen Wallbox" [host = "XXX.XXX.XXX.XX", port=502, id=200] {
        Bridge poller wb_status [start=1100, length=5, refresh=60000, type="holding"] {
                Thing data WB_MaxLadestrom_Station      [readStart="1100", readValueType="float32"]
                Thing data WB_Temperatur                [readStart="1102", readValueType="float32"]
                // Thing data WB_Status_OCPP               [readStart="1104", readValueType="uint16"]
                // Anzahl Sockets --> Wallbox hat nur einen Ausgang
        }
        // SCN Register: Nur relevant für Smart Charging Network (Mehrere Wallboxen direkt miteinander per Bus verbunden)
}

Bridge modbus:tcp:wallboxsocket "Alfen Wallbox" [host = "192.168.178.57", port=502, id=1] {

        Bridge poller wb_leistungsmessung [start=300, length=94, refresh=5000, type="holding"] {
                //Name der Wallbox
                //Thing data WB_Energiemessung_Status     [readStart="300", readValueType="uint16"]
                //Thing data WB_Energiemessung_Update     [readStart="301", readValueType="uint64"]
                //Thing data WB_Energiemessung_Typ        [readStart="305", readValueType="uint16"]
                Thing data WB_Spannung_L1               [readStart="306", readValueType="float32"]
                Thing data WB_Spannung_L2               [readStart="308", readValueType="float32"]
                Thing data WB_Spannung_L3               [readStart="310", readValueType="float32"]
                Thing data WB_Strom_L1                  [readStart="320", readValueType="float32"]
                Thing data WB_Strom_L2                  [readStart="322", readValueType="float32"]
                Thing data WB_Strom_L3                  [readStart="324", readValueType="float32"]
                Thing data WB_Strom_Gesamt              [readStart="326", readValueType="float32"]
                Thing data WB_Frequenz                  [readStart="336", readValueType="float32"]
                Thing data WB_Leistung_L1               [readStart="338", readValueType="float32"]
                Thing data WB_Leistung_L2               [readStart="340", readValueType="float32"]
                Thing data WB_Leistung_L3               [readStart="342", readValueType="float32"]
                Thing data WB_Leistung_Gesamt           [readStart="344", readValueType="float32"]
                //Thing data WB_Arbeit_Geliefert_L1      [readStart="362", readValueType="int64"] //Reserviert, gibt kein Wert zurück (nur NaN)
                //Thing data WB_Arbeit_Geliefert_L2      [readStart="366", readValueType="int64"] //Reserviert, gibt kein Wert zurück (nur NaN)
                //Thing data WB_Arbeit_Geliefert_L3      [readStart="370", readValueType="int64"] //Reserviert, gibt kein Wert zurück (nur NaN)
                //Thing data WB_Arbeit_Geliefert_Gesamt  [readStart="374", readValueType="int64"]
                Thing data WB_Arbeit_Geliefert_Gesamt_1  [readStart="374", readValueType="uint16"]
                Thing data WB_Arbeit_Geliefert_Gesamt_2  [readStart="375", readValueType="uint16"]
                Thing data WB_Arbeit_Geliefert_Gesamt_3  [readStart="376", readValueType="uint16"]
                Thing data WB_Arbeit_Geliefert_Gesamt_4  [readStart="377", readValueType="uint16"]
                //Thing data WB_Arbeit_Bezogen_L1        [readStart="378", readValueType="int64"] //Reserviert, gibt kein Wert zurück (nur NaN)
                //Thing data WB_Arbeit_Bezogen_L2        [readStart="382", readValueType="int64"] //Reserviert, gibt kein Wert zurück (nur NaN)
                //Thing data WB_Arbeit_Bezogen_L3        [readStart="386", readValueType="int64"] //Reserviert, gibt kein Wert zurück (nur NaN)
                //Thing data WB_Arbeit_Bezogen_Gesamt    [readStart="390", readValueType="int64"] //Reserviert, gibt kein Wert zurück (nur NaN)
        }
        
        Bridge poller wb_statusundkonfig [start=1200, length=16, refresh=2500, type="holding"] {
                Thing data WB_Status_Ausgang            [readStart="1200", readValueType="uint16"]
                Thing data WB_Status_Mode3_1            [readStart="1201", readValueType="uint16"]
                Thing data WB_Status_Mode3_2            [readStart="1202", readValueType="uint16"]
                Thing data WB_Status_Mode3_3            [readStart="1203", readValueType="uint16"]
                Thing data WB_Status_Mode3_4            [readStart="1204", readValueType="uint16"]
                Thing data WB_Status_Mode3_5            [readStart="1205", readValueType="uint16"]
                Thing data WB_MaxLadestrom_Aktiv        [readStart="1206", readValueType="float32"]
                Thing data WB_MaxLadestrom_Timeout      [readStart="1208", readValueType="uint32"]
                Thing data WB_MaxLadestrom_Vorgabe      [readStart="1210", readValueType="float32", writeStart="1210", writeType="holding", writeValueType="float32"]
                Thing data WB_AnzahlPhasen              [readStart="1215", readValueType="int16", writeStart="1215", writeType="holding", writeValueType="int16"]
        }
}
Number WB_MaxLadestrom_Station "Max. Ladestrom [%.1f A]"                        { channel="modbus:data:wallbox:wb_status:WB_MaxLadestrom_Station:number" }
Number WB_Temperatur "Temperatur [%.1f °C]"                     <temperature>   { channel="modbus:data:wallbox:wb_status:WB_Temperatur:number" }

Number WB_Auto "Auto [%s]" // 1=Cupra Born, (2=Zweitwagen), 3=Sonstige, 4=Geschäft

//Number WB_Energiemessung_Status "Status Energiemessung [%s]"                    { channel="modbus:data:wallboxsocket:wb_leistungsmessung:WB_Energiemessung_Status:number" }
//Number WB_Energiemessung_Update "Energiem. letzes Update [%ms]"                 { channel="modbus:data:wallboxsocket:wb_leistungsmessung:WB_Energiemessung_Update:number" }
//Number WB_Energiemessung_Typ "Energiemessung Typ/Quelle [%s]"                   { channel="modbus:data:wallboxsocket:wb_leistungsmessung:WB_Energiemessung_Typ:number" }
Number WB_Strom_L1 "Ladestrom L1 [%.1f A]"                                      { channel="modbus:data:wallboxsocket:wb_leistungsmessung:WB_Strom_L1:number" }
Number WB_Strom_L2 "Ladestrom L2 [%.1f A]"                                      { channel="modbus:data:wallboxsocket:wb_leistungsmessung:WB_Strom_L2:number" }
Number WB_Strom_L3 "Ladestrom L3 [%.1f A]"                                      { channel="modbus:data:wallboxsocket:wb_leistungsmessung:WB_Strom_L3:number" }
Number WB_Strom_Gesamt "Ladestrom [%.1f A]"                                     { channel="modbus:data:wallboxsocket:wb_leistungsmessung:WB_Strom_Gesamt:number" }
Number WB_Leistung_Gesamt "Ladeleistung [%.0f W]"              <energy>        { channel="modbus:data:wallboxsocket:wb_leistungsmessung:WB_Leistung_Gesamt:number" }

Number WB_Arbeit_Geliefert_Gesamt "Ladeenergie Gesamt [%.1f kWh]" 
    Number WB_Arbeit_Geliefert_Gesamt_1 "Arbeit geliefert - Hilfszelle [%s]"    { channel="modbus:data:wallboxsocket:wb_leistungsmessung:WB_Arbeit_Geliefert_Gesamt_1:number" }
    Number WB_Arbeit_Geliefert_Gesamt_2 "Arbeit geliefert - Hilfszelle [%s]"    { channel="modbus:data:wallboxsocket:wb_leistungsmessung:WB_Arbeit_Geliefert_Gesamt_2:number" }
    Number WB_Arbeit_Geliefert_Gesamt_3 "Arbeit geliefert - Hilfszelle [%s]"    { channel="modbus:data:wallboxsocket:wb_leistungsmessung:WB_Arbeit_Geliefert_Gesamt_3:number" }
    Number WB_Arbeit_Geliefert_Gesamt_4 "Arbeit geliefert - Hilfszelle [%s]"    { channel="modbus:data:wallboxsocket:wb_leistungsmessung:WB_Arbeit_Geliefert_Gesamt_4:number" }

String WB_Status "Status [%s]"                                      <status>
    Number WB_Status_Ausgang "Status [%s]"                                      { channel="modbus:data:wallboxsocket:wb_statusundkonfig:WB_Status_Ausgang:number", expire="5m,state=2" }
    String WB_Status_Mode3 "Status Mode 3 [%s]" 
        Number WB_Status_Mode3_1 "Status Mode 3 - Register 1 [%s]"              { channel="modbus:data:wallboxsocket:wb_statusundkonfig:WB_Status_Mode3_1:number" }
        Number WB_Status_Mode3_2 "Status Mode 3 - Register 2 [%s]"              { channel="modbus:data:wallboxsocket:wb_statusundkonfig:WB_Status_Mode3_2:number" }
        Number WB_Status_Mode3_3 "Status Mode 3 - Register 3 [%s]"              { channel="modbus:data:wallboxsocket:wb_statusundkonfig:WB_Status_Mode3_3:number" }
        Number WB_Status_Mode3_4 "Status Mode 3 - Register 4 [%s]"              { channel="modbus:data:wallboxsocket:wb_statusundkonfig:WB_Status_Mode3_4:number" }
        Number WB_Status_Mode3_5 "Status Mode 3 - Register 5 [%s]"              { channel="modbus:data:wallboxsocket:wb_statusundkonfig:WB_Status_Mode3_5:number" }

Number WB_Lademodus "Lademodus [%s]"                                <settings> // 1=PV, 2=Zeitvorgabe, 3= 50 %, 3=100 %
Number WB_MaxLadestrom_Aktiv "Max. Ladestrom (Aktiv) [%.1f A]"                  { channel="modbus:data:wallboxsocket:wb_statusundkonfig:WB_MaxLadestrom_Aktiv:number" }
Number WB_MaxLadestrom_Timeout "Max. Ladestrom Timeout [%.0f s]"                { channel="modbus:data:wallboxsocket:wb_statusundkonfig:WB_MaxLadestrom_Timeout:number" }
Number WB_MaxLadestrom_Vorgabe "Max. Ladestrom [%.1f A]"                        { channel="modbus:data:wallboxsocket:wb_statusundkonfig:WB_MaxLadestrom_Vorgabe:number" }
Number WB_AnzahlPhasen "Anzahl Phasen [%s]"                                     { channel="modbus:data:wallboxsocket:wb_statusundkonfig:WB_AnzahlPhasen:number" }

rule "String zusammen setzen - Mode 3"
    when 
        Item WB_Status_Mode3_1 changed      
    then
        Thread::sleep(50)
        var Int1_1 = ((WB_Status_Mode3_1.state as DecimalType).intValue())/256
        var Int1_2 = ((WB_Status_Mode3_1.state as DecimalType).intValue()) - Int1_1 * 256
         
        var String Mode3_String = String::valueOf(Character::toChars(Int1_1))
        if (Int1_2 != 0) Mode3_String = Mode3_String + String::valueOf(Character::toChars(Int1_2))
                
        //logInfo("Wallbox: Mode3-String", "Mode3_String: " + Mode3_String + ", Länge Mode3_String: " + Mode3_String.length)
        
        WB_Status_Mode3.postUpdate(Mode3_String)
end 

rule "Wallbox Statusstring"
    when
        Item WB_Status_Ausgang received update or
        Item WB_Status_Mode3 changed or
        Time cron "0 0 0 * * ?" // Status nicht betriebsbereit ist sonst nicht zuverlässig gesetzt
    then
        var String Ausgabe
        if (WB_Status_Ausgang.state != 1) Ausgabe = "Nicht betriebsbereit"
        else {
            // Mode 3: Siehe ACE Service Installer und https://tff-forum.de/t/ladestatus-mode-3-nach-iec-62196-1-iec-61851/83576
            var String Mode3_String = WB_Status_Mode3.state
            if (Mode3_String == "A") Ausgabe = "Nicht eingesteckt" 
            else if (Mode3_String == "B1") Ausgabe = "Eingesteckt (kein PWM)"
            else if (Mode3_String == "B2") Ausgabe = "Eingesteckt"
            else if (Mode3_String == "C1") Ausgabe = "Auto nicht bereit zum Laden"
            else if (Mode3_String == "C2") Ausgabe = "Ladevorgang läuft"
            else if (Mode3_String == "D1") Ausgabe = "Auto nicht bereit zum Laden"
            else if (Mode3_String == "D2") Ausgabe = "Ladevorgang läuft, Belüftung erforderlich"
            else if (Mode3_String == "E") Ausgabe = "Kein Kabel angeschlossen"
            else if (Mode3_String == "F") Ausgabe = "Fehler"
            else Ausgabe = "Code: " + Mode3_String
        }
        
        WB_Status.postUpdate(Ausgabe)
end

rule "Umwandlung Wert: Arbeit_Gesamt"
    when
        Item WB_Arbeit_Geliefert_Gesamt_1 received update
    then
        // https://babbage.cs.qc.cuny.edu/ieee-754.old/64bit.html
        // https://babbage.cs.qc.cuny.edu/IEEE-754/
        // https://en.wikipedia.org/wiki/Double-precision_floating-point_format
        // https://de.wikipedia.org/wiki/IEEE_754
        // https://community.openhab.org/t/serial-port-and-pulse-counter-byte-array-little-endian-to-double-precision/24782/15

        Thread::sleep(50)
        var int1 = WB_Arbeit_Geliefert_Gesamt_1.state as Number
        var int2 = WB_Arbeit_Geliefert_Gesamt_2.state as Number
        var int3 = WB_Arbeit_Geliefert_Gesamt_3.state as Number  
        var int4 = WB_Arbeit_Geliefert_Gesamt_4.state as Number 

        var int1_hex = Integer::toHexString(int1.intValue)
        var int2_hex = Integer::toHexString(int2.intValue)
        var int3_hex = Integer::toHexString(int3.intValue)
        var int4_hex = Integer::toHexString(int4.intValue)

        while (int1_hex.length < 4) int1_hex = "0" + int1_hex 
        while (int2_hex.length < 4) int2_hex = "0" + int2_hex
        while (int3_hex.length < 4) int3_hex = "0" + int3_hex 
        while (int4_hex.length < 4) int4_hex = "0" + int4_hex

        var hex_komplett = String::format(int1_hex + int2_hex + int3_hex + int4_hex)
         
        //logInfo("Float64 konvertieren", "Hex-String: " + hex_komplett)
        var hex_vorzexp = hex_komplett.substring(0, 3)
        var hex_sig = hex_komplett.substring(3, hex_komplett.length)
        //logInfo("Float64 konvertieren", "Geteilter Hex-String: Erste 3 Zeichen (Vorzeichen + Exponent): " + hex_vorzexp + ", Rest (Signifikant): " + hex_sig)

        var dec_vorzexp = new DecimalType(Integer.parseInt(hex_vorzexp, 16))       
        var dec_sig = new DecimalType(Long.valueOf(hex_sig, 16).longValue())
        //logInfo("Float64 konvertieren", "Vorzeichen + Exponent als Dezimalwert: " + dec_vorzexp + ", Signifikant (roh-Wert): " + dec_sig)
        
        var bin_vorzexp = Integer::toBinaryString(dec_vorzexp.intValue)
        //logInfo("Float64 konvertieren", "Vorzeichen + Exponent als Binärwert: " + bin_vorzexp)
            var vorz = 0
            var double dec_exp = 0
            if (bin_vorzexp.substring(0, 1) == "1" && bin_vorzexp.length == 12) {
                vorz = -1
                dec_exp = (dec_vorzexp - Math.pow(2, bin_vorzexp.length - 1)).doubleValue
            }
            else {
                vorz = 1
                dec_exp = (dec_vorzexp).doubleValue
            }
        //logInfo("Float64 konvertieren", "Vorzeichen: " + vorz + ", Exponent (roh-Wert): " + dec_exp)
        
        var sig = (1 + (dec_sig / Math.pow(2,52)))
        //logInfo("Float64 konvertieren", "Signifikant: " + sig)
        
        var double exp = dec_exp - 1023
        //logInfo("Float64 konvertieren", "Exponent: " + exp)

        var ergebnis = vorz * sig * Math.pow(2, exp)
        //logInfo("Float64 konvertieren", "Wert: " + ergebnis)
        //logInfo("Float64 konvertieren", "Wert (formatiert): " + String::format("%1$.3f", ergebnis))

        WB_Arbeit_Geliefert_Gesamt.postUpdate(ergebnis / 1000)
end

3 Likes

The integrations I see are only for reading the values from the Alfen Eve. Is it also possible to write values, e.g. control the charging amperage from openhab with Modbus?

Sure, you can write in the current and the number of phases (1 or 3). That works just like a charme.

1 Like

looks really great.
Most of the topics I could also achieve with my implementation.
But the float64 was still a miracle for me.

Could you also share your sitemap how you integrated all the items?
Also it looks like you are controlling the current based on the solar power you have available.
Do you also have there some examples how to control this via openhab?
Thanks

Hi @OpenWan,

I can provide you my sitemap, but it isn´t really special. About what I´m happy about are my rules. They feature mainly two things. On the one side they provide a statistical logging of the charged capacity, solar ratio etc. for different cars. On the other side I have different charging modes like solar charging, solar charging plus using the home battery, target time charging, manual mode etc…

However, the rules are not really prepared to get shared. They use items from different bindings like my solar inverter, solar forecast and the car. And these items are in German as well as my comments in the code.

When you are fine with that, I can just past the rules in. Otherwise I have to spend some time to prepare it.

Hello Tobias,
would be great as an starting example.
German is perfect, makes it easier to read for me :slight_smile:

Hi Bernd,

Great, here is the code.

var Number NeuEingesteckt = 0
var Timer StromTimer = null
var Number Ph_1_3_Umschaltung = 0

rule "WB: Modbus-Poller aktivieren/deaktivieren"
    when
        Item WB_ModbusPoller changed
    then
        // Authentifizierung mit Benutzername/Passwort erfordert Aktivierung von "Basic Authentication Erlauben" unter "API-Sicherheit"
        if (WB_ModbusPoller.state == ON) {
            sendHttpPutRequest("http://Username:Password@192.168.178.25:8081/rest/things/modbus%3Apoller%3Awallbox%3Awb_status/enable","text/plain", 'true')
            sendHttpPutRequest("http://Username:Password@192.168.178.25:8081/rest/things/modbus%3Apoller%3Awallboxsocket%3Awb_leistungsmessung/enable","text/plain", 'true')
            sendHttpPutRequest("http://Username:Password@192.168.178.25:8081/rest/things/modbus%3Apoller%3Awallboxsocket%3Awb_statusundkonfig/enable","text/plain", 'true')
        }
        else {
            sendHttpPutRequest("http://Username:Password@192.168.178.25:8081/rest/things/modbus%3Apoller%3Awallbox%3Awb_status/enable","text/plain", 'disabled')
            sendHttpPutRequest("http://Username:Password@192.168.178.25:8081/rest/things/modbus%3Apoller%3Awallboxsocket%3Awb_leistungsmessung/enable","text/plain", 'disabled')
            sendHttpPutRequest("http://Username:Password@192.168.178.25:8081/rest/things/modbus%3Apoller%3Awallboxsocket%3Awb_statusundkonfig/enable","text/plain", 'disabled')
        }
        
end

rule "String zusammen setzen - Mode 3"
    when 
        Item WB_Status_Mode3_1 received update      
    then
        Thread::sleep(50)
        var Int1_1 = ((WB_Status_Mode3_1.state as DecimalType).intValue())/256
        var Int1_2 = ((WB_Status_Mode3_1.state as DecimalType).intValue()) - Int1_1 * 256
         
        var String Mode3_String = String::valueOf(Character::toChars(Int1_1))
        if (Int1_2 != 0) Mode3_String = Mode3_String + String::valueOf(Character::toChars(Int1_2))
                
        //logInfo("Wallbox: Mode3-String", "Mode3_String: " + Mode3_String + ", Länge Mode3_String: " + Mode3_String.length)
        
        WB_Status_Mode3.postUpdate(Mode3_String)
end 

rule "Wallbox Statusstring"
    when
        Item WB_ModbusPoller received update or
        Item WB_Status_Ausgang received update or
        Item WB_Status_Mode3 changed or
        Time cron "0 0 0 * * ?" // Status nicht betriebsbereit ist sonst nicht zuverlässig gesetzt
    then
        var String Ausgabe
        if (WB_ModbusPoller.state == OFF) Ausgabe = "Wallbox ausgeschalten"
        else if (WB_Status_Ausgang.state != 1) Ausgabe = "Nicht betriebsbereit"
        else {
            // Mode 3: Siehe ACE Service Installer und https://tff-forum.de/t/ladestatus-mode-3-nach-iec-62196-1-iec-61851/83576
            var String Mode3_String = WB_Status_Mode3.state
            if (Mode3_String == "A") Ausgabe = "Nicht eingesteckt" 
            else if (Mode3_String == "B1") Ausgabe = "Eingesteckt (kein PWM)"
            else if (Mode3_String == "B2") Ausgabe = "Eingesteckt"
            else if (Mode3_String == "C1") {
                if (WB_Auto.state == 2 && Born_Ladestand.state >= WB_Zielladestand.state) Ausgabe = "Zielladestand erreicht"
                else if (WB_Auto.state == 2 && WB_MaxLadestrom_Vorgabe.state == 0) Ausgabe ="Ladevorgang angehalten"
                else Ausgabe = "Auto nicht bereit zum Laden"
            }
            else if (Mode3_String == "C2") Ausgabe = "Ladevorgang läuft"
            else if (Mode3_String == "D1") Ausgabe = "Auto nicht bereit zum Laden"
            else if (Mode3_String == "D2") Ausgabe = "Ladevorgang läuft, Belüftung erforderlich"
            else if (Mode3_String == "E") Ausgabe = "Kein Kabel angeschlossen"
            else if (Mode3_String == "F") Ausgabe = "Fehler"
            else Ausgabe = "Code: " + Mode3_String
        }
        
        WB_Status.postUpdate(Ausgabe)
end

rule "Lademodus"
    when 
        Item WB_Lademodus changed or
        Item WB_Status_Mode3 changed or
        Item WB_Zielladestand changed
    then 
        // If-Klausel zur Ermöglichung einer stabilen 1/3-Phasenumschaltung: Timer wird nicht abgebrochen und neu gestartet für die Dauer einer Zykluszeit
        if (WB_Lademodus.state != 1 && WB_Lademodus.state != 2 && WB_Lademodus.state != 6 && WB_Lademodus.state != 7) Ph_1_3_Umschaltung = 0
        if (Ph_1_3_Umschaltung == 0) {
            var Number Strom
            var Number Merker_maxEntladung = null
            
            StromTimer?.cancel
            
            // Im Fall vom Cupra Born die max. Ladestände anpassen
            if (WB_Auto.state == 2) {
                // Max. Ladestand der Wallbox hier nicht anpassen da ansonsten dieser auch manuell nicht mehr unter das Limit reduziert werden kann
                // Max. Ladestand im Cupra bei Bedarf anpassen
                if (WB_Zielladestand.state > Born_Laden_Zielladestand.state) {
                    // Auf 10 aufrunden
                    var Number Zielladestand = WB_Zielladestand.state as Number / 10
                    Zielladestand = Math::round(Zielladestand.floatValue()) * 10
                    if (Zielladestand < 50) Zielladestand = 50
                    Born_Laden_Zielladestand.sendCommand(Zielladestand)
                }
            }
            
            // PV, PV + Zeit, PV Voll und PV Voll + Speicher
            // ------------------------------------------------------------------------------------------------
            if (WB_Lademodus.state == 1 || WB_Lademodus.state == 2 || WB_Lademodus.state == 6 || WB_Lademodus.state == 7) {
                var Number Zaehler_Phasen = 99 // 99 um beim Start bei Bedarf eine sofortige Umschaltung auf 1-Phasenbetrieb zu ermöglichen
                var Number Zaehler_Stopp = 99 // 99 um beim PV-laden gar nicht mit dem Laden zu beginnen wenn nicht ausreichend PV-Leistung vorhanden ist
                var Number Zeitladen_aktiv = 0
                var Number LeistungkW
                StromTimer = createTimer(now, [ |
                    // Laden nach Zeitlimit einleiten
                    //-------------------------------------------------------
                    if (WB_Lademodus.state == 2 && WB_Auto.state == 2) {
                        var Number kWh_zu_laden = 77 * 1.1 / 100 * (WB_Zielladestand.state as Number - Born_Ladestand.state as Number)
                        var Number Minuten_zu_laden = kWh_zu_laden / 11 * 60
                        var Number Startzeit_in_Minuten = WB_Zielladen_Uhrzeit.state as Number * 60 - Minuten_zu_laden - 15
                        var Number Tag = WB_Zielladen_Tag.state as Number
                        if (Startzeit_in_Minuten < 0) {
                            Startzeit_in_Minuten = 24 * 60 + Startzeit_in_Minuten
                            if (Tag > 2) Tag = Tag - 1
                            else Tag = 7
                        }
                        if (now.getDayOfWeek.getValue == Tag && (now.getHour * 60 + now.getMinute) > Startzeit_in_Minuten) {
                            // Max. Entladungstiefe der Hausbatterie hochzusetzen um nicht mit dem Speicherstrom zu laden
                            if (PV_Batterie_Ladezustand.state > PV_Batterie_maxEntladung_lese.state) {
                                Merker_maxEntladung = PV_Batterie_maxEntladung_lese.state
                                PV_Batterie_maxEntladung.sendCommand((((PV_Batterie_Ladezustand.state as Number / 10).intValue + 1) *10).toString)
                            }
                            Zeitladen_aktiv = 1
                        }
                    }
                    
                    // Verfügbare PV-Ladeleistung berechnen
                    //-------------------------------------------------------
                    // Aktuelle Leistung die ins Stromnetz eingespeist wird
                    LeistungkW = Stromzaehler_AktuelleLeistung.state as Number / 1000 * -1
                    // Aktuelle Auto-Ladeleistung ergänzen
                    LeistungkW = LeistungkW + WB_Leistung_Gesamt.state as Number / 1000
                    // Für normales PV-Laden
                    if (WB_Lademodus.state == 1 || WB_Lademodus.state == 2) {
                        // PV-Batterie Entladeleistung abziehen 
                        if (PV_Batterie_Stromrichtung.state == 1) {
                            LeistungkW = LeistungkW - 3.7 / 16 * PV_Batterie_Entladestrom.state as Number
                        }
                        // PV-Batterie Ladeleistung je nach Füllstand ergänzen
                        else if (PV_Batterie_Stromrichtung.state == 0) {
                            LeistungkW = LeistungkW + 3.7 / 16 * PV_Batterie_Entladestrom.state as Number
                            if (PV_Batterie_Ladezustand.state > 95) LeistungkW = LeistungkW + 0.25
                            else if (PV_Batterie_Ladezustand.state > 90) LeistungkW = LeistungkW
                            else if (PV_Batterie_Ladezustand.state > 80) LeistungkW = LeistungkW - 0.25
                            else LeistungkW = LeistungkW - 0.5
                        }   
                        //logInfo ("Lademodus", "Verfügbarer Ladestrom: " + LeistungkW + " kW") 
                    }
                    // Für PV Voll und PV Voll + Speicher
                    if (WB_Lademodus.state == 6 || WB_Lademodus.state == 7) {
                        // PV-Batterie Entladeleistung abziehen 
                        if (PV_Batterie_Stromrichtung.state == 1) {
                            LeistungkW = LeistungkW - 3.7 / 16 * PV_Batterie_Entladestrom.state as Number
                        }
                        // PV-Batterie Ladeleistung ergänzen
                        else if (PV_Batterie_Stromrichtung.state == 0) {
                            LeistungkW = LeistungkW + 3.7 / 16 * PV_Batterie_Entladestrom.state as Number
                        }
                        // Für PV Voll + Speicher Batterientladung je nach Ladestand ergänzen
                        if (WB_Lademodus.state == 7) {
                            if (PV_Batterie_Ladezustand.state > 75) LeistungkW = LeistungkW + 1.5
                            else if (PV_Batterie_Ladezustand.state > 50) LeistungkW = LeistungkW + 1
                            else if (PV_Batterie_Ladezustand.state > 30) LeistungkW = LeistungkW + 0.5
                            // Limiteren dass max. Leistung des Wechselrichters von 6 kW nicht überschritten wird, andernfalls wird Netzstrom bezogen
                            if (LeistungkW + (PV_Eigenverbrauch.state as Number - WB_Leistung_Gesamt.state as Number) / 1000 > 6) {
                                LeistungkW = 6 - (PV_Eigenverbrauch.state as Number - WB_Leistung_Gesamt.state as Number) / 1000 - 0.3
                            }
                        } 
                    }

                    // Phasen konfigurieren
                    //-------------------------------------------------------
                    if (Zeitladen_aktiv == 1) {
                        if (WB_AnzahlPhasen.state != 3) WB_AnzahlPhasen.sendCommand(3)
                    }
                    // Phasenumschaltung mit Verzögerung
                    else {
                        Ph_1_3_Umschaltung = 0
                        if (LeistungkW > 4) {
                            if (WB_AnzahlPhasen.state != 3) {
                                if ((PV_Batterie_Ladezustand.state > 80 && Zaehler_Phasen >= 5) || Zaehler_Phasen >= 15) {
                                    Ph_1_3_Umschaltung = 1
                                    WB_AnzahlPhasen.sendCommand(3)
                                    Zaehler_Phasen = 0
                                    Thread::sleep(50)
                                }
                                else {
                                    if (Zaehler_Phasen < 0) Zaehler_Phasen = 0
                                    Zaehler_Phasen = Zaehler_Phasen + 1
                                }
                            } 
                        }
                        else {
                            if (WB_AnzahlPhasen.state != 1) {
                                if (Zaehler_Phasen == 99 || (PV_Batterie_Ladezustand.state < 70 && Zaehler_Phasen <= -5) || Zaehler_Phasen <= -15) {
                                    Ph_1_3_Umschaltung = 1
                                    WB_AnzahlPhasen.sendCommand(1)
                                    Zaehler_Phasen = 0
                                    Thread::sleep(50)
                                }
                                else {
                                    if (Zaehler_Phasen > 0) Zaehler_Phasen = 0
                                    Zaehler_Phasen = Zaehler_Phasen - 1
                                    // Beschleunigtes Umschalten bei hohem Netzbezug
                                    if (PV_Haus_Netz.state > 1000 && Zaehler_Phasen > -4) Zaehler_Phasen = -4
                                }
                            }
                        }
                        if (Zaehler_Phasen == 99) Zaehler_Phasen = 0
                    }
                    
                    // Strom konfigurieren
                    //-------------------------------------------------------
                    if (WB_AnzahlPhasen.state == 1) {
                        Strom = 16.00 / 3.70 * LeistungkW
                        if (Strom < 6) {
                            // PV-Batterie wird geladen
                            if (Zaehler_Stopp == 99) {
                                Strom = 0
                            }
                            else if (PV_Batterie_Stromrichtung.state == 0) {
                                Strom = 6
                            } 
                            // PV-Batterie wird entladen
                            else if (PV_Batterie_Entladestrom.state < 3) { 
                                Strom = 6
                            }
                            else {
                                // Zeitverzögertes Stoppen
                                if (Zaehler_Stopp < 10) {
                                    Strom = 6
                                    Zaehler_Stopp = Zaehler_Stopp + 1
                                }
                                else {
                                    Strom = 0
                                }
                            }
                        }
                        else Zaehler_Stopp = 0
                    }
                    else {
                        Strom = 16.00 / 11.00 * LeistungkW
                        if (Strom < 6) Strom = 6
                    }
                    if (Strom > 16) Strom = 16
                    //logInfo ("Lademodus", "Strom: " + Strom + " A")
                    
                    // Bei normalem PV-Laden, laden nicht beginnen wenn Hausspeicherladestand < 30 %
                    // Nicht nur auf aktuellen WB_Leistung_Gesamt-Wert achten da dieser bei der 3 auf 1-Phasenumschaltung auf 0 geht
                    if (WB_Lademodus.state != 6 && WB_Lademodus.state != 7 && PV_Batterie_Ladezustand.state < 30 && WB_Leistung_Gesamt.maximumSince(now.minusMinutes(2)).state == 0) Strom = 0

                    // Laden stoppen so dass Batteriespeicher noch vollgeladen werden kann
                    if (PV_Vorhersage_Solcast_Sued_Energie_HeuteVerbleibend.state < (100 - PV_Batterie_Ladezustand.state as Number) / 100 * 8) Strom = 0
                    
                    // Laden nach Zeitlimit
                    if (Zeitladen_aktiv == 1) Strom = 16
                    
                    // Laden stoppen wenn Cupra geladen wird und Zielladestand erreicht ist
                    if (WB_Auto.state == 2 && Born_Ladestand.state >= WB_Zielladestand.state) Strom = 0

                    WB_MaxLadestrom_Vorgabe.sendCommand(Strom)
                    
                    if (WB_Status_Mode3.state != "A") StromTimer.reschedule(now.plusMinutes(1))
                ])
                if (Merker_maxEntladung != null) PV_Batterie_maxEntladung.sendCommand(Merker_maxEntladung.toString)
                Zeitladen_aktiv = 0
            }
            
            // 50 % oder 100 %
            // ------------------------------------------------------------------------------------------------
            else if (WB_Lademodus.state == 3 || WB_Lademodus.state == 4) {
                // Max. Entladungstiefe der Hausbatterie hochzusetzen um nicht mit dem Speicherstrom zu laden
                if (PV_Batterie_Ladezustand.state > PV_Batterie_maxEntladung_lese.state) {
                    Merker_maxEntladung = PV_Batterie_maxEntladung_lese.state
                    PV_Batterie_maxEntladung.sendCommand((((PV_Batterie_Ladezustand.state as Number / 10).intValue + 1) *10).toString)
                }
                
                if (WB_AnzahlPhasen.state != 3) WB_AnzahlPhasen.sendCommand(3)
                
                StromTimer = createTimer(now, [ |
                    if (WB_Lademodus.state == 3) Strom = 8
                    if (WB_Lademodus.state == 4) Strom = 16

                    // Laden stoppen wenn Cupra geladen wird und Zielladestand erreicht ist
                    if (WB_Auto.state == 2 && Born_Ladestand.state >= WB_Zielladestand.state) {
                        Strom = 0
                        if (Merker_maxEntladung != null && Merker_maxEntladung != PV_Batterie_maxEntladung_lese.state) PV_Batterie_maxEntladung.sendCommand(Merker_maxEntladung.toString)
                    }

                    WB_MaxLadestrom_Vorgabe.sendCommand(Strom)
                    if (WB_Status_Mode3.state != "A") StromTimer.reschedule(now.plusMinutes(5))
                ])
                if (Merker_maxEntladung != null && Merker_maxEntladung != PV_Batterie_maxEntladung_lese.state) PV_Batterie_maxEntladung.sendCommand(Merker_maxEntladung.toString)
            }
            // Manuelle Vorgabe
            // ------------------------------------------------------------------------------------------------
            else if (WB_Lademodus.state == 5) {
                StromTimer = createTimer(now, [ |
                    Strom = WB_MaxLadestrom_Vorgabe.state

                    // Laden stoppen wenn Cupra geladen wird und Zielladestand erreicht ist
                    if (WB_Auto.state == 2 && Born_Ladestand.state >= WB_Zielladestand.state) Strom = 0

                    WB_MaxLadestrom_Vorgabe.sendCommand(Strom)
                    if (WB_Status_Mode3.state != "A") StromTimer.reschedule(now.plusMinutes(5))
                ])
            }
        }
        Ph_1_3_Umschaltung = 0
end

rule "Datum für Zielladen auf nächsten Tag vorstellen"
    when 
        Time cron "0 0 0 * * ?" or
        Item WB_Lademodus changed from 2
    then 
        if (WB_Lademodus.state != 2) WB_Zielladen_Tag.postUpdate(now.plusDays(1).getDayOfWeek.getValue)
end

rule "Aktionen beim Kabel ein- und ausstecken sowie starten des Ladevorgangs"
    when
        Item WB_Status_Mode3 changed
    then
        var String Durchsage

        // Kabel wurde eingesteckt
        if(previousState == "A") {
            NeuEingesteckt = 1
            // Im Fall vom Cupra Born die max. Ladestände anpassen
            if (WB_Auto.state == 2) {
                // Bei PV-Laden Zielladestand auf min. 80 % erhöhen
                if (WB_Lademodus.state == 1 || WB_Lademodus.state == 2) {
                    if (WB_Zielladestand.state < 80) WB_Zielladestand.postUpdate(80)
                }
            }
        }

        // Vereinzelt ist der Status nach dem Ausstecken nochmals von "A" auf "B1" gesprungen ohne dass das Kabel eingesteckt wurde. 
        // In solch einem Fall wird hiermit die Initialisierung vermieden da nicht nur <previousState == "A"> als Trigger verwendet wird
        if((previousState == "A" && WB_Status_Mode3.state != "B1" && WB_Status_Mode3.state != "A") ||
            (previousState == "B1" && WB_Status_Mode3.state != "A" && NeuEingesteckt == 1)) {
            logInfo("Wallbox Debug", "previousState: " + previousState + " Akueller State: " + WB_Status_Mode3.state)
            if (PV_Batterie_intelligenteSteuerung.state == ON) PV_Batterie_intelligenteSteuerung.sendCommand(OFF)
            WB_ArbeitPV_Ladevorgang_MerkerZwischenstand.postUpdate(0)
            WB_Arbeit_Geliefert_Gesamt_MerkerEingesteckt.postUpdate(WB_Arbeit_Geliefert_Gesamt.state)

            // Durchsage
            var String Lademodus
            if (WB_Lademodus.state == 1) Lademodus = "Photovoltaik"
            if (WB_Lademodus.state == 2) Lademodus = "Photovoltaik mit Zeitvorgabe"
            if (WB_Lademodus.state == 3) Lademodus = "50 %"
            if (WB_Lademodus.state == 4) Lademodus = "100 %"
            if (WB_Lademodus.state == 5) Lademodus = "Manuell"
            if (WB_Lademodus.state == 6) Lademodus = "Photovoltaik mit voller Leistung"
            if (WB_Lademodus.state == 7) Lademodus = "Photovoltaik mit voller Leistung und Speicherunterstützung"
            
            if (WB_Auto.state == 2) {
                //Durchsage = "Der Cupra Born wurde eingesteckt." 
                //Durchsage = Durchsage + " Er wird im Lademodus " + Lademodus + " geladen."
                //Durchsage = Durchsage + " Der aktuelle Ladestand beträgt " + Born_Ladestand.state + " %, der Zielladestand " + WB_Zielladestand.state + " %."
                Durchsage = "Der Ladestand des Cupra Born beträgt " + Born_Ladestand.state + " %."
                Durchsage = Durchsage + " Er wird im Lademodus " + Lademodus + " auf " + WB_Zielladestand.state + " % aufgeladen."
                if (WB_Lademodus.state == 1 || WB_Lademodus.state == 6 || WB_Lademodus.state == 7) {
                    var Number kWh_zu_laden = 77 * 1.1 / 100 * (WB_Zielladestand.state as Number - Born_Ladestand.state as Number)
                    var Number kWh_verfuegbar_heute = (PV_Vorhersage_Solcast_Sued_Energie_HeuteVerbleibend.state as QuantityType<Number>).toUnit("kWh").toBigDecimal - 6 / 100 * (100 - PV_Batterie_Ladezustand.state as Number)
                    if (now.getHour < 17) kWh_verfuegbar_heute = kWh_verfuegbar_heute - (10 / 17 * (17 - now.getHour))
                    if (kWh_verfuegbar_heute < 0) kWh_verfuegbar_heute = 0
                    var Number kWh_Rest_heute = kWh_zu_laden - kWh_verfuegbar_heute
                    //logInfo("Test1", "kWh_verfuegbar_heute=" + kWh_verfuegbar_heute)
                    //logInfo("Test2", "kWh_Rest_heute=" + kWh_Rest_heute)
                    Durchsage = Durchsage + " "
                    if (kWh_Rest_heute < 0) Durchsage = Durchsage + "Der Zielladestand sollte heute erreicht werden können."
                    else {
                        var Number kWh_verfuegbar_morgen = (PV_Vorhersage_Solcast_Sued_Energie_Tag1.state as QuantityType<Number>).toUnit("kWh").toBigDecimal - 15
                        if (kWh_verfuegbar_morgen < 0) kWh_verfuegbar_morgen = 0
                        var Number kWh_Rest_morgen = kWh_Rest_heute - kWh_verfuegbar_morgen
                        //logInfo("Test3", "kWh_verfuegbar_morgen=" + kWh_verfuegbar_morgen)
                        //logInfo("Test4", "kWh_Rest_morgen=" + kWh_Rest_morgen)
                        if (kWh_Rest_heute < 5 && kWh_Rest_morgen < 0) Durchsage = Durchsage + "Der Zielladestand wird heute voraussichtlich nicht erreicht, aber morgen."
                        else if (kWh_Rest_heute > 5 && kWh_Rest_morgen < 0) Durchsage = Durchsage + "Der Zielladestand wird heute nicht erreicht, aber morgen."
                        else {
                            var Number kWh_verfuegbar_uebermorgen = (PV_Vorhersage_Solcast_Sued_Energie_Tag2.state as QuantityType<Number>).toUnit("kWh").toBigDecimal - 15
                            if (kWh_verfuegbar_uebermorgen < 0) kWh_verfuegbar_uebermorgen = 0
                            var Number kWh_Rest_uebermorgen = kWh_Rest_morgen - kWh_verfuegbar_uebermorgen
                            //logInfo("Test5", "kWh_verfuegbar_uebermorgen=" + kWh_verfuegbar_uebermorgen)
                            //logInfo("Test6", "kWh_Rest_uebermorgen=" + kWh_Rest_uebermorgen)
                            if (kWh_Rest_uebermorgen < 0) Durchsage = Durchsage + "Der Zielladestand sollte übermorgen erreicht werden können."
                            else Durchsage = Durchsage + "Der Zielladestand wird in den nächsten beiden Tagen nicht erreicht werden können."
                        }
                    } 
                }
            }
            else Durchsage = " Das Auto wird im Lademodus " + Lademodus + " geladen."
            
            if (A2_M2_Praesenz.state == OPEN) say(Durchsage, "voicerss:deDE", "squeezebox:squeezeboxplayer:myServer:Garage", new PercentType(35))
        }
        
        // Ladevorgang startet erstmalig
        if (NeuEingesteckt == 1 && (WB_Status_Mode3.state == "C2" || WB_Status_Mode3.state == "D2")) {
            NeuEingesteckt = 0
            
            // Durchsage
            Durchsage = "Der Ladevorgang ist gestartet. "
            if (A2_M2_Praesenz.state == OPEN) say(Durchsage, "voicerss:deDE", "squeezebox:squeezeboxplayer:myServer:Garage", new PercentType(35))
        }

        // Ladevorgang startet
        if (WB_Status_Mode3.state == "C2" || WB_Status_Mode3.state == "D2") {
            Stromzaehler_Bezug_MerkerStartLaden.postUpdate(Stromzaehler_Bezug.state)
            WB_Arbeit_Geliefert_Gesamt_MerkerStartLaden.postUpdate(WB_Arbeit_Geliefert_Gesamt.state)
        }

        // Ladevorgang ist gestoppt/angehalten
        if ((previousState == "C2" || previousState == "D2") && WB_Status_Mode3.state != "C2" && WB_Status_Mode3.state != "D2") {
            WB_ArbeitPV_Ladevorgang_MerkerZwischenstand.postUpdate(WB_ArbeitPV_Ladevorgang.state)
            Stromzaehler_Bezug_MerkerStartLaden.postUpdate(Stromzaehler_Bezug.state)
            WB_Arbeit_Geliefert_Gesamt_MerkerStartLaden.postUpdate(WB_Arbeit_Geliefert_Gesamt.state)
            }

        // Kabel wurde ausgesteckt
        if (WB_Status_Mode3.state == "A") {
            if (WB_Arbeit_Geliefert_Gesamt.state != WB_Arbeit_Geliefert_Gesamt_MerkerEingesteckt.state && NeuEingesteckt == 0 && WB_Arbeit_Geliefert_Gesamt.state > (WB_Arbeit_Gesamt.state as Number + WB_Arbeit_Ladevorgang.state as Number - 0.1)) {
                if (WB_Auto.state == 1) {
                    WB_Arbeit_Cupra.postUpdate(WB_Arbeit_Cupra.state as Number + WB_Arbeit_Ladevorgang.state as Number)
                    WB_KostenTheo_Cupra.postUpdate(WB_KostenTheo_Cupra.state as Number + WB_KostenTheo_Ladevorgang.state as Number)
                    WB_ArbeitPV_Cupra.postUpdate(WB_ArbeitPV_Cupra.state as Number + WB_ArbeitPV_Ladevorgang.state as Number)
                    WB_ArbeitPV_Prozent_Cupra.postUpdate(WB_ArbeitPV_Prozent_Ladevorgang.state as Number)
                    Thread::sleep(50)
                    WB_ArbeitPV_ProzentSumme_Cupra.postUpdate(WB_ArbeitPV_Cupra.state as Number / WB_Arbeit_Cupra.state as Number * 100)
                    WB_KostenReal_Cupra.postUpdate(WB_KostenReal_Cupra.state as Number + WB_KostenReal_Ladevorgang.state as Number)
                }
                if (WB_Auto.state == 2) {
                    WB_Arbeit_Cupra2.postUpdate(WB_Arbeit_Cupra2.state as Number + WB_Arbeit_Ladevorgang.state as Number)
                    WB_KostenTheo_Cupra2.postUpdate(WB_KostenTheo_Cupra2.state as Number + WB_KostenTheo_Ladevorgang.state as Number)
                    WB_ArbeitPV_Cupra2.postUpdate(WB_ArbeitPV_Cupra2.state as Number + WB_ArbeitPV_Ladevorgang.state as Number)
                    WB_ArbeitPV_Prozent_Cupra2.postUpdate(WB_ArbeitPV_Prozent_Ladevorgang.state as Number)
                    Thread::sleep(50)
                    WB_ArbeitPV_ProzentSumme_Cupra2.postUpdate(WB_ArbeitPV_Cupra2.state as Number / WB_Arbeit_Cupra2.state as Number * 100)
                    WB_KostenReal_Cupra2.postUpdate(WB_KostenReal_Cupra2.state as Number + WB_KostenReal_Ladevorgang.state as Number)
                }
                if (WB_Auto.state == 3) {
                    WB_Arbeit_Sonstige.postUpdate(WB_Arbeit_Sonstige.state as Number + WB_Arbeit_Ladevorgang.state as Number)
                    WB_KostenTheo_Sonstige.postUpdate(WB_KostenTheo_Sonstige.state as Number + WB_KostenTheo_Ladevorgang.state as Number)
                    WB_ArbeitPV_.postUpdate(WB_ArbeitPV_Sonstige.state as Number + WB_ArbeitPV_Ladevorgang.state as Number)
                    WB_ArbeitPV_Prozent_Sonstige.postUpdate(WB_ArbeitPV_Prozent_Ladevorgang.state as Number)
                    Thread::sleep(50)
                    WB_ArbeitPV_ProzentSumme_Sonstige.postUpdate(WB_ArbeitPV_Sonstige.state as Number / WB_Arbeit_Sonstige.state as Number * 100)
                    WB_KostenReal_Sonstige.postUpdate(WB_KostenReal_Sonstige.state as Number + WB_KostenReal_Ladevorgang.state as Number)
                }
                if (WB_Auto.state == 4) {
                    WB_Arbeit_Geschaeft.postUpdate(WB_Arbeit_Geschaeft.state as Number + WB_Arbeit_Ladevorgang.state as Number)
                    WB_KostenTheo_Geschaeft.postUpdate(WB_KostenTheo_Geschaeft.state as Number + WB_KostenTheo_Ladevorgang.state as Number)
                    WB_ArbeitPV_Geschaeft.postUpdate(WB_ArbeitPV_Geschaeft.state as Number + WB_ArbeitPV_Ladevorgang.state as Number)
                    WB_ArbeitPV_Prozent_Geschaeft.postUpdate(WB_ArbeitPV_Prozent_Ladevorgang.state as Number)
                    Thread::sleep(50)
                    WB_ArbeitPV_ProzentSumme_Geschaeft.postUpdate(WB_ArbeitPV_Geschaeft.state as Number / WB_Arbeit_Geschaeft.state as Number * 100)
                    WB_KostenReal_Geschaeft.postUpdate(WB_KostenReal_Geschaeft.state as Number + WB_KostenReal_Ladevorgang.state as Number)
                }
                WB_Arbeit_Gesamt.postUpdate(WB_Arbeit_Gesamt.state as Number + WB_Arbeit_Ladevorgang.state as Number)
                WB_KostenTheo_Gesamt.postUpdate(WB_KostenTheo_Gesamt.state as Number + WB_KostenTheo_Ladevorgang.state as Number)
                WB_ArbeitPV_Gesamt.postUpdate(WB_ArbeitPV_Gesamt.state as Number + WB_ArbeitPV_Ladevorgang.state as Number)
                Thread::sleep(50)
                WB_ArbeitPV_ProzentSumme_Gesamt.postUpdate(WB_ArbeitPV_Gesamt.state as Number / WB_Arbeit_Gesamt.state as Number * 100)
                WB_KostenReal_Gesamt.postUpdate(WB_KostenReal_Gesamt.state as Number + WB_KostenReal_Ladevorgang.state as Number)
            
                // Durchsage
                if (WB_Auto.state == 2) Durchsage = "Der Ladestand des Cupra Born beträgt " + Born_Ladestand.state + " %."
                Durchsage = Durchsage + " Es wurden " + WB_Arbeit_Ladevorgang.state.format("%.1f").toString.replace(".",",")  + " Kilowattstunden mit " + WB_ArbeitPV_Prozent_Ladevorgang.state.format("%.0f") + " % Photovoltaik-Anteil geladen."
                if (A2_M2_Praesenz.state == OPEN) say(Durchsage, "voicerss:deDE", "squeezebox:squeezeboxplayer:myServer:Garage", new PercentType(30))
            }
        }
end
 
rule "Umwandlung Wert: Arbeit_Gesamt"
    when
        Item WB_Arbeit_Geliefert_Gesamt_1 received update
    then
        // https://babbage.cs.qc.cuny.edu/ieee-754.old/64bit.html
        // https://babbage.cs.qc.cuny.edu/IEEE-754/
        // https://en.wikipedia.org/wiki/Double-precision_floating-point_format
        // https://de.wikipedia.org/wiki/IEEE_754
        // https://community.openhab.org/t/serial-port-and-pulse-counter-byte-array-little-endian-to-double-precision/24782/15

        Thread::sleep(50)
                                                                    // Wallbox neu      // Ergebnis 112.34      // Ergebnis -1512.78    // Ergebnis 0
        var int1 = WB_Arbeit_Geliefert_Gesamt_1.state as Number     // 16422            // 16476 (405C)         // 49303 (C097)         // 0
        var int2 = WB_Arbeit_Geliefert_Gesamt_2.state as Number     // 0                // 5570  (15C2)         // 41758 (A31E)         // 0
        var int3 = WB_Arbeit_Geliefert_Gesamt_3.state as Number     // 0                // 36700 (8F5C)         // 47185 (B851)         // 0
        var int4 = WB_Arbeit_Geliefert_Gesamt_4.state as Number     // 0                // 10486 (28F6)         // 60293 (EB85)         // 0

        var int1_hex = Integer::toHexString(int1.intValue)
        var int2_hex = Integer::toHexString(int2.intValue)
        var int3_hex = Integer::toHexString(int3.intValue)
        var int4_hex = Integer::toHexString(int4.intValue)

        while (int1_hex.length < 4) int1_hex = "0" + int1_hex 
        while (int2_hex.length < 4) int2_hex = "0" + int2_hex
        while (int3_hex.length < 4) int3_hex = "0" + int3_hex 
        while (int4_hex.length < 4) int4_hex = "0" + int4_hex

        var hex_komplett = String::format(int1_hex + int2_hex + int3_hex + int4_hex)
         
        //logInfo("Float64 konvertieren", "Hex-String: " + hex_komplett)
        var hex_vorzexp = hex_komplett.substring(0, 3)
        var hex_sig = hex_komplett.substring(3, hex_komplett.length)
        //logInfo("Float64 konvertieren", "Geteilter Hex-String: Erste 3 Zeichen (Vorzeichen + Exponent): " + hex_vorzexp + ", Rest (Signifikant): " + hex_sig)

        var dec_vorzexp = new DecimalType(Integer.parseInt(hex_vorzexp, 16))       
        var dec_sig = new DecimalType(Long.valueOf(hex_sig, 16).longValue())
        //logInfo("Float64 konvertieren", "Vorzeichen + Exponent als Dezimalwert: " + dec_vorzexp + ", Signifikant (roh-Wert): " + dec_sig)
        
        var bin_vorzexp = Integer::toBinaryString(dec_vorzexp.intValue)
        //logInfo("Float64 konvertieren", "Vorzeichen + Exponent als Binärwert: " + bin_vorzexp)
            var vorz = 0
            var double dec_exp = 0
            if (bin_vorzexp.substring(0, 1) == "1" && bin_vorzexp.length == 12) {
                vorz = -1
                dec_exp = (dec_vorzexp - Math.pow(2, bin_vorzexp.length - 1)).doubleValue
            }
            else {
                vorz = 1
                dec_exp = (dec_vorzexp).doubleValue
            }
        //logInfo("Float64 konvertieren", "Vorzeichen: " + vorz + ", Exponent (roh-Wert): " + dec_exp)
        
        var sig = (1 + (dec_sig / Math.pow(2,52)))
        //logInfo("Float64 konvertieren", "Signifikant: " + sig)
        
        var double exp = dec_exp - 1023
        //logInfo("Float64 konvertieren", "Exponent: " + exp)

        var ergebnis = vorz * sig * Math.pow(2, exp)
        //logInfo("Float64 konvertieren", "Wert: " + ergebnis)
        //logInfo("Float64 konvertieren", "Wert (formatiert): " + String::format("%1$.3f", ergebnis))

        WB_Arbeit_Geliefert_Gesamt.postUpdate(ergebnis / 1000)
end

rule "Tagesenergiezähler: Zählerstand merken und Tageszähler zurücksetzen"
    when
        Time cron "30 55 23 * * ?"
    then 
        WB_Arbeit_Geliefert_Gesamt_MerkerMitternacht.postUpdate(WB_Arbeit_Geliefert_Gesamt.state)
        WB_Arbeit_Tag.postUpdate(0)
end

rule "Tagesenergiezähler und Ladevorgang"
    when
        Item WB_Arbeit_Geliefert_Gesamt changed or 
        Item WB_Arbeit_Geliefert_Gesamt_MerkerEingesteckt changed or
        Item System_started_verzoegert received update
    then
        WB_Arbeit_Tag.postUpdate(WB_Arbeit_Geliefert_Gesamt.state as Number - WB_Arbeit_Geliefert_Gesamt_MerkerMitternacht.state as Number)
        WB_Arbeit_Ladevorgang.postUpdate(WB_Arbeit_Geliefert_Gesamt.state as Number - WB_Arbeit_Geliefert_Gesamt_MerkerEingesteckt.state as Number)
        Thread::sleep(50)
        WB_KostenTheo_Ladevorgang.postUpdate(WB_Arbeit_Ladevorgang.state as Number * Strompreis_Bezug_Aktuell.state as Number)

        // Berechnung der anteiligen PV-Leistung
        // Da insbesondere bei PV-Laden die Ladung unterbrochen werden kann, müssen die Ladezyklen einzeln betrachet werden
        // um bei nicht aktiver Ladung den Strombezug des Hauses nicht mit einzurechnen.
        var Number Zyklus_Arbeit = WB_Arbeit_Geliefert_Gesamt.state as Number - WB_Arbeit_Geliefert_Gesamt_MerkerStartLaden.state as Number
        var Number Zyklus_ArbeitNetz = Stromzaehler_Bezug.state as Number - Stromzaehler_Bezug_MerkerStartLaden.state as Number
        if (Zyklus_ArbeitNetz > Zyklus_Arbeit) Zyklus_Arbeit = Zyklus_ArbeitNetz
        var Number Zyklus_ArbeitPV = Zyklus_Arbeit - Zyklus_ArbeitNetz
        WB_ArbeitPV_Ladevorgang.postUpdate(WB_ArbeitPV_Ladevorgang_MerkerZwischenstand.state as Number + Zyklus_ArbeitPV)
        
        Thread::sleep(50)
        if (WB_Arbeit_Ladevorgang.state > 0) WB_ArbeitPV_Prozent_Ladevorgang.postUpdate(WB_ArbeitPV_Ladevorgang.state as Number / WB_Arbeit_Ladevorgang.state as Number * 100)

        Thread::sleep(50)
        WB_KostenReal_Ladevorgang.postUpdate(WB_ArbeitPV_Ladevorgang.state as Number * Strompreis_Einspeisung_Aktuell.state as Number + (WB_Arbeit_Ladevorgang.state as Number - WB_ArbeitPV_Ladevorgang.state as Number) * Strompreis_Bezug_Aktuell.state as Number)
end

//-------------------------------------------------------------
// Externe Ladungen

rule "Eintrag speichern"
    when
        Item ExternGeladen_EingabeSpeichern changed to ON
    then
        Born_ExternGeladen_Arbeit_Ladevorgang.postUpdate(ExternGeladen_Arbeit_Ladevorgang_kWh.state as Number + ExternGeladen_Arbeit_Ladevorgang_Wh.state as Number / 100)
        Born_ExternGeladen_Kosten_Ladevorgang.postUpdate(ExternGeladen_Kosten_Ladevorgang_Euro.state as Number + ExternGeladen_Kosten_Ladevorgang_Cent.state as Number / 100)
        Thread::sleep(50)
        Born_ExternGeladen_Arbeit_Gesamt.postUpdate(Born_ExternGeladen_Arbeit_Gesamt.state as Number + Born_ExternGeladen_Arbeit_Ladevorgang.state as Number)
        Born_ExternGeladen_Kosten_Gesamt.postUpdate(Born_ExternGeladen_Kosten_Gesamt.state as Number + Born_ExternGeladen_Kosten_Ladevorgang.state as Number)
        
        ExternGeladen_Arbeit_Ladevorgang_kWh.postUpdate(0)
        ExternGeladen_Arbeit_Ladevorgang_Wh.postUpdate(0)
        ExternGeladen_Kosten_Ladevorgang_Euro.postUpdate(0)
        ExternGeladen_Kosten_Ladevorgang_Cent.postUpdate(0)
        ExternGeladen_EingabeSpeichern.postUpdate(OFF)
end

Hello Tobias,
thanks a lot, might help me also with my adaptions