name: GoodWe RS485 Controller version: "0.3.0" slug: goodwe_controller description: >- Drives a GoodWe ES/BP battery inverter over RS485 by emulating its smart meter, holding net grid exchange at zero and running a monthly battery maintenance cycle. url: https://gittea.kammenstraatha.duckdns.org/admin/goodwe-addon arch: - aarch64 - amd64 - armv7 init: false startup: application boot: auto # Needed to read the meter and to write the ESPHome setpoint number. homeassistant_api: true hassio_api: true # Own diagnostics/operations UI inside HA. This is what a field tech and a # remote supporter both look at, so it is not optional. ingress: true ingress_port: 8099 panel_icon: mdi:battery-sync panel_title: GoodWe # Optional: publish status entities by MQTT discovery. `want` rather than # `need` - the add-on runs fine with no broker, it just publishes nothing. services: - mqtt:want options: # --- sources (required) --------------------------------------------------- meter_entity: sensor.p1_meter_active_power meter_invert: false soc_entity: "" batt_entity: "" batt_invert: false setpoint_entity: "" # --- P1 meter ingestion (specs §5.2 / §14 `meter:`) ------------------------- # `off` keeps the original single meter_entity path above, so an existing # install is untouched until it opts in. ha_dsmr subscribes to the DSMR # integration's entities over the HA WebSocket; mqtt_p1 reads the topic below; # ha_signed reads ONE signed HA entity (+ import / - export), which is what a # HomeWizard P1 publishes and what ha_dsmr cannot consume. # homewizard_local skips Home Assistant entirely and polls the meter's own # local API. ⚠️ It is the ONLY mode whose sensor.p1_sample_age_s measures when # the meter REPORTED rather than when the value last CHANGED - HA emits # nothing at all for a repeated reading - so it is the only mode a firmware # watchdog may threshold. See DOCS.md. meter_source: "off" meter_phases: 1 meter_max_age_s: 30 meter_mqtt_topic: "" # homewizard_local only. The meter's own address, `host` or `host:port`. p1_host: "" # homewizard_local only. Seconds between polls. Must be well under # meter_max_age_s - the age is never fresher than this interval - and there is # nothing to gain below the meter's own ~5.0 s update rate (NOTES.md:640). meter_poll_s: 5 # The two UNSIGNED Belgian registers. The EMS derives net power from them # (import - export); do NOT point these at a signed template sensor. p1_import_entity: "" p1_export_entity: "" # Optional, in L1..L3 order. Required for the capacity-tariff peak on a # three-phase connection; the list length must equal meter_phases. p1_phase_import_entities: [] p1_phase_export_entities: [] # ha_signed only. ONE signed net-power sensor: positive = import from the # grid, negative = export to it. Do NOT split it into two template sensors - # the split is done in the add-on (p1.split_signed) precisely so the sign # convention is tested rather than living in unreviewed YAML. p1_net_entity: "" # Optional, in L1..L3 order, each one signed the same way. Same role as # p1_phase_import_entities: the capacity-tariff peak on a three-phase # connection. The list length must equal meter_phases. p1_phase_net_entities: [] # --- control --------------------------------------------------------------- max_w: 2000 gain: 0.6 slew_w: 1000 deadband_w: 15 target_grid_w: -10 step_w: 10 saturation_w: 500 saturation_cycles: 3 integrator_max_w: 0 heartbeat_s: 10 stale_input_s: 15 auto_start: false # --- maintenance ----------------------------------------------------------- maintenance_enabled: false maintenance_interval_days: 28 maintenance_start_hour: 10 maintenance_discharge_w: 2500 maintenance_charge_w: 2500 maintenance_soc_floor: 11 maintenance_soc_target: 99 maintenance_hold_min: 120 # --- tariff / capacity tariff (all optional) ------------------------------ peak_forecast_entity: "" peak_cap_w: 3500 price_now_entity: "" price_avg_entity: "" # --- site ------------------------------------------------------------------ estop_fitted: false log_level: info schema: meter_entity: str meter_invert: bool soc_entity: str batt_entity: str batt_invert: bool setpoint_entity: str meter_source: list(off|ha_dsmr|mqtt_p1|ha_signed|homewizard_local) # ⚠️ 2 is accepted by this range but is not a real Belgian connection. A # telegram whose phase count disagrees is rejected at ingest and logged, so a # mis-set 2 shows up immediately as "0 telegrams accepted" rather than as a # quietly wrong number. meter_phases: int(1,3) meter_max_age_s: int(5,300) meter_poll_s: int(1,60) meter_mqtt_topic: str? p1_host: str? p1_import_entity: str? p1_export_entity: str? p1_phase_import_entities: - str p1_phase_export_entities: - str p1_net_entity: str? p1_phase_net_entities: - str max_w: int(100,5000) gain: float(0.05,1.0) slew_w: int(50,5000) deadband_w: int(0,500) target_grid_w: float(-200,200) step_w: int(1,100) saturation_w: int(100,2000) saturation_cycles: int(1,10) integrator_max_w: int(0,15000) heartbeat_s: int(2,25) stale_input_s: int(5,120) auto_start: bool maintenance_enabled: bool maintenance_interval_days: int(1,90) maintenance_start_hour: int(0,23) maintenance_discharge_w: int(500,5000) maintenance_charge_w: int(500,5000) maintenance_soc_floor: int(5,30) maintenance_soc_target: int(50,100) maintenance_hold_min: int(5,480) peak_forecast_entity: str? peak_cap_w: int(500,15000) price_now_entity: str? price_avg_entity: str? estop_fitted: bool log_level: list(trace|debug|info|warning|error)