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.