Hauptmenü

Neueste Beiträge

#1
Sonstiges / Aw: regex101 findet 12 Treffer...
Letzter Beitrag von Prof. Dr. Peter Henning - 04 April 2026, 03:45:50
Zitat von: Marko1976 am 03 April 2026, 20:58:31Leider bewirkt auch das herauslöschen der Leerzeichen eine extreme Änderung der Regex - das hatte ich selbst schon mal ausprobiert und völlig andere Ergebnisse bzw. Regex bekommen als mit.
Zitat von: RalfRog am 04 April 2026, 00:25:45Die Frage ist dann doch schon ob das so Sinn macht, da die kleinste Änderung alles durcheinander bringt.
Nein, eben nicht.

Der "Threat"-Ersteller kann es drehen und wenden wie er will, er kann auch noch so oft um Hilfe rufen und andere beleidigen:
Das ist kein Anwendungsfall für Reguläre Ausdrücke
Sondern der klassische Anwendungsfall für XPath.

LG

pah


#2
FHEM Code changes / Revision 31073: 72_FritzSmart....
Letzter Beitrag von System - 04 April 2026, 00:30:17
Revision 31073: 72_FritzSmart.pm: Version 26.04.03

72_FritzSmart.pm: Version 26.04.03

Source: Revision 31073: 72_FritzSmart.pm: Version 26.04.03
#3
FHEM Code changes / Revision 31072: 72_FritzSmart....
Letzter Beitrag von System - 04 April 2026, 00:30:17
Revision 31072: 72_FritzSmart.pm: Version 26.04.03

72_FritzSmart.pm: Version 26.04.03

Source: Revision 31072: 72_FritzSmart.pm: Version 26.04.03
#4
Sonstiges / Aw: regex101 findet 12 Treffer...
Letzter Beitrag von RalfRog - 04 April 2026, 00:25:45
Zitat von: Marko1976 am 03 April 2026, 20:58:31Die Teamnamen und das Serien-Ergebniss habe ich bereits rausbekommen. Wo ich aktuell dran bin sind die Einzelergebnisse

Mit ein bischen spielen gehts wohl - allerdings ist nicht ganz klar zu welchen Readings du genau willst.

Auch hier für mich einfach der Versuch sich mit deinem Ansatz und Hilfe von regex101.com schrittweise zu nähern.
=> Livecode aus https://www.penny-del.org/spiele

Attributes
attr eis reading01Name name
attr eis reading01RegOpt g
attr eis reading01Regex <div class="col-5 text-center">\v +<div class="imgcontainer">\v +<img class="img-fluid poteamlogo" alt.*images.*" \/>\v.*\v +\b(.*)\v.*\v.*\v.*Serie
attr eis reading02Name standing
attr eis reading02RegOpt g
attr eis reading02Regex <h3>(.*)<\/h3>
attr eis reading03Name einzel
attr eis reading03RegOpt g
attr eis reading03Regex <div class="col text-center gameentry">\v.*teamshorts">(\w+:\w+).*\v.*\v +(\d:\d)\v

Readings
einzel-1 SWW:KEC                      2026-04-04 22:08:51
einzel-2 0:1                          2026-04-04 22:08:51
einzel-3 KEC:SWW                      2026-04-04 22:08:51
einzel-4 4:2                          2026-04-04 22:08:51
einzel-5 SWW:KEC                      2026-04-04 22:08:51
einzel-6 2:4                          2026-04-04 22:08:51
einzel-7 KEC:SWW                      2026-04-04 22:08:51
einzel-8 2:1                          2026-04-04 22:08:51
einzel-9 MAN:BHV                      2026-04-04 22:08:51
einzel-10 5:2                         2026-04-04 22:08:51
einzel-11 BHV:MAN                     2026-04-04 22:08:51
einzel-12 4:5                         2026-04-04 22:08:51
einzel-13 MAN:BHV                     2026-04-04 22:08:51
einzel-14 5:1                         2026-04-04 22:08:51
einzel-15 BHV:MAN                     2026-04-04 22:08:51
einzel-16 6:1                         2026-04-04 22:08:51
einzel-17 MAN:BHV                     2026-04-04 22:08:51
einzel-18 4:3                         2026-04-04 22:08:51
einzel-19 STR:EBB                     2026-04-04 22:08:51
einzel-20 5:1                         2026-04-04 22:08:51
einzel-21 EBB:STR                     2026-04-04 22:08:51
einzel-22 2:1                         2026-04-04 22:08:51
einzel-23 STR:EBB                     2026-04-04 22:08:51
einzel-24 2:4                         2026-04-04 22:08:51
einzel-25 EBB:S                       2026-04-04 22:08:51
einzel-26 2:1                         2026-04-04 22:08:51
einzel-27 STR:EBB                     2026-04-04 22:08:51
einzel-28 2:1                         2026-04-04 22:08:51
einzel-29 RBM:ING                     2026-04-04 22:08:51
einzel-30 5:6                         2026-04-04 22:08:51
einzel-31 ING:RBM                     2026-04-04 22:08:51
einzel-32 1:6                         2026-04-04 22:08:51
einzel-33 RBM:ING                     2026-04-04 22:08:51
einzel-34 5:2                         2026-04-04 22:08:51
einzel-35 ING:RBM                     2026-04-04 22:08:51
einzel-36 7:2                         2026-04-04 22:08:51
einzel-37 RBM:ING                     2026-04-04 22:08:51
einzel-38 6:3                         2026-04-04 22:08:51
einzel-39 BHV:NIT                     2026-04-04 22:08:51
einzel-40 4:0                         2026-04-04 22:08:51
einzel-41 NIT:BHV                     2026-04-04 22:08:51
einzel-42 2:3                         2026-04-04 22:08:51
einzel-43 WOB:SWW                     2026-04-04 22:08:51
einzel-44 3:1                         2026-04-04 22:08:51
einzel-45 SWW:WOB                     2026-04-04 22:08:51
einzel-46 5:1                         2026-04-04 22:08:51
einzel-47 WOB:SWW                     2026-04-04 22:08:51
einzel-48 2:3                         2026-04-04 22:08:51
name-1 Kölner Haie                    2026-04-04 22:08:51
name-2 Adler Mannheim                 2026-04-04 22:08:51
name-3 Kölner Haie                    2026-04-04 22:08:51
name-4 Adler Mannheim                 2026-04-04 22:08:51
name-5 Straubing Tigers               2026-04-04 22:08:51
name-6 EHC Red Bull München           2026-04-04 22:08:51
name-7 Pinguins Bremerhaven           2026-04-04 22:08:51
name-8 Grizzlys Wolfsburg             2026-04-04 22:08:51
standing-1 0:0                        2026-04-04 22:08:51
standing-2 0:0                        2026-04-04 22:08:51
standing-3 4:0                        2026-04-04 22:08:51
standing-4 4:1                        2026-04-04 22:08:51
standing-5 2:3                        2026-04-04 22:08:51
standing-6 3:2                        2026-04-04 22:08:51
standing-7 2:0                        2026-04-04 22:08:51
standing-8 1:2                        2026-04-04 22:08:51

Zitat von: Marko1976 am 03 April 2026, 20:58:31...wird erst hinzugefügt wenn die entsprechenden Mannschaften feststehen. Das bedeutet aber leider, dass der Inhalt der jetzt im ersten Reading landet (zur Zeit Kölner Haie, weil 1. Halbfinalbegegnung) stände dann plötzlich im 3. gefundenen Reading...
Die Frage ist dann doch schon ob das so Sinn macht, da die kleinste Änderung alles durcheinander bringt.

Hilfreich in dem Zusammenhang zum Erkennen der Struktur sind die Entwicklertools z.B. Firefox (F12) oder ganz gezielt per Rechtsklick "Untersuchen (Q)"


#5
Bastelecke / Aw: ESP RGBWW Controller - Fir...
Letzter Beitrag von pjakobs - 03 April 2026, 23:38:14
und noch ein Update, ich hab ein paar UI Elemente aufgefrischt (an die Szenen muss ich nochmal ran, die sind noch nix), ein paar Bugs gefixt und - einen integrierten Log Viewer im UI gebaut.

Nun ist das mit den Logs so eine Sache, ich hatte mal versucht, Logs über websocket zu schicken, aber das hat beim ESP8266 immer dazu geführt, dass der heap ruck zuck voll war und der Controller abgestürzt ist. Die rSyslog Lösung funktioniert prima, der Controller "wirft" seine Logzeile einfach zu einer IP und hofft, dass sie ankommt, der Overhead ist minimal.

Nun könntet Ihr alle einen rSyslog server aufsetzen (siehe oben, ist nicht wirklich schwer) aber ich will ja lieber eine integrierte Lösung, und hier ist sie:
Es gibt jetzt einen kleinen Container mit rSyslog und einem http Interface. Wenn Ihr auf die Log Viewer Karte im Lightinator UI geht, dann findet Ihr dort eine Erklärung, wie Ihr den Log Service aktivieren könnt. Dazu braucht Ihr (irgendein) Linux mit Podman oder Docker sowie git, auf dem Ihr das Teil ausführen könnt. Wenn der Container gestartet ist, seht ihr die ip-addresse auf der Konsole, die tragt Ihr im entsprechenden Feld der Log Viewer Karte ein, das konfiguiert den aktuellen Controller so, dass er seine Logs an den Log Service schickt, von wo das Frontend sie abholt. Von dort könnt ihr das Log auch speichern und ggf. versenden.
Ihr könnt den Log Collector einfach laufen lassen und alle Controller loggen dahin, die Daten werden 7 Tage dort vorgehalten und dann gelöscht. So könnt Ihr bei Problemen immer in die Vergangenheit schauen.


Du darfst diesen Dateianhang nicht ansehen.
#6
Sonstige Systeme / Aw: Netatmo Modul - 38_netatmo...
Letzter Beitrag von Dirk070 - 03 April 2026, 23:09:56
Neustart von FHEM, jetzt läuft auch Netatmo wieder.
#7
Sonstige Systeme / Aw: Rademacher DuoFern USB Sti...
Letzter Beitrag von dennisk - 03 April 2026, 22:49:46
Hallo zusammen,

mir ist aufgefallen, dass der Duofernstick bis Perl 5.42.0 einwandfrei funktioniert. Mit Perl 5.42.1 und 5.42.2 funktioniert er leider nicht mehr und kann laut Log nicht initialisiert werden und wird ignoriert. Dabei ist mir außerdem noch aufgefallen, dass wenn der Stick unter Perl 5.42.0 initialisiert wird, die Parameter über stty -F /dev/... -a u.a. eine Baudrate von 115200 anzeigen. Wenn der Stick über 5.42.1 oder 5.42.2 initialisiert wird, dann zeigt der stty-Befehl eine Baudrate von 4800 oder 9600 an, was ja falsch ist. Entscheidend ist: an der Config habe ich nichts geändert, mit 5.42.0 läuft alles, ab 5.42.1 nicht mehr. Also muss es einen Commit in Perl geben, der Auswirkungen auf das DUOFERN-Modul hat bzw. es könnte auch an DevIO liegen, oder?

FHEM ist auf dem aktuellen Stand, läuft auf Arch Linux und einem RPi 5, Linux 6.18.20.

Kann das Verhalten jemand nachvollziehen?
#8
Sonstiges / Aw: Neues Modul - 70_KEBA.pm z...
Letzter Beitrag von msfox - 03 April 2026, 22:12:35
Ich habe mir von der KI (https://claude.ai) ein Modul generieren lassen.
##############################################################################
# $Id: 98_KebaP30.pm $
#
# 98_KebaP30.pm
#
# FHEM module to control a Keba KeContact P30 wallbox via Modbus TCP
#
# Prerequisites:
#   - FHEM ModbusTCP / ModbusAttr support (98_Modbus.pm, 98_ModbusAttr.pm)
#   - Keba P30 with Modbus TCP enabled (DIP switch DSW1.3 = ON)
#   - Network connectivity to the wallbox on TCP port 502
#
# Usage in FHEM:
#   define myKeba KebaP30 <IP-Address> [<Interval>]
#   Example: define myKeba KebaP30 192.168.1.50 30
#
#   set myKeba chargingCurrent 10000   (in mA, e.g. 10000 = 10A)
#   set myKeba enable 1                (1=enable, 0=disable)
#   set myKeba unlock 0                (0=unlock plug)
#   set myKeba setEnergy 5000          (in 0.1 Wh)
#   set myKeba failsafeCurrent 6000    (in mA)
#   set myKeba failsafeTimeout 60      (in seconds)
#   set myKeba failsafePersist 0       (0 or 1)
#
##############################################################################

package main;

use strict;
use warnings;
use IO::Socket::INET;
use DevIo;

# Modbus Function Codes
use constant FC_READ_HOLDING  => 0x03;
use constant FC_WRITE_SINGLE  => 0x06;

# Keba P30 Modbus Unit ID (must be 255)
use constant KEBA_UNIT_ID => 255;

# Modbus TCP Port
use constant KEBA_PORT => 502;

# Default polling interval in seconds
use constant DEFAULT_INTERVAL => 30;

# ============================================================================
#  Register-Definitionen (Keba P30 Modbus TCP Programmers Guide V1.04)
# ============================================================================

# Lesbare Register (Holding Registers, FC3, UINT32 = 2 Words)
my %readRegisters = (
    'chargingState'     => { addr => 1000, len => 2, type => 'uint32', unit => '',    desc => 'Ladezustand (0=Startup,1=NotReady,2=Ready,3=Charging,4=Error,5=Suspended)' },
    'cableState'        => { addr => 1004, len => 2, type => 'uint32', unit => '',    desc => 'Kabelzustand' },
    'errorCode'         => { addr => 1006, len => 2, type => 'uint32', unit => '',    desc => 'Fehlercode' },
    'currentL1'         => { addr => 1008, len => 2, type => 'uint32', unit => 'mA',  desc => 'Ladestrom Phase 1', factor => 1 },
    'currentL2'         => { addr => 1010, len => 2, type => 'uint32', unit => 'mA',  desc => 'Ladestrom Phase 2', factor => 1 },
    'currentL3'         => { addr => 1012, len => 2, type => 'uint32', unit => 'mA',  desc => 'Ladestrom Phase 3', factor => 1 },
    'serialNumber'      => { addr => 1014, len => 2, type => 'uint32', unit => '',    desc => 'Seriennummer' },
    'productType'       => { addr => 1016, len => 2, type => 'uint32', unit => '',    desc => 'Produkttyp und Features' },
    'firmwareVersion'   => { addr => 1018, len => 2, type => 'uint32', unit => '',    desc => 'Firmware-Version' },
    'activePower'       => { addr => 1020, len => 2, type => 'uint32', unit => 'mW',  desc => 'Aktive Leistung', factor => 1 },
    'totalEnergy'       => { addr => 1036, len => 2, type => 'uint32', unit => '0.1Wh', desc => 'Gesamtenergie', factor => 0.1 },
    'voltageL1'         => { addr => 1040, len => 2, type => 'uint32', unit => 'V',   desc => 'Spannung Phase 1', factor => 1 },
    'voltageL2'         => { addr => 1042, len => 2, type => 'uint32', unit => 'V',   desc => 'Spannung Phase 2', factor => 1 },
    'voltageL3'         => { addr => 1044, len => 2, type => 'uint32', unit => 'V',   desc => 'Spannung Phase 3', factor => 1 },
    'powerFactor'       => { addr => 1046, len => 2, type => 'uint32', unit => '0.1%', desc => 'Leistungsfaktor (cos phi)', factor => 0.1 },
    'maxChargingCurrent'=> { addr => 1100, len => 2, type => 'uint32', unit => 'mA',  desc => 'Max. Ladestrom der Station', factor => 1 },
    'maxSupportedCurr'  => { addr => 1110, len => 2, type => 'uint32', unit => 'mA',  desc => 'Max. unterstuetzter Ladestrom', factor => 1 },
    'rfidCard'          => { addr => 1500, len => 2, type => 'uint32', unit => '',    desc => 'RFID Karten UID' },
    'sessionEnergy'     => { addr => 1502, len => 2, type => 'uint32', unit => '0.1Wh', desc => 'Energie aktuelle Session', factor => 0.1 },
);

# Schreibbare Register (FC6, UINT16 = 1 Word)
my %writeRegisters = (
    'chargingCurrent'   => { addr => 5004, len => 1, type => 'uint16', unit => 'mA',  desc => 'Ladestrom setzen (0 oder 6000-63000 mA)', min => 0, max => 63000 },
    'setEnergy'         => { addr => 5010, len => 1, type => 'uint16', unit => '0.1Wh', desc => 'Energielimit setzen (0=kein Limit)', min => 0, max => 65535 },
    'unlock'            => { addr => 5012, len => 1, type => 'uint16', unit => '',    desc => 'Stecker entriegeln (0=unlock)', min => 0, max => 0 },
    'enable'            => { addr => 5014, len => 1, type => 'uint16', unit => '',    desc => 'Station ein/ausschalten (0=aus, 1=ein)', min => 0, max => 1 },
    'failsafeCurrent'   => { addr => 5016, len => 1, type => 'uint16', unit => 'mA',  desc => 'Failsafe Ladestrom (6000-32000 mA)', min => 6000, max => 32000 },
    'failsafeTimeout'   => { addr => 5018, len => 1, type => 'uint16', unit => 's',   desc => 'Failsafe Timeout (0=deaktiviert, 10-600 s)', min => 0, max => 600 },
    'failsafePersist'   => { addr => 5020, len => 1, type => 'uint16', unit => '',    desc => 'Failsafe persistent speichern (0 oder 1)', min => 0, max => 1 },
);

# ============================================================================
#  FHEM Interface Functions
# ============================================================================

sub KebaP30_Initialize($) {
    my ($hash) = @_;

    $hash->{DefFn}      = "KebaP30_Define";
    $hash->{UndefFn}    = "KebaP30_Undef";
    $hash->{SetFn}      = "KebaP30_Set";
    $hash->{GetFn}      = "KebaP30_Get";
    $hash->{AttrFn}     = "KebaP30_Attr";
    $hash->{NotifyFn}   = "KebaP30_Notify";

    $hash->{AttrList} = "disable:0,1 "
                       . "interval "
                       . "timeout "
                       . $readingFnAttributes;

    return undef;
}

# ----------------------------------------------------------------------------
#  Define: define myKeba KebaP30 <IP> [<Interval>]
# ----------------------------------------------------------------------------
sub KebaP30_Define($$) {
    my ($hash, $def) = @_;
    my @args = split("[ \t]+", $def);

    return "Usage: define <name> KebaP30 <IP-Address> [<Interval>]"
        if (@args < 3 || @args > 4);

    my $name     = $args[0];
    my $ip       = $args[2];
    my $interval = (@args > 3) ? int($args[3]) : DEFAULT_INTERVAL;

    $hash->{HOST}       = $ip;
    $hash->{PORT}       = KEBA_PORT;
    $hash->{UNIT_ID}    = KEBA_UNIT_ID;
    $hash->{INTERVAL}   = $interval;
    $hash->{TRANSID}    = 0;
    $hash->{STATE}      = "initialized";

    # Readings initialisieren
    readingsBeginUpdate($hash);
    readingsBulkUpdate($hash, "state", "initialized");
    readingsEndUpdate($hash, 1);

    # Timer fuer zyklisches Polling starten
    RemoveInternalTimer($hash);
    InternalTimer(gettimeofday() + 5, "KebaP30_Poll", $hash, 0);

    Log3 $name, 3, "KebaP30 ($name): defined with host=$ip interval=${interval}s";

    return undef;
}

# ----------------------------------------------------------------------------
#  Undef
# ----------------------------------------------------------------------------
sub KebaP30_Undef($$) {
    my ($hash, $name) = @_;
    RemoveInternalTimer($hash);
    KebaP30_Disconnect($hash);
    return undef;
}

# ----------------------------------------------------------------------------
#  Notify (z.B. bei INITIALIZED)
# ----------------------------------------------------------------------------
sub KebaP30_Notify($$) {
    my ($hash, $devHash) = @_;
    my $name = $hash->{NAME};

    return if (IsDisabled($name));

    my $events = deviceEvents($devHash, 1);
    return if (!$events);

    foreach my $event (@{$events}) {
        if ($event =~ /^INITIALIZED$/ || $event =~ /^REREADCFG$/) {
            KebaP30_Poll($hash);
        }
    }
    return undef;
}

# ----------------------------------------------------------------------------
#  Attr
# ----------------------------------------------------------------------------
sub KebaP30_Attr(@) {
    my ($cmd, $name, $attrName, $attrVal) = @_;
    my $hash = $defs{$name};

    if ($attrName eq "interval") {
        if ($cmd eq "set") {
            return "interval must be a positive integer" unless ($attrVal =~ /^\d+$/ && $attrVal > 0);
            $hash->{INTERVAL} = $attrVal;
        } else {
            $hash->{INTERVAL} = DEFAULT_INTERVAL;
        }
        RemoveInternalTimer($hash);
        InternalTimer(gettimeofday() + $hash->{INTERVAL}, "KebaP30_Poll", $hash, 0);
    }

    if ($attrName eq "disable") {
        if ($cmd eq "set" && $attrVal) {
            RemoveInternalTimer($hash);
            readingsSingleUpdate($hash, "state", "disabled", 1);
        } else {
            readingsSingleUpdate($hash, "state", "initialized", 1);
            InternalTimer(gettimeofday() + 2, "KebaP30_Poll", $hash, 0);
        }
    }

    return undef;
}

# ============================================================================
#  Set-Befehle
# ============================================================================
sub KebaP30_Set($@) {
    my ($hash, @a) = @_;
    my $name = shift @a;
    my $cmd  = shift @a;
    my $val  = shift @a;

    # Hilfe / Liste der Set-Befehle
    my @cmds = sort keys %writeRegisters;
    return "Unknown argument $cmd, choose one of " . join(" ", @cmds)
        if (!defined($writeRegisters{$cmd}));

    return "$cmd requires a numeric value" unless (defined($val) && $val =~ /^\d+$/);

    my $reg = $writeRegisters{$cmd};

    # Wertbereich pruefen (spezielle Behandlung fuer chargingCurrent: 0 oder 6000-63000)
    if ($cmd eq 'chargingCurrent') {
        return "$cmd: value must be 0 or between 6000 and 63000 mA"
            unless ($val == 0 || ($val >= 6000 && $val <= 63000));
    } elsif ($cmd eq 'failsafeTimeout') {
        return "$cmd: value must be 0 or between 10 and 600"
            unless ($val == 0 || ($val >= 10 && $val <= 600));
    } else {
        return "$cmd: value must be between $reg->{min} and $reg->{max}"
            if ($val < $reg->{min} || $val > $reg->{max});
    }

    # Modbus Write
    my $result = KebaP30_ModbusWrite($hash, $reg->{addr}, $val);

    if (defined($result)) {
        readingsSingleUpdate($hash, "last_set_$cmd", $val, 1);
        Log3 $name, 4, "KebaP30 ($name): set $cmd = $val";
        return undef;
    } else {
        return "KebaP30 ($name): Error writing register $reg->{addr} for $cmd";
    }
}

# ============================================================================
#  Get-Befehle
# ============================================================================
sub KebaP30_Get($@) {
    my ($hash, @a) = @_;
    my $name = shift @a;
    my $cmd  = shift @a;

    my @cmds = sort keys %readRegisters;
    push @cmds, "update";

    return "Unknown argument $cmd, choose one of " . join(" ", @cmds)
        if (!grep { $_ eq $cmd } @cmds);

    if ($cmd eq "update") {
        KebaP30_Poll($hash);
        return "Polling all registers...";
    }

    my $reg = $readRegisters{$cmd};
    my $val = KebaP30_ModbusRead($hash, $reg->{addr}, $reg->{len});

    if (defined($val)) {
        my $factor = $reg->{factor} // 1;
        my $display = ($factor != 1) ? sprintf("%.1f", $val * $factor) : $val;
        my $unit = $reg->{unit} || '';
        return "$cmd: $display $unit";
    } else {
        return "Error reading $cmd from register $reg->{addr}";
    }
}

# ============================================================================
#  Polling (Timer-basiert)
# ============================================================================
sub KebaP30_Poll($) {
    my ($hash) = @_;
    my $name = $hash->{NAME};

    RemoveInternalTimer($hash);

    return if (IsDisabled($name));

    Log3 $name, 5, "KebaP30 ($name): polling registers...";

    readingsBeginUpdate($hash);

    my $errorCount = 0;

    foreach my $reading (sort keys %readRegisters) {
        my $reg = $readRegisters{$reading};
        my $val = KebaP30_ModbusRead($hash, $reg->{addr}, $reg->{len});

        if (defined($val)) {
            my $factor = $reg->{factor} // 1;
            my $display = ($factor != 1) ? sprintf("%.1f", $val * $factor) : $val;
            readingsBulkUpdate($hash, $reading, $display);
        } else {
            $errorCount++;
            Log3 $name, 4, "KebaP30 ($name): Error reading $reading (register $reg->{addr})";
        }
    }

    # Abgeleitete Readings
    my $chargingState = ReadingsVal($name, "chargingState", "");
    my %stateMap = (
        0 => "startup",
        1 => "not_ready",
        2 => "ready",
        3 => "charging",
        4 => "error",
        5 => "suspended",
    );
    my $stateText = $stateMap{$chargingState} // "unknown ($chargingState)";
    readingsBulkUpdate($hash, "chargingStateText", $stateText);

    # Leistung in kW
    my $powerMw = ReadingsVal($name, "activePower", 0);
    if ($powerMw =~ /^\d+$/) {
        readingsBulkUpdate($hash, "activePower_kW", sprintf("%.3f", $powerMw / 1000000));
    }

    # Gesamtenergie in kWh
    my $totalE = ReadingsVal($name, "totalEnergy", 0);
    if ($totalE =~ /^[\d.]+$/) {
        readingsBulkUpdate($hash, "totalEnergy_kWh", sprintf("%.3f", $totalE / 1000));
    }

    # Session-Energie in kWh
    my $sessE = ReadingsVal($name, "sessionEnergy", 0);
    if ($sessE =~ /^[\d.]+$/) {
        readingsBulkUpdate($hash, "sessionEnergy_kWh", sprintf("%.3f", $sessE / 1000));
    }

    if ($errorCount == 0) {
        readingsBulkUpdate($hash, "state", "connected");
    } else {
        readingsBulkUpdate($hash, "state", "polling_errors: $errorCount");
    }

    readingsEndUpdate($hash, 1);

    # Naechsten Poll-Zyklus einplanen
    my $interval = $hash->{INTERVAL} || DEFAULT_INTERVAL;
    InternalTimer(gettimeofday() + $interval, "KebaP30_Poll", $hash, 0);

    return undef;
}

# ============================================================================
#  Modbus TCP Kommunikation
# ============================================================================

sub KebaP30_Connect($) {
    my ($hash) = @_;
    my $name = $hash->{NAME};
    my $host = $hash->{HOST};
    my $port = $hash->{PORT};
    my $timeout = AttrVal($name, "timeout", 3);

    my $sock = IO::Socket::INET->new(
        PeerAddr => $host,
        PeerPort => $port,
        Proto    => 'tcp',
        Timeout  => $timeout,
    );

    if (!$sock) {
        Log3 $name, 2, "KebaP30 ($name): Cannot connect to $host:$port - $!";
        return undef;
    }

    $sock->autoflush(1);
    Log3 $name, 4, "KebaP30 ($name): Connected to $host:$port";
    return $sock;
}

sub KebaP30_Disconnect($) {
    my ($hash) = @_;
    if ($hash->{SOCKET}) {
        close($hash->{SOCKET});
        delete $hash->{SOCKET};
    }
}

# Naechste Transaction-ID
sub KebaP30_NextTransId($) {
    my ($hash) = @_;
    $hash->{TRANSID} = ($hash->{TRANSID} + 1) & 0xFFFF;
    return $hash->{TRANSID};
}

# ----------------------------------------------------------------------------
#  Modbus Read (FC3) - Liest ein UINT32 (2 Register) oder UINT16 (1 Register)
# ----------------------------------------------------------------------------
sub KebaP30_ModbusRead($$$) {
    my ($hash, $addr, $len) = @_;
    my $name = $hash->{NAME};

    my $sock = KebaP30_Connect($hash);
    return undef unless $sock;

    my $transId = KebaP30_NextTransId($hash);
    my $unitId  = KEBA_UNIT_ID;

    # Modbus TCP ADU: TransID(2) + ProtocolID(2) + Length(2) + UnitID(1) + FC(1) + Addr(2) + Qty(2)
    my $request = pack("nnnCCnn",
        $transId,           # Transaction ID
        0,                  # Protocol ID (Modbus)
        6,                  # Length (UnitID + FC + Addr + Qty = 6 bytes)
        $unitId,            # Unit ID
        FC_READ_HOLDING,    # Function Code 3
        $addr,              # Starting Register Address
        $len                # Quantity of Registers
    );

    my $written = $sock->send($request);
    if (!$written) {
        Log3 $name, 2, "KebaP30 ($name): Send error for register $addr";
        close($sock);
        return undef;
    }

    # Antwort lesen (max. 256 Bytes, Timeout beachten)
    my $timeout = AttrVal($name, "timeout", 3);
    my $rin = '';
    vec($rin, fileno($sock), 1) = 1;
    my $nfound = select($rin, undef, undef, $timeout);

    if ($nfound <= 0) {
        Log3 $name, 2, "KebaP30 ($name): Timeout reading register $addr";
        close($sock);
        return undef;
    }

    my $response = '';
    $sock->recv($response, 256);
    close($sock);

    if (length($response) < 9) {
        Log3 $name, 2, "KebaP30 ($name): Short response for register $addr (" . length($response) . " bytes)";
        return undef;
    }

    # Antwort parsen
    my ($rTransId, $rProtoId, $rLen, $rUnitId, $rFC, $byteCount) = unpack("nnnCCC", $response);

    # Fehlercode pruefen (FC + 0x80)
    if ($rFC & 0x80) {
        my $exCode = unpack("C", substr($response, 8, 1));
        Log3 $name, 2, "KebaP30 ($name): Modbus exception $exCode for register $addr";
        return undef;
    }

    # Datenbytes extrahieren
    my $data = substr($response, 9);

    if ($len == 2 && length($data) >= 4) {
        # UINT32: Big-Endian, 2 Register = 4 Bytes
        return unpack("N", $data);
    } elsif ($len == 1 && length($data) >= 2) {
        # UINT16: Big-Endian, 1 Register = 2 Bytes
        return unpack("n", $data);
    }

    Log3 $name, 2, "KebaP30 ($name): Unexpected data length for register $addr";
    return undef;
}

# ----------------------------------------------------------------------------
#  Modbus Write Single Register (FC6) - Schreibt UINT16
# ----------------------------------------------------------------------------
sub KebaP30_ModbusWrite($$$) {
    my ($hash, $addr, $value) = @_;
    my $name = $hash->{NAME};

    my $sock = KebaP30_Connect($hash);
    return undef unless $sock;

    my $transId = KebaP30_NextTransId($hash);
    my $unitId  = KEBA_UNIT_ID;

    # Modbus TCP ADU: TransID(2) + ProtocolID(2) + Length(2) + UnitID(1) + FC(1) + Addr(2) + Value(2)
    my $request = pack("nnnCCnn",
        $transId,           # Transaction ID
        0,                  # Protocol ID
        6,                  # Length
        $unitId,            # Unit ID
        FC_WRITE_SINGLE,    # Function Code 6
        $addr,              # Register Address
        $value              # Value
    );

    my $written = $sock->send($request);
    if (!$written) {
        Log3 $name, 2, "KebaP30 ($name): Send error writing register $addr";
        close($sock);
        return undef;
    }

    # Antwort lesen
    my $timeout = AttrVal($name, "timeout", 3);
    my $rin = '';
    vec($rin, fileno($sock), 1) = 1;
    my $nfound = select($rin, undef, undef, $timeout);

    if ($nfound <= 0) {
        Log3 $name, 2, "KebaP30 ($name): Timeout writing register $addr";
        close($sock);
        return undef;
    }

    my $response = '';
    $sock->recv($response, 256);
    close($sock);

    if (length($response) < 12) {
        Log3 $name, 2, "KebaP30 ($name): Short write response for register $addr";
        return undef;
    }

    my ($rTransId, $rProtoId, $rLen, $rUnitId, $rFC) = unpack("nnnCC", $response);

    if ($rFC & 0x80) {
        my $exCode = unpack("C", substr($response, 8, 1));
        Log3 $name, 2, "KebaP30 ($name): Modbus write exception $exCode for register $addr";
        return undef;
    }

    Log3 $name, 4, "KebaP30 ($name): Wrote value $value to register $addr";
    return 1;
}

1;

# ============================================================================
#  Commandref-Dokumentation (fuer FHEM)
# ============================================================================

=pod

=item device
=item summary    Keba KeContact P30 Wallbox via Modbus TCP
=item summary_DE Keba KeContact P30 Wallbox ueber Modbus TCP steuern

=begin html

<a name="KebaP30"></a>
<h3>KebaP30</h3>
<ul>
  FHEM module to monitor and control a Keba KeContact P30 wallbox via Modbus TCP.<br>
  The wallbox must have Modbus TCP enabled (DIP switch DSW1.3 = ON).<br><br>

  <a name="KebaP30define"></a>
  <b>Define</b>
  <ul>
    <code>define &lt;name&gt; KebaP30 &lt;IP-Address&gt; [&lt;Interval&gt;]</code><br><br>
    Example: <code>define myKeba KebaP30 192.168.1.50 30</code><br>
    Interval is the polling interval in seconds (default: 30).
  </ul><br>

  <a name="KebaP30set"></a>
  <b>Set</b>
  <ul>
    <li><b>chargingCurrent</b> &lt;mA&gt; - Set charging current (0 or 6000-63000 mA)</li>
    <li><b>enable</b> &lt;0|1&gt; - Enable (1) or disable (0) charging station</li>
    <li><b>unlock</b> 0 - Unlock the charging plug</li>
    <li><b>setEnergy</b> &lt;0.1Wh&gt; - Set energy limit (0 = no limit)</li>
    <li><b>failsafeCurrent</b> &lt;mA&gt; - Failsafe current (6000-32000 mA)</li>
    <li><b>failsafeTimeout</b> &lt;s&gt; - Failsafe timeout (0=off, 10-600 s)</li>
    <li><b>failsafePersist</b> &lt;0|1&gt; - Persist failsafe settings</li>
  </ul><br>

  <a name="KebaP30get"></a>
  <b>Get</b>
  <ul>
    <li><b>update</b> - Force immediate polling of all registers</li>
    <li><b>chargingState</b> - Current charging state</li>
    <li><b>cableState</b> - Cable connection state</li>
    <li><b>errorCode</b> - Error code</li>
    <li><b>currentL1/L2/L3</b> - Charging current per phase (mA)</li>
    <li><b>voltageL1/L2/L3</b> - Voltage per phase (V)</li>
    <li><b>activePower</b> - Active power (mW)</li>
    <li><b>totalEnergy</b> - Total charged energy</li>
    <li><b>sessionEnergy</b> - Energy of current session</li>
    <li><b>powerFactor</b> - Power factor (cos phi)</li>
    <li><b>serialNumber</b> - Serial number</li>
    <li><b>firmwareVersion</b> - Firmware version</li>
    <li>... and more (see register definitions)</li>
  </ul><br>

  <a name="KebaP30readings"></a>
  <b>Readings</b>
  <ul>
    All readable registers are available as readings. Additional computed readings:
    <li><b>chargingStateText</b> - Human-readable charging state</li>
    <li><b>activePower_kW</b> - Active power in kW</li>
    <li><b>totalEnergy_kWh</b> - Total energy in kWh</li>
    <li><b>sessionEnergy_kWh</b> - Session energy in kWh</li>
  </ul><br>

  <a name="KebaP30attr"></a>
  <b>Attributes</b>
  <ul>
    <li><b>interval</b> - Polling interval in seconds (default: 30)</li>
    <li><b>timeout</b> - Connection timeout in seconds (default: 3)</li>
    <li><b>disable</b> - Disable the device (0|1)</li>
  </ul>
</ul>

=end html

=cut


Die Doku dazu:
ZitatInstallation & Nutzung:
Die Datei als 98_KebaP30.pm ins FHEM-Modulverzeichnis (üblicherweise /opt/fhem/FHEM/) legen, dann in FHEM:
reload 98_KebaP30.pm
define myKeba KebaP30 192.168.1.50 30
Einige Hinweise zur Keba P30:

Der TCP-Port 502 ist reserviert und die Unit ID muss auf 255 gesetzt sein Wallbox Center – das ist im Modul bereits korrekt konfiguriert.
Modbus TCP wird über den DIP-Schalter DSW1.3 auf der Platine aktiviert TapHome, danach muss die Wallbox neu gestartet werden.
Es ist nicht möglich, mehrere Register gleichzeitig zu lesen – die maximale Leselänge beträgt 2 Words Wallbox Center, weshalb das Modul jedes Register einzeln abfragt.
Das empfohlene Timing-Intervall zum Lesen beträgt mindestens 0,5 Sekunden Wallbox Center – das Modul hält dies durch sequenzielles Lesen automatisch ein.

Mein erster Test damit:
- Es verbindet sich mit meiner Wallbox und liest auch Werte aus.
- Ich konnte den Ladestrom auf 6A setzen.

Bevor ich jetzte weiter teste, kann mal jemand über das Coding schauen, ob das überhaupt Sinn macht?
Ich traue den KI's noch nicht. Zumindest in ABAP (SAP Coding) kommt da z.T. nur Mist raus. Hier scheint es aber funktioniert zu haben.
#9
Sonstige Systeme / Aw: Netatmo Modul - 38_netatmo...
Letzter Beitrag von tomcat.x - 03 April 2026, 22:02:55
Bei mir sieht noch alles gut aus.
#10
Sprachsteuerung / Aw: (WIP) FHEMWEB interaktiv (...
Letzter Beitrag von schwatter - 03 April 2026, 21:46:11
Ok,
danke. Dein JS habe ich kurz reingeschaut. Du hast einfach den Freischaltprocess ausgeschaltet um direkt Google SpeechToText zu starten.
Ja Chromium, da werde ich nochmal in Ruhe schauen warum der Button nicht auftaucht.
5 Minuten Suche im Internet hat mir aber offenbart, genau so schlecht wie bei Firefox.
Quasi wie ein ASOP Rom für ein Androidhandy. Der is blank. Keys kann man selber einbauen aber....ist mist. Fully z.B greift da auf Android Systemdienste zu.

Gruß schwatter