🪓 Zerlegen von Wert und Einheit, Split, RegEx, "42.0°C" -> "42.0" und "°C"

Begonnen von Torxgewinde, 11 Juni 2023, 11:10:57

Vorheriges Thema - Nächstes Thema

Torxgewinde

Oftmals werden die Readingwerte in FHEM ohne Einheit angegeben, nur selten findet man eine Angabe mit Einheit. Wenn man jedoch den Wert, beispielsweise 42.0°C, in Einheit und Wert aufteilen möchte, gestaltet sich dies nicht so einfach, wie es eigentlich sein sollte. Aus diesem Grund habe ich einen regulären Ausdruck entwickelt, der mit den gängigen Fällen robust umgeht. Sollte eine Einheit nicht bekannt sein oder das Format des Readings nicht dem vorgegebenen Schema entsprechen, wird der Wert unverändert zurückgegeben und die Einheit wird als "xxx" angezeigt.

Dieses Codesnippet kann nach Belieben angepasst werden. Es wäre sicherlich wünschenswert, wenn FHEM zukünftig besser mit Einheiten und Zahlenwerten umgehen könnte, jedoch sind hierbei wahrscheinlich auch einige Herausforderungen zu bewältigen.

Beispiele:
set testdevice wert new york --> Einheit: xxx, Wert: new york
set testdevice wert 42.0 °C --> Einheit: °C, Wert: 42.0
set testdevice wert 42.0°C --> Einheit: °C, Wert: 42.0
set testdevice wert 1234 --> Einheit: xxx, Wert: 1234

Vorerst dachte ich, RegEx Boundaries wären die Antwort, aber die funktionieren nur für einfache Zeichen und versagen schon bei ²,³ oder auch dem Degreesymbol °. Wenn man diese Funktion mit weiteren Einheiten erweitern möchte, ist die mehrzeilige Version des regulären Ausdrucks einfacher zu editieren.
my $val = ReadingsVal("$name", "wert", "???");

my ($value, $unit) = $val =~ /^(-?\d+(?:\.\d+)?)?    #Matche die Zahl, mit oder ohne Punkt als Trenner
    \s*             #Whitespace als Wortgrenze, kann aber auch weg sein
    (?:(mm          #Millimeter
        |m²     #Quadratmeter
        |m³     #Qubicmeter
        |cm     #Zentimeter
        |Watt   #Leistung, Watt
        |VA     #Scheinleistung
        |Volt   #Spannung in Volt
        |V      #Spannung in Volt
        |A      #Strom in Ampere
        |Ampere #Strom in Ampere
        |L      #Liter
        |l      #Liter
        |Liter  #Liter
        |dm³    #Kubikdezimeter
        |%      #Prozent
        |°C     #Temperatur
        |l\/h    #Liter pro Stunde
    ))(?=$) #Lookahead bis Ende vom String
    /x ? ($1, $2) : ($val, "xxx");

return "Einheit: $unit, Wert: $value";

