Marstek B2500 Batteriespeicher

Hallo

Ich habe auch einen ESP32 zum Auslesen des Marstek B2500 per Bluetooth genommen. So braucht man nicht in das Gerät eingreifen und bekommt auch keine Probleme mit Garantie/Gewährleistung. Funktioniert einwandfrei. Wie das geht steht hier: GitHub - tomquist/esphome-b2500 und hier: ESPHome for B2500.

Die Automation für die Nulleinspeisung (ich hab das aber auf Restbezug 20 Watt eingestellt, um ja nichts zu verschenken) hab ich mir von ChatGPT bauen lassen. Und ich muss sagen, was die KI hier geliefert hat, funktioniert seeeehr gut. Das ganze hat ein paar Stunden gedauert, aber selber hätte ich sowas niemals ! hinbekommen.

alias: Nulleinspeisung - inkrementeller PI mit Akku-Schutz (3s)
description: >
Inkrementeller PI-Regler: regelt alle 3s, max 200 W Sprungweite, 0–800 W. Nur
aktiv bei Akku > 20 %. Unter 20 % Akku wird auf festen Wert 125 W gesetzt.

triggers:

- seconds: /3
  trigger: time_pattern
  conditions:
- condition: state
  entity_id: input_boolean.nulleinspeisung
  state: “on”
- condition: template
  value_template: >-
  {{ states(‘sensor.energy_netzbezug’) not in
  \[‘unknown’,‘unavailable’,‘uninitialized’\] }}
  actions:
- choose:
  - conditions:
    - condition: template
      value_template: “{{ soc <= 20 }}”
      sequence:
    - target:
      entity_id: number.b2500_01_b2500_1_b2500_01_timer_1_output_power
      data:
      value: “{{ output_final | round(0) }}”
      action: number.set_value
    - data:
      entity_id: input_number.nulleinspeisung_integral
      value: “{{ integral_final }}”
      action: input_number.set_value
  - conditions:
    - condition: template
      value_template: “{{ soc > 20 }}”
      sequence:
    - target:
      entity_id: number.b2500_01_b2500_1_b2500_01_timer_1_output_power
      data:
      value: “{{ output_final | round(0) }}”
      action: number.set_value
    - data:
      entity_id: input_number.nulleinspeisung_integral
      value: “{{ integral_final | round(3) }}”
      action: input_number.set_value
      variables:
      sensor: “{{ states(‘sensor.energy_netzbezug’) | float(0) }}”
      setpoint: 10
      soc: “{{ states(‘sensor.b2500_01_b2500_1_b2500_01_battery_level’) | float(0) }}”
      prev_output: >-
      {{ states(‘number.b2500_01_b2500_1_b2500_01_timer_1_output_power’) |
      float(0) }}
      prev_integral: “{{ states(‘input_number.nulleinspeisung_integral’) | float(0) }}”
      kp: 0.5
      ki: 0.02
      dt: 3
      max_step: 200
      out_min: 0
      out_max: 800
      error: “{{ (sensor - setpoint) }}”
      integral_raw: “{{ (prev_integral + (error \* dt)) }}”
      delta_raw: “{{ (kp \* error) + (ki \* integral_raw) }}”
      delta_step: |
      {% if delta_raw > max_step %}
      {{ max_step }}
      {% elif delta_raw < -max_step %}
      {{ -max_step }}
      {% else %}
      {{ delta_raw }}
      {% endif %}
      tentative_output: “{{ (prev_output | float) + (delta_step | float) }}”
      clamped_output: “{{ \[out_min, \[tentative_output | float, out_max\] | min\] | max }}”
      integral_new: >
      {% set tentative = tentative_output | float %} {% set clamped =
      clamped_output | float %} {% if (clamped == tentative) and (clamped >
      out_min) and (clamped < out_max) %}
      {{ integral_raw }}
      {% else %}
      {{ prev_integral }}
      {% endif %}
      output_final: |
      {% if soc <= 20 %}
      125
      {% else %}
      {{ clamped_output }}
      {% endif %}
      integral_final: |
      {% if soc <= 20 %}
      0
      {% else %}
      {{ integral_new }}
      {% endif %}
      mode: single

:crayon:by HarryP: Code-/Logzeilen formatiert (bitte immer in </> einbinden)
s.a.: (Neues Update & Features - Hier in der Community 🫶)