Marstek Venus E Modulentwicklung

Begonnen von chri0815, 01 April 2026, 20:39:11

Vorheriges Thema - Nächstes Thema

Moli

Endlich hat Marstek geantwortet und mir ein Update zugewiesen, was ich auch sofort installiert habe.

Leider keine Änderung, Port offen, keine Daten.

Moli

So noch mal Danke für deine Arbeit, mit dem Gen 3 ging es sofort, ärgerlich.

Habe mal noch stateFormat hinzugefügt.

######################################################################
# 99_Marstek.pm
# FHEM Modul für Marstek Geräte (Venus C/E/D)
# Liest SOC, Bat-Power, PV-Power, Lade-/Entladeleistung
# Unterstützt Startwert für SOC (soc_start) und Polling
######################################################################

package main;

use strict;
use warnings;
use IO::Socket::INET;
use JSON::PP;
use Time::HiRes qw(gettimeofday);

##########################
# Modul-Initialisierung
##########################
sub Marstek_Initialize {
    my ($hash) = @_;
    $hash->{DefFn}    = "Marstek_Define";
    $hash->{UndefFn}  = "Marstek_Undef";
    $hash->{AttrFn}   = "Marstek_Attr";
    $hash->{GetFn}   = "Marstek_Get";
    $hash->{NotifyFn} = "Marstek_Notify";
    $hash->{AttrList} = "interval" . $readingFnAttributes;
}

##########################
# Define
##########################
sub Marstek_Define {
    my ($hash, $def) = @_;
    my @args = split(" ", $def);

    return "Usage: define <name> Marstek <IP> <PORT>" unless @args == 4;

    $hash->{IP}       = $args[2];
    $hash->{PORT}     = $args[3];
    $hash->{INTERVAL} = 60;

    InternalTimer(gettimeofday()+1, sub { Marstek_Poll($hash) }, 0);
    return undef;
}

##########################
# Undef
##########################
sub Marstek_Undef {
    my ($hash) = @_;
    RemoveInternalTimer($hash);
    return undef;
}

##########################
# Attribute
##########################
sub Marstek_Attr {
    my ($cmd, $name, $attrName, $attrValue) = @_;
    my $hash = $defs{$name};

    if ($attrName eq "interval" && $attrValue =~ /^\d+$/) {
        $hash->{INTERVAL} = $attrValue;
    }
    return undef;
}

##########################
# Notify
##########################
sub Marstek_Notify {
    my ($hash, $dev) = @_;
    return undef;
}

##########################
# Gemeinsame Request-Funktion
##########################
sub Marstek_DoRequest {
    my ($hash, $method, $units) = @_;
    my $success = 0;
    my $sock = IO::Socket::INET->new(
        PeerAddr => $hash->{IP},
        PeerPort => $hash->{PORT},
        Proto    => 'udp',
        Timeout  => 2
    );

    if ($sock) {
        my $req = {
            id     => 1,
            method => $method,
            params => { id => 0 }
        };

        my $json = encode_json($req);
        $sock->send($json);

        my $resp = "";

        eval {
            local $SIG{ALRM} = sub { die "Timeout\n" };
            alarm 2;
            $sock->recv($resp, 4096);
            alarm 0;
        };

        if ($@ && $@ ne "Timeout\n") {
            Log 3, "Marstek Poll recv error ($method): $@";
        }

        my $data;
        eval { $data = decode_json($resp) };

        if ($@) {
            Log 3, "Marstek JSON decode error ($method): $@";
        }

        if ($data && $data->{result}) {
            for my $key (keys %{$units}) {
                if (exists $data->{result}{$key}) {
                    readingsSingleUpdate(
                        $hash,
                        $key,
                        $data->{result}{$key} . " " . $units->{$key},
                        1
                    );
                    $success = 1;  # mark success if at least one reading updated
                }
            }
        }

        close($sock);
    }
    else {
        readingsSingleUpdate($hash, "state", "timeout", 1);
    }
    return $success;
}

##########################
# Polling
##########################
sub Marstek_Poll {
    my ($hash) = @_;
    return unless $hash;
    my $success = 0;
    eval {
        $success ||= Marstek_DoRequest($hash, "ES.GetStatus", {
            bat_soc       => "%",
            bat_cap       => "Wh",
            pv_power      => "W",
            ongrid_power  => "W",
            offgrid_power => "W",
            total_pv_energy           => "Wh",
            total_grid_output_energy  => "Wh",
            total_grid_input_energy   => "Wh",
            total_load_energy         => "Wh",
        });

        $success ||= Marstek_DoRequest($hash, "Bat.GetStatus", {
            id              => "",
            soc             => "%",
            charge_flag     => "",
            dischrg_flag    => "",
            bat_temp        => "C",
            bat_capacity    => "Wh",
            rated_capacity  => "Wh",
        });

        $success ||= Marstek_DoRequest($hash, "Wifi.GetStatus", {
            id        => "",
            wifi_mac  => "",
            ssid      => "",
            rssi      => "",
            sta_ip    => "",
        });

        $success ||= Marstek_DoRequest($hash, "ES.GetMode", {
            id             => "",
            mode           => "",
            ct_state       => "",
            total_power    => "W",
            input_energy   => "Wh",
            output_energy  => "Wh",
        });
    };

    if ($@) {
        Log 3, "Marstek Poll error: $@";
    }

    # State korrekt setzen
    readingsSingleUpdate($hash, "state", $success ? "ok" : "timeout", 1);


    InternalTimer(
        gettimeofday() + ($hash->{INTERVAL} // 60),
        sub { Marstek_Poll($hash) },
        0
    );
}
##########################
# Get
##########################
sub Marstek_Get {
    my ($hash, $name, $opt, @args) = @_;

    return "\"get $name\" needs at least one argument" unless(defined($opt));

    if ($opt eq "update") {
        Marstek_Poll($hash);
        return "Update triggered";
    }else {
    # IMPORTANT! This defines the list of possible commands
    my $list = "update:noArg";
        return "Unknown argument $opt, choose one of $list";
    }
  return -1;
   }
1;

Moli

Habe noch bisschen optimiert und Versionierung gemacht.

99_Marstek.pm

vitolinker

Vielen Dank für das Modul, das habe ich heute direkt bei einer neuen Marstek Venus 3 sofort zum Laufen gebracht!

chri0815

#19
Hallo Moli
Ich war den Sommer über leider mit anderen Aufgaben von meiner Familie voll beschäftigt und hatte für das Thema daher keine Zeit. Vielen Dank für deine Weiterentwicklung und Fehlerbeseitigung. Das Modul schaut jetzt ja wirklich gut aus.
Aber seit dem letzten Firmwareupdate seitens Marstek, wird nach kurzer Zeit immer wieder die Local API Schnittstelle im Akku deaktiviert oder mein Shelly Pro 3EM fliegt aus der Konfiguration, wenn das FHEM Modul läuft. Geht das nur mir so?

Gruß
Chris

Moli

Hi, tatsächlich hatte ich das auch eine Zeitlang.
Dann kam ein weiteres Update und es war eledigt.

Ich würde im Zweifel mal einen Reset probieren.

Gruß
M