Hier ein Testdevice, um einfach verschiedene Werte ausprobieren zu können:
defmod testdevice dummy
attr testdevice readingList wert
attr testdevice setList wert
attr testdevice userReadings Version2:wert.* {\
my $val = ReadingsVal("$name", "wert", "???");;\
\
#Regex mit Lookahead, möglichst kompakt\
my ($value, $unit) = $val =~ /^(-?\d+(?:\.\d+)?)?\s*(?:(mm|m²|m³|cm|Watt|VA|Volt|V|A|Ampere|L|l|Liter|dm³|%|°C|l\/h))(?=$)/ ? ($1, $2) : ($val, "xxx");;\
return "Einheit: $unit, Wert: $value";;\
},\
Version3:wert.* {\
my $val = ReadingsVal("$name", "wert", "???");;\
\
#Mit Lookahead und Kommentaren, das kommt auch mit Einheiten wie °C, m² und m³ klar\
my ($value, $unit) = $val =~ /^(-?\d+(?:\.\d+)?)? #Matche die Zahl, mit oder ohne Punkt als Trenner\
\s* #Whitespace als Wortgrenze, kann aber auch weg sein\
(?:(mm      #Millimeter\
|m²     #Quadratmeter\
|m³     #Qubicmeter\
|cm     #Zentimeter\
|Watt   #Leistung, Watt\
|VA     #Scheinleistung\
|Volt   #Spannung in Volt\
|V      #Spannung in Volt\
|A      #Strom in Ampere\
|Ampere #Strom in Ampere\
|L      #Liter\
|l      #Liter\
|Liter  #Liter\
|dm³    #Kubikdezimeter\
|%      #Prozent\
|°C     #Temperatur\
|l\/h #Liter pro Stunde\
))(?=$) #Lookahead bis Ende vom String\
/x ? ($1, $2) : ($val, "xxx");;\
\
return "Einheit: $unit, Wert: $value";;\
},\
unit:wert.* {\
my $val = ReadingsVal("$name", "wert", "???");;\
\
#Regex mit Boundaries, das klappt aber nicht für Unicode und Dingen wie °C, m², m³\
#my ($value, $unit) = $val =~ /^(-?\d+(?:\.\d+)?)?\s*(\b(mm|°C|cm|m³|m²|Watt|VA|Volt|V|A|Ampere|L|l|Liter|dm³|%)\b)$/ ? ($1, $2) : ($val, "xxx");;\
\
#Mit Lookahead und Kommentaren, das kommt auch mit Einheiten wie °C, m² und m³ klar\
my ($value, $unit) = $val =~ /^(-?\d+(?:\.\d+)?)? #Matche die Zahl, mit oder ohne Punkt als Trenner\
\s* #Whitespace als Wortgrenze, kann aber auch weg sein\
(?:(mm      #Millimeter\
|m²     #Quadratmeter\
|m³     #Qubicmeter\
|cm     #Zentimeter\
|Watt   #Leistung, Watt\
|VA     #Scheinleistung\
|Volt   #Spannung in Volt\
|V      #Spannung in Volt\
|A      #Strom in Ampere\
|Ampere #Strom in Ampere\
|L      #Liter\
|l      #Liter\
|Liter  #Liter\
|dm³    #Kubikdezimeter\
|%      #Prozent\
|°C     #Temperatur\
|l\/h #Liter pro Stunde\
))(?=$) #Lookahead bis Ende vom String\
/x ? ($1, $2) : ($val, "xxx");;\
\
return "$unit";;\
},\
value:wert.* {\
my $val = ReadingsVal("$name", "wert", "???");;\
\
#Regex mit Boundaries, das klappt aber nicht für Unicode und Dingen wie °C, m², m³\
#my ($value, $unit) = $val =~ /^(-?\d+(?:\.\d+)?)?\s*(\b(mm|°C|cm|m³|m²|Watt|VA|Volt|V|A|Ampere|L|l|Liter|dm³|%)\b)$/ ? ($1, $2) : ($val, "xxx");;\
\
#Mit Lookahead und Kommentaren, das kommt auch mit Einheiten wie °C, m² und m³ klar\
my ($value, $unit) = $val =~ /^(-?\d+(?:\.\d+)?)? #Matche die Zahl, mit oder ohne Punkt als Trenner\
\s* #Whitespace als Wortgrenze, kann aber auch weg sein\
(?:(mm      #Millimeter\
|m²     #Quadratmeter\
|m³     #Qubicmeter\
|cm     #Zentimeter\
|Watt   #Leistung, Watt\
|VA     #Scheinleistung\
|Volt   #Spannung in Volt\
|V      #Spannung in Volt\
|A      #Strom in Ampere\
|Ampere #Strom in Ampere\
|L      #Liter\
|l      #Liter\
|Liter  #Liter\
|dm³    #Kubikdezimeter\
|%      #Prozent\
|°C     #Temperatur\
|l\/h #Liter pro Stunde\
))(?=$) #Lookahead bis Ende vom String\
/x ? ($1, $2) : ($val, "xxx");;\
\
return "$value";;\
}


Du darfst diesen Dateianhang nicht ansehen.

Jamo

