Solarbatterieen von Marstek mit MQTT steuern

Begonnen von AlexMuc, 05 Juni 2025, 14:15:09

Vorheriges Thema - Nächstes Thema

rudolfkoenig

ZitatIch bin gespannt, ob ich eine Lösung zu Umschreibung diverser Werte herausbekomme. Meine Perlkentnisse beziehe ich nämlich zu 90% aus google :-)
Bei sowas sind die LLMs relativ gut.
Frage ist ob man das Ergebnis im Problemfall fixen kann.

Hier ist dafuer eine moegliche Loesung, ohne LLM erstellt:
sub
splitCD01($$)
{
  my ($dev, $answer) = @_;
  my %map = (
      p1=>"batStatePV1",
      p2=>"batStatePV2",
      o1=>"batStateOut1",
      o2=>{n=>"batStateOut2",v=>["off","on","Bypass on"]}
  );

  my %ret;
  for my $el (split(m/,/, $answer)) {
    my ($key, $val) = split(m/=/, $el);
    my $m = $map{$key};
    if($m) {
      if(ref($m) eq "HASH") {
        $ret{$m->{n}} = defined($m->{v}[$val]) ? $m->{v}[$val] : $val;
      } else {
        $ret{$m} = $val;
      }
    } else {
      $ret{$key} = $val;
    }
  }
  return \%ret;
}

AlexMuc

Oh weh, ich verstehe nur Bahnhof... :-)
Mal sehen, ob ich das umgesetzt kriege. Das mit den LLMs ist ja so eine Sache, auch da muß man wissen was man fragen soll. Und das möglichst genau. Aber wenn dann was nicht paßt, dann fehlt mir das Wissen um das zu verstehen.

