Neues Modul - 70_KEBA.pm zur Steuerung Keba KeContect P20

Begonnen von marcus42, 29 November 2015, 12:38:12

Vorheriges Thema - Nächstes Thema

BirneDe

Hallo zusammen,
ich nutze ebenfalls das Modul 70_KEBA.pm für meine KEBA KC-P30 Wallbox.
Auch mich stört, dass das Modul das Logfile (fhem.log) übermäßig füllt.
Wie können die Reports (1-3) deaktiviert, auf Report 1 beschränkt oder die Häufigkeit (aktuell jede Minute) auf ein größeres Intervall verändert werden?

Schon mal vielen Dank für hilfreiche Antworten.

Herzliche Grüße, Mario

BirneDe

Hallo zusammen,
die Häufigkeit der Reports wird bei der Definition der KEBA festgelegt.
define KEBA KEBA 192.168.xxx.xxx 7090 600
Der letzte Wert ist das Abfrageintervall.

Beste Grüße, Mario

NewMatic

Hallo,

ich bin kurz vor dem Kauf einer Keba P30 c serie.
Diese sollte auch mit diesem Modul steuerbar sein? Hat hier jemand zufällig die P30 im Einsatz?


Danke,
Tobi

msfox

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.

rudolfkoenig

Ich habe es mal ueberflogen.
Achtung: von dem verwendeten Protokoll habe ich keine Ahnung.

Zusammengefasst: das Modul funkioniert vermutlich im Normalfall.
Wenn das Geraet nicht erreichbar ist, dann wird FHEM fuer 19*3 Sekunden blockiert, nach 30 Sekunden dann wieder, usw.

Im Einzelnen:
- den Sinn der Funktion KebaP30_Notify habe ich nicht verstanden, ich wuerde es entfernen, samt den Hinweis darauf in KebaP30_Initialize
- KebaP30_Disconnect ist sicher sinnlos: $hash->{SOCKET} wird nie gesetzt.
- fuer die Abfrage wird IO::Socket::INET verwendet, was FHEM blockieren kann. Richtig waere DevIO_OpenDev im Define und ein ReadyFn, um bei Abbruch die Verbindung neu aufzumachen.
- die Verbindung wird fuer jeden Lese- oder Schreibvorgang neu aufgemacht, das sollte man cachen, wenn man schon kein DevIO_OpenDev haben will. Dann koennte auch KebaP30_Disconnect was Sinnvolles tun.
- der Hinweis auf "FHEM ModbusTCP" ist sinnlos, genauso wie das "use DevIo", beides wird nicht verwendet

Interessant zu sehen, dass:
- manchmal readingsBeginUpdate+BulkUpdate+EndUpdate verwendet wird, manchmal das einfacher lesbare readingsSingleUpdate.
- Variablen in $hash/%readRegisters gesetzt werden (interval , UNIT_ID, factor), um spaeter im Code doch die Konstanten zu verwenden.
- es dokumentiert jeden einzelnen Register in $readRegisters->desc, um es dann nicht zu verwenden.

msfox

Erst einmal VIELEN DANK für das Analysieren. Wie gesagt, ich kenne mich zwar in ABAP, Java aus, aber bei FHEM-Modulen hört es dann auch. Trotzdem brauche ich aber eine Funktion, für die Wallbox.
Das grobe Gerüst habe ich ja nun und werden versuchen, deine Anmerkungen einzupflegen - möglichst ohne die KI.
Einfacher wäre natürlich, wenn der TE oder wer auch immer den Code für das Modul hier aus dem Thread noch hätte.
--
Ich war ja nun hier wirklich Unerfahrener. Auf Kurz oder Lang soll aber die KI Coding übernehmen und die Firmen verzichten schon auf Juniorentwickler. In ABAP kann ich den Mist der KI kontrollieren und wie ich sehen, hat es auch hier nicht 100% geklappt, dass die KI was vernünftiges liefert. Was wird erst, wenn das keine mehr kontrolliert :(.


msfox

Da das mit der KI nicht so 100% funktioniert, habe ich mal das Module 98_Pluggit.pm genommen. Dies scheint ja auf den Beispielen https://wiki.fhem.de/wiki/Modbus zu basieren. Damit wäre es einheitlich zu anderen Modulen und nicht irgendwas KI-generiertes.
D.h. wenn ich mein 98_KebaP30.pm auf die Logik gemäß Anleitung umstelle, sollte ich ich die genannten Problem nicht haben?
Das Coding sieht auch deutlich kürzer aus, als das von der KI.
Der Aufbau der Verbindung zur Wallbox läuft dann sicher analog.
--
Entscheidend ist am Ende also das Mapping:
Beispiel
my %XYparseInfo = (
    "h256"  =>  {   reading => "Temp_Wasser_Ein",   # name of the reading for this value
                },
    "h258"  =>  {   reading => "Temp_Wasser_Aus",
                },

KI-Code
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' },
Code vom PlugIt-Modul
my %PluggitparseInfo = (
    "h472" =>  {  reading => "CurrentUnitMode",        # name of the reading for this value
                    name    => "prmCurrentBLState",               # internal name of this register in the hardware doc
                    min     => 0,                  # input validation for set: min value
                    max     => 4,                  # input validation for set: max value
                    len     => 2,


Wofür ist denn das "h472","h256", "h258"? Das sind noch nicht die Namen von der Hardware?
Bei Pluggit-Modul ist Name = "prmCurrentBLState", Muss ich bei der Keba dafür "1000" bzw. "1004" nehmen? Also ist das die Adresse vom Modbus?

rudolfkoenig

ZitatD.h. wenn ich mein 98_KebaP30.pm auf die Logik gemäß Anleitung umstelle, sollte ich ich die genannten Problem nicht haben?
Ich kenne das Modbus Modul nicht, aber ein erster Blick zeigt, dass es die "richtigen" Funktionen verwendet.
Weiterhin wird das Modbus Modul in FHEM Umfeld haeufig verwendet, das waere auch ein Argument dafuer.

Die Zeile
    require "$attr{global}{modpath}/FHEM/DevIo.pm";
ist im 98_Pluggit.pm ueberfluessig.

msfox

ZitatDie Zeile...ist im 98_Pluggit.pm ueberfluessig.
Das steht so im Wiki (https://wiki.fhem.de/wiki/Modbus)
Das war nur als Beispiel. Das Modul nutze ich nur...
--
Ich muss zugeben, ich habe mir die neue .pm-Datei auch wieder via KI generieren lassen. Der KI einfach die Anweisung gegeben, das Coding gemäß fhem-Wiki (URL) zu erstellen.
Ergebnis, was bei mir aktuell auch läuft:

(Irgendwie finde ich nicht, wie man Dateien hier im Forum hochladen kann)
##############################################################################
# $Id: 98_KebaP30.pm $
#
# 98_KebaP30.pm
#
# FHEM module to control a Keba KeContact P30 wallbox via Modbus TCP.
# Based on the 98_Modbus.pm library (parseInfo / deviceInfo structure).
#
# Prerequisites:
#   - 98_Modbus.pm must be present in the FHEM installation
#   - Keba P30 with Modbus TCP enabled (DIP switch DSW1.3 = ON)
#   - Network connectivity to the wallbox on TCP port 502
#
# Define (Modbus TCP):
#   define <name> KebaP30 <Interval> <IP>:502 TCP
#
# Example:
#   define myKeba KebaP30 30 192.168.1.50:502 TCP
#
# Notes:
#   - Keba P30 requires Unit ID 255 (set via attr <name> dev-h-defPoll 1)
#   - Supported Modbus function codes: FC3 (Read) and FC6 (Write)
#   - Registers cannot be read in bulk; combine is set to 1
#   - Recommended polling interval >= 0.5s per register
#
# Register reference:
#   Keba KeContact P30 Modbus TCP Programmers Guide V1.04
#
##############################################################################

package main;

use strict;
use warnings;

# ============================================================================
#  parseInfo: Mapping Modbus-Register -> FHEM Readings
#
#  Keba P30 uses Holding Registers (FC3 read, FC6 write)
#  - Readable registers: address 1000-1502, UINT32 (len=2)
#  - Writable registers: address 5004-5020, UINT16 (len=1)
#  - Unit ID must be 255
#  - Register address count starts at 0
# ============================================================================

my %KebaP30parseInfo = (

    # -----------------------------------------------------------------------
    #  Lesbare Register (UINT32, 2 Register = 4 Bytes, Big Endian)
    # -----------------------------------------------------------------------

    # 1000 - Charging state
    "h1000" => {
        reading     => "chargingState",
        name        => "State",
        len         => 2,
        unpack      => "N",             # UINT32 Big Endian
        poll        => 1,
        showget     => 1,
        map         => "0:startup, 1:not_ready, 2:ready, 3:charging, 4:error, 5:suspended",
    },

    # 1004 - Cable state
    "h1004" => {
        reading     => "cableState",
        name        => "Cable",
        len         => 2,
        unpack      => "N",
        poll        => 1,
        showget     => 1,
        map         => "0:no_cable, 1:cable_no_car, 3:cable_locked_no_car, 5:cable_locked_car, 7:cable_locked_car_ok",
    },

    # 1006 - Error code
    "h1006" => {
        reading     => "errorCode",
        name        => "Error",
        len         => 2,
        unpack      => "N",
        poll        => 1,
        showget     => 1,
    },

    # 1008 - Charging current phase 1 (mA)
    "h1008" => {
        reading     => "currentL1",
        name        => "CurrL1",
        len         => 2,
        unpack      => "N",
        expr        => '$val / 1000',       # mA -> A
        format      => '%.3f',
        poll        => 1,
        showget     => 1,
    },

    # 1010 - Charging current phase 2 (mA)
    "h1010" => {
        reading     => "currentL2",
        name        => "CurrL2",
        len         => 2,
        unpack      => "N",
        expr        => '$val / 1000',
        format      => '%.3f',
        poll        => 1,
        showget     => 1,
    },

    # 1012 - Charging current phase 3 (mA)
    "h1012" => {
        reading     => "currentL3",
        name        => "CurrL3",
        len         => 2,
        unpack      => "N",
        expr        => '$val / 1000',
        format      => '%.3f',
        poll        => 1,
        showget     => 1,
    },

    # 1014 - Serial number
    "h1014" => {
        reading     => "serialNumber",
        name        => "Serial",
        len         => 2,
        unpack      => "N",
        poll        => "once",
        showget     => 1,
    },

    # 1016 - Product type and features
    "h1016" => {
        reading     => "productType",
        name        => "ProdType",
        len         => 2,
        unpack      => "N",
        poll        => "once",
        showget     => 1,
    },

    # 1018 - Firmware version (decimal, convert to hex to read)
    "h1018" => {
        reading     => "firmwareVersion",
        name        => "FWVersion",
        len         => 2,
        unpack      => "N",
        expr        => 'sprintf("%x", $val)',   # Dezimal -> Hex-String
        poll        => "once",
        showget     => 1,
    },

    # 1020 - Active power (mW)
    "h1020" => {
        reading     => "activePower",
        name        => "Power",
        len         => 2,
        unpack      => "N",
        expr        => '$val / 1000000',        # mW -> kW
        format      => '%.3f',
        poll        => 1,
        showget     => 1,
    },

    # 1036 - Total energy (0.1 Wh)
    "h1036" => {
        reading     => "totalEnergy",
        name        => "TotalEnergy",
        len         => 2,
        unpack      => "N",
        expr        => '$val / 10000',           # 0.1Wh -> kWh
        format      => '%.3f',
        poll        => 1,
        showget     => 1,
    },

    # 1040 - Voltage phase 1 (V)
    "h1040" => {
        reading     => "voltageL1",
        name        => "VoltL1",
        len         => 2,
        unpack      => "N",
        poll        => 1,
        showget     => 1,
    },

    # 1042 - Voltage phase 2 (V)
    "h1042" => {
        reading     => "voltageL2",
        name        => "VoltL2",
        len         => 2,
        unpack      => "N",
        poll        => 1,
        showget     => 1,
    },

    # 1044 - Voltage phase 3 (V)
    "h1044" => {
        reading     => "voltageL3",
        name        => "VoltL3",
        len         => 2,
        unpack      => "N",
        poll        => 1,
        showget     => 1,
    },

    # 1046 - Power factor (cos phi, in 0.1%)
    "h1046" => {
        reading     => "powerFactor",
        name        => "CosPhi",
        len         => 2,
        unpack      => "N",
        expr        => '$val / 10',             # 0.1% -> %
        format      => '%.1f',
        poll        => 1,
        showget     => 1,
    },

    # 1100 - Max charging current of the station (mA)
    "h1100" => {
        reading     => "maxChargingCurrent",
        name        => "MaxCurr",
        len         => 2,
        unpack      => "N",
        expr        => '$val / 1000',           # mA -> A
        format      => '%.1f',
        poll        => "once",
        showget     => 1,
    },

    # 1110 - Max supported current (mA)
    "h1110" => {
        reading     => "maxSupportedCurrent",
        name        => "MaxSupCurr",
        len         => 2,
        unpack      => "N",
        expr        => '$val / 1000',
        format      => '%.1f',
        poll        => "once",
        showget     => 1,
    },

    # 1500 - RFID card UID
    "h1500" => {
        reading     => "rfidCard",
        name        => "RFID",
        len         => 2,
        unpack      => "N",
        expr        => 'sprintf("%08X", $val)',  # als Hex-String
        poll        => 1,
        showget     => 1,
    },

    # 1502 - Session energy (0.1 Wh)
    "h1502" => {
        reading     => "sessionEnergy",
        name        => "SessEnergy",
        len         => 2,
        unpack      => "N",
        expr        => '$val / 10000',           # 0.1Wh -> kWh
        format      => '%.3f',
        poll        => 1,
        showget     => 1,
    },

    # -----------------------------------------------------------------------
    #  Schreibbare Register (UINT16, 1 Register, FC6)
    # -----------------------------------------------------------------------

    # 5004 - Set charging current (mA, 0 or 6000-63000)
    "h5004" => {
        reading     => "setChargingCurrent",
        name        => "SetCurr",
        len         => 1,
        unpack      => "n",             # UINT16 Big Endian
        set         => 1,
        min         => 0,
        max         => 63000,
        hint        => "0,6000,8000,10000,13000,16000,20000,25000,32000",
        poll        => 0,
    },

    # 5010 - Set energy limit (0.1 Wh, 0 = no limit)
    "h5010" => {
        reading     => "setEnergy",
        name        => "SetEnergy",
        len         => 1,
        unpack      => "n",
        set         => 1,
        min         => 0,
        max         => 65535,
        poll        => 0,
    },

    # 5012 - Unlock plug (0 = unlock)
    "h5012" => {
        reading     => "unlock",
        name        => "Unlock",
        len         => 1,
        unpack      => "n",
        set         => 1,
        min         => 0,
        max         => 0,
        hint        => "0:unlock",
        poll        => 0,
    },

    # 5014 - Enable/Disable station (0 = disable, 1 = enable)
    "h5014" => {
        reading     => "enableStation",
        name        => "Enable",
        len         => 1,
        unpack      => "n",
        set         => 1,
        min         => 0,
        max         => 1,
        map         => "0:disable, 1:enable",
        poll        => 0,
    },

    # 5016 - Failsafe current (mA, 6000-32000)
    "h5016" => {
        reading     => "failsafeCurrent",
        name        => "FsCurr",
        len         => 1,
        unpack      => "n",
        set         => 1,
        min         => 6000,
        max         => 32000,
        poll        => 0,
    },

    # 5018 - Failsafe timeout (s, 0 = off, 10-600)
    "h5018" => {
        reading     => "failsafeTimeout",
        name        => "FsTimeout",
        len         => 1,
        unpack      => "n",
        set         => 1,
        min         => 0,
        max         => 600,
        poll        => 0,
    },

    # 5020 - Failsafe persist (0 or 1)
    "h5020" => {
        reading     => "failsafePersist",
        name        => "FsPersist",
        len         => 1,
        unpack      => "n",
        set         => 1,
        min         => 0,
        max         => 1,
        map         => "0:no, 1:yes",
        poll        => 0,
    },

    # 5050 - Phase switch toggle (0 = 1-phase, 1 = 3-phase)
    #        Requires external contactor (e.g. Keba S10) on X2 contact.
    #        Only available on x-series / firmware supporting V1.04+.
    "h5050" => {
        reading     => "phaseSwitchToggle",
        name        => "PhaseToggle",
        len         => 1,
        unpack      => "n",
        set         => 1,
        min         => 0,
        max         => 1,
        map         => "0:1-phase, 1:3-phase",
        poll        => 0,
    },

    # 5052 - Trigger phase switch
    #        Writing 1 triggers the actual phase switch.
    #        Charging must be stopped before switching.
    #        Requires external contactor on X2 contact.
    "h5052" => {
        reading     => "phaseSwitchTrigger",
        name        => "PhaseTrigger",
        len         => 1,
        unpack      => "n",
        set         => 1,
        min         => 1,
        max         => 1,
        hint        => "1:trigger",
        poll        => 0,
    },
);

# ============================================================================
#  deviceInfo: Geraete-Defaults und Timing
#
#  Keba P30 Besonderheiten:
#  - Unit ID = 255
#  - Nur Holding Registers (FC3 lesen, FC6 schreiben)
#  - Kein Bulk-Read moeglich (combine = 1)
#  - Min. 0.5s zwischen Lesevorgaengen
# ============================================================================

my %KebaP30deviceInfo = (
    "timing" => {
        timeout     => 3,           # 3 Sekunden Timeout
        commDelay   => 0.7,         # 0.7s Pause zwischen Kommunikation
        sendDelay   => 0.7,         # 0.7s Pause zwischen Sende-Vorgaengen
    },
    "h" => {                        # Holding Registers
        read        => 3,           # FC3 (Read Holding Registers)
        write       => 6,           # FC6 (Write Single Register)
        combine     => 1,           # Kein Bulk-Read, nur einzeln!
        defLen      => 2,           # Default: UINT32 = 2 Register
        defUnpack   => "N",         # Default: UINT32 Big Endian unsigned
        defPoll     => 1,           # Default: alle Register pollen
        defShowGet  => 1,           # Default: Get in FHEMWEB anzeigen
    },
);

# ============================================================================
#  Initialize: Modul bei FHEM registrieren
# ============================================================================

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

    # Modbus-Basismodul laden
    LoadModule "Modbus";
    require "$attr{global}{modpath}/FHEM/DevIo.pm";

    # parseInfo und deviceInfo zuweisen
    $modHash->{parseInfo}  = \%KebaP30parseInfo;
    $modHash->{deviceInfo} = \%KebaP30deviceInfo;

    # Generische Modbus-Initialisierung (setzt DefFn, SetFn, GetFn, etc.)
    ModbusLD_Initialize($modHash);

    # Attribute: Standard + ObjAttr (pro Register) + DevAttr (Geraete-Defaults)
    $modHash->{AttrList} = $modHash->{AttrList} . " " .
        $modHash->{ObjAttrList} . " " .
        $modHash->{DevAttrList} . " " .
        "disable:0,1 ";
}

1;

# ============================================================================
#  Commandref-Dokumentation
# ============================================================================

=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 id="KebaP30"></a>
<h3>KebaP30</h3>
<ul>
  FHEM module to monitor and control a Keba KeContact P30 wallbox
  via Modbus TCP. Uses the 98_Modbus.pm base module as a library.<br><br>

  The wallbox must have Modbus TCP enabled (DIP switch DSW1.3 = ON).<br>
  The Keba P30 requires Modbus Unit ID 255 and uses TCP port 502.<br><br>

  <a name="KebaP30define"></a>
  <b>Define</b>
  <ul>
    <code>define &lt;name&gt; KebaP30 255 &lt;Interval&gt; &lt;IP&gt;:502 TCP</code><br><br>
    <ul>
      <li>255 = Modbus Unit ID (required by Keba P30)</li>
      <li>Interval = polling interval in seconds (e.g. 30)</li>
      <li>IP:502 = IP address and Modbus TCP port</li>
      <li>TCP = Modbus TCP mode</li>
    </ul><br>
    Example:<br>
    <code>define myKeba KebaP30 255 30 192.168.1.50:502 TCP</code><br>
  </ul><br>

  <a name="KebaP30set"></a>
  <b>Set</b>
  <ul>
    <li><b>setChargingCurrent</b> &lt;mA&gt; - Charging current
        (0=stop or 6000-63000 mA)</li>
    <li><b>enableStation</b> enable|disable - Enable or disable station</li>
    <li><b>unlock</b> unlock - Unlock the charging plug</li>
    <li><b>setEnergy</b> &lt;0.1Wh&gt; - Energy limit (0=no limit)</li>
    <li><b>failsafeCurrent</b> &lt;mA&gt; - Failsafe current (6000-32000)</li>
    <li><b>failsafeTimeout</b> &lt;s&gt; - Failsafe timeout (0=off, 10-600)</li>
    <li><b>failsafePersist</b> no|yes - Persist failsafe settings</li>
    <li><b>phaseSwitchToggle</b> 1-phase|3-phase - Select phase mode
        (requires external contactor on X2)</li>
    <li><b>phaseSwitchTrigger</b> trigger - Execute the phase switch
        (charging must be stopped first)</li>
  </ul><br>

  <a name="KebaP30readings"></a>
  <b>Readings (active during polling)</b>
  <ul>
    <li><b>chargingState</b> - Charging state (startup/not_ready/ready/
        charging/error/suspended)</li>
    <li><b>cableState</b> - Cable connection state</li>
    <li><b>errorCode</b> - Error code (0 = no error)</li>
    <li><b>currentL1/L2/L3</b> - Charging current per phase (A)</li>
    <li><b>voltageL1/L2/L3</b> - Voltage per phase (V)</li>
    <li><b>activePower</b> - Active power (kW)</li>
    <li><b>totalEnergy</b> - Total charged energy (kWh)</li>
    <li><b>sessionEnergy</b> - Energy of current session (kWh)</li>
    <li><b>powerFactor</b> - Power factor / cos phi (%)</li>
    <li><b>maxChargingCurrent</b> - Max charging current (A)</li>
    <li><b>maxSupportedCurrent</b> - Max supported current (A)</li>
    <li><b>serialNumber</b> - Serial number (polled once)</li>
    <li><b>productType</b> - Product type (polled once)</li>
    <li><b>firmwareVersion</b> - Firmware version as hex (polled once)</li>
    <li><b>rfidCard</b> - Last RFID card UID</li>
  </ul><br>

  <a name="KebaP30attr"></a>
  <b>Attributes</b>
  <ul>
    Standard attributes from the Modbus base module are available.<br>
    Per-register overrides are possible via obj-hXXXX-* attributes.<br>
    Device defaults can be modified via dev-* attributes.<br><br>
    <li><b>disable</b> 0|1 - Disable the device</li>
  </ul><br>

  <b>Notes</b>
  <ul>
    <li>The Keba P30 does not support reading multiple registers at once
        (combine is set to 1).</li>
    <li>Static values (serial, firmware, product type, max currents) are
        polled only once to reduce bus load.</li>
    <li>Voltages may read 0 when no vehicle is connected/charging.</li>
    <li>The minimum charging current is 6A (6000 mA). Below 6A the Keba
        automatically switches from 3-phase to 1-phase.</li>
    <li>To stop charging, set setChargingCurrent to 0.</li>
  </ul>
</ul>

=end html

=begin html_DE

<a id="KebaP30"></a>
<h3>KebaP30</h3>
<ul>
  FHEM-Modul zur Ueberwachung und Steuerung einer Keba KeContact P30
  Wallbox ueber Modbus TCP. Nutzt das 98_Modbus.pm Basismodul.<br><br>

  Voraussetzungen:<br>
  <ul>
    <li>Modbus TCP an der Wallbox aktiviert (DIP-Schalter DSW1.3 = ON)</li>
    <li>Netzwerkverbindung zur Wallbox auf TCP Port 502</li>
  </ul><br>

  <a name="KebaP30define"></a>
  <b>Define</b>
  <ul>
    <code>define &lt;name&gt; KebaP30 255 &lt;Interval&gt; &lt;IP&gt;:502 TCP</code><br><br>
    Beispiel:<br>
    <code>define myKeba KebaP30 255 30 192.168.1.50:502 TCP</code><br>
  </ul><br>

  <a name="KebaP30set"></a>
  <b>Set-Befehle</b>
  <ul>
    <li><b>setChargingCurrent</b> - Ladestrom in mA (0 oder 6000-63000)</li>
    <li><b>enableStation</b> - Ladestation ein-/ausschalten</li>
    <li><b>unlock</b> - Ladestecker entriegeln</li>
    <li><b>setEnergy</b> - Energielimit in 0.1 Wh (0 = kein Limit)</li>
    <li><b>failsafeCurrent</b> - Failsafe-Strom in mA</li>
    <li><b>failsafeTimeout</b> - Failsafe-Timeout in Sekunden</li>
    <li><b>failsafePersist</b> - Failsafe-Einstellungen persistent speichern</li>
    <li><b>phaseSwitchToggle</b> - Phasenmodus waehlen: 1-phasig oder 3-phasig
        (benoetigt externen Schuetz am X2-Kontakt)</li>
    <li><b>phaseSwitchTrigger</b> - Phasenumschaltung ausfuehren
        (Ladevorgang muss vorher gestoppt sein)</li>
  </ul><br>

  <b>Readings</b>
  <ul>
    <li>chargingState - Ladezustand</li>
    <li>cableState - Kabelzustand</li>
    <li>errorCode - Fehlercode</li>
    <li>currentL1/L2/L3 - Ladestrom pro Phase (A)</li>
    <li>voltageL1/L2/L3 - Spannung pro Phase (V)</li>
    <li>activePower - Aktive Leistung (kW)</li>
    <li>totalEnergy - Gesamtenergie (kWh)</li>
    <li>sessionEnergy - Session-Energie (kWh)</li>
    <li>powerFactor - Leistungsfaktor cos phi (%)</li>
    <li>maxChargingCurrent - Max. Ladestrom (A)</li>
    <li>serialNumber - Seriennummer</li>
    <li>firmwareVersion - Firmware-Version (hex)</li>
    <li>rfidCard - Letzte RFID-Karten-UID</li>
  </ul>
</ul>

=end html_DE

=cut

Infos dazu:
- Die Register 5050 und 5052 sind nur nutzbar, wenn man einen Schütz verwendet (Steht auch in der CommandRef.)
- Leider wird die "Device specific help" bei mir nicht angezeigt: "No help found for module: kebap30"
- Die Zeile
require "$attr{global}{modpath}/FHEM/DevIo.pm";hat die KI auch mit generiert. Warum wird diese nicht benötigt? Macht das LoadModule "Modbus"; schon  mit?
- Bei der Pluggit.pm gibt es noch ein Methode, wo ich nicht weiß, wofür die ist.:
sub
Pluggit_Define($$)
{
    my ($hash, $def) = @_;
    my @a = split("[ \t]+", $def);
    my ($name, $module, $interval, $dest) = @a;
    my $ret = "";
    return "wrong syntax: define <name> $module [interval] [host]"
        if(@a < 2);

    return ModbusLD_Define ($hash, $name . " " . $module . " " . $id . " " . $interval . " " . $dest . ":" . $port . " " . $proto);
}
welche dann vermutlich hier dynamisch gerufen wird:
Pluggit_Initialize($)
{
[...]
ModbusLD_Initialize($modHash);                # Generic function of the Modbus module does the rest

$modHash->{DefFn}    = "Pluggit_Define"; #<----HIER-----

$modHash->{AttrList} = $modHash->{AttrList} . " " .     # Standard Attributes like IODEv etc

[...]
...da fehlen mir etwas die Kenntnisse in der Perl-Syntax :).

rudolfkoenig

Zitatrequire "$attr{global}{modpath}/FHEM/DevIo.pm";
hat die KI auch mit generiert. Warum wird diese nicht benötigt? Macht das
Wenn man keine DevIo Funktionen in diesem Modul aufruft, dann braucht man auch nicht die Definition dieser Funktionen.
98_Modbus.pm verwendet DevIo, das Modul bindet es auch ein. Ueber "use DevIo", weil require manchmal problematisch ist.

Zitat- Bei der Pluggit.pm gibt es noch ein Methode, wo ich nicht weiß, wofür die ist.:
Die Pluggit_Define Funktion wird bei define (oder defmod) aufgerufen, prueft Parameter und initialisiert die Verbindung, indem sie ModbusLD_Define mit dem richtigen Parameter aufruft.
Pluggit_Initialize wird beim Laden des Moduls aufgerufen, der Name ist fest vorgegeben (<ModulName>_Initialize).

Apropos Syntax:
    return ModbusLD_Define ($hash, $name . " " . $module . " " . $id . " " . $interval . " " . $dest . ":" . $port . " " . $proto);
wuerde ich als
    return ModbusLD_Define ($hash, "$name $module $id $interval $dest:$port $proto");
schreiben, ich finde es besser lesbar.