Hattest Du Dir auch mal ReadingsNum($name,'wert',0) angeschaut? ReadingsNum liefert den ersten Zahlenwert eines Readings zurück.
Bullseye auf iNUC, Homematic + HMIP(UART/HMUSB), Debmatic, HUEBridge, Zigbee/ConbeeII, FB, Alexa (fhem-lazy), Livetracking, LaCrosse JeeLink, LoRaWan / TTN / Chirpstack

betateilchen

Zitat von: Jamo am 11 Juni 2023, 14:47:30Hattest Du Dir auch mal ReadingsNum($name,'wert',0) angeschaut? ReadingsNum liefert den ersten Zahlenwert eines Readings zurück.

Stimmt zwar grundsätzlich, aber darum geht es ja hier nicht.
Mit ReadingsNum() alleine würdest Du eine Einheit nicht zurückbekommen.

Trotzdem ist der Codeschnipsel nicht wirklich zuverlässiger als bereits existierende Ansätze und er ist schon gar nicht effizient.

Eigentlich sollte jeder Modulautor, der numerische Werte mit Einheiten in events liefert, auch eine passende SplitFn() bereitstellen, die z.B. von DbLog jetzt schon aufgerufen wird, wenn sie für einen Modultype existiert. Die Hoffnung, dass das irgendwann mal verpflichtend wird, hat sich in den vergangenen Jahren leider nicht erfüllt.
-----------------------
Formuliere die Aufgabe möglichst einfach und
setze die Lösung richtig um - dann wird es auch funktionieren.
-----------------------
Lesen gefährdet die Unwissenheit!

betateilchen

Zitat von: Torxgewinde am 11 Juni 2023, 11:10:57Es wäre sicherlich wünschenswert, wenn FHEM zukünftig besser mit Einheiten und Zahlenwerten umgehen könnte, jedoch sind hierbei wahrscheinlich auch einige Herausforderungen zu bewältigen.

Diese Idee ist wahrlich nicht neu.
Nicht ohne Grund gibt es Dateien wie ./FHEM/RTypes.pm und ./FHEM/Unit.pm
-----------------------
Formuliere die Aufgabe möglichst einfach und
setze die Lösung richtig um - dann wird es auch funktionieren.
-----------------------
Lesen gefährdet die Unwissenheit!

Torxgewinde

#4
@Jamo: Natürlich, das ist auch sehr praktisch. Alternativ kann man auch ein RegEx nehmen um die Nummer herauszufiltern. Ich habe es in dem Demodevice ergänzt.
Eine sinnvolle Ergänzung wäre eine Ersatzfunktion, die mit der alten ReadingsNum() kompatibel ist und ein Array mit ($unit, $value) zurückgibt. Um das besser zu verdeutlichen, hier ist das erweiterte Device mit einer möglichen Ersatzfunktion für ReadingsNum(), die abwärtskompatibel zu bestehenden Skripten ist. Perl hat den Vorteil, dass es je nach Kontext automatisch erkennt, ob ein Array oder ein Skalar als Rückgabewert erwartet wird. Dadurch kann die Antwort entsprechend angepasst werden, um in das aufrufende Skript zu passen.

So in der Art also:
# Vorschlag einer neuen Funktion als Ersatz für ReadingsNum, die abwärtskompatibel wäre
sub ReadingsNumReplacement ($$$) {
    my ($name, $reading, $default) = @_;

    my $val = ReadingsVal($name, $reading, $default);
    return undef if (!defined($val));

    # List der Einheiten auf die ein split erfolgen soll
    my ($value, $unit) = $val =~ /^(-?\d+(?:\.\d+)?)+    # Matche die Zahl, mit oder ohne Punkt als Trenner
        \s*             # Whitespace als Wortgrenze, kann aber auch weg sein
                        # Liste der Einheiten:
            (mm         # Millimeter
            |m²         # Quadratmeter
            |m³         # Qubicmeter
            |cm         # Zentimeter
            |Watt       # Leistung, Watt
            |VA         # Scheinleistung
            |Volt       # Spannung in Volt
            |V          # Spannung in Volt
            |A          # Strom in Ampere
            |Ampere     # Strom in Ampere
            |L          # Liter
            |l          # Liter
            |Liter      # Liter
            |dm³        # Kubikdezimeter
            |%          # Prozent
            |°C         # Temperatur
            |℃         # Unicodesymbol-Test
            |l\/h       # Liter pro Stunde
            )
        (?=$)           # Lookahead bis Ende vom String
        /x;

    # Treffer, konnte Unit und Zahlwert finden:
    return ($unit, $value+0) if (defined $value && length $unit);

    # Versuche wenigstens eine Zahl zu finden, bei mehreren Treffern nur den Ersten
    $value = ($val =~ /(-?\d+(?:\.\d+)?)/)[0];

    # Liefere Defaultwert zurück wenn nicht gefunden, sonst Treffer zurückgeben
    # Unit wurde nicht gefunden, deswegen leeren String für Einheit zurückgeben
    return ($value eq "") ? ("", $default) : ("", $value+0);
}

defmod testdevice dummy
attr testdevice readingList wert
attr testdevice setList wert
attr testdevice userReadings Version2:wert.* {\
    my $val = ReadingsVal("$name", "wert", "???");;\
    \
    #Regex mit Lookahead, möglichst kompakt\
    my ($value, $unit) = $val =~ /^(-?\d+(?:\.\d+)?)?\s*(?:(mm|m²|m³|cm|Watt|VA|Volt|V|A|Ampere|L|l|Liter|dm³|%|°C|l\/h))(?=$)/ ? ($1, $2) : ($val, "xxx");;\
    return "Einheit: $unit, Wert: $value";;\
},\
Version3:wert.* {\
    my $val = ReadingsVal("$name", "wert", "???");;\
\
    #Mit Lookahead und Kommentaren, das kommt auch mit Einheiten wie °C, m² und m³ klar\
    my ($value, $unit) = $val =~ /^(-?\d+(?:\.\d+)?)?    #Matche die Zahl, mit oder ohne Punkt als Trenner\
        \s* #Whitespace als Wortgrenze, kann aber auch weg sein\
        (?:(mm      #Millimeter\
            |m²     #Quadratmeter\
            |m³     #Qubicmeter\
            |cm     #Zentimeter\
            |Watt   #Leistung, Watt\
            |VA     #Scheinleistung\
            |Volt   #Spannung in Volt\
            |V      #Spannung in Volt\
            |A      #Strom in Ampere\
            |Ampere #Strom in Ampere\
            |L      #Liter\
            |l      #Liter\
            |Liter  #Liter\
            |dm³    #Kubikdezimeter\
            |%      #Prozent\
            |°C     #Temperatur\
            |l\/h    #Liter pro Stunde\
        ))(?=$) #Lookahead bis Ende vom String\
        /x ? ($1, $2) : ($val, "xxx");;\
    \
    return "Einheit: $unit, Wert: $value";;\
},\
unit:wert.* {\
    my $val = ReadingsVal("$name", "wert", "???");;\
    \
    #Regex mit Boundaries, das klappt aber nicht für Unicode und Dingen wie °C, m², m³\
    #my ($value, $unit) = $val =~ /^(\d+(?:\.\d+)?)?\s*(\b(mm|°C|cm|m³|m²|Watt|VA|Volt|V|A|Ampere|L|l|Liter|dm³|%)\b)$/ ? ($1, $2) : ($val, "xxx");;\
    \
    #Mit Lookahead und Kommentaren, das kommt auch mit Einheiten wie °C, m² und m³ klar\
    my ($value, $unit) = $val =~ /^(-?\d+(?:\.\d+)?)?    #Matche die Zahl, mit oder ohne Punkt als Trenner\
        \s* #Whitespace als Wortgrenze, kann aber auch weg sein\
        (?:(mm      #Millimeter\
            |m²     #Quadratmeter\
            |m³     #Qubicmeter\
            |cm     #Zentimeter\
            |Watt   #Leistung, Watt\
            |VA     #Scheinleistung\
            |Volt   #Spannung in Volt\
            |V      #Spannung in Volt\
            |A      #Strom in Ampere\
            |Ampere #Strom in Ampere\
            |L      #Liter\
            |l      #Liter\
            |Liter  #Liter\
            |dm³    #Kubikdezimeter\
            |%      #Prozent\
            |°C     #Temperatur\
            |l\/h    #Liter pro Stunde\
        ))(?=$) #Lookahead bis Ende vom String\
        /x ? ($1, $2) : ($val, "xxx");;\
    \
    return "$unit";;\
},\
value:wert.* {\
    my $val = ReadingsVal("$name", "wert", "???");;\
    \
    #Regex mit Boundaries, das klappt aber nicht für Unicode und Dingen wie °C, m², m³\
    #my ($value, $unit) = $val =~ /^(\d+(?:\.\d+)?)?\s*(\b(mm|°C|cm|m³|m²|Watt|VA|Volt|V|A|Ampere|L|l|Liter|dm³|%)\b)$/ ? ($1, $2) : ($val, "xxx");;\
    \
    #Mit Lookahead und Kommentaren, das kommt auch mit Einheiten wie °C, m² und m³ klar\
    my ($value, $unit) = $val =~ /^(-?\d+(?:\.\d+)?)?    #Matche die Zahl, mit oder ohne Punkt als Trenner\
        \s* #Whitespace als Wortgrenze, kann aber auch weg sein\
        (?:(mm      #Millimeter\
            |m²     #Quadratmeter\
            |m³     #Qubicmeter\
            |cm     #Zentimeter\
            |Watt   #Leistung, Watt\
            |VA     #Scheinleistung\
            |Volt   #Spannung in Volt\
            |V      #Spannung in Volt\
            |A      #Strom in Ampere\
            |Ampere #Strom in Ampere\
            |L      #Liter\
            |l      #Liter\
            |Liter  #Liter\
            |dm³    #Kubikdezimeter\
            |%      #Prozent\
            |°C     #Temperatur\
            |l\/h    #Liter pro Stunde\
        ))(?=$) #Lookahead bis Ende vom String\
        /x ? ($1, $2) : ($val, "xxx");;\
    \
    return "$value";;\
},\
Zahl_mit_ReadingsNum:wert.* {\
    #Extrahiere die Zahl mit FHEM Funktion\
    my $val = ReadingsNum("$name", "wert", 0);;\
    \
    return $val;;\
},\
Zahl_mit_RegEx:wert.* {\
    my $val = ReadingsVal("$name", "wert", 0);;\
    \
    #Extrahiere die erste Zahl die wir finden\
    my $value = ($val =~ /(-?\d+(?:\.\d+)?)/)[0];;\
    \
    return $value;;\
},\
ErsatzFunktion_fuer_ReadingNum_Demo1:wert.* {\
    no warnings 'redefine';;\
\
# Vorschlag einer neuen Funktion als Ersatz für ReadingsNum, die abwärtskompatibel wäre\
sub ReadingsNumReplacement ($$$) {\
    my ($name, $reading, $default) = @_;;\
\
    my $val = ReadingsVal($name, $reading, $default);;\
    return undef if (!defined($val));;\
\
    # List der Einheiten auf die ein split erfolgen soll\
    my ($value, $unit) = $val =~ /^(-?\d+(?:\.\d+)?)+    # Matche die Zahl, mit oder ohne Punkt als Trenner\
        \s*             # Whitespace als Wortgrenze, kann aber auch weg sein\
                        # Liste der Einheiten:\
            (mm         # Millimeter\
            |m²         # Quadratmeter\
            |m³         # Qubicmeter\
            |cm         # Zentimeter\
            |Watt       # Leistung, Watt\
            |VA         # Scheinleistung\
            |Volt       # Spannung in Volt\
            |V          # Spannung in Volt\
            |A          # Strom in Ampere\
            |Ampere     # Strom in Ampere\
            |L          # Liter\
            |l          # Liter\
            |Liter      # Liter\
            |dm³        # Kubikdezimeter\
            |%          # Prozent\
            |°C         # Temperatur\
            |℃         # Unicodesymbol-Test\
            |l\/h       # Liter pro Stunde\
            )\
        (?=$)           # Lookahead bis Ende vom String\
        /x;;\
\
    # Treffer, konnte Unit und Zahlwert finden:\
    return ($unit, $value+0) if (defined $value && length $unit);;\
\
    # Versuche wenigstens eine Zahl zu finden, bei mehreren Treffern nur den Ersten\
    $value = ($val =~ /(-?\d+(?:\.\d+)?)/)[0];;\
\
    # Liefere Defaultwert zurück wenn nicht gefunden, sonst Treffer zurückgeben\
    # Unit wurde nicht gefunden, deswegen leeren String für Einheit zurückgeben\
    return ($value eq "") ? ("", $default) : ("", $value+0);;\
}\
\
    #Extrahiere die Zahl mit Ersatzfunktion und wie bisher aufgerufen (Zuweisung auf Skalar)\
    my $val_oldway = ReadingsNumReplacement("$name", "wert", 0);;\
    \
    #Extrahiere die Zahl mit Ersatzfunktion und wie bisher aufgerufen (Zuweisung auf Array)\
    my ($unit_newway, $val_newway) = ReadingsNumReplacement("$name", "wert", 0);;\
    \
    return "Bisherige-Methode-nur-Nummer: $val_oldway\nNeuer Vorschlag: ($val_newway, $unit_newway)";;\
}

Hier ein paar Testcase als pures Perl als Ersatz für Unittests/Unit-Tests:
#!/bin/perl

# Vorschlag einer neuen Funktion als Ersatz für ReadingsNum, die abwärtskompatibel wäre
sub ReadingsNumReplacement ($$$) {
    my ($name, $reading, $default) = @_;

    my $val = ReadingsVal($name, $reading, $default);
    return undef if (!defined($val));

    # List der Einheiten auf die ein split erfolgen soll
    my ($value, $unit) = $val =~ /^(-?\d+(?:\.\d+)?)+    # Matche die Zahl, mit oder ohne Punkt als Trenner
        \s*             # Whitespace als Wortgrenze, kann aber auch weg sein
                        # Liste der Einheiten:
            (mm         # Millimeter
            |m²         # Quadratmeter
            |m³         # Qubicmeter
            |cm         # Zentimeter
            |Watt       # Leistung, Watt
            |VA         # Scheinleistung
            |Volt       # Spannung in Volt
            |V          # Spannung in Volt
            |A          # Strom in Ampere
            |Ampere     # Strom in Ampere
            |L          # Liter
            |l          # Liter
            |Liter      # Liter
            |dm³        # Kubikdezimeter
            |%          # Prozent
            |°C         # Temperatur
            |℃         # Unicodesymbol-Test
            |l\/h       # Liter pro Stunde
            )
        (?=$)           # Lookahead bis Ende vom String
        /x;

    # Treffer, konnte Unit und Zahlwert finden:
    return ($unit, $value+0) if (defined $value && length $unit);

    # Versuche wenigstens eine Zahl zu finden, bei mehreren Treffern nur den Ersten
    $value = ($val =~ /(-?\d+(?:\.\d+)?)/)[0];

    # Liefere Defaultwert zurück wenn nicht gefunden, sonst Treffer zurückgeben
# Unit wurde nicht gefunden, deswegen leeren String für Einheit zurückgeben
    return ($value eq "") ? ("", $default) : ("", $value+0);
}

# Define an array of test cases
my @testCases = (
    # Matching unit and value
    { name => "test1", value => "42mm", default => 0, expected_unit => "mm", expected_value => "42" },
    { name => "test2", value => "7.89Volt", default => 0, expected_unit => "Volt", expected_value => "7.89" },
    { name => "test3", value => "-12.34℃", default => 0, expected_unit => "℃", expected_value => "-12.34" },
    { name => "test4", value => "99.99%", default => 0, expected_unit => "%", expected_value => "99.99" },
    { name => "test5", value => "-0V", default => 0, expected_unit => "V", expected_value => "0" },
    { name => "test6", value => "1m²", default => 0, expected_unit => "m²", expected_value => "1" },
    { name => "test7", value => "10l\/h", default => 0, expected_unit => "l\/h", expected_value => "10" },
    { name => "test8", value => "1234VA", default => 0, expected_unit => "VA", expected_value => "1234" },
    { name => "test9", value => "-5Watt", default => 0, expected_unit => "Watt", expected_value => "-5" },
    { name => "test10", value => "2.5m³", default => 0, expected_unit => "m³", expected_value => "2.5" },

    # Matching value only
    { name => "test11", value => "-3.14", default => 0, expected_unit => "", expected_value => "-3.14" },
    { name => "test12", value => "10.5", default => 0, expected_unit => "", expected_value => "10.5" },
    { name => "test13", value => "0", default => 0, expected_unit => "", expected_value => "0" },
    { name => "test14", value => "-7", default => 0, expected_unit => "", expected_value => "-7" },
    { name => "test15", value => "999", default => 0, expected_unit => "", expected_value => "999" },
    { name => "test16", value => "3.14159", default => 0, expected_unit => "", expected_value => "3.14159" },
    { name => "test17", value => "123456789", default => 0, expected_unit => "", expected_value => "123456789" },
    { name => "test18", value => "42.195", default => 0, expected_unit => "", expected_value => "42.195" },
    { name => "test19", value => "-10.0", default => 0, expected_unit => "", expected_value => "-10" },
    { name => "test20", value => "7.777", default => 0, expected_unit => "", expected_value => "7.777" },

    # Returning default value
    { name => "test21", value => "invalid", default => 10, expected_unit => "", expected_value => "10" },
    { name => "test22", value => "ABC", default => 10, expected_unit => "", expected_value => "10" },
    { name => "test23", value => "", default => 10, expected_unit => "", expected_value => "10" },
    { name => "test24", value => "XYZ", default => 10, expected_unit => "", expected_value => "10" },
    { name => "test25", value => "missing", default => 10, expected_unit => "", expected_value => "10" },
    { name => "test26", value => "undefined", default => 10, expected_unit => "", expected_value => "10" },
    { name => "test27", value => "NaN", default => 10, expected_unit => "", expected_value => "10" },
    { name => "test28", value => "NULL", default => 10, expected_unit => "", expected_value => "10" },
    { name => "test29", value => "nil", default => 10, expected_unit => "", expected_value => "10" },
    { name => "test30", value => "none", default => 10, expected_unit => "", expected_value => "10" },
);

sub ReadingsVal($$$) {
        my ($name, $reading, $default) = @_;

        foreach my $test (@testCases) {
            if ($test->{name} eq $name) {
                return $test->{value};
            }
        }

        return $default;
    }

# Iterate through the test cases
foreach my $testCase (@testCases) {
    my $name = $testCase->{name};
    my $value = $testCase->{value};
    my $default = $testCase->{default};
    my $expected_unit = $testCase->{expected_unit};
    my $expected_value = $testCase->{expected_value};

    # Call the ReadingsNumReplacement function
    my ($unit, $result_value) = ReadingsNumReplacement($name, "wert", $default);

    # Compare the retrieved results with the expected results
    if ("$unit" eq "$expected_unit" && "$result_value" eq "$expected_value") {
        print "Test Passed - ReadingsNumReplacement($name, \"wert\", $default): ".ReadingsVal($name, $reading, $default)." --> Expected Unit: $expected_unit ($unit), Value: $expected_value ($result_value)\n";
    } else {
        print "Test Failed - ReadingsNumReplacement($name, \"wert\", $default): ".ReadingsVal($name, $reading, $default)." --> Expected Unit: $expected_unit ($unit), Value: $expected_value ($result_value)\n"
    }
}

Ergebnisse:
Test Passed - ReadingsNumReplacement(test1, "wert", 0): 42mm --> Expected Unit: mm (mm), Value: 42 (42)
Test Passed - ReadingsNumReplacement(test2, "wert", 0): 7.89Volt --> Expected Unit: Volt (Volt), Value: 7.89 (7.89)
Test Passed - ReadingsNumReplacement(test3, "wert", 0): -12.34℃ --> Expected Unit: ℃ (℃), Value: -12.34 (-12.34)
Test Passed - ReadingsNumReplacement(test4, "wert", 0): 99.99% --> Expected Unit: % (%), Value: 99.99 (99.99)
Test Passed - ReadingsNumReplacement(test5, "wert", 0): -0V --> Expected Unit: V (V), Value: 0 (0)
Test Passed - ReadingsNumReplacement(test6, "wert", 0): 1m² --> Expected Unit: m² (m²), Value: 1 (1)
Test Passed - ReadingsNumReplacement(test7, "wert", 0): 10l/h --> Expected Unit: l/h (l/h), Value: 10 (10)
Test Passed - ReadingsNumReplacement(test8, "wert", 0): 1234VA --> Expected Unit: VA (VA), Value: 1234 (1234)
Test Passed - ReadingsNumReplacement(test9, "wert", 0): -5Watt --> Expected Unit: Watt (Watt), Value: -5 (-5)
Test Passed - ReadingsNumReplacement(test10, "wert", 0): 2.5m³ --> Expected Unit: m³ (m³), Value: 2.5 (2.5)
Test Passed - ReadingsNumReplacement(test11, "wert", 0): -3.14 --> Expected Unit:  (), Value: -3.14 (-3.14)
Test Passed - ReadingsNumReplacement(test12, "wert", 0): 10.5 --> Expected Unit:  (), Value: 10.5 (10.5)
Test Passed - ReadingsNumReplacement(test13, "wert", 0): 0 --> Expected Unit:  (), Value: 0 (0)
Test Passed - ReadingsNumReplacement(test14, "wert", 0): -7 --> Expected Unit:  (), Value: -7 (-7)
Test Passed - ReadingsNumReplacement(test15, "wert", 0): 999 --> Expected Unit:  (), Value: 999 (999)
Test Passed - ReadingsNumReplacement(test16, "wert", 0): 3.14159 --> Expected Unit:  (), Value: 3.14159 (3.14159)
Test Passed - ReadingsNumReplacement(test17, "wert", 0): 123456789 --> Expected Unit:  (), Value: 123456789 (123456789)
Test Passed - ReadingsNumReplacement(test18, "wert", 0): 42.195 --> Expected Unit:  (), Value: 42.195 (42.195)
Test Passed - ReadingsNumReplacement(test19, "wert", 0): -10.0 --> Expected Unit:  (), Value: -10 (-10)
Test Passed - ReadingsNumReplacement(test20, "wert", 0): 7.777 --> Expected Unit:  (), Value: 7.777 (7.777)
Test Passed - ReadingsNumReplacement(test21, "wert", 10): invalid --> Expected Unit:  (), Value: 10 (10)
Test Passed - ReadingsNumReplacement(test22, "wert", 10): ABC --> Expected Unit:  (), Value: 10 (10)
Test Passed - ReadingsNumReplacement(test23, "wert", 10):  --> Expected Unit:  (), Value: 10 (10)
Test Passed - ReadingsNumReplacement(test24, "wert", 10): XYZ --> Expected Unit:  (), Value: 10 (10)
Test Passed - ReadingsNumReplacement(test25, "wert", 10): missing --> Expected Unit:  (), Value: 10 (10)
Test Passed - ReadingsNumReplacement(test26, "wert", 10): undefined --> Expected Unit:  (), Value: 10 (10)
Test Passed - ReadingsNumReplacement(test27, "wert", 10): NaN --> Expected Unit:  (), Value: 10 (10)
Test Passed - ReadingsNumReplacement(test28, "wert", 10): NULL --> Expected Unit:  (), Value: 10 (10)
Test Passed - ReadingsNumReplacement(test29, "wert", 10): nil --> Expected Unit:  (), Value: 10 (10)
Test Passed - ReadingsNumReplacement(test30, "wert", 10): none --> Expected Unit:  (), Value: 10 (10)

@betateilchen: Bitte diese Kritik mit Fakten untermauern:
ZitatTrotzdem ist der Codeschnipsel nicht wirklich zuverlässiger als bereits existierende Ansätze und er ist schon gar nicht effizient.