Daher hier meine vorläufige aktuelle Version die zumindest schon mal tut, was sie soll:
# splitCD01($EVENT)
sub
splitCD01($$)
{
my ($dev, $answer) = @_;

my $ret = {};
my $i = 0;

my %map;
my ($key, $val);
my $element;

my $myCmd = ReadingsVal( $dev,"state","");
fhem("setreading $dev myCmd $myCmd");
#Log3( undef, 2,"--->: myCmd: $myCmd ");


  if ($myCmd eq "Info") {
# cd = 01
%map = (
p1 => "batStatePV1", p2 => "batStatePV2",
w1 => "batPowerPV1", w2 => "batPowerPV2",

o1 => "batStateOut1", o2 => "batStateOut2",
g1 => "batPowerOut1", g2 => "batPowerOut2",
 
kn => "batEnergy_kWh",
pe => "batEnergy_perc",
do => "batDoD",

cs => "batChargeMode", # chargeDischargeSimultaneously or chargeThenDischarge
cd => "batDischargeMode",
md => "batDischargeState",

d1 => "batDischargePlan1State", e1 => "batDischargePlan1Start", f1 => "batDischargePlan1End", h1 => "batDischargePlan1Value",
d2 => "batDischargePlan2State", e2 => "batDischargePlan2Start", f2 => "batDischargePlan2End", h2 => "batDischargePlan2Value",
d3 => "batDischargePlan3State", e3 => "batDischargePlan3Start", f3 => "batDischargePlan3End", h3 => "batDischargePlan3Value",
d4 => "batDischargePlan4State", e4 => "batDischargePlan4Start", f4 => "batDischargePlan4End", h4 => "batDischargePlan4Value",
d5 => "batDischargePlan5State", e5 => "batDischargePlan5Start", f5 => "batDischargePlan5End", h5 => "batDischargePlan5Value",

vv => "batFirmwareMain",
sv => "batFirmwareSub",
fc => "batFirmwareFC4VersionNumber",
id => "batDeviceID",
uv => "batBootloaderVersion",

tl => "batTempLow", th => "batTempHigh",
# Temperaturalarm on/off = 0/1

fhemfatale

#17
Hallo AlexMuc,
wie hast du deinen Speicher in Fhem angebunden?
Ich habe mit diesem Tool über das Smartphone Bluetooth verbunden und die MQTT Werte  eingegeben. In dem Tool wird auch verbunden angezeigt.
Ich dachte, daß dann über den MQTT-Broker automatisch ein Device angelegt wird und die Werte abgerufen werden. Ist aber nicht.
Dann habe ich mir mit hm2mqtt mit der manuellen Installation das git laut Beschreibung installiert. Bei "npm run build" gibt es eine Fehlermeldung "sh: 1: tsc: not found".
Ich habe einen RaspiB mit Raspbian. Alle Aktualisierungen habe ich durchgeführt.

Wie hast du das gemacht, daß die MQTT-Werte in Fhem reinkommen?
(hier habe ich ein voriges Fehlerprotokoll gelöscht)

Oder bin ich auf dem Holzweg und die Batterie muß über Bluetooth mit dem Fhem-Server verbunden werden? Ich dachte, wenn ich die MQTT-Server Infos eintrage, läuft das über WLAN. In der Fritzbox habe ich den Speicher als WLAN-Gerät eingetragen, allerdings wird es als inaktiv angezeigt.

AlexMuc

Ich hab das device komplett ,,zu Fuß" angelegt weil ich kein aus meiner Sicht passendes Template gefunden habe und auch keine wirkliche Ahnung hab.
Inzwischen habe ich dort noch einiges ergänzt aber prinzipiell kannst du ,,einfach" das von mir gepostete Listing als Starter übernehmen. Beim Topic mußt du halt die MAC ergänzen, die dir bei der MQTT Freischaltung angezeigt wurde.
Damit hat es ,,sofort" funktioniert und es wurde die Daten vom Speicher 1x pro Minute abgeholt.

AlexMuc

So, hier der 2. Versuch, jetzt mit der kompletten Funktion. Immer noch ohne den letzten Vorschlag von Rudi...
sub
splitCD01($$)
{
my ($dev, $answer) = @_;

my $ret = {};
my $i = 0;

my %map;
my ($key, $val);
my $element;

my $myCmd = ReadingsVal( $dev,"state","");
fhem("setreading $dev myCmd $myCmd");
#Log3( undef, 2,"--->: myCmd: $myCmd ");


  if ($myCmd eq "Info") {
# cd = 01
%map = (
p1 => "batStatePV1", p2 => "batStatePV2",
w1 => "batPowerPV1", w2 => "batPowerPV2",

o1 => "batStateOut1", o2 => "batStateOut2",
g1 => "batPowerOut1", g2 => "batPowerOut2",
 
kn => "batEnergy_kWh",
pe => "batEnergy_perc",
do => "batDoD",

cs => "batChargeMode", # chargeDischargeSimultaneously or chargeThenDischarge
cd => "batDischargeMode",
md => "batDischargeState",

d1 => "batDischargePlan1State", e1 => "batDischargePlan1Start", f1 => "batDischargePlan1End", h1 => "batDischargePlan1Value",
d2 => "batDischargePlan2State", e2 => "batDischargePlan2Start", f2 => "batDischargePlan2End", h2 => "batDischargePlan2Value",
d3 => "batDischargePlan3State", e3 => "batDischargePlan3Start", f3 => "batDischargePlan3End", h3 => "batDischargePlan3Value",
d4 => "batDischargePlan4State", e4 => "batDischargePlan4Start", f4 => "batDischargePlan4End", h4 => "batDischargePlan4Value",
d5 => "batDischargePlan5State", e5 => "batDischargePlan5Start", f5 => "batDischargePlan5End", h5 => "batDischargePlan5Value",

vv => "batFirmwareMain",
sv => "batFirmwareSub",
fc => "batFirmwareFC4VersionNumber",
id => "batDeviceID",
uv => "batBootloaderVersion",

tl => "batTempLow", th => "batTempHigh",
# Temperaturalarm on/off = 0/1
tc => "batTempAlarmCharging",
tf => "batTempAlarmDischarging",
tc_dis => "batTempDischarging",

a0 => "batCapacityHost",
a1 => "batCapacityExtraBat1",
a2 => "batCapacityExtraBat2",
b1 => "batExtraBatConnected1",
b2 => "batExtraBatConnected2",

# readonly bzw nur über Zeitplan?
lv => "batOutputThreshold",

# for Smartmeter
c0 => "batCtHostChannel",
c1 => "batCtHostStatus",
sg => "batSMConnected",
  sp => "batSMAutoPowerSize",
st => "batPowerTransmitted",
m0 => "batSMClip_1", m1 => "batSMClip_2", m2 => "batSMClip_3",
m3 => "batPowerOfInverter",

# Tageswerte
bc => "_energy_CurrentDay_Charge", # Summe Batterie Ladung war batEnergyTotal_Charge, batEnergyTotal
bs => "_energy_CurrentDay_Discharge", # Summe Batterie Entladung war batEnergyTotal_Out
pt => "_energy_CurrentDay_PV", # Summe PV-Leistung
it => "_energy_CurrentDay_Inverter", # Summe an Wechselrichter

lmf => "batEnergyLimiting_InOut",
lmi => "batEnergyRated_In", # Summe xxx
lmo => "batEnergyRated_Out", # Summe yyy

cj => "batScene",

# Batterie lädt / entlädt
l0 => "batSignPos_Host",
l1 => "batSignPos_Ex1_Ex2",

# unknown
am => "batX_am",
ct_t => "batX_ct_t",
am => "batX_bn",
sm => "batX_sm"
);

$ret = {};
# kn => "batEnergy_kWh",
# it => "batEnergyTotal_Inverter"

for $element (split m{,}x, $answer) {
($key, $val) = split m{=}x, $element;

if ($key eq "kn") {$val = $val / 1000}
elsif ($key eq "it") {$val = $val / 1000}
elsif ($key eq "bc") {$val = $val / 1000}
elsif ($key eq "bs") {$val = $val / 1000}
elsif ($key eq "pt") {$val = $val / 1000}

elsif ($key eq "lmi") {$val = $val / 1000}
elsif ($key eq "lmo") {$val = $val / 1000}

$ret->{ $map{$key} } = $val if ($map{ $key} );
};


} elsif ($myCmd eq "Info_Voltage_Cells") {
# cd = 13
%map = (
# .e & .f immer 0 ?
a0 => "cellVoltage_a0", a1 => "cellVoltage_a1", a2 => "cellVoltage_a2", a3 => "cellVoltage_a3", a4 => "cellVoltage_a4", a5 => "cellVoltage_a5", a6 => "cellVoltage_a6", a7 => "cellVoltage_a7", a8 => "cellVoltage_a8", a9 => "cellVoltage_a9",
aa => "cellVoltage_aa", ab => "cellVoltage_ab", ac => "cellVoltage_ac", ad => "cellVoltage_ad", ae => "cellVoltage_ae", af => "cellVoltage_af",

b0 => "cellVoltage_b0", b1 => "cellVoltage_b1", b2 => "cellVoltage_b2", b3 => "cellVoltage_b3", b4 => "cellVoltage_b4", b5 => "cellVoltage_b5", b6 => "cellVoltage_b6", b7 => "cellVoltage_b7", b8 => "cellVoltage_b8", b9 => "cellVoltage_b9",
ba => "cellVoltage_ba", bb => "cellVoltage_bb", bc => "cellVoltage_bc", bd => "cellVoltage_bd", be => "cellVoltage_be", bf => "cellVoltage_bf",

c0 => "cellVoltage_c0", c1 => "cellVoltage_c1", c2 => "cellVoltage_c2", c3 => "cellVoltage_c3", c4 => "cellVoltage_c4", c5 => "cellVoltage_c5", c6 => "cellVoltage_c6", c7 => "cellVoltage_c7", c8 => "cellVoltage_c8", c9 => "cellVoltage_c9",
ca => "cellVoltage_ca", cb => "cellVoltage_cb", cc => "cellVoltage_cc", cd => "cellVoltage_cd", ce => "cellVoltage_ce", cf => "cellVoltage_cf"
);

$ret = {};
for $element (split m{,}x, $answer) {
($key, $val) = split m{=}x, $element;
$ret->{ $map{$key} } = $val if ($map{ $key} );
};


} elsif ($myCmd eq "Info_xxx") {
# cd = 14
%map = (
# .e & .f immer 0 ?
ds => "xxx_ds",
ps => "xxx_ps",
ch => "xxx_ch",
as => "xxx_as",
e0 => "xxx_e0",
e1 => "xxx_e1",
op => "xxx_op",
cp => "xxx_cp",
cr => "xxx_cr",
c0 => "xxx_c0",
c1 => "xxx_c1",
c2 => "xxx_c2",
g1 => "xxx_g1",
g2 => "xxx_g2"
);

$ret = {};
for $element (split m{,}x, $answer) {
($key, $val) = split m{=}x, $element;
$ret->{ $map{$key} } = $val if ($map{ $key} );
};


} elsif ($myCmd eq "Info_Voltage_PV") {
# cd = 16
%map = (
# .e & .f immer 0 ?
g1 => "xxx_g1",
g2 => "xxx_g2"
);

$ret = {};
for $element (split m{,}x, $answer) {
($key, $val) = split m{=}x, $element;
$ret->{ $map{$key} } = $val if ($map{ $key} );
};
}



return $ret;
}


DeeSPe

Danke für das Teilen der kompletten Funktion.
Hab mal noch eine Stelle etwas optimiert.

Aus:
if ($key eq "kn") {$val = $val / 1000}
elsif ($key eq "it") {$val = $val / 1000}
elsif ($key eq "bc") {$val = $val / 1000}
elsif ($key eq "bs") {$val = $val / 1000}
elsif ($key eq "pt") {$val = $val / 1000}

elsif ($key eq "lmi") {$val = $val / 1000}
elsif ($key eq "lmo") {$val = $val / 1000}

Wurde:
                        $val = $val/1000 if ($key =~ /^(kn|it|bc|bs|pt|lmi|lmo)$/);
Gruß
Dan
MAINTAINER: 22_HOMEMODE, 98_Hyperion, 98_FileLogConvert, 98_serviced

Als kleine Unterstützung für meine Programmierungen könnt ihr mir gerne einen Kaffee spendieren: https://buymeacoff.ee/DeeSPe

fhemfatale

#21
Bei mir scheint sich der Speicher nicht mit dem MQTT-Server zu verbinden.
Auf der Tomquist-Seite wird der Speicher als connected und die MQTT-Daten angezeigt. Im dortigen Monitor ist die version=106.2. Das MQTT-Passwort habe ich nochmals mit einem Shellyplug verifiziert (dachte schon, daß ich das evtl. mal geändert hatte).
Den Event-Monitor im Fhem Device hatte ich zu laufen. Nur die set-Anfrage wird dort aufgeführt.
Daher nochmal die Frage: Speicher und Fhem sollen sich über WLAN per MQTT verbinden? Oder läuft alles über Bluetooth, dh. ich müßte einen BT-Stick an den Raspi anschließen?
In der Fritzbox habe ich die auf der Tomquist-Seite angezeigte MAC-Adresse freigeschaltet. Es wird aber keine zusätzliche IP-Adresse angezeigt.

Hier noch meine Device Definition. Bei MAC steht meine Adresse drin. Als sub habe ich das von heute von dir und Dan gepostete in 99_myutils.pm eingefügt.

defmod MQTT2_DVES_HMJ2 MQTT2_DEVICE HMJ_2
attr MQTT2_DVES_HMJ2 IODev myBroker
attr MQTT2_DVES_HMJ2 comment Eingerichtet nach https://forum.fhem.de/index.php?msg=1343327\
MQTT Aktivierung der Batterie durch https://tomquist.github.io/hame-relay/b2500.html
attr MQTT2_DVES_HMJ2 devicetopic hame_energy/HMJ-2/App/MAC/ctrl
attr MQTT2_DVES_HMJ2 event-on-change-reading .*
attr MQTT2_DVES_HMJ2 icon batterie
attr MQTT2_DVES_HMJ2 periodicCmd info:1
attr MQTT2_DVES_HMJ2 readingList HMJ_2:hame_energy/HMJ-2/device/MAC/ctrl:.* {splitCD01( $NAME, $EVENT)}\
HMJ_2:hame_energy/HMJ-2/App/MAC/ctrl:.* ctrl
attr MQTT2_DVES_HMJ2 room MQTT2_DEVICE
attr MQTT2_DVES_HMJ2 setList info:noArg $DEVICETOPIC cd=01\
plan1_400:noArg $DEVICETOPIC cd=20,md=0,a1=0,b1=00:01,e1=23:56,v1=400\
plan1_600:noArg $DEVICETOPIC cd=20,md=0,a1=0,b1=00:01,e1=23:56,v1=600\
plan1_700:noArg $DEVICETOPIC cd=20,md=0,a1=0,b1=00:01,e1=23:56,v1=700\
plan1_800:noArg $DEVICETOPIC cd=20,md=0,a1=0,b1=00:01,e1=23:56,v1=800\
batDoD:slider,0,5,100 $DEVICETOPIC cd=05,md=$EVTPART1\
planx:slider,0,5,100 $DEVICETOPIC cd=20,md=0,a1=0,b1=00:01,e1=23:56,v1=$EVTPART1
attr MQTT2_DVES_HMJ2 stateFormat {\
    my $tempLow = ReadingsNum( "$name","batTempLow","—-");;\
    my $tempHigh = ReadingsNum( "$name","batTempHigh","—-");;\
    my $temp = int( ($tempLow + $tempHigh)/2);;\
    my $tempCol;;\
    if ($tempLow < 7) {$tempCol = "blue"}\
    elsif ($tempHigh > 28) {$tempCol = "red"}\
    else {$tempCol = "black"};;\
   \
    sprintf( qq{<div style="text-align: right;;">\\
in: <b>%.0f W</b> ∑<b>%.3f kWh</b>, \\
out: <b>%.0f W</b>, \\
Akku: <b>%.3f kWh</b> (<b>%d %%</b>), \\
<font color="$tempCol"><b>%.0f °C</b></font>\\
</div>}\
    , ReadingsNum( "$name","batPowerPVin",0)\
    , ReadingsNum( "$name","batEnergyTotal_PV",0) /1000\
    , ReadingsNum( "$name","batPowerOut",0)\
    , ReadingsNum( "$name","batEnergy_kWh",0) /1000\
    , ReadingsNum( "$name","batEnergy_perc","—-")\
    , $temp    )\
\
}
attr MQTT2_DVES_HMJ2 userReadings batPowerPVin:batPowerPV2.* {\
  ReadingsVal( $name, "batPowerPV1", 0)\
+ ReadingsVal( $name, "batPowerPV2", 0)\
},\
batPowerOut:batPowerOut2.* {\
  ReadingsVal( $name, "batPowerOut1", 0)\
+ ReadingsVal( $name, "batPowerOut2", 0)\
},\
batFirmware:batFirmwareMain.* {\
    sprintf( "%d.%d",\
          ReadingsVal( $name, "batFirmwareMain", 0),\
        ReadingsVal( $name, "batFirmwareSub", 0)\
    )\
}
attr MQTT2_DVES_HMJ2 verbose 2

setstate MQTT2_DVES_HMJ2 <div style="text-align: right;;">\
in: <b>0 W</b> ∑<b>0.000 kWh</b>, \
out: <b>0 W</b>, \
Akku: <b>0.000 kWh</b> (<b>0 %</b>), \
<font color="blue"><b>0 °C</b></font>\
</div>

AlexMuc

Ich hab da noch einen _ in der defmod Zeile, also HMJ_2_
Wobei ich keine Ahnung habe, wofür der Parameter da steht ;-)

Danke an DeeSP für die Optimierung.

fhemfatale

@AlexMuc: Das bei mir der zweite Unterstrich fehlt, ist mir schon bewußt. An den Stellen, wo das in einer Definition steht (zB. bei readingslist) habe ich es angepaßt. Ich meine, das ist ein frei definierbarer Name, könnte also auch B2500 heißen.
Was meine obige Frage war, ist ob die Batterie über Bluetooth mit dem MQTT-Server kommuniziert oder über WLAN. Meiner Meinung nach ist Bluetooth nur zum Einstellen der MQTT-Daten notwendig, danach müßte es über WLAN laufen. Ich stecke da aber nicht so tief in den Abläufen drin, sondern bin nur Anwender.
Über deine Definition des Devices und Sub habe ich nur gestaunt. Das hätte ich nie zusammenbekommen, zumal mir Programmierkenntnisse fehlen. Respekt und großen Dank dafür.

AlexMuc

Ok, wenn da nur ein Name steht, werde ich das bei mir mal mit was anderem testen :-)
Zur Kommunikation: der Speicher wird zu erst mit der App über Bluetooth konfiguriert. wlan nur 2,4GHz, Passwort nicht zu lang, Wlan-Name auch erstmal mit was einfachem testen...
Anschließen sollte der Speicher im Router sichtbar sein und eine IP haben. In der App sollten BT und WLAN grün sein. Die Kommunikation per MQTT aus Fhem findet dann nur noch über WLAN statt.

fhemfatale

Ich wollte die App nicht verwenden, um unabhängig davon zu sein. So hatte ich auch die github Seite von tomquist verstanden. Dort ist mit dem Tool der Zugang über BT zur Batterie möglich und man kann die MQTT-Daten eingeben. Diese bleiben auch nach einem Neustart in der Batterie erhalten.

Nur sehe ich dann keine Kommunikation zu meinem MQTT-Server in Fhem. Und im WLAN taucht sie auch nicht auf.

Wahrscheinlich ist dieses Programm auch noch notwendig. Ich habe bei der Installation aber einige Auffälligkeiten gehabt, so daß es noch nicht läuft. Und ich hatte gehofft, daß mit deiner MQTT-Anbindung in Fhem ich diesen Schritt übergehen kann.

AlexMuc

Hallo fhemfatale,
setze den Speicher mal auf Werkseinstellungen zurück und fang von vorne an. Und bevor du irgendwas mit MQTT machst, konfiguriere ihn mit der App damit er im WLAN ist. Solange du ihn nicht in deinem Router siehst, wird das auch mit hm2mqtt nicht besser bei dir denn auch das geht nur über WLAN. Erst wenn die App dir das WLAN mit dem grünen Sybol anzeigt, solltest du mit dem Toolvon Tomquist MQTT konfigurieren. Und ab dann kannst du den Speicher aus FHEM steuern.

fhemfatale

So, es hat jetzt geklappt, daß ich ihn in der App anmelden konnte. Er ist über WLAN im Gastnetz mit einem einfachen Kennwort angemeldet.Die Einbindung in Fhem wird dann irgendwann später erfolgen, wenn ich einige Erfahrung zu den besten Einstellungen für mich gesammelt habe. Da werde ich mich sicherlich nochmal melden, wenn es Fragen gibt.

Nochmal zusammengefaßt, was in meinem Fall ging/nicht ging. Auch wenn das zum eigentlichen Thread-Thema OT ist.
Die Anmeldung in der App ist notwendig. Ein direkter Zugriff über andere Programme ist erst danach möglich.
Die Marstek App konnte keine BT-Verbindung herstellen, nur mit der Power App funktionierte es. Danach sollte auch nur noch diese App verwendet werden, da sonst nicht-konfigurierte Einstellungen aus der Marstek App in den Batteriespeicher geschrieben werden, bzw. vorige überschrieben werden.
Nachdem man den Zugriff über BT hat, muß die WLAN Verbindung eingerichtet werden. Manche Einstellungen in der App sind kurioserweise später wieder nicht da und man muß neu konfigurieren (ging mir beim DOD und Zeitplan so).