Last active
August 14, 2026 03:58
-
-
Save SunEnergyXT/38cab611ce8fe5b8040613299e8a3c6f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| blueprint: | |
| name: SunEnergyXT 500 Series - Zero Feed-in | |
| description: > | |
| Zero feed-in controller for SunEnergyXT 500 / 500 Pro using an external Home | |
| Assistant meter. It does not use the device operation-mode or discharge-mode | |
| controls. Select the meter device and the SunEnergyXT device; the blueprint | |
| auto-detects common HA / HACS meter shapes such as Shelly, BitShake / | |
| Tasmota, EcoTracker, signed total power, three-phase power, and | |
| import/export pairs. The automation then controls the SunEnergyXT system | |
| grid-port power setpoint and system max inverter power setpoint with the | |
| same topology logic used by the simulator. | |
| domain: automation | |
| homeassistant: | |
| min_version: "2024.6.0" | |
| source_url: https://github.com/SunEnergyXT/sunenergyxt-500-zero-feed-in-blueprint/blob/main/blueprints/automation/sunenergyxt/sunenergyxt-500-zero-feed-in.en.yaml | |
| input: | |
| required_devices: | |
| name: SunEnergyXT setup | |
| icon: mdi:devices | |
| description: > | |
| Select the SunEnergyXT all-in-one device and the main user settings. | |
| The blueprint will auto-bind matching entities from this device and | |
| write the SOC limits to the device's own settings. | |
| input: | |
| aio_device: | |
| name: SunEnergyXT device | |
| description: > | |
| Select the SunEnergyXT 500 / 500 Pro device. The blueprint will | |
| auto-find the system grid-port power setpoint, system max inverter | |
| power setpoint, load-port power, grid-port power, PV total input | |
| power, system battery level, and system SOC limit entities from | |
| this device. | |
| selector: | |
| device: | |
| filter: | |
| - integration: sunenergyxt | |
| full_battery_mode: | |
| name: Full-battery behavior | |
| default: follow_load | |
| selector: | |
| select: | |
| options: | |
| - label: Follow load after full | |
| value: follow_load | |
| - label: Follow PV after full | |
| value: follow_pv | |
| gs_max_w: | |
| name: Maximum on-grid output power | |
| description: > | |
| Maximum allowed on-grid output power. Use 800 W for SunEnergyXT 500 | |
| and 2400 W for SunEnergyXT 500 Pro unless a stricter local limit is | |
| required. | |
| default: 2400 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 2400 | |
| step: 1 | |
| unit_of_measurement: W | |
| mode: box | |
| min_discharge_soc: | |
| name: System Min Discharge SOC | |
| description: > | |
| Sets the selected SunEnergyXT device's System Min Discharge SOC | |
| number entity. The automation also uses this value as the low-SOC | |
| control threshold. | |
| default: 10 | |
| selector: | |
| number: | |
| min: 1 | |
| max: 30 | |
| step: 1 | |
| unit_of_measurement: "%" | |
| mode: box | |
| max_charge_soc: | |
| name: System Max Charge SOC | |
| description: > | |
| Sets the selected SunEnergyXT device's System Max Charge SOC number | |
| entity. The automation also uses this value as the full-battery | |
| control threshold. | |
| default: 100 | |
| selector: | |
| number: | |
| min: 70 | |
| max: 100 | |
| step: 1 | |
| unit_of_measurement: "%" | |
| mode: box | |
| ac_couple_max_charge_power_w: | |
| name: AC-coupled maximum charge power | |
| default: 2400 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 2400 | |
| step: 1 | |
| unit_of_measurement: W | |
| mode: box | |
| meter_setup: | |
| name: Meter setup | |
| icon: mdi:meter-electric | |
| description: > | |
| Select exactly one meter type. Home Assistant blueprints cannot | |
| dynamically hide fields based on this selection, so expand only the | |
| matching meter form below and leave the other forms blank. | |
| input: | |
| meter_power_source: | |
| name: Meter type | |
| description: > | |
| Choose the meter integration shape. Only fill the fields that | |
| match the selected type. | |
| selector: | |
| select: | |
| options: | |
| - label: Shelly Pro 3EM | |
| value: shelly_total_power | |
| - label: EcoTracker | |
| value: ecotracker_power | |
| - label: BitShake / Tasmota | |
| value: bitshake_tasmota_power | |
| - label: Shelly 3EM | |
| value: shelly_three_phase_sum | |
| - label: Custom meter | |
| value: custom | |
| meter_pro3em_setup: | |
| name: Shelly Pro 3EM meter | |
| icon: mdi:meter-electric | |
| collapsed: true | |
| description: > | |
| Fill this section only when Meter type is Shelly Pro 3EM. | |
| input: | |
| pro3em_meter_device: | |
| name: Shelly Pro 3EM device | |
| description: > | |
| Select the Shelly Pro 3EM meter device. | |
| default: "" | |
| selector: | |
| device: | |
| entity: | |
| - domain: sensor | |
| meter_ecotracker_setup: | |
| name: EcoTracker meter | |
| icon: mdi:meter-electric | |
| collapsed: true | |
| description: > | |
| Fill this section only when Meter type is EcoTracker. | |
| input: | |
| ecotracker_meter_device: | |
| name: EcoTracker device | |
| description: > | |
| Select the EcoTracker meter device. | |
| default: "" | |
| selector: | |
| device: | |
| entity: | |
| - domain: sensor | |
| meter_bitshake_setup: | |
| name: BitShake / Tasmota meter | |
| icon: mdi:meter-electric | |
| collapsed: true | |
| description: > | |
| Fill this section only when Meter type is BitShake / Tasmota. | |
| input: | |
| bitshake_meter_device: | |
| name: BitShake / Tasmota device | |
| description: > | |
| Select the BitShake or Tasmota meter device. | |
| default: "" | |
| selector: | |
| device: | |
| entity: | |
| - domain: sensor | |
| meter_shelly3em_setup: | |
| name: Shelly 3EM meter | |
| icon: mdi:meter-electric | |
| collapsed: true | |
| description: > | |
| Fill this section only when Meter type is Shelly 3EM. Select the three | |
| phase devices; the blueprint will read each phase power from its device. | |
| input: | |
| meter_l1_device: | |
| name: Shelly 3EM phase A device | |
| description: > | |
| Select the HA device for phase A / L1. | |
| default: "" | |
| selector: | |
| device: | |
| entity: | |
| - domain: sensor | |
| meter_l2_device: | |
| name: Shelly 3EM phase B device | |
| description: > | |
| Select the HA device for phase B / L2. | |
| default: "" | |
| selector: | |
| device: | |
| entity: | |
| - domain: sensor | |
| meter_l3_device: | |
| name: Shelly 3EM phase C device | |
| description: > | |
| Select the HA device for phase C / L3. | |
| default: "" | |
| selector: | |
| device: | |
| entity: | |
| - domain: sensor | |
| meter_custom_setup: | |
| name: Custom meter | |
| icon: mdi:meter-electric | |
| collapsed: true | |
| description: > | |
| Fill this section only when Meter type is Custom meter. Custom meter | |
| does not need a meter device; select a formula and provide the matching | |
| entities. | |
| input: | |
| custom_meter_formula: | |
| name: Custom meter power formula | |
| description: > | |
| The blueprint applies this formula to the custom entities below. | |
| default: custom_signed_total_power | |
| selector: | |
| select: | |
| options: | |
| - label: Signed total power | |
| value: custom_signed_total_power | |
| - label: L1 + L2 + L3 | |
| value: custom_three_phase_sum | |
| - label: Export - import | |
| value: import_export_pair | |
| - label: L1-L3 export - import | |
| value: custom_three_phase_import_export | |
| - label: Entity attribute | |
| value: ecotracker_attribute_power | |
| meter_power_sensor: | |
| name: Custom total/current power entity | |
| description: > | |
| Use for Custom signed total power or Custom entity attribute. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| meter_power_attribute: | |
| name: Custom power attribute | |
| description: > | |
| Use only when Custom meter power formula is Entity attribute. The | |
| usual attribute name is power. | |
| default: power | |
| selector: | |
| text: | |
| meter_l1_power_sensor: | |
| name: Custom L1 / phase A power entity | |
| description: > | |
| Use for Custom formula L1 + L2 + L3. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| meter_l2_power_sensor: | |
| name: Custom L2 / phase B power entity | |
| description: > | |
| Use for Custom formula L1 + L2 + L3. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| meter_l3_power_sensor: | |
| name: Custom L3 / phase C power entity | |
| description: > | |
| Use for Custom formula L1 + L2 + L3. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| meter_import_power_sensor: | |
| name: Custom import power entity | |
| description: > | |
| Use for Custom formula Export - import. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| meter_export_power_sensor: | |
| name: Custom export power entity | |
| description: > | |
| Use for Custom formula Export - import. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| meter_l1_import_power_sensor: | |
| name: Custom L1 / phase A import power entity | |
| description: > | |
| Use for Custom formula L1-L3 export - import. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| meter_l2_import_power_sensor: | |
| name: Custom L2 / phase B import power entity | |
| description: > | |
| Use for Custom formula L1-L3 export - import. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| meter_l3_import_power_sensor: | |
| name: Custom L3 / phase C import power entity | |
| description: > | |
| Use for Custom formula L1-L3 export - import. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| meter_l1_export_power_sensor: | |
| name: Custom L1 / phase A export power entity | |
| description: > | |
| Use for Custom formula L1-L3 export - import. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| meter_l2_export_power_sensor: | |
| name: Custom L2 / phase B export power entity | |
| description: > | |
| Use for Custom formula L1-L3 export - import. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| meter_l3_export_power_sensor: | |
| name: Custom L3 / phase C export power entity | |
| description: > | |
| Use for Custom formula L1-L3 export - import. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| advanced_entity_overrides: | |
| name: Advanced entity overrides | |
| icon: mdi:form-select | |
| collapsed: true | |
| description: > | |
| Leave these blank for automatic device-based binding. Expand only if the | |
| selected device exposes unusual entity names and the auto-bind result is | |
| wrong. | |
| input: | |
| gs_number: | |
| name: Override System Grid Port Power Setpoint entity | |
| default: "" | |
| selector: | |
| entity: | |
| domain: number | |
| is_number: | |
| name: Override System Max Inverter Power Setpoint entity | |
| default: "" | |
| selector: | |
| entity: | |
| domain: number | |
| load_power_sensor: | |
| name: Override load-port power sensor | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| grid_port_power_sensor: | |
| name: Override grid-port power sensor | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| pv_power_sensor: | |
| name: Override PV power sensor | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| soc_sensor: | |
| name: Override SOC sensor | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| min_discharge_soc_number: | |
| name: Override min-discharge SOC number | |
| default: "" | |
| selector: | |
| entity: | |
| domain: number | |
| max_charge_soc_number: | |
| name: Override max-charge SOC number | |
| default: "" | |
| selector: | |
| entity: | |
| domain: number | |
| advanced_meter_settings: | |
| name: Advanced meter sign and unit settings | |
| icon: mdi:meter-electric | |
| collapsed: true | |
| input: | |
| meter_sign_mode: | |
| name: Meter sign convention | |
| description: > | |
| Auto uses the selected meter type default. Use an explicit override | |
| if your CT direction, script, or template reports the opposite | |
| sign. | |
| default: auto_from_preset | |
| selector: | |
| select: | |
| options: | |
| - label: Auto from selected meter type | |
| value: auto_from_preset | |
| - label: Export/feed-in is positive | |
| value: export_positive | |
| - label: Import/grid consumption is positive | |
| value: import_positive | |
| meter_power_multiplier: | |
| name: External meter power multiplier | |
| description: > | |
| Use 1 when the entity is already W. Use 1000 when the entity is kW. | |
| default: 1 | |
| selector: | |
| number: | |
| min: 0.001 | |
| max: 1000 | |
| step: 0.001 | |
| mode: box | |
| advanced_control_settings: | |
| name: Advanced control settings | |
| icon: mdi:cog-outline | |
| collapsed: true | |
| input: | |
| target_grid_power_w: | |
| name: Target grid power | |
| description: > | |
| Zero feed-in uses 0 W. Positive values intentionally keep a small | |
| export; negative values intentionally keep a small import. | |
| default: 0 | |
| selector: | |
| number: | |
| min: -200 | |
| max: 200 | |
| step: 1 | |
| unit_of_measurement: W | |
| mode: box | |
| target_grid_power_entity: | |
| name: Optional target grid power helper | |
| description: > | |
| Optional input_number helper for a dynamic target value. If no | |
| helper is selected or its state is invalid, the static target grid | |
| power configured above is used. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: input_number | |
| gs_min_w: | |
| name: System Grid Port Power Setpoint lower limit | |
| description: > | |
| Minimum allowed system grid-port power setpoint. Negative values | |
| allow grid import / charging to absorb surplus power. | |
| default: -2400 | |
| selector: | |
| number: | |
| min: -2400 | |
| max: 0 | |
| step: 1 | |
| unit_of_measurement: W | |
| mode: box | |
| gs_resolution_w: | |
| name: System Grid Port Power Setpoint write resolution | |
| default: 1 | |
| selector: | |
| number: | |
| min: 1 | |
| max: 50 | |
| step: 1 | |
| unit_of_measurement: W | |
| mode: box | |
| gain_percent: | |
| name: Correction gain | |
| default: 100 | |
| selector: | |
| number: | |
| min: 20 | |
| max: 150 | |
| step: 5 | |
| unit_of_measurement: "%" | |
| mode: slider | |
| small_error_w: | |
| name: Small-error threshold | |
| default: 30 | |
| selector: | |
| number: | |
| min: 1 | |
| max: 300 | |
| step: 1 | |
| unit_of_measurement: W | |
| mode: box | |
| large_error_w: | |
| name: Large-error threshold | |
| default: 150 | |
| selector: | |
| number: | |
| min: 10 | |
| max: 1000 | |
| step: 10 | |
| unit_of_measurement: W | |
| mode: box | |
| slow_interval_s: | |
| name: Slow write interval | |
| default: 7 | |
| selector: | |
| number: | |
| min: 1 | |
| max: 120 | |
| step: 0.5 | |
| unit_of_measurement: s | |
| mode: box | |
| medium_interval_s: | |
| name: Medium write interval | |
| default: 2.5 | |
| selector: | |
| number: | |
| min: 0.5 | |
| max: 60 | |
| step: 0.5 | |
| unit_of_measurement: s | |
| mode: box | |
| fast_interval_s: | |
| name: Fast write interval | |
| default: 1 | |
| selector: | |
| number: | |
| min: 1 | |
| max: 20 | |
| step: 1 | |
| unit_of_measurement: s | |
| mode: box | |
| gs_feedback_settle_s: | |
| name: Control feedback settle time | |
| description: > | |
| Minimum time after a system grid-port power setpoint or system max | |
| inverter power setpoint write before the next control write. The | |
| automation also waits for fresh grid-port power and meter samples | |
| after the last control change. The default matches observed | |
| SunEnergyXT feedback latency and reduces hunting when an external | |
| device changes output. | |
| default: 5 | |
| selector: | |
| number: | |
| min: 1 | |
| max: 30 | |
| step: 1 | |
| unit_of_measurement: s | |
| mode: box | |
| meter_stabilization_delay_s: | |
| name: Delayed-meter feedback wait | |
| description: > | |
| Optional additional wait after a GS or IS write before another | |
| control write. Keep 0 s for meters whose power value follows the | |
| physical change without noticeable delay. Set this slightly above | |
| the measured content delay when a meter keeps reporting new Home | |
| Assistant timestamps but its power value still describes the state | |
| before the last write. A value that is too small can allow hunting; | |
| a value that is too large slows response to real load changes. | |
| default: 0 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 60 | |
| step: 1 | |
| unit_of_measurement: s | |
| mode: box | |
| small_max_step_w: | |
| name: Small-error max adjustment | |
| default: 20 | |
| selector: | |
| number: | |
| min: 1 | |
| max: 300 | |
| step: 1 | |
| unit_of_measurement: W | |
| mode: box | |
| medium_max_step_w: | |
| name: Medium-error max adjustment | |
| default: 120 | |
| selector: | |
| number: | |
| min: 10 | |
| max: 1000 | |
| step: 10 | |
| unit_of_measurement: W | |
| mode: box | |
| fast_max_step_w: | |
| name: Large-error max adjustment | |
| default: 450 | |
| selector: | |
| number: | |
| min: 50 | |
| max: 2400 | |
| step: 10 | |
| unit_of_measurement: W | |
| mode: box | |
| max_meter_age_s: | |
| name: Maximum meter age | |
| description: > | |
| Maximum age of the external meter reading before the current | |
| control cycle pauses and holds the current GS value. An explicitly | |
| invalid critical input must remain invalid for at least the same | |
| time before a nonzero GS is reset once to 0 W. | |
| default: 30 | |
| selector: | |
| number: | |
| min: 2 | |
| max: 300 | |
| step: 1 | |
| unit_of_measurement: s | |
| mode: box | |
| is_resolution_w: | |
| name: System Max Inverter Power Setpoint write resolution | |
| default: 10 | |
| selector: | |
| number: | |
| min: 1 | |
| max: 100 | |
| step: 1 | |
| unit_of_measurement: W | |
| mode: box | |
| diagnostic_log_level: | |
| name: Diagnostic log level | |
| description: > | |
| Off writes no diagnostic logs. Events logs SOC hold helper | |
| transitions. Write adds setpoint write logs. Decision adds periodic | |
| control decision logs. Debug writes decision logs every 5 seconds. | |
| default: off | |
| selector: | |
| select: | |
| options: | |
| - label: "Off" | |
| value: "off" | |
| - label: "Events" | |
| value: "events" | |
| - label: "Write events" | |
| value: "write" | |
| - label: "Decisions" | |
| value: "decision" | |
| - label: "Debug" | |
| value: "debug" | |
| diagnostic_log_interval_s: | |
| name: Diagnostic decision log interval | |
| description: > | |
| Minimum interval for periodic Decisions logs. Debug decision logs | |
| use a fixed 5 s interval. Event and write logs are emitted only when | |
| the related action actually happens. | |
| default: 60 | |
| selector: | |
| number: | |
| min: 10 | |
| max: 3600 | |
| step: 10 | |
| unit_of_measurement: s | |
| mode: box | |
| full_charge_hold_helper: | |
| name: Optional full-charge hold helper | |
| description: > | |
| Optional. Leave blank unless you want the automation to remember | |
| full-charge hold state across restarts. Without this helper, the | |
| automation derives full-charge hold from current SOC, with a 1% | |
| full-battery bypass memory band for Follow PV after full. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: input_boolean | |
| low_discharge_hold_helper: | |
| name: Optional low-SOC hold helper | |
| description: > | |
| Optional. Leave blank unless you want the automation to remember | |
| low-SOC hold state across restarts. Without this helper, the | |
| automation derives low-SOC hold directly from current SOC. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: input_boolean | |
| mode: single | |
| max_exceeded: silent | |
| trigger: | |
| - platform: time_pattern | |
| seconds: "/1" | |
| variables: | |
| aio_device: !input aio_device | |
| pro3em_meter_device: !input pro3em_meter_device | |
| ecotracker_meter_device: !input ecotracker_meter_device | |
| bitshake_meter_device: !input bitshake_meter_device | |
| meter_l1_device: !input meter_l1_device | |
| meter_l2_device: !input meter_l2_device | |
| meter_l3_device: !input meter_l3_device | |
| meter_power_source: !input meter_power_source | |
| custom_meter_formula: !input custom_meter_formula | |
| meter_entity_override: !input meter_power_sensor | |
| meter_power_attribute: !input meter_power_attribute | |
| meter_l1_entity_override: !input meter_l1_power_sensor | |
| meter_l2_entity_override: !input meter_l2_power_sensor | |
| meter_l3_entity_override: !input meter_l3_power_sensor | |
| meter_import_entity_override: !input meter_import_power_sensor | |
| meter_export_entity_override: !input meter_export_power_sensor | |
| meter_l1_import_entity_override: !input meter_l1_import_power_sensor | |
| meter_l2_import_entity_override: !input meter_l2_import_power_sensor | |
| meter_l3_import_entity_override: !input meter_l3_import_power_sensor | |
| meter_l1_export_entity_override: !input meter_l1_export_power_sensor | |
| meter_l2_export_entity_override: !input meter_l2_export_power_sensor | |
| meter_l3_export_entity_override: !input meter_l3_export_power_sensor | |
| meter_sign_mode: !input meter_sign_mode | |
| meter_power_multiplier: !input meter_power_multiplier | |
| external_meter_device: >- | |
| {% set source = meter_power_source | trim %} | |
| {% if source == 'shelly_total_power' %} | |
| {{ pro3em_meter_device | string | trim }} | |
| {% elif source == 'ecotracker_power' %} | |
| {{ ecotracker_meter_device | string | trim }} | |
| {% elif source == 'bitshake_tasmota_power' %} | |
| {{ bitshake_meter_device | string | trim }} | |
| {% else %} | |
| {{ '' }} | |
| {% endif %} | |
| effective_meter_power_source: >- | |
| {% if meter_power_source | trim == 'custom' %} | |
| {{ custom_meter_formula | trim }} | |
| {% else %} | |
| {{ meter_power_source | trim }} | |
| {% endif %} | |
| gs_entity_override: !input gs_number | |
| is_entity_override: !input is_number | |
| load_entity_override: !input load_power_sensor | |
| gp_entity_override: !input grid_port_power_sensor | |
| pv_entity_override: !input pv_power_sensor | |
| soc_entity_override: !input soc_sensor | |
| min_soc_entity_override: !input min_discharge_soc_number | |
| max_soc_entity_override: !input max_charge_soc_number | |
| full_hold_entity: !input full_charge_hold_helper | |
| low_hold_entity: !input low_discharge_hold_helper | |
| full_battery_mode: !input full_battery_mode | |
| target_grid_power_static_w: !input target_grid_power_w | |
| target_grid_power_entity: !input target_grid_power_entity | |
| target_grid_power_w: >- | |
| {% set entity = target_grid_power_entity | string | trim %} | |
| {% set fallback = target_grid_power_static_w | float(0) %} | |
| {% set invalid = ['unknown', 'unavailable', 'none', ''] %} | |
| {% if entity and states(entity) not in invalid %} | |
| {{ states(entity) | float(fallback) }} | |
| {% else %} | |
| {{ fallback }} | |
| {% endif %} | |
| gs_min_w: !input gs_min_w | |
| gs_max_w: !input gs_max_w | |
| gs_resolution_w: !input gs_resolution_w | |
| gain_percent: !input gain_percent | |
| small_error_w: !input small_error_w | |
| large_error_w: !input large_error_w | |
| slow_interval_s: !input slow_interval_s | |
| medium_interval_s: !input medium_interval_s | |
| fast_interval_s: !input fast_interval_s | |
| gs_feedback_settle_s: !input gs_feedback_settle_s | |
| meter_stabilization_delay_s: !input meter_stabilization_delay_s | |
| small_max_step_w: !input small_max_step_w | |
| medium_max_step_w: !input medium_max_step_w | |
| fast_max_step_w: !input fast_max_step_w | |
| max_meter_age_s: !input max_meter_age_s | |
| min_discharge_soc: !input min_discharge_soc | |
| max_charge_soc: !input max_charge_soc | |
| ac_couple_max_charge_power_w: !input ac_couple_max_charge_power_w | |
| is_resolution_w: !input is_resolution_w | |
| diagnostic_log_level: !input diagnostic_log_level | |
| diagnostic_log_interval_s: !input diagnostic_log_interval_s | |
| gs_entity: >- | |
| {% set override = gs_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in device_entities(aio_device) if ns.entity == '' and entity.startswith('number.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% if eid.endswith('_gs') or 'grid port power setpoint' in name or '市电口功率设定' in name or ('netzanschluss' in name and 'sollwert' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| is_entity: >- | |
| {% set override = is_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in device_entities(aio_device) if ns.entity == '' and entity.startswith('number.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% if eid.endswith('_is') or 'max inverter power setpoint' in name or '最大逆变功率设定' in name or 'wechselrichterleistung' in name %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| load_entity: >- | |
| {% set override = load_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in device_entities(aio_device) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% if eid.endswith('_lp') or 'load port power' in name or '负载口功率' in name or 'lastanschluss' in name %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| gp_entity: >- | |
| {% set override = gp_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in device_entities(aio_device) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% if eid.endswith('_gp') or 'grid port power' in name or '并网口功率' in name or ('netzanschluss' in name and 'leistung' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| pv_entity: >- | |
| {% set override = pv_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in device_entities(aio_device) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% if eid.endswith('_pv') or 'pv total input power' in name or 'pv总输入功率' in name or 'pv-gesamteingangsleistung' in name %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| soc_entity: >- | |
| {% set override = soc_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in device_entities(aio_device) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% if eid.endswith('_sc') or 'battery level' in name or '系统总电量' in name or 'speicherlevel' in name %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| min_soc_entity: >- | |
| {% set override = min_soc_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in device_entities(aio_device) if ns.entity == '' and entity.startswith('number.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% set is_load_port = 'load port' in name or 'load_port' in eid or '负载口' in name %} | |
| {% set is_min_soc = 'min_discharge_soc' in eid | |
| or 'system min discharge soc' in name | |
| or 'min discharge soc' in name | |
| or ('minimum' in name and 'discharge' in name and 'soc' in name) | |
| or ('放电' in name and ('下限' in name or '最低' in name)) %} | |
| {% if is_min_soc and not is_load_port %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| max_soc_entity: >- | |
| {% set override = max_soc_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in device_entities(aio_device) if ns.entity == '' and entity.startswith('number.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% set is_max_soc = 'max_charge_soc' in eid | |
| or 'system max charge soc' in name | |
| or 'max charge soc' in name | |
| or ('maximum' in name and 'charge' in name and 'soc' in name) | |
| or ('充电' in name and ('上限' in name or '最高' in name)) %} | |
| {% if is_max_soc %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| meter_entity: >- | |
| {% set override = meter_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in (device_entities(external_meter_device) if external_meter_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% set unit = (state_attr(entity, 'unit_of_measurement') or '') | lower %} | |
| {% set dc = (state_attr(entity, 'device_class') or '') | lower %} | |
| {% set is_power = dc == 'power' or unit in ['w', 'kw'] or 'power' in name or 'leistung' in name %} | |
| {% set is_excluded = 'energy' in name or 'voltage' in name or 'current' in name or '电量' in name or '电压' in name or '电流' in name or 'energie' in name or 'spannung' in name or 'strom' in name %} | |
| {% set is_phase = ' l1' in name or ' l2' in name or ' l3' in name or '_l1' in eid or '_l2' in eid or '_l3' in eid or 'phase' in name %} | |
| {% set is_pair = 'import' in name or 'export' in name or 'returned' in name or 'consumption' in name or 'feed' in name %} | |
| {% if is_power and not is_excluded and not is_phase and not is_pair %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {% if ns.entity == '' %} | |
| {% for entity in (device_entities(external_meter_device) if external_meter_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set unit = (state_attr(entity, 'unit_of_measurement') or '') | lower %} | |
| {% set dc = (state_attr(entity, 'device_class') or '') | lower %} | |
| {% if dc == 'power' or unit in ['w', 'kw'] %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {% endif %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| meter_l1_entity: >- | |
| {% set override = meter_l1_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% set search_device = meter_l1_device if (meter_power_source | trim) == 'shelly_three_phase_sum' else external_meter_device %} | |
| {% for entity in (device_entities(search_device) if search_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% set unit = (state_attr(entity, 'unit_of_measurement') or '') | lower %} | |
| {% set dc = (state_attr(entity, 'device_class') or '') | lower %} | |
| {% set is_phase_match = (meter_power_source | trim) == 'shelly_three_phase_sum' | |
| or 'l1' in name or '_l1' in eid or 'phase a' in name %} | |
| {% if (dc == 'power' or unit in ['w', 'kw'] or 'power' in name or 'leistung' in name) and is_phase_match and not ('import' in name or 'export' in name or 'returned' in name or 'consumption' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| meter_l2_entity: >- | |
| {% set override = meter_l2_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% set search_device = meter_l2_device if (meter_power_source | trim) == 'shelly_three_phase_sum' else external_meter_device %} | |
| {% for entity in (device_entities(search_device) if search_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% set unit = (state_attr(entity, 'unit_of_measurement') or '') | lower %} | |
| {% set dc = (state_attr(entity, 'device_class') or '') | lower %} | |
| {% set is_phase_match = (meter_power_source | trim) == 'shelly_three_phase_sum' | |
| or 'l2' in name or '_l2' in eid or 'phase b' in name %} | |
| {% if (dc == 'power' or unit in ['w', 'kw'] or 'power' in name or 'leistung' in name) and is_phase_match and not ('import' in name or 'export' in name or 'returned' in name or 'consumption' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| meter_l3_entity: >- | |
| {% set override = meter_l3_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% set search_device = meter_l3_device if (meter_power_source | trim) == 'shelly_three_phase_sum' else external_meter_device %} | |
| {% for entity in (device_entities(search_device) if search_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% set unit = (state_attr(entity, 'unit_of_measurement') or '') | lower %} | |
| {% set dc = (state_attr(entity, 'device_class') or '') | lower %} | |
| {% set is_phase_match = (meter_power_source | trim) == 'shelly_three_phase_sum' | |
| or 'l3' in name or '_l3' in eid or 'phase c' in name %} | |
| {% if (dc == 'power' or unit in ['w', 'kw'] or 'power' in name or 'leistung' in name) and is_phase_match and not ('import' in name or 'export' in name or 'returned' in name or 'consumption' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| meter_import_entity: >- | |
| {% set override = meter_import_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in (device_entities(external_meter_device) if external_meter_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set unit = (state_attr(entity, 'unit_of_measurement') or '') | lower %} | |
| {% set dc = (state_attr(entity, 'device_class') or '') | lower %} | |
| {% if (dc == 'power' or unit in ['w', 'kw'] or 'power' in name or 'leistung' in name) and ('import' in name or 'consumption' in name or 'grid in' in name) and not ('l1' in name or 'l2' in name or 'l3' in name or 'phase' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| meter_export_entity: >- | |
| {% set override = meter_export_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in (device_entities(external_meter_device) if external_meter_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set unit = (state_attr(entity, 'unit_of_measurement') or '') | lower %} | |
| {% set dc = (state_attr(entity, 'device_class') or '') | lower %} | |
| {% if (dc == 'power' or unit in ['w', 'kw'] or 'power' in name or 'leistung' in name) and ('export' in name or 'returned' in name or 'feed' in name) and not ('l1' in name or 'l2' in name or 'l3' in name or 'phase' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| meter_l1_import_entity: >- | |
| {% set override = meter_l1_import_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in (device_entities(external_meter_device) if external_meter_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% if ('l1' in name or '_l1' in eid or 'phase a' in name) and ('import' in name or 'consumption' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| meter_l2_import_entity: >- | |
| {% set override = meter_l2_import_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in (device_entities(external_meter_device) if external_meter_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% if ('l2' in name or '_l2' in eid or 'phase b' in name) and ('import' in name or 'consumption' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| meter_l3_import_entity: >- | |
| {% set override = meter_l3_import_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in (device_entities(external_meter_device) if external_meter_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% if ('l3' in name or '_l3' in eid or 'phase c' in name) and ('import' in name or 'consumption' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| meter_l1_export_entity: >- | |
| {% set override = meter_l1_export_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in (device_entities(external_meter_device) if external_meter_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% if ('l1' in name or '_l1' in eid or 'phase a' in name) and ('export' in name or 'returned' in name or 'feed' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| meter_l2_export_entity: >- | |
| {% set override = meter_l2_export_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in (device_entities(external_meter_device) if external_meter_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% if ('l2' in name or '_l2' in eid or 'phase b' in name) and ('export' in name or 'returned' in name or 'feed' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| meter_l3_export_entity: >- | |
| {% set override = meter_l3_export_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in (device_entities(external_meter_device) if external_meter_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% if ('l3' in name or '_l3' in eid or 'phase c' in name) and ('export' in name or 'returned' in name or 'feed' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| condition: [] | |
| action: | |
| # Validate all critical inputs before calculating or writing control targets. | |
| - variables: | |
| failsafe_bad_states: | |
| - unknown | |
| - unavailable | |
| - none | |
| - "" | |
| failsafe_signed_sources: | |
| - shelly_total_power | |
| - bitshake_tasmota_power | |
| - ecotracker_power | |
| - custom_signed_total_power | |
| failsafe_phase_sources: | |
| - shelly_three_phase_sum | |
| - custom_three_phase_sum | |
| failsafe_phase_pair_sources: | |
| - shelly_three_phase_import_export | |
| - custom_three_phase_import_export | |
| failsafe_limit_s: "{{ max_meter_age_s | float(30) }}" | |
| failsafe_invalid_confirm_s: >- | |
| {{ [failsafe_limit_s | float(30), | |
| (gs_feedback_settle_s | float(5)) * 2, | |
| 10] | max }} | |
| failsafe_gs_valid: >- | |
| {% set entity = gs_entity | string | trim %} | |
| {% set value = states(entity) %} | |
| {{ entity.startswith('number.') | |
| and value not in failsafe_bad_states | |
| and is_number(value) }} | |
| failsafe_is_valid: >- | |
| {% set entity = is_entity | string | trim %} | |
| {% set value = states(entity) %} | |
| {{ entity.startswith('number.') | |
| and value not in failsafe_bad_states | |
| and is_number(value) }} | |
| failsafe_load_valid: >- | |
| {% set value = states(load_entity | trim) %} | |
| {{ load_entity | string | trim | length > 0 | |
| and value not in failsafe_bad_states | |
| and is_number(value) }} | |
| failsafe_gp_valid: >- | |
| {% set value = states(gp_entity | trim) %} | |
| {{ gp_entity | string | trim | length > 0 | |
| and value not in failsafe_bad_states | |
| and is_number(value) }} | |
| failsafe_pv_valid: >- | |
| {% set value = states(pv_entity | trim) %} | |
| {{ pv_entity | string | trim | length > 0 | |
| and value not in failsafe_bad_states | |
| and is_number(value) }} | |
| failsafe_soc_valid: >- | |
| {% set value = states(soc_entity | trim) %} | |
| {{ soc_entity | string | trim | length > 0 | |
| and value not in failsafe_bad_states | |
| and is_number(value) }} | |
| failsafe_min_soc_valid: >- | |
| {% set value = states(min_soc_entity | trim) %} | |
| {{ min_soc_entity | string | trim | length > 0 | |
| and value not in failsafe_bad_states | |
| and is_number(value) }} | |
| failsafe_max_soc_valid: >- | |
| {% set value = states(max_soc_entity | trim) %} | |
| {{ max_soc_entity | string | trim | length > 0 | |
| and value not in failsafe_bad_states | |
| and is_number(value) }} | |
| failsafe_meter_valid: >- | |
| {% set source = effective_meter_power_source | trim %} | |
| {% set bad = failsafe_bad_states %} | |
| {% if source in failsafe_signed_sources %} | |
| {% set value = states(meter_entity | trim) %} | |
| {{ meter_entity | string | trim | length > 0 | |
| and value not in bad | |
| and is_number(value) }} | |
| {% elif source == 'ecotracker_attribute_power' %} | |
| {% set value = states(meter_entity | trim) %} | |
| {% set power = state_attr(meter_entity | trim, meter_power_attribute) %} | |
| {{ meter_entity | string | trim | length > 0 | |
| and value not in bad | |
| and power is not none | |
| and is_number(power) }} | |
| {% elif source in failsafe_phase_sources %} | |
| {{ meter_l1_entity | string | trim | length > 0 | |
| and meter_l2_entity | string | trim | length > 0 | |
| and meter_l3_entity | string | trim | length > 0 | |
| and states(meter_l1_entity | trim) not in bad | |
| and states(meter_l2_entity | trim) not in bad | |
| and states(meter_l3_entity | trim) not in bad | |
| and is_number(states(meter_l1_entity | trim)) | |
| and is_number(states(meter_l2_entity | trim)) | |
| and is_number(states(meter_l3_entity | trim)) }} | |
| {% elif source == 'import_export_pair' %} | |
| {{ meter_import_entity | string | trim | length > 0 | |
| and meter_export_entity | string | trim | length > 0 | |
| and states(meter_import_entity | trim) not in bad | |
| and states(meter_export_entity | trim) not in bad | |
| and is_number(states(meter_import_entity | trim)) | |
| and is_number(states(meter_export_entity | trim)) }} | |
| {% elif source in failsafe_phase_pair_sources %} | |
| {{ meter_l1_import_entity | string | trim | length > 0 | |
| and meter_l2_import_entity | string | trim | length > 0 | |
| and meter_l3_import_entity | string | trim | length > 0 | |
| and meter_l1_export_entity | string | trim | length > 0 | |
| and meter_l2_export_entity | string | trim | length > 0 | |
| and meter_l3_export_entity | string | trim | length > 0 | |
| and states(meter_l1_import_entity | trim) not in bad | |
| and states(meter_l2_import_entity | trim) not in bad | |
| and states(meter_l3_import_entity | trim) not in bad | |
| and states(meter_l1_export_entity | trim) not in bad | |
| and states(meter_l2_export_entity | trim) not in bad | |
| and states(meter_l3_export_entity | trim) not in bad | |
| and is_number(states(meter_l1_import_entity | trim)) | |
| and is_number(states(meter_l2_import_entity | trim)) | |
| and is_number(states(meter_l3_import_entity | trim)) | |
| and is_number(states(meter_l1_export_entity | trim)) | |
| and is_number(states(meter_l2_export_entity | trim)) | |
| and is_number(states(meter_l3_export_entity | trim)) }} | |
| {% else %} | |
| {{ false }} | |
| {% endif %} | |
| failsafe_meter_fresh: >- | |
| {% set source = effective_meter_power_source | trim %} | |
| {% set limit = failsafe_limit_s | float(30) %} | |
| {% if not (failsafe_meter_valid | bool(false)) %} | |
| {{ false }} | |
| {% elif source in failsafe_signed_sources or source == 'ecotracker_attribute_power' %} | |
| {% set age = as_timestamp(now()) - | |
| as_timestamp(states[meter_entity | trim].last_reported | |
| | default(states[meter_entity | trim].last_updated), 0) %} | |
| {{ age >= 0 and age <= limit }} | |
| {% elif source in failsafe_phase_sources %} | |
| {% set ages = [ | |
| as_timestamp(now()) - as_timestamp(states[meter_l1_entity | trim].last_reported | default(states[meter_l1_entity | trim].last_updated), 0), | |
| as_timestamp(now()) - as_timestamp(states[meter_l2_entity | trim].last_reported | default(states[meter_l2_entity | trim].last_updated), 0), | |
| as_timestamp(now()) - as_timestamp(states[meter_l3_entity | trim].last_reported | default(states[meter_l3_entity | trim].last_updated), 0)] %} | |
| {{ ages | select('ge', 0) | select('le', limit) | list | count > 0 }} | |
| {% elif source == 'import_export_pair' %} | |
| {% set import_age = as_timestamp(now()) - as_timestamp(states[meter_import_entity | trim].last_reported | default(states[meter_import_entity | trim].last_updated), 0) %} | |
| {% set export_age = as_timestamp(now()) - as_timestamp(states[meter_export_entity | trim].last_reported | default(states[meter_export_entity | trim].last_updated), 0) %} | |
| {{ import_age >= 0 and import_age <= limit | |
| and export_age >= 0 and export_age <= limit }} | |
| {% elif source in failsafe_phase_pair_sources %} | |
| {% set ages = [ | |
| as_timestamp(now()) - as_timestamp(states[meter_l1_import_entity | trim].last_reported | default(states[meter_l1_import_entity | trim].last_updated), 0), | |
| as_timestamp(now()) - as_timestamp(states[meter_l2_import_entity | trim].last_reported | default(states[meter_l2_import_entity | trim].last_updated), 0), | |
| as_timestamp(now()) - as_timestamp(states[meter_l3_import_entity | trim].last_reported | default(states[meter_l3_import_entity | trim].last_updated), 0), | |
| as_timestamp(now()) - as_timestamp(states[meter_l1_export_entity | trim].last_reported | default(states[meter_l1_export_entity | trim].last_updated), 0), | |
| as_timestamp(now()) - as_timestamp(states[meter_l2_export_entity | trim].last_reported | default(states[meter_l2_export_entity | trim].last_updated), 0), | |
| as_timestamp(now()) - as_timestamp(states[meter_l3_export_entity | trim].last_reported | default(states[meter_l3_export_entity | trim].last_updated), 0)] %} | |
| {{ ages | select('ge', 0) | select('le', limit) | list | count > 0 }} | |
| {% else %} | |
| {{ false }} | |
| {% endif %} | |
| - variables: | |
| failsafe_inputs_ok: >- | |
| {{ failsafe_gs_valid | bool(false) | |
| and failsafe_is_valid | bool(false) | |
| and failsafe_meter_valid | bool(false) | |
| and failsafe_meter_fresh | bool(false) | |
| and failsafe_gp_valid | bool(false) | |
| and failsafe_load_valid | bool(false) | |
| and failsafe_pv_valid | bool(false) | |
| and failsafe_soc_valid | bool(false) | |
| and failsafe_min_soc_valid | bool(false) | |
| and failsafe_max_soc_valid | bool(false) }} | |
| failsafe_reason: >- | |
| {% set ns = namespace(parts=[]) %} | |
| {% if not (failsafe_gs_valid | bool(false)) %}{% set ns.parts = ns.parts + ['GS invalid'] %}{% endif %} | |
| {% if not (failsafe_is_valid | bool(false)) %}{% set ns.parts = ns.parts + ['IS invalid'] %}{% endif %} | |
| {% if not (failsafe_meter_valid | bool(false)) %} | |
| {% set ns.parts = ns.parts + ['grid meter invalid'] %} | |
| {% elif not (failsafe_meter_fresh | bool(false)) %} | |
| {% set ns.parts = ns.parts + ['grid meter stale; GS held'] %} | |
| {% endif %} | |
| {% if not (failsafe_gp_valid | bool(false)) %} | |
| {% set ns.parts = ns.parts + ['grid port invalid'] %} | |
| {% endif %} | |
| {% if not (failsafe_load_valid | bool(false)) %} | |
| {% set ns.parts = ns.parts + ['load port invalid'] %} | |
| {% endif %} | |
| {% if not (failsafe_pv_valid | bool(false)) %} | |
| {% set ns.parts = ns.parts + ['PV invalid'] %} | |
| {% endif %} | |
| {% if not (failsafe_soc_valid | bool(false)) %} | |
| {% set ns.parts = ns.parts + ['SOC invalid'] %} | |
| {% endif %} | |
| {% if not (failsafe_min_soc_valid | bool(false)) %}{% set ns.parts = ns.parts + ['minimum SOC invalid'] %}{% endif %} | |
| {% if not (failsafe_max_soc_valid | bool(false)) %}{% set ns.parts = ns.parts + ['maximum SOC invalid'] %}{% endif %} | |
| {{ ns.parts | join(', ') }} | |
| failsafe_hard_failure_confirmed: >- | |
| {% set bad = failsafe_bad_states %} | |
| {% set confirm = failsafe_invalid_confirm_s | float(30) %} | |
| {% set ns = namespace(ages=[]) %} | |
| {% for entity in [is_entity, gp_entity, load_entity, pv_entity, soc_entity, | |
| min_soc_entity, max_soc_entity] %} | |
| {% set entity_id = entity | string | trim %} | |
| {% if entity_id | length > 0 | |
| and (states(entity_id) in bad or not is_number(states(entity_id))) %} | |
| {% if expand(entity_id) | count > 0 %} | |
| {% set age = as_timestamp(now()) - as_timestamp(states[entity_id].last_changed, 0) %} | |
| {% set ns.ages = ns.ages + [age] %} | |
| {% endif %} | |
| {% endif %} | |
| {% endfor %} | |
| {% if not (failsafe_meter_valid | bool(false)) %} | |
| {% set source = effective_meter_power_source | trim %} | |
| {% if source in failsafe_signed_sources or source == 'ecotracker_attribute_power' %} | |
| {% set meter_entities = [meter_entity] %} | |
| {% elif source in failsafe_phase_sources %} | |
| {% set meter_entities = [meter_l1_entity, meter_l2_entity, meter_l3_entity] %} | |
| {% elif source == 'import_export_pair' %} | |
| {% set meter_entities = [meter_import_entity, meter_export_entity] %} | |
| {% elif source in failsafe_phase_pair_sources %} | |
| {% set meter_entities = [meter_l1_import_entity, meter_l2_import_entity, | |
| meter_l3_import_entity, meter_l1_export_entity, | |
| meter_l2_export_entity, meter_l3_export_entity] %} | |
| {% else %} | |
| {% set meter_entities = [] %} | |
| {% endif %} | |
| {% for entity in meter_entities %} | |
| {% set entity_id = entity | string | trim %} | |
| {% if entity_id | length > 0 | |
| and expand(entity_id) | count > 0 | |
| and source == 'ecotracker_attribute_power' | |
| and (state_attr(entity_id, meter_power_attribute) is none | |
| or not is_number(state_attr(entity_id, meter_power_attribute))) %} | |
| {% set age = as_timestamp(now()) - as_timestamp(states[entity_id].last_updated, 0) %} | |
| {% set ns.ages = ns.ages + [age] %} | |
| {% elif entity_id | length > 0 | |
| and expand(entity_id) | count > 0 | |
| and (states(entity_id) in bad or not is_number(states(entity_id))) %} | |
| {% set age = as_timestamp(now()) - as_timestamp(states[entity_id].last_changed, 0) %} | |
| {% set ns.ages = ns.ages + [age] %} | |
| {% endif %} | |
| {% endfor %} | |
| {% endif %} | |
| {{ ns.ages | count > 0 and (ns.ages | min) >= confirm }} | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: "{{ not (failsafe_inputs_ok | bool(false)) }}" | |
| sequence: | |
| - variables: | |
| failsafe_should_reset_gs: >- | |
| {{ failsafe_gs_valid | bool(false) | |
| and (failsafe_hard_failure_confirmed | bool(false)) | |
| and (states(gs_entity | trim) | float(0) | abs) >= 0.5 }} | |
| failsafe_log_due: >- | |
| {% set level = diagnostic_log_level | string | trim %} | |
| {% set interval = [diagnostic_log_interval_s | int(60), 60] | max %} | |
| {{ level in ['events', 'write', 'decision', 'debug'] | |
| and (as_timestamp(now()) | int(0)) % interval == 0 }} | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: "{{ failsafe_should_reset_gs | bool(false) }}" | |
| sequence: | |
| - service: number.set_value | |
| continue_on_error: true | |
| target: | |
| entity_id: "{{ gs_entity | trim }}" | |
| data: | |
| value: 0 | |
| - delay: | |
| seconds: "{{ [gs_feedback_settle_s | float(5), 1] | max }}" | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: "{{ failsafe_log_due | bool(false) }}" | |
| sequence: | |
| - service: system_log.write | |
| data: | |
| level: warning | |
| message: >- | |
| SunEnergyXT zero feed-in | fail-safe active, control cycle stopped | GS reset={{ failsafe_should_reset_gs | bool(false) }} | reason: {{ failsafe_reason }} | |
| - stop: >- | |
| SunEnergyXT fail-safe: invalid input or stale meter sample; setpoints held. | |
| - variables: | |
| meter_signed_total_sources: | |
| - shelly_total_power | |
| - bitshake_tasmota_power | |
| - ecotracker_power | |
| - custom_signed_total_power | |
| meter_phase_sum_sources: | |
| - shelly_three_phase_sum | |
| - custom_three_phase_sum | |
| meter_phase_pair_sources: | |
| - shelly_three_phase_import_export | |
| - custom_three_phase_import_export | |
| meter_import_positive_presets: | |
| - shelly_total_power | |
| - shelly_three_phase_sum | |
| - bitshake_tasmota_power | |
| - ecotracker_power | |
| - ecotracker_attribute_power | |
| - custom_signed_total_power | |
| - custom_three_phase_sum | |
| meter_raw_power_before_multiplier_w: >- | |
| {% set source = effective_meter_power_source | trim %} | |
| {% if source in meter_signed_total_sources %} | |
| {{ states(meter_entity | trim) | float(0) }} | |
| {% elif source == 'ecotracker_attribute_power' %} | |
| {{ state_attr(meter_entity | trim, meter_power_attribute) | float(0) }} | |
| {% elif source in meter_phase_sum_sources %} | |
| {{ (states(meter_l1_entity | trim) | float(0)) | |
| + (states(meter_l2_entity | trim) | float(0)) | |
| + (states(meter_l3_entity | trim) | float(0)) }} | |
| {% elif source == 'import_export_pair' %} | |
| {{ (states(meter_export_entity | trim) | float(0)) | |
| - (states(meter_import_entity | trim) | float(0)) }} | |
| {% elif source in meter_phase_pair_sources %} | |
| {{ (states(meter_l1_export_entity | trim) | float(0)) | |
| + (states(meter_l2_export_entity | trim) | float(0)) | |
| + (states(meter_l3_export_entity | trim) | float(0)) | |
| - (states(meter_l1_import_entity | trim) | float(0)) | |
| - (states(meter_l2_import_entity | trim) | float(0)) | |
| - (states(meter_l3_import_entity | trim) | float(0)) }} | |
| {% else %} | |
| {{ 0 }} | |
| {% endif %} | |
| meter_raw_w: >- | |
| {{ (meter_raw_power_before_multiplier_w | float(0)) | |
| * (meter_power_multiplier | float(1)) }} | |
| meter_control_sample_ts: >- | |
| {% set source = effective_meter_power_source | trim %} | |
| {% if source in meter_signed_total_sources or source == 'ecotracker_attribute_power' %} | |
| {{ as_timestamp(states[meter_entity | trim].last_reported | default(states[meter_entity | trim].last_updated), 0) }} | |
| {% elif source in meter_phase_sum_sources %} | |
| {{ [as_timestamp(states[meter_l1_entity | trim].last_reported | default(states[meter_l1_entity | trim].last_updated), 0), | |
| as_timestamp(states[meter_l2_entity | trim].last_reported | default(states[meter_l2_entity | trim].last_updated), 0), | |
| as_timestamp(states[meter_l3_entity | trim].last_reported | default(states[meter_l3_entity | trim].last_updated), 0)] | max }} | |
| {% elif source == 'import_export_pair' %} | |
| {{ [as_timestamp(states[meter_import_entity | trim].last_reported | default(states[meter_import_entity | trim].last_updated), 0), | |
| as_timestamp(states[meter_export_entity | trim].last_reported | default(states[meter_export_entity | trim].last_updated), 0)] | max }} | |
| {% elif source in meter_phase_pair_sources %} | |
| {{ [as_timestamp(states[meter_l1_import_entity | trim].last_reported | default(states[meter_l1_import_entity | trim].last_updated), 0), | |
| as_timestamp(states[meter_l2_import_entity | trim].last_reported | default(states[meter_l2_import_entity | trim].last_updated), 0), | |
| as_timestamp(states[meter_l3_import_entity | trim].last_reported | default(states[meter_l3_import_entity | trim].last_updated), 0), | |
| as_timestamp(states[meter_l1_export_entity | trim].last_reported | default(states[meter_l1_export_entity | trim].last_updated), 0), | |
| as_timestamp(states[meter_l2_export_entity | trim].last_reported | default(states[meter_l2_export_entity | trim].last_updated), 0), | |
| as_timestamp(states[meter_l3_export_entity | trim].last_reported | default(states[meter_l3_export_entity | trim].last_updated), 0)] | max }} | |
| {% else %} | |
| {{ 0 }} | |
| {% endif %} | |
| effective_meter_sign_mode: >- | |
| {% set source = effective_meter_power_source | trim %} | |
| {% if source == 'import_export_pair' or source in meter_phase_pair_sources %} | |
| {{ 'export_positive' }} | |
| {% elif meter_sign_mode == 'auto_from_preset' %} | |
| {% if source in meter_import_positive_presets %} | |
| {{ 'import_positive' }} | |
| {% else %} | |
| {{ 'export_positive' }} | |
| {% endif %} | |
| {% else %} | |
| {{ meter_sign_mode }} | |
| {% endif %} | |
| meter_factor: >- | |
| {{ 1 if (effective_meter_sign_mode | trim) == 'export_positive' else -1 }} | |
| current_gs_w: "{{ states(gs_entity | trim) | float(0) }}" | |
| inverter_rated_w: 2400 | |
| has_soc: >- | |
| {{ soc_entity | string | length > 0 | |
| and states(soc_entity | trim) not in ['unknown', 'unavailable', 'none', ''] }} | |
| has_is: >- | |
| {{ is_entity | string | length > 0 | |
| and states(is_entity | trim) not in ['unknown', 'unavailable', 'none', ''] }} | |
| has_load: >- | |
| {{ load_entity | string | length > 0 | |
| and states(load_entity | trim) not in ['unknown', 'unavailable', 'none', ''] }} | |
| has_gp: >- | |
| {{ gp_entity | string | length > 0 | |
| and states(gp_entity | trim) not in ['unknown', 'unavailable', 'none', ''] }} | |
| has_pv: >- | |
| {{ pv_entity | string | length > 0 | |
| and states(pv_entity | trim) not in ['unknown', 'unavailable', 'none', ''] }} | |
| - variables: | |
| gs_entity_min_w: >- | |
| {% if gs_entity | string | trim | length > 0 %} | |
| {{ state_attr(gs_entity | trim, 'min') | float(gs_min_w | float(-2400)) }} | |
| {% else %} | |
| {{ gs_min_w | float(-2400) }} | |
| {% endif %} | |
| gs_entity_max_w: >- | |
| {% if gs_entity | string | trim | length > 0 %} | |
| {{ state_attr(gs_entity | trim, 'max') | float(gs_max_w | float(2400)) }} | |
| {% else %} | |
| {{ gs_max_w | float(2400) }} | |
| {% endif %} | |
| is_entity_min_w: >- | |
| {% if has_is | bool(false) %} | |
| {{ state_attr(is_entity | trim, 'min') | float(0) }} | |
| {% else %} | |
| {{ 0 }} | |
| {% endif %} | |
| is_entity_max_w: >- | |
| {% if has_is | bool(false) %} | |
| {{ state_attr(is_entity | trim, 'max') | float(inverter_rated_w | float(2400)) }} | |
| {% else %} | |
| {{ inverter_rated_w | float(2400) }} | |
| {% endif %} | |
| normalized_grid_w: >- | |
| {{ (meter_raw_w | float(0)) * (meter_factor | float(1)) }} | |
| soc_value: >- | |
| {% if has_soc | bool(false) %} | |
| {{ states(soc_entity | trim) | float(50) }} | |
| {% else %} | |
| {{ 50 }} | |
| {% endif %} | |
| current_is_w: >- | |
| {% if has_is | bool(false) %} | |
| {{ states(is_entity | trim) | float(0) }} | |
| {% else %} | |
| {{ 0 }} | |
| {% endif %} | |
| load_power_raw_w: >- | |
| {% if has_load | bool(false) %} | |
| {{ states(load_entity | trim) | float(0) }} | |
| {% else %} | |
| {{ 0 }} | |
| {% endif %} | |
| load_power_w: >- | |
| {% set raw = load_power_raw_w | float(0) %} | |
| {% if raw > 32767 %} | |
| {{ raw - 65536 }} | |
| {% else %} | |
| {{ raw }} | |
| {% endif %} | |
| gp_power_raw_w: >- | |
| {% if has_gp | bool(false) %} | |
| {{ states(gp_entity | trim) | float(0) }} | |
| {% else %} | |
| {{ 0 }} | |
| {% endif %} | |
| gp_power_w: >- | |
| {% set raw = gp_power_raw_w | float(0) %} | |
| {% if raw > 32767 %} | |
| {{ raw - 65536 }} | |
| {% else %} | |
| {{ raw }} | |
| {% endif %} | |
| pv_power_w: >- | |
| {% if has_pv | bool(false) %} | |
| {{ states(pv_entity | trim) | float(0) }} | |
| {% else %} | |
| {{ 0 }} | |
| {% endif %} | |
| effective_min_discharge_soc: >- | |
| {{ min_discharge_soc | float(10) }} | |
| effective_max_charge_soc: >- | |
| {{ max_charge_soc | float(100) }} | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ ((states(min_soc_entity | trim) | float(0)) - (min_discharge_soc | float(10))) | abs >= 1 }} | |
| sequence: | |
| - service: number.set_value | |
| continue_on_error: true | |
| target: | |
| entity_id: "{{ min_soc_entity | trim }}" | |
| data: | |
| value: "{{ min_discharge_soc }}" | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ ((states(max_soc_entity | trim) | float(0)) - (max_charge_soc | float(100))) | abs >= 1 }} | |
| sequence: | |
| - service: number.set_value | |
| continue_on_error: true | |
| target: | |
| entity_id: "{{ max_soc_entity | trim }}" | |
| data: | |
| value: "{{ max_charge_soc }}" | |
| - variables: | |
| soc_hysteresis_value: 5 | |
| has_full_hold_helper: >- | |
| {{ full_hold_entity | string | trim | length > 0 }} | |
| has_low_hold_helper: >- | |
| {{ low_hold_entity | string | trim | length > 0 }} | |
| current_full_hold_state: >- | |
| {{ has_full_hold_helper | bool(false) | |
| and is_state(full_hold_entity | trim, 'on') }} | |
| current_low_hold_state: >- | |
| {{ has_low_hold_helper | bool(false) | |
| and is_state(low_hold_entity | trim, 'on') }} | |
| state_mismatch_threshold_w: "{{ [small_error_w | float(30), 30] | max }}" | |
| full_hold_memory_band_percent: 1 | |
| full_hold_bypass_command_active: >- | |
| {% set gs_tolerance = [gs_resolution_w | float(1), 5] | max %} | |
| {% set is_tolerance = [is_resolution_w | float(10), 20] | max %} | |
| {% set bypass_is_target = [is_entity_max_w | float(inverter_rated_w | float(2400)), inverter_rated_w | float(2400)] | min %} | |
| {{ full_battery_mode == 'follow_pv' | |
| and has_is | bool(false) | |
| and ((current_gs_w | float(0)) | abs) <= (gs_tolerance | float(5)) | |
| and (current_is_w | float(0)) >= ((bypass_is_target | float(2400)) - (is_tolerance | float(20))) }} | |
| full_hold_bypass_memory_active: >- | |
| {% set soc = soc_value | float(50) %} | |
| {% set upper = effective_max_charge_soc | float(100) %} | |
| {{ full_hold_bypass_command_active | bool(false) | |
| and soc < upper | |
| and soc >= (upper - (full_hold_memory_band_percent | float(1))) }} | |
| full_hold_state: >- | |
| {% set soc = soc_value | float(50) %} | |
| {% set upper = effective_max_charge_soc | float(100) %} | |
| {% set release = upper - (soc_hysteresis_value | float(2)) %} | |
| {% if has_full_hold_helper | bool(false) %} | |
| {% if soc >= upper %} | |
| {{ true }} | |
| {% elif soc <= release %} | |
| {{ false }} | |
| {% else %} | |
| {{ current_full_hold_state | bool(false) }} | |
| {% endif %} | |
| {% else %} | |
| {{ soc >= upper or full_hold_bypass_memory_active | bool(false) }} | |
| {% endif %} | |
| low_hold_state_prelim: >- | |
| {% set soc = soc_value | float(50) %} | |
| {% set lower = effective_min_discharge_soc | float(10) %} | |
| {% set release = lower + (soc_hysteresis_value | float(2)) %} | |
| {% if has_low_hold_helper | bool(false) %} | |
| {% if soc <= lower %} | |
| {{ true }} | |
| {% elif soc >= release %} | |
| {{ false }} | |
| {% else %} | |
| {{ current_low_hold_state | bool(false) }} | |
| {% endif %} | |
| {% else %} | |
| {{ soc <= lower }} | |
| {% endif %} | |
| - variables: | |
| control_error_w: >- | |
| {{ (normalized_grid_w | float(0)) - (target_grid_power_w | float(0)) }} | |
| positive_load_power_w: >- | |
| {{ [load_power_w | float(0), 0] | max }} | |
| load_port_backfeed_w: >- | |
| {{ [(-1 * (load_power_w | float(0))), 0] | max }} | |
| effective_load_power_w: >- | |
| {{ load_power_w | float(0) if has_load | bool(false) else 0 }} | |
| hardware_output_limit_w: >- | |
| {% if full_battery_mode == 'follow_pv' | |
| and full_hold_state | bool(false) | |
| and has_pv | bool(false) %} | |
| {{ inverter_rated_w | float(2400) }} | |
| {% else %} | |
| {{ [current_is_w | float(0), inverter_rated_w | float(2400)] | min }} | |
| {% endif %} | |
| load_port_from_inverter_w: >- | |
| {% if full_battery_mode == 'follow_pv' | |
| and full_hold_state | bool(false) | |
| and has_pv | bool(false) %} | |
| {{ [positive_load_power_w | float(0), inverter_rated_w | float(2400)] | min }} | |
| {% else %} | |
| {{ [positive_load_power_w | float(0), [current_is_w | float(0), inverter_rated_w | float(2400)] | min] | min }} | |
| {% endif %} | |
| load_port_grid_supply_w: >- | |
| {% if full_battery_mode == 'follow_pv' | |
| and full_hold_state | bool(false) | |
| and has_pv | bool(false) %} | |
| {{ [(positive_load_power_w | float(0)) - ([positive_load_power_w | float(0), inverter_rated_w | float(2400)] | min), 0] | max }} | |
| {% else %} | |
| {{ [(positive_load_power_w | float(0)) - ([positive_load_power_w | float(0), [current_is_w | float(0), inverter_rated_w | float(2400)] | min] | min), 0] | max }} | |
| {% endif %} | |
| estimated_home_load_w: >- | |
| {{ (gp_power_w | float(0)) - (normalized_grid_w | float(0)) }} | |
| - variables: | |
| load_grid_supply_target_w: >- | |
| {% if (positive_load_power_w | float(0)) > 0 %} | |
| {{ [[-1 * ((target_grid_power_w | float(0)) + (estimated_home_load_w | float(0))), 0] | max, positive_load_power_w | float(0)] | min }} | |
| {% else %} | |
| {{ 0 }} | |
| {% endif %} | |
| raw_grid_port_target_w: >- | |
| {% if (load_port_backfeed_w | float(0)) > 0 %} | |
| {{ (target_grid_power_w | float(0)) + (estimated_home_load_w | float(0)) }} | |
| {% else %} | |
| {{ (target_grid_power_w | float(0)) + (estimated_home_load_w | float(0)) + (load_grid_supply_target_w | float(0)) }} | |
| {% endif %} | |
| desired_load_port_output_w: >- | |
| {% if (positive_load_power_w | float(0)) > 0 %} | |
| {{ (positive_load_power_w | float(0)) - (load_grid_supply_target_w | float(0)) }} | |
| {% else %} | |
| {{ 0 }} | |
| {% endif %} | |
| bypass_active: >- | |
| {{ full_battery_mode == 'follow_pv' | |
| and full_hold_state | bool(false) | |
| and has_pv | bool(false) }} | |
| bypass_grid_available_w: >- | |
| {% if full_battery_mode == 'follow_pv' | |
| and full_hold_state | bool(false) | |
| and has_pv | bool(false) %} | |
| {% set pv_after_load = [(pv_power_w | float(0)) - (positive_load_power_w | float(0)), 0] | max %} | |
| {{ [[pv_after_load + (load_port_backfeed_w | float(0)), 0] | max, gs_max_w | float(2400)] | min }} | |
| {% else %} | |
| {{ 0 }} | |
| {% endif %} | |
| bypass_only_candidate: >- | |
| {{ bypass_active | bool(false) | |
| and ([raw_grid_port_target_w | float(0), 0] | max) <= (bypass_grid_available_w | float(0)) }} | |
| bypass_feedback_missing: >- | |
| {% set threshold = state_mismatch_threshold_w | float(30) %} | |
| {% set required_gp = [raw_grid_port_target_w | float(0), 0] | max %} | |
| {{ bypass_active | bool(false) | |
| and required_gp > threshold | |
| and (gp_power_w | float(0)) < (required_gp - threshold) | |
| and (normalized_grid_w | float(0)) < ((target_grid_power_w | float(0)) - threshold) }} | |
| bypass_only: >- | |
| {{ bypass_only_candidate | bool(false) and not (bypass_feedback_missing | bool(false)) }} | |
| raw_is_target_w: >- | |
| {{ (desired_load_port_output_w | float(0)) + ([raw_grid_port_target_w | float(0), 0] | max) }} | |
| pre_soc_target_is_w: >- | |
| {% if bypass_only | bool(false) %} | |
| {{ inverter_rated_w | float(2400) }} | |
| {% else %} | |
| {{ raw_is_target_w | float(0) }} | |
| {% endif %} | |
| - variables: | |
| low_hold_state: "{{ low_hold_state_prelim | bool(false) }}" | |
| charge_blocked_by_soc: >- | |
| {{ full_hold_state | bool(false) }} | |
| discharge_blocked_by_soc: >- | |
| {{ low_hold_state | bool(false) }} | |
| needs_grid_absorption: >- | |
| {{ ((target_grid_power_w | float(0)) + (estimated_home_load_w | float(0))) < 0 }} | |
| lp_load_import_guard: >- | |
| {{ (positive_load_power_w | float(0)) > 0 | |
| and (normalized_grid_w | float(0)) < ((target_grid_power_w | float(0)) - (state_mismatch_threshold_w | float(30))) }} | |
| - variables: | |
| charge_limited_gs_min_w: >- | |
| {% set configured_min = gs_min_w | float(-2400) %} | |
| {% set entity_min = gs_entity_min_w | float(configured_min) %} | |
| {{ [configured_min, entity_min, -1 * (ac_couple_max_charge_power_w | float(2400))] | max }} | |
| effective_gs_min_w: >- | |
| {% set configured_min = charge_limited_gs_min_w | float(-2400) %} | |
| {% if bypass_only | bool(false) %} | |
| {{ 0 }} | |
| {% elif (load_port_backfeed_w | float(0)) > 0 and not (needs_grid_absorption | bool(false)) %} | |
| {{ [configured_min, 0] | max }} | |
| {% elif lp_load_import_guard | bool(false) %} | |
| {{ [configured_min, 0] | max }} | |
| {% elif charge_blocked_by_soc | bool(false) %} | |
| {{ [configured_min, 0] | max }} | |
| {% else %} | |
| {{ configured_min }} | |
| {% endif %} | |
| effective_gs_max_w: >- | |
| {% set configured_max = [gs_max_w | float(2400), gs_entity_max_w | float(2400)] | min %} | |
| {% if bypass_only | bool(false) %} | |
| {{ 0 }} | |
| {% elif discharge_blocked_by_soc | bool(false) %} | |
| {{ [configured_max, 0] | min }} | |
| {% else %} | |
| {{ configured_max }} | |
| {% endif %} | |
| - variables: | |
| unbounded_target_gs_w: >- | |
| {% if bypass_only | bool(false) %} | |
| {{ 0 }} | |
| {% else %} | |
| {{ raw_grid_port_target_w | float(0) }} | |
| {% endif %} | |
| target_gs_w: >- | |
| {% set low = effective_gs_min_w | float(-2400) %} | |
| {% set high = effective_gs_max_w | float(2400) %} | |
| {{ [[unbounded_target_gs_w | float(0), low] | max, high] | min }} | |
| unclamped_target_is_w: >- | |
| {% if bypass_only | bool(false) %} | |
| {{ inverter_rated_w | float(2400) }} | |
| {% elif discharge_blocked_by_soc | bool(false) %} | |
| {{ [pre_soc_target_is_w | float(0), pv_power_w | float(0)] | min }} | |
| {% else %} | |
| {{ pre_soc_target_is_w | float(0) }} | |
| {% endif %} | |
| target_is_w: >- | |
| {% set low = is_entity_min_w | float(0) %} | |
| {% set high = [is_entity_max_w | float(inverter_rated_w | float(2400)), inverter_rated_w | float(2400)] | min %} | |
| {{ [[unclamped_target_is_w | float(0), low] | max, high] | min }} | |
| gs_error_w: >- | |
| {{ (target_gs_w | float(0)) - (current_gs_w | float(0)) }} | |
| is_error_w: >- | |
| {{ (target_is_w | float(0)) - (current_is_w | float(0)) }} | |
| overall_abs_error_w: >- | |
| {{ [control_error_w | float(0) | abs, | |
| is_error_w | float(0) | abs, | |
| gs_error_w | float(0) | abs] | max }} | |
| required_interval_s: >- | |
| {% if (overall_abs_error_w | float(0)) >= (large_error_w | float(150)) %} | |
| {{ fast_interval_s | float(1) }} | |
| {% elif (overall_abs_error_w | float(0)) >= (small_error_w | float(30)) %} | |
| {{ medium_interval_s | float(5) }} | |
| {% else %} | |
| {{ slow_interval_s | float(15) }} | |
| {% endif %} | |
| proportional_delta_w: >- | |
| {{ (gs_error_w | float(0)) * (gain_percent | float(100)) / 100 }} | |
| next_is_w: >- | |
| {% set resolution = [is_resolution_w | float(10), 1] | max %} | |
| {% set rounded = (((target_is_w | float(0)) / resolution) | round(0) * resolution) | float(0) %} | |
| {% set low = is_entity_min_w | float(0) %} | |
| {% set high = [is_entity_max_w | float(inverter_rated_w | float(2400)), inverter_rated_w | float(2400)] | min %} | |
| {{ [[rounded, low] | max, high] | min | round(0) }} | |
| seconds_since_is_change: >- | |
| {% if has_is | bool(false) %} | |
| {{ as_timestamp(now()) - as_timestamp(states[is_entity | trim].last_changed, 0) }} | |
| {% else %} | |
| {{ 0 }} | |
| {% endif %} | |
| is_last_change_ts: >- | |
| {% if has_is | bool(false) %} | |
| {{ as_timestamp(states[is_entity | trim].last_changed, 0) }} | |
| {% else %} | |
| {{ 0 }} | |
| {% endif %} | |
| gs_last_change_ts: "{{ as_timestamp(states[gs_entity | trim].last_changed, 0) }}" | |
| control_last_change_ts: >- | |
| {{ [as_timestamp(states[gs_entity | trim].last_changed, 0), | |
| is_last_change_ts | float(0)] | max }} | |
| seconds_since_control_change: >- | |
| {{ as_timestamp(now()) - (control_last_change_ts | float(0)) }} | |
| control_gp_sample_after_change: >- | |
| {{ as_timestamp(states[gp_entity | trim].last_reported | default(states[gp_entity | trim].last_updated), 0) > (control_last_change_ts | float(0)) }} | |
| control_meter_sample_after_change: >- | |
| {{ (meter_control_sample_ts | float(0)) > (control_last_change_ts | float(0)) }} | |
| control_feedback_interval_s: >- | |
| {{ [required_interval_s | float(1), gs_feedback_settle_s | float(4), | |
| meter_stabilization_delay_s | float(0)] | max }} | |
| - variables: | |
| should_write_is: >- | |
| {{ (seconds_since_control_change | float(999)) >= (control_feedback_interval_s | float(8)) | |
| and (control_gp_sample_after_change | bool(false)) | |
| and (control_meter_sample_after_change | bool(false)) | |
| and ((next_is_w | float(0)) - (current_is_w | float(0))) | abs >= (is_resolution_w | float(10)) }} | |
| - variables: | |
| max_adjustment_w: >- | |
| {% if (overall_abs_error_w | float(0)) >= (large_error_w | float(150)) %} | |
| {{ fast_max_step_w | float(500) }} | |
| {% elif (overall_abs_error_w | float(0)) >= (small_error_w | float(30)) %} | |
| {{ medium_max_step_w | float(100) }} | |
| {% else %} | |
| {{ small_max_step_w | float(20) }} | |
| {% endif %} | |
| - variables: | |
| limited_delta_w: >- | |
| {% set delta = proportional_delta_w | float(0) %} | |
| {% set limit = max_adjustment_w | float(20) %} | |
| {{ [[delta, -limit] | max, limit] | min }} | |
| - variables: | |
| stepped_target_gs_w: "{{ (current_gs_w | float(0)) + (limited_delta_w | float(0)) }}" | |
| - variables: | |
| next_gs_w: >- | |
| {% set resolution = [gs_resolution_w | float(1), 1] | max %} | |
| {% set rounded = (((stepped_target_gs_w | float(0)) / resolution) | round(0) * resolution) | float(0) %} | |
| {% set low = effective_gs_min_w | float(-2400) %} | |
| {% set high = effective_gs_max_w | float(2400) %} | |
| {{ [[rounded, low] | max, high] | min | round(0) }} | |
| - variables: | |
| seconds_since_gs_change: >- | |
| {{ as_timestamp(now()) - as_timestamp(states[gs_entity | trim].last_changed, 0) }} | |
| - variables: | |
| should_write_gs: >- | |
| {{ (seconds_since_control_change | float(999)) >= (control_feedback_interval_s | float(8)) | |
| and (control_gp_sample_after_change | bool(false)) | |
| and (control_meter_sample_after_change | bool(false)) | |
| and (next_gs_w | float(0)) != (current_gs_w | float(0)) }} | |
| diagnostic_decision_log_due: >- | |
| {% set level = diagnostic_log_level | string | trim %} | |
| {% set interval = 5 if level == 'debug' else ([diagnostic_log_interval_s | float(60), 10] | max) %} | |
| {% set now_ts = as_timestamp(now()) | int(0) %} | |
| {{ level in ['decision', 'debug'] | |
| and (now_ts % (interval | int(60))) == 0 }} | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ has_full_hold_helper | bool(false) | |
| and full_hold_state | bool(false) | |
| and not (current_full_hold_state | bool(false)) }} | |
| sequence: | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ (diagnostic_log_level | string | trim) in ['events', 'write', 'decision', 'debug'] }} | |
| sequence: | |
| - service: system_log.write | |
| data: | |
| level: info | |
| message: >- | |
| SunEnergyXT zero feed-in | event=full_hold_on | soc={{ soc_value | float(0) | round(1) }}% | lower={{ effective_min_discharge_soc | float(0) }}% | upper={{ effective_max_charge_soc | float(0) }}% | meter={{ normalized_grid_w | float(0) | round(0) }}W | gp={{ gp_power_w | float(0) | round(0) }}W | lp={{ load_power_w | float(0) | round(0) }}W | pv={{ pv_power_w | float(0) | round(0) }}W | |
| - service: input_boolean.turn_on | |
| target: | |
| entity_id: "{{ full_hold_entity | trim }}" | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ has_full_hold_helper | bool(false) | |
| and not (full_hold_state | bool(false)) | |
| and current_full_hold_state | bool(false) }} | |
| sequence: | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ (diagnostic_log_level | string | trim) in ['events', 'write', 'decision', 'debug'] }} | |
| sequence: | |
| - service: system_log.write | |
| data: | |
| level: info | |
| message: >- | |
| SunEnergyXT zero feed-in | event=full_hold_off | soc={{ soc_value | float(0) | round(1) }}% | lower={{ effective_min_discharge_soc | float(0) }}% | upper={{ effective_max_charge_soc | float(0) }}% | meter={{ normalized_grid_w | float(0) | round(0) }}W | gp={{ gp_power_w | float(0) | round(0) }}W | lp={{ load_power_w | float(0) | round(0) }}W | pv={{ pv_power_w | float(0) | round(0) }}W | |
| - service: input_boolean.turn_off | |
| target: | |
| entity_id: "{{ full_hold_entity | trim }}" | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ has_low_hold_helper | bool(false) | |
| and low_hold_state | bool(false) | |
| and not (current_low_hold_state | bool(false)) }} | |
| sequence: | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ (diagnostic_log_level | string | trim) in ['events', 'write', 'decision', 'debug'] }} | |
| sequence: | |
| - service: system_log.write | |
| data: | |
| level: info | |
| message: >- | |
| SunEnergyXT zero feed-in | event=low_hold_on | soc={{ soc_value | float(0) | round(1) }}% | lower={{ effective_min_discharge_soc | float(0) }}% | upper={{ effective_max_charge_soc | float(0) }}% | meter={{ normalized_grid_w | float(0) | round(0) }}W | gp={{ gp_power_w | float(0) | round(0) }}W | lp={{ load_power_w | float(0) | round(0) }}W | pv={{ pv_power_w | float(0) | round(0) }}W | |
| - service: input_boolean.turn_on | |
| target: | |
| entity_id: "{{ low_hold_entity | trim }}" | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ has_low_hold_helper | bool(false) | |
| and not (low_hold_state | bool(false)) | |
| and current_low_hold_state | bool(false) }} | |
| sequence: | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ (diagnostic_log_level | string | trim) in ['events', 'write', 'decision', 'debug'] }} | |
| sequence: | |
| - service: system_log.write | |
| data: | |
| level: info | |
| message: >- | |
| SunEnergyXT zero feed-in | event=low_hold_off | soc={{ soc_value | float(0) | round(1) }}% | lower={{ effective_min_discharge_soc | float(0) }}% | upper={{ effective_max_charge_soc | float(0) }}% | meter={{ normalized_grid_w | float(0) | round(0) }}W | gp={{ gp_power_w | float(0) | round(0) }}W | lp={{ load_power_w | float(0) | round(0) }}W | pv={{ pv_power_w | float(0) | round(0) }}W | |
| - service: input_boolean.turn_off | |
| target: | |
| entity_id: "{{ low_hold_entity | trim }}" | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: "{{ should_write_is }}" | |
| sequence: | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ (diagnostic_log_level | string | trim) in ['write', 'decision', 'debug'] }} | |
| sequence: | |
| - service: system_log.write | |
| data: | |
| level: info | |
| message: >- | |
| SunEnergyXT zero feed-in | event=write_is | old={{ current_is_w | float(0) | round(0) }}W | new={{ next_is_w | float(0) | round(0) }}W | target={{ target_is_w | float(0) | round(0) }}W | meter={{ normalized_grid_w | float(0) | round(0) }}W | gp={{ gp_power_w | float(0) | round(0) }}W | lp={{ load_power_w | float(0) | round(0) }}W | pv={{ pv_power_w | float(0) | round(0) }}W | soc={{ soc_value | float(0) | round(1) }}% | full_hold={{ full_hold_state | bool(false) }} | low_hold={{ low_hold_state | bool(false) }} | |
| - service: number.set_value | |
| continue_on_error: true | |
| target: | |
| entity_id: "{{ is_entity | trim }}" | |
| data: | |
| value: "{{ next_is_w }}" | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: "{{ should_write_gs }}" | |
| sequence: | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ (diagnostic_log_level | string | trim) in ['write', 'decision', 'debug'] }} | |
| sequence: | |
| - service: system_log.write | |
| data: | |
| level: info | |
| message: >- | |
| SunEnergyXT zero feed-in | event=write_gs | old={{ current_gs_w | float(0) | round(0) }}W | new={{ next_gs_w | float(0) | round(0) }}W | target={{ target_gs_w | float(0) | round(0) }}W | meter={{ normalized_grid_w | float(0) | round(0) }}W | gp={{ gp_power_w | float(0) | round(0) }}W | lp={{ load_power_w | float(0) | round(0) }}W | pv={{ pv_power_w | float(0) | round(0) }}W | soc={{ soc_value | float(0) | round(1) }}% | full_hold={{ full_hold_state | bool(false) }} | low_hold={{ low_hold_state | bool(false) }} | |
| - service: number.set_value | |
| target: | |
| entity_id: "{{ gs_entity | trim }}" | |
| data: | |
| value: "{{ next_gs_w }}" | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: "{{ diagnostic_decision_log_due | bool(false) }}" | |
| sequence: | |
| - service: system_log.write | |
| data: | |
| level: info | |
| message: >- | |
| SunEnergyXT zero feed-in | event=decision | meter={{ normalized_grid_w | float(0) | round(0) }}W | target_grid={{ target_grid_power_w | float(0) | round(0) }}W | gp={{ gp_power_w | float(0) | round(0) }}W | lp={{ load_power_w | float(0) | round(0) }}W | pv={{ pv_power_w | float(0) | round(0) }}W | soc={{ soc_value | float(0) | round(1) }}% | gs={{ current_gs_w | float(0) | round(0) }}W->{{ next_gs_w | float(0) | round(0) }}W | is={{ current_is_w | float(0) | round(0) }}W->{{ next_is_w | float(0) | round(0) }}W | write_gs={{ should_write_gs | bool(false) }} | write_is={{ should_write_is | bool(false) }} | full_hold={{ full_hold_state | bool(false) }} | low_hold={{ low_hold_state | bool(false) }} | bypass={{ bypass_only | bool(false) }} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| blueprint: | |
| name: SunEnergyXT 500 Serie - Nulleinspeisung | |
| description: > | |
| Nulleinspeise-Regler für SunEnergyXT 500 / 500 Pro mit einem externen Home | |
| Assistant-Zähler. Die Automatisierung verwendet keine Betriebsmodus- oder | |
| Entlademodus-Schalter des Geräts. Wählen Sie den Zählertyp und das | |
| SunEnergyXT-Gerät; der Blueprint erkennt gängige HA-/HACS-Zählerformen wie | |
| Shelly, BitShake / Tasmota, EcoTracker, vorzeichenbehaftete Gesamtleistung, | |
| Dreiphasenleistung sowie Bezug-/Einspeisepaare. Die Automatisierung steuert | |
| anschließend den Sollwert Leistung Netzanschluss und den Sollwert max. | |
| Wechselrichterleistung mit derselben Topologielogik wie der Simulator. | |
| domain: automation | |
| homeassistant: | |
| min_version: "2024.6.0" | |
| source_url: https://github.com/SunEnergyXT/sunenergyxt-500-zero-feed-in-blueprint/blob/main/blueprints/automation/sunenergyxt/sunenergyxt-500-zero-feed-in.yaml | |
| input: | |
| required_devices: | |
| name: SunEnergyXT-Einrichtung | |
| icon: mdi:devices | |
| description: > | |
| Wählen Sie das SunEnergyXT-All-in-one-Gerät und die wichtigsten | |
| Benutzereinstellungen. Der Blueprint bindet passende Entitäten dieses | |
| Geräts automatisch und schreibt die SOC-Grenzen in die Geräteeinstellungen. | |
| input: | |
| aio_device: | |
| name: SunEnergyXT-Gerät | |
| description: > | |
| Wählen Sie das SunEnergyXT 500 / 500 Pro Gerät. Der Blueprint sucht | |
| automatisch den Sollwert Leistung Netzanschluss, den Sollwert max. | |
| Wechselrichterleistung, die Systemleistung am Lastanschluss, die | |
| Systemleistung am Netzanschluss, die PV-Gesamteingangsleistung, | |
| das System-Speicherlevel und die SOC-Grenzentitäten dieses Geräts. | |
| selector: | |
| device: | |
| filter: | |
| - integration: sunenergyxt | |
| full_battery_mode: | |
| name: Verhalten bei voller Batterie | |
| default: follow_load | |
| selector: | |
| select: | |
| options: | |
| - label: Nach Vollladung Last folgen | |
| value: follow_load | |
| - label: Nach Vollladung PV folgen | |
| value: follow_pv | |
| gs_max_w: | |
| name: Maximale Netzeinspeiseleistung | |
| description: > | |
| Maximal erlaubte Netzeinspeiseleistung. Verwenden Sie 800 W für | |
| SunEnergyXT 500 und 2400 W für SunEnergyXT 500 Pro, sofern vor Ort | |
| kein niedrigerer Grenzwert erforderlich ist. | |
| default: 2400 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 2400 | |
| step: 1 | |
| unit_of_measurement: W | |
| mode: box | |
| min_discharge_soc: | |
| name: System Entladegrenze | |
| description: > | |
| Schreibt die System Entladegrenze des ausgewählten SunEnergyXT- | |
| Geräts. Die Automatisierung verwendet diesen Wert außerdem als | |
| Steuergrenze bei niedrigem SOC. | |
| default: 10 | |
| selector: | |
| number: | |
| min: 1 | |
| max: 30 | |
| step: 1 | |
| unit_of_measurement: "%" | |
| mode: box | |
| max_charge_soc: | |
| name: System Ladegrenze | |
| description: > | |
| Schreibt die System Ladegrenze des ausgewählten SunEnergyXT-Geräts. | |
| Die Automatisierung verwendet diesen Wert außerdem als Steuergrenze | |
| bei voller Batterie. | |
| default: 100 | |
| selector: | |
| number: | |
| min: 70 | |
| max: 100 | |
| step: 1 | |
| unit_of_measurement: "%" | |
| mode: box | |
| ac_couple_max_charge_power_w: | |
| name: Maximale AC-gekoppelte Ladeleistung | |
| description: > | |
| Begrenzt die AC-seitige Lade-/Aufnahmeleistung. SunEnergyXT 500 | |
| und 500 Pro können hier bis zu 2400 W verwenden, sofern vor Ort | |
| kein niedrigerer Grenzwert gilt. | |
| default: 2400 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 2400 | |
| step: 1 | |
| unit_of_measurement: W | |
| mode: box | |
| meter_setup: | |
| name: Zähler-Einrichtung | |
| icon: mdi:meter-electric | |
| description: > | |
| Wählen Sie genau einen Zählertyp. Home Assistant Blueprints können | |
| Felder anhand dieser Auswahl nicht dynamisch ausblenden. Öffnen Sie | |
| daher nur das passende Zählerformular unten und lassen Sie die anderen | |
| Formulare leer. | |
| input: | |
| meter_power_source: | |
| name: Zählertyp | |
| description: > | |
| Wählen Sie die Form der Zählerintegration. Füllen Sie nur die Felder | |
| aus, die zum ausgewählten Typ passen. | |
| selector: | |
| select: | |
| options: | |
| - label: Shelly Pro 3EM | |
| value: shelly_total_power | |
| - label: EcoTracker | |
| value: ecotracker_power | |
| - label: BitShake / Tasmota | |
| value: bitshake_tasmota_power | |
| - label: Shelly 3EM | |
| value: shelly_three_phase_sum | |
| - label: Benutzerdefinierter Zähler | |
| value: custom | |
| meter_pro3em_setup: | |
| name: Shelly Pro 3EM Zähler | |
| icon: mdi:meter-electric | |
| collapsed: true | |
| description: > | |
| Dieses Formular nur ausfüllen, wenn als Zählertyp Shelly Pro 3EM ausgewählt ist. | |
| input: | |
| pro3em_meter_device: | |
| name: Shelly Pro 3EM Gerät | |
| description: > | |
| Wählen Sie das Shelly Pro 3EM Zählergerät. | |
| default: "" | |
| selector: | |
| device: | |
| entity: | |
| - domain: sensor | |
| meter_ecotracker_setup: | |
| name: EcoTracker-Zähler | |
| icon: mdi:meter-electric | |
| collapsed: true | |
| description: > | |
| Dieses Formular nur ausfüllen, wenn als Zählertyp EcoTracker ausgewählt ist. | |
| input: | |
| ecotracker_meter_device: | |
| name: EcoTracker-Gerät | |
| description: > | |
| Wählen Sie das EcoTracker-Zählergerät. | |
| default: "" | |
| selector: | |
| device: | |
| entity: | |
| - domain: sensor | |
| meter_bitshake_setup: | |
| name: BitShake-/Tasmota-Zähler | |
| icon: mdi:meter-electric | |
| collapsed: true | |
| description: > | |
| Dieses Formular nur ausfüllen, wenn als Zählertyp BitShake / Tasmota | |
| ausgewählt ist. | |
| input: | |
| bitshake_meter_device: | |
| name: BitShake-/Tasmota-Gerät | |
| description: > | |
| Wählen Sie das BitShake- oder Tasmota-Zählergerät. | |
| default: "" | |
| selector: | |
| device: | |
| entity: | |
| - domain: sensor | |
| meter_shelly3em_setup: | |
| name: Shelly-3EM-Zähler | |
| icon: mdi:meter-electric | |
| collapsed: true | |
| description: > | |
| Dieses Formular nur ausfüllen, wenn als Zählertyp Shelly 3EM ausgewählt | |
| ist. Wählen Sie die drei Phasengeräte; der Blueprint liest die Leistung | |
| jeder Phase vom jeweiligen Gerät. | |
| input: | |
| meter_l1_device: | |
| name: Shelly 3EM Phase A / L1 Gerät | |
| description: > | |
| Wählen Sie das HA-Gerät für Phase A / L1. | |
| default: "" | |
| selector: | |
| device: | |
| entity: | |
| - domain: sensor | |
| meter_l2_device: | |
| name: Shelly 3EM Phase B / L2 Gerät | |
| description: > | |
| Wählen Sie das HA-Gerät für Phase B / L2. | |
| default: "" | |
| selector: | |
| device: | |
| entity: | |
| - domain: sensor | |
| meter_l3_device: | |
| name: Shelly 3EM Phase C / L3 Gerät | |
| description: > | |
| Wählen Sie das HA-Gerät für Phase C / L3. | |
| default: "" | |
| selector: | |
| device: | |
| entity: | |
| - domain: sensor | |
| meter_custom_setup: | |
| name: Benutzerdefinierter Zähler | |
| icon: mdi:meter-electric | |
| collapsed: true | |
| description: > | |
| Dieses Formular nur ausfüllen, wenn als Zählertyp Benutzerdefinierter | |
| Zähler ausgewählt ist. Ein benutzerdefinierter Zähler benötigt kein | |
| Zählergerät; wählen Sie eine Formel und geben Sie die passenden | |
| Entitäten an. | |
| input: | |
| custom_meter_formula: | |
| name: Leistungsformel des benutzerdefinierten Zählers | |
| description: > | |
| Der Blueprint wendet diese Formel auf die unten angegebenen Entitäten an. | |
| default: custom_signed_total_power | |
| selector: | |
| select: | |
| options: | |
| - label: Vorzeichenbehaftete Gesamtleistung | |
| value: custom_signed_total_power | |
| - label: L1 + L2 + L3 | |
| value: custom_three_phase_sum | |
| - label: Einspeisung - Bezug | |
| value: import_export_pair | |
| - label: L1-L3 Einspeisung - Bezug | |
| value: custom_three_phase_import_export | |
| - label: Entitätsattribut | |
| value: ecotracker_attribute_power | |
| meter_power_sensor: | |
| name: Benutzerdefinierte Gesamt-/Aktualleistungsentität | |
| description: > | |
| Für vorzeichenbehaftete Gesamtleistung oder Entitätsattribut verwenden. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| meter_power_attribute: | |
| name: Benutzerdefiniertes Leistungsattribut | |
| description: > | |
| Nur verwenden, wenn die Leistungsformel Entitätsattribut ist. Der | |
| übliche Attributname ist power. | |
| default: power | |
| selector: | |
| text: | |
| meter_l1_power_sensor: | |
| name: Benutzerdefinierte L1-/Phase-A-Leistungsentität | |
| description: > | |
| Für die benutzerdefinierte Formel L1 + L2 + L3 verwenden. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| meter_l2_power_sensor: | |
| name: Benutzerdefinierte L2-/Phase-B-Leistungsentität | |
| description: > | |
| Für die benutzerdefinierte Formel L1 + L2 + L3 verwenden. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| meter_l3_power_sensor: | |
| name: Benutzerdefinierte L3-/Phase-C-Leistungsentität | |
| description: > | |
| Für die benutzerdefinierte Formel L1 + L2 + L3 verwenden. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| meter_import_power_sensor: | |
| name: Benutzerdefinierte Bezugsleistungsentität | |
| description: > | |
| Für die benutzerdefinierte Formel Einspeisung - Bezug verwenden. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| meter_export_power_sensor: | |
| name: Benutzerdefinierte Einspeiseleistungsentität | |
| description: > | |
| Für die benutzerdefinierte Formel Einspeisung - Bezug verwenden. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| meter_l1_import_power_sensor: | |
| name: Benutzerdefinierte L1-/Phase-A-Bezugsleistungsentität | |
| description: > | |
| Für die benutzerdefinierte Formel L1-L3 Einspeisung - Bezug verwenden. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| meter_l2_import_power_sensor: | |
| name: Benutzerdefinierte L2-/Phase-B-Bezugsleistungsentität | |
| description: > | |
| Für die benutzerdefinierte Formel L1-L3 Einspeisung - Bezug verwenden. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| meter_l3_import_power_sensor: | |
| name: Benutzerdefinierte L3-/Phase-C-Bezugsleistungsentität | |
| description: > | |
| Für die benutzerdefinierte Formel L1-L3 Einspeisung - Bezug verwenden. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| meter_l1_export_power_sensor: | |
| name: Benutzerdefinierte L1-/Phase-A-Einspeiseleistungsentität | |
| description: > | |
| Für die benutzerdefinierte Formel L1-L3 Einspeisung - Bezug verwenden. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| meter_l2_export_power_sensor: | |
| name: Benutzerdefinierte L2-/Phase-B-Einspeiseleistungsentität | |
| description: > | |
| Für die benutzerdefinierte Formel L1-L3 Einspeisung - Bezug verwenden. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| meter_l3_export_power_sensor: | |
| name: Benutzerdefinierte L3-/Phase-C-Einspeiseleistungsentität | |
| description: > | |
| Für die benutzerdefinierte Formel L1-L3 Einspeisung - Bezug verwenden. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| advanced_entity_overrides: | |
| name: Erweiterte Entitätsüberschreibungen | |
| icon: mdi:form-select | |
| collapsed: true | |
| description: > | |
| Für die automatische gerätebasierte Bindung leer lassen. Nur öffnen, | |
| wenn das ausgewählte Gerät ungewöhnliche Entitätsnamen bereitstellt und | |
| das automatische Ergebnis falsch ist. | |
| input: | |
| gs_number: | |
| name: Entität für Sollwert Leistung Netzanschluss überschreiben | |
| default: "" | |
| selector: | |
| entity: | |
| domain: number | |
| is_number: | |
| name: Entität für Sollwert max. Wechselrichterleistung überschreiben | |
| default: "" | |
| selector: | |
| entity: | |
| domain: number | |
| load_power_sensor: | |
| name: Sensor für Systemleistung am Lastanschluss überschreiben | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| grid_port_power_sensor: | |
| name: Sensor für Systemleistung am Netzanschluss überschreiben | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| pv_power_sensor: | |
| name: Sensor für PV-Gesamteingangsleistung überschreiben | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| soc_sensor: | |
| name: Sensor für System-Speicherlevel überschreiben | |
| default: "" | |
| selector: | |
| entity: | |
| domain: sensor | |
| min_discharge_soc_number: | |
| name: Number-Entität für System Entladegrenze überschreiben | |
| default: "" | |
| selector: | |
| entity: | |
| domain: number | |
| max_charge_soc_number: | |
| name: Number-Entität für System Ladegrenze überschreiben | |
| default: "" | |
| selector: | |
| entity: | |
| domain: number | |
| advanced_meter_settings: | |
| name: Erweiterte Zähler-Vorzeichen- und Einheitseinstellungen | |
| icon: mdi:meter-electric | |
| collapsed: true | |
| input: | |
| meter_sign_mode: | |
| name: Vorzeichenkonvention des Zählers | |
| description: > | |
| Automatisch verwendet die Vorgabe des ausgewählten Zählertyps. Wählen | |
| Sie eine feste Vorgabe, wenn Stromwandler, Skript oder Template das | |
| entgegengesetzte Vorzeichen liefern. | |
| default: auto_from_preset | |
| selector: | |
| select: | |
| options: | |
| - label: Automatisch nach ausgewähltem Zählertyp | |
| value: auto_from_preset | |
| - label: Einspeisung ist positiv | |
| value: export_positive | |
| - label: Netzbezug ist positiv | |
| value: import_positive | |
| meter_power_multiplier: | |
| name: Leistungsfaktor externer Zähler | |
| description: > | |
| 1 verwenden, wenn die Entität bereits W liefert. 1000 verwenden, wenn die Entität kW liefert. | |
| default: 1 | |
| selector: | |
| number: | |
| min: 0.001 | |
| max: 1000 | |
| step: 0.001 | |
| mode: box | |
| advanced_control_settings: | |
| name: Erweiterte Steuerungseinstellungen | |
| icon: mdi:cog-outline | |
| collapsed: true | |
| input: | |
| target_grid_power_w: | |
| name: Ziel-Netzleistung | |
| description: > | |
| Nulleinspeisung verwendet 0 W. Positive Werte halten bewusst eine | |
| kleine Einspeisung; negative Werte halten bewusst einen kleinen Bezug. | |
| default: 0 | |
| selector: | |
| number: | |
| min: -200 | |
| max: 200 | |
| step: 1 | |
| unit_of_measurement: W | |
| mode: box | |
| target_grid_power_entity: | |
| name: Optionaler Helper für Ziel-Netzleistung | |
| description: > | |
| Optionaler input_number-Helper für einen dynamischen Zielwert. Ist | |
| kein Helper ausgewählt oder sein Zustand ungültig, wird die oben | |
| eingestellte Ziel-Netzleistung verwendet. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: input_number | |
| gs_min_w: | |
| name: Untere Grenze für Sollwert Leistung Netzanschluss | |
| description: > | |
| Minimal erlaubter Sollwert Leistung Netzanschluss. Negative Werte | |
| erlauben Netzbezug bzw. Laden, um Überschussleistung aufzunehmen. | |
| default: -2400 | |
| selector: | |
| number: | |
| min: -2400 | |
| max: 0 | |
| step: 1 | |
| unit_of_measurement: W | |
| mode: box | |
| gs_resolution_w: | |
| name: Schreibauflösung Sollwert Leistung Netzanschluss | |
| default: 1 | |
| selector: | |
| number: | |
| min: 1 | |
| max: 50 | |
| step: 1 | |
| unit_of_measurement: W | |
| mode: box | |
| gain_percent: | |
| name: Korrekturverstärkung | |
| default: 100 | |
| selector: | |
| number: | |
| min: 20 | |
| max: 150 | |
| step: 5 | |
| unit_of_measurement: "%" | |
| mode: slider | |
| small_error_w: | |
| name: Schwelle für kleinen Fehler | |
| default: 30 | |
| selector: | |
| number: | |
| min: 1 | |
| max: 300 | |
| step: 1 | |
| unit_of_measurement: W | |
| mode: box | |
| large_error_w: | |
| name: Schwelle für großen Fehler | |
| default: 150 | |
| selector: | |
| number: | |
| min: 10 | |
| max: 1000 | |
| step: 10 | |
| unit_of_measurement: W | |
| mode: box | |
| slow_interval_s: | |
| name: Langsames Schreibintervall | |
| default: 7 | |
| selector: | |
| number: | |
| min: 1 | |
| max: 120 | |
| step: 0.5 | |
| unit_of_measurement: s | |
| mode: box | |
| medium_interval_s: | |
| name: Mittleres Schreibintervall | |
| default: 2.5 | |
| selector: | |
| number: | |
| min: 0.5 | |
| max: 60 | |
| step: 0.5 | |
| unit_of_measurement: s | |
| mode: box | |
| fast_interval_s: | |
| name: Schnelles Schreibintervall | |
| default: 1 | |
| selector: | |
| number: | |
| min: 1 | |
| max: 20 | |
| step: 1 | |
| unit_of_measurement: s | |
| mode: box | |
| gs_feedback_settle_s: | |
| name: Einschwingzeit der Regelungsrückmeldung | |
| description: > | |
| Mindestzeit nach dem Schreiben des Sollwerts Leistung Netzanschluss | |
| oder des Sollwerts max. Wechselrichterleistung bis zum nächsten | |
| Schreibvorgang. Die Automatisierung wartet außerdem auf neue | |
| Netzanschluss- und Zählerwerte nach der letzten Änderung. Der | |
| Standardwert passt zur beobachteten SunEnergyXT-Rückmeldezeit und | |
| reduziert Schwingen bei Änderungen externer Geräte. | |
| default: 5 | |
| selector: | |
| number: | |
| min: 1 | |
| max: 30 | |
| step: 1 | |
| unit_of_measurement: s | |
| mode: box | |
| meter_stabilization_delay_s: | |
| name: Zusätzliche Wartezeit bei verzögerter Zählerrückmeldung | |
| description: > | |
| Optionale zusätzliche Wartezeit nach einem GS- oder IS-Schreibvorgang, | |
| bevor erneut geregelt wird. Bei Zählern ohne merkliche Verzögerung | |
| 0 s beibehalten. Den Wert etwas größer als die gemessene inhaltliche | |
| Verzögerung einstellen, wenn der Zähler in Home Assistant zwar neue | |
| Zeitstempel meldet, der Leistungswert aber noch den Zustand vor dem | |
| letzten Schreibvorgang zeigt. Ein zu kleiner Wert kann Schwingen | |
| zulassen; ein zu großer Wert verlangsamt die Reaktion auf echte | |
| Laständerungen. | |
| default: 0 | |
| selector: | |
| number: | |
| min: 0 | |
| max: 60 | |
| step: 1 | |
| unit_of_measurement: s | |
| mode: box | |
| small_max_step_w: | |
| name: Maximale Anpassung bei kleinem Fehler | |
| default: 20 | |
| selector: | |
| number: | |
| min: 1 | |
| max: 300 | |
| step: 1 | |
| unit_of_measurement: W | |
| mode: box | |
| medium_max_step_w: | |
| name: Maximale Anpassung bei mittlerem Fehler | |
| default: 120 | |
| selector: | |
| number: | |
| min: 10 | |
| max: 1000 | |
| step: 10 | |
| unit_of_measurement: W | |
| mode: box | |
| fast_max_step_w: | |
| name: Maximale Anpassung bei großem Fehler | |
| default: 450 | |
| selector: | |
| number: | |
| min: 50 | |
| max: 2400 | |
| step: 10 | |
| unit_of_measurement: W | |
| mode: box | |
| max_meter_age_s: | |
| name: Maximales Alter des Zählerwerts | |
| description: > | |
| Maximales Alter des externen Zählerwerts, bevor die aktuelle | |
| Regelrunde pausiert und den aktuellen GS-Wert beibehält. Ein | |
| ausdrücklich ungültiger kritischer Eingang muss mindestens ebenso | |
| lange ungültig bleiben, bevor ein GS ungleich 0 einmalig auf 0 W | |
| zurückgesetzt wird. | |
| default: 30 | |
| selector: | |
| number: | |
| min: 2 | |
| max: 300 | |
| step: 1 | |
| unit_of_measurement: s | |
| mode: box | |
| is_resolution_w: | |
| name: Schreibauflösung Sollwert max. Wechselrichterleistung | |
| default: 10 | |
| selector: | |
| number: | |
| min: 1 | |
| max: 100 | |
| step: 1 | |
| unit_of_measurement: W | |
| mode: box | |
| diagnostic_log_level: | |
| name: Diagnose-Log-Stufe | |
| description: > | |
| Aus schreibt keine Diagnose-Logs. Ereignisse protokolliert | |
| SOC-Haltewechsel. Schreibvorgänge ergänzt Setpoint-Schreibvorgänge. | |
| Entscheidungen ergänzt periodische Regelentscheidungen. Debug | |
| schreibt Entscheidungs-Logs alle 5 Sekunden. | |
| default: off | |
| selector: | |
| select: | |
| options: | |
| - label: "Aus" | |
| value: "off" | |
| - label: "Ereignisse" | |
| value: "events" | |
| - label: "Schreibvorgänge" | |
| value: "write" | |
| - label: "Entscheidungen" | |
| value: "decision" | |
| - label: "Debug" | |
| value: "debug" | |
| diagnostic_log_interval_s: | |
| name: Intervall für Entscheidungs-Logs | |
| description: > | |
| Mindestintervall für periodische Entscheidungs-Logs. Debug- | |
| Entscheidungs-Logs verwenden ein festes Intervall von 5 s. | |
| Ereignis- und Schreib-Logs werden nur ausgegeben, wenn die | |
| jeweilige Aktion tatsächlich passiert. | |
| default: 60 | |
| selector: | |
| number: | |
| min: 10 | |
| max: 3600 | |
| step: 10 | |
| unit_of_measurement: s | |
| mode: box | |
| full_charge_hold_helper: | |
| name: Optionaler Helper für Haltezustand volle Batterie | |
| description: > | |
| Optional. Leer lassen, außer die Automatisierung soll den Haltezustand | |
| volle Batterie über Neustarts hinweg speichern. Ohne diesen Helper | |
| leitet die Automatisierung den Zustand aus dem aktuellen SOC ab, mit | |
| einem 1%-Speicherband für volle Batterie im Modus Nach Vollladung PV | |
| folgen. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: input_boolean | |
| low_discharge_hold_helper: | |
| name: Optionaler Helper für Niedrig-SOC-Haltezustand | |
| description: > | |
| Optional. Leer lassen, außer die Automatisierung soll den Niedrig-SOC- | |
| Haltezustand über Neustarts hinweg speichern. Ohne diesen Helper | |
| leitet die Automatisierung den Zustand direkt aus dem aktuellen SOC ab. | |
| default: "" | |
| selector: | |
| entity: | |
| domain: input_boolean | |
| mode: single | |
| max_exceeded: silent | |
| trigger: | |
| - platform: time_pattern | |
| seconds: "/1" | |
| variables: | |
| aio_device: !input aio_device | |
| pro3em_meter_device: !input pro3em_meter_device | |
| ecotracker_meter_device: !input ecotracker_meter_device | |
| bitshake_meter_device: !input bitshake_meter_device | |
| meter_l1_device: !input meter_l1_device | |
| meter_l2_device: !input meter_l2_device | |
| meter_l3_device: !input meter_l3_device | |
| meter_power_source: !input meter_power_source | |
| custom_meter_formula: !input custom_meter_formula | |
| meter_entity_override: !input meter_power_sensor | |
| meter_power_attribute: !input meter_power_attribute | |
| meter_l1_entity_override: !input meter_l1_power_sensor | |
| meter_l2_entity_override: !input meter_l2_power_sensor | |
| meter_l3_entity_override: !input meter_l3_power_sensor | |
| meter_import_entity_override: !input meter_import_power_sensor | |
| meter_export_entity_override: !input meter_export_power_sensor | |
| meter_l1_import_entity_override: !input meter_l1_import_power_sensor | |
| meter_l2_import_entity_override: !input meter_l2_import_power_sensor | |
| meter_l3_import_entity_override: !input meter_l3_import_power_sensor | |
| meter_l1_export_entity_override: !input meter_l1_export_power_sensor | |
| meter_l2_export_entity_override: !input meter_l2_export_power_sensor | |
| meter_l3_export_entity_override: !input meter_l3_export_power_sensor | |
| meter_sign_mode: !input meter_sign_mode | |
| meter_power_multiplier: !input meter_power_multiplier | |
| external_meter_device: >- | |
| {% set source = meter_power_source | trim %} | |
| {% if source == 'shelly_total_power' %} | |
| {{ pro3em_meter_device | string | trim }} | |
| {% elif source == 'ecotracker_power' %} | |
| {{ ecotracker_meter_device | string | trim }} | |
| {% elif source == 'bitshake_tasmota_power' %} | |
| {{ bitshake_meter_device | string | trim }} | |
| {% else %} | |
| {{ '' }} | |
| {% endif %} | |
| effective_meter_power_source: >- | |
| {% if meter_power_source | trim == 'custom' %} | |
| {{ custom_meter_formula | trim }} | |
| {% else %} | |
| {{ meter_power_source | trim }} | |
| {% endif %} | |
| gs_entity_override: !input gs_number | |
| is_entity_override: !input is_number | |
| load_entity_override: !input load_power_sensor | |
| gp_entity_override: !input grid_port_power_sensor | |
| pv_entity_override: !input pv_power_sensor | |
| soc_entity_override: !input soc_sensor | |
| min_soc_entity_override: !input min_discharge_soc_number | |
| max_soc_entity_override: !input max_charge_soc_number | |
| full_hold_entity: !input full_charge_hold_helper | |
| low_hold_entity: !input low_discharge_hold_helper | |
| full_battery_mode: !input full_battery_mode | |
| target_grid_power_static_w: !input target_grid_power_w | |
| target_grid_power_entity: !input target_grid_power_entity | |
| target_grid_power_w: >- | |
| {% set entity = target_grid_power_entity | string | trim %} | |
| {% set fallback = target_grid_power_static_w | float(0) %} | |
| {% set invalid = ['unknown', 'unavailable', 'none', ''] %} | |
| {% if entity and states(entity) not in invalid %} | |
| {{ states(entity) | float(fallback) }} | |
| {% else %} | |
| {{ fallback }} | |
| {% endif %} | |
| gs_min_w: !input gs_min_w | |
| gs_max_w: !input gs_max_w | |
| gs_resolution_w: !input gs_resolution_w | |
| gain_percent: !input gain_percent | |
| small_error_w: !input small_error_w | |
| large_error_w: !input large_error_w | |
| slow_interval_s: !input slow_interval_s | |
| medium_interval_s: !input medium_interval_s | |
| fast_interval_s: !input fast_interval_s | |
| gs_feedback_settle_s: !input gs_feedback_settle_s | |
| meter_stabilization_delay_s: !input meter_stabilization_delay_s | |
| small_max_step_w: !input small_max_step_w | |
| medium_max_step_w: !input medium_max_step_w | |
| fast_max_step_w: !input fast_max_step_w | |
| max_meter_age_s: !input max_meter_age_s | |
| min_discharge_soc: !input min_discharge_soc | |
| max_charge_soc: !input max_charge_soc | |
| ac_couple_max_charge_power_w: !input ac_couple_max_charge_power_w | |
| is_resolution_w: !input is_resolution_w | |
| diagnostic_log_level: !input diagnostic_log_level | |
| diagnostic_log_interval_s: !input diagnostic_log_interval_s | |
| gs_entity: >- | |
| {% set override = gs_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in device_entities(aio_device) if ns.entity == '' and entity.startswith('number.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% if eid.endswith('_gs') or 'grid port power setpoint' in name or '市电口功率设定' in name or ('netzanschluss' in name and 'sollwert' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| is_entity: >- | |
| {% set override = is_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in device_entities(aio_device) if ns.entity == '' and entity.startswith('number.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% if eid.endswith('_is') or 'max inverter power setpoint' in name or '最大逆变功率设定' in name or 'wechselrichterleistung' in name %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| load_entity: >- | |
| {% set override = load_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in device_entities(aio_device) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% if eid.endswith('_lp') or 'load port power' in name or '负载口功率' in name or 'lastanschluss' in name %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| gp_entity: >- | |
| {% set override = gp_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in device_entities(aio_device) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% if eid.endswith('_gp') or 'grid port power' in name or '并网口功率' in name or ('netzanschluss' in name and 'leistung' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| pv_entity: >- | |
| {% set override = pv_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in device_entities(aio_device) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% if eid.endswith('_pv') or 'pv total input power' in name or 'pv总输入功率' in name or 'pv-gesamteingangsleistung' in name or ('pv' in name and 'gesamtleistung' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| soc_entity: >- | |
| {% set override = soc_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in device_entities(aio_device) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% if eid.endswith('_sc') or 'battery level' in name or '系统总电量' in name or 'speicherlevel' in name %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| min_soc_entity: >- | |
| {% set override = min_soc_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in device_entities(aio_device) if ns.entity == '' and entity.startswith('number.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% set is_load_port = 'load port' in name or 'load_port' in eid or '负载口' in name or 'lastanschluss' in name %} | |
| {% set is_min_soc = 'min_discharge_soc' in eid | |
| or eid.endswith('_si') | |
| or 'system min discharge soc' in name | |
| or 'min discharge soc' in name | |
| or ('minimum' in name and 'discharge' in name and 'soc' in name) | |
| or ('system' in name and 'entladegrenze' in name) | |
| or ('放电' in name and ('下限' in name or '最低' in name)) %} | |
| {% if is_min_soc and not is_load_port %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| max_soc_entity: >- | |
| {% set override = max_soc_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in device_entities(aio_device) if ns.entity == '' and entity.startswith('number.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% set is_max_soc = 'max_charge_soc' in eid | |
| or eid.endswith('_sa') | |
| or 'system max charge soc' in name | |
| or 'max charge soc' in name | |
| or ('maximum' in name and 'charge' in name and 'soc' in name) | |
| or ('system' in name and 'ladegrenze' in name and 'entladegrenze' not in name) | |
| or ('充电' in name and ('上限' in name or '最高' in name)) %} | |
| {% if is_max_soc %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| meter_entity: >- | |
| {% set override = meter_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in (device_entities(external_meter_device) if external_meter_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% set unit = (state_attr(entity, 'unit_of_measurement') or '') | lower %} | |
| {% set dc = (state_attr(entity, 'device_class') or '') | lower %} | |
| {% set is_power = dc == 'power' or unit in ['w', 'kw'] or 'power' in name or 'leistung' in name %} | |
| {% set is_excluded = 'energy' in name or 'voltage' in name or 'current' in name or '电量' in name or '电压' in name or '电流' in name or 'energie' in name or 'spannung' in name or 'strom' in name %} | |
| {% set is_phase = ' l1' in name or ' l2' in name or ' l3' in name or '_l1' in eid or '_l2' in eid or '_l3' in eid or 'phase' in name %} | |
| {% set is_pair = 'import' in name or 'export' in name or 'returned' in name or 'consumption' in name or 'feed' in name %} | |
| {% if is_power and not is_excluded and not is_phase and not is_pair %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {% if ns.entity == '' %} | |
| {% for entity in (device_entities(external_meter_device) if external_meter_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set unit = (state_attr(entity, 'unit_of_measurement') or '') | lower %} | |
| {% set dc = (state_attr(entity, 'device_class') or '') | lower %} | |
| {% if dc == 'power' or unit in ['w', 'kw'] %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {% endif %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| meter_l1_entity: >- | |
| {% set override = meter_l1_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% set search_device = meter_l1_device if (meter_power_source | trim) == 'shelly_three_phase_sum' else external_meter_device %} | |
| {% for entity in (device_entities(search_device) if search_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% set unit = (state_attr(entity, 'unit_of_measurement') or '') | lower %} | |
| {% set dc = (state_attr(entity, 'device_class') or '') | lower %} | |
| {% set is_phase_match = (meter_power_source | trim) == 'shelly_three_phase_sum' | |
| or 'l1' in name or '_l1' in eid or 'phase a' in name %} | |
| {% if (dc == 'power' or unit in ['w', 'kw'] or 'power' in name or 'leistung' in name) and is_phase_match and not ('import' in name or 'export' in name or 'returned' in name or 'consumption' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| meter_l2_entity: >- | |
| {% set override = meter_l2_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% set search_device = meter_l2_device if (meter_power_source | trim) == 'shelly_three_phase_sum' else external_meter_device %} | |
| {% for entity in (device_entities(search_device) if search_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% set unit = (state_attr(entity, 'unit_of_measurement') or '') | lower %} | |
| {% set dc = (state_attr(entity, 'device_class') or '') | lower %} | |
| {% set is_phase_match = (meter_power_source | trim) == 'shelly_three_phase_sum' | |
| or 'l2' in name or '_l2' in eid or 'phase b' in name %} | |
| {% if (dc == 'power' or unit in ['w', 'kw'] or 'power' in name or 'leistung' in name) and is_phase_match and not ('import' in name or 'export' in name or 'returned' in name or 'consumption' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| meter_l3_entity: >- | |
| {% set override = meter_l3_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% set search_device = meter_l3_device if (meter_power_source | trim) == 'shelly_three_phase_sum' else external_meter_device %} | |
| {% for entity in (device_entities(search_device) if search_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% set unit = (state_attr(entity, 'unit_of_measurement') or '') | lower %} | |
| {% set dc = (state_attr(entity, 'device_class') or '') | lower %} | |
| {% set is_phase_match = (meter_power_source | trim) == 'shelly_three_phase_sum' | |
| or 'l3' in name or '_l3' in eid or 'phase c' in name %} | |
| {% if (dc == 'power' or unit in ['w', 'kw'] or 'power' in name or 'leistung' in name) and is_phase_match and not ('import' in name or 'export' in name or 'returned' in name or 'consumption' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| meter_import_entity: >- | |
| {% set override = meter_import_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in (device_entities(external_meter_device) if external_meter_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set unit = (state_attr(entity, 'unit_of_measurement') or '') | lower %} | |
| {% set dc = (state_attr(entity, 'device_class') or '') | lower %} | |
| {% if (dc == 'power' or unit in ['w', 'kw'] or 'power' in name or 'leistung' in name) and ('import' in name or 'consumption' in name or 'grid in' in name) and not ('l1' in name or 'l2' in name or 'l3' in name or 'phase' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| meter_export_entity: >- | |
| {% set override = meter_export_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in (device_entities(external_meter_device) if external_meter_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set unit = (state_attr(entity, 'unit_of_measurement') or '') | lower %} | |
| {% set dc = (state_attr(entity, 'device_class') or '') | lower %} | |
| {% if (dc == 'power' or unit in ['w', 'kw'] or 'power' in name or 'leistung' in name) and ('export' in name or 'returned' in name or 'feed' in name) and not ('l1' in name or 'l2' in name or 'l3' in name or 'phase' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| meter_l1_import_entity: >- | |
| {% set override = meter_l1_import_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in (device_entities(external_meter_device) if external_meter_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% if ('l1' in name or '_l1' in eid or 'phase a' in name) and ('import' in name or 'consumption' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| meter_l2_import_entity: >- | |
| {% set override = meter_l2_import_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in (device_entities(external_meter_device) if external_meter_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% if ('l2' in name or '_l2' in eid or 'phase b' in name) and ('import' in name or 'consumption' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| meter_l3_import_entity: >- | |
| {% set override = meter_l3_import_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in (device_entities(external_meter_device) if external_meter_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% if ('l3' in name or '_l3' in eid or 'phase c' in name) and ('import' in name or 'consumption' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| meter_l1_export_entity: >- | |
| {% set override = meter_l1_export_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in (device_entities(external_meter_device) if external_meter_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% if ('l1' in name or '_l1' in eid or 'phase a' in name) and ('export' in name or 'returned' in name or 'feed' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| meter_l2_export_entity: >- | |
| {% set override = meter_l2_export_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in (device_entities(external_meter_device) if external_meter_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% if ('l2' in name or '_l2' in eid or 'phase b' in name) and ('export' in name or 'returned' in name or 'feed' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| meter_l3_export_entity: >- | |
| {% set override = meter_l3_export_entity_override | string %} | |
| {% if override | length > 0 %} | |
| {{ override | trim }} | |
| {% else %} | |
| {% set ns = namespace(entity='') %} | |
| {% for entity in (device_entities(external_meter_device) if external_meter_device | string | trim | length > 0 else []) if ns.entity == '' and entity.startswith('sensor.') %} | |
| {% set name = (state_attr(entity, 'friendly_name') or entity) | lower %} | |
| {% set eid = entity | lower %} | |
| {% if ('l3' in name or '_l3' in eid or 'phase c' in name) and ('export' in name or 'returned' in name or 'feed' in name) %} | |
| {% set ns.entity = entity %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ ns.entity | trim }} | |
| {% endif %} | |
| condition: [] | |
| action: | |
| # Validate all critical inputs before calculating or writing control targets. | |
| - variables: | |
| failsafe_bad_states: | |
| - unknown | |
| - unavailable | |
| - none | |
| - "" | |
| failsafe_signed_sources: | |
| - shelly_total_power | |
| - bitshake_tasmota_power | |
| - ecotracker_power | |
| - custom_signed_total_power | |
| failsafe_phase_sources: | |
| - shelly_three_phase_sum | |
| - custom_three_phase_sum | |
| failsafe_phase_pair_sources: | |
| - shelly_three_phase_import_export | |
| - custom_three_phase_import_export | |
| failsafe_limit_s: "{{ max_meter_age_s | float(30) }}" | |
| failsafe_invalid_confirm_s: >- | |
| {{ [failsafe_limit_s | float(30), | |
| (gs_feedback_settle_s | float(5)) * 2, | |
| 10] | max }} | |
| failsafe_gs_valid: >- | |
| {% set entity = gs_entity | string | trim %} | |
| {% set value = states(entity) %} | |
| {{ entity.startswith('number.') | |
| and value not in failsafe_bad_states | |
| and is_number(value) }} | |
| failsafe_is_valid: >- | |
| {% set entity = is_entity | string | trim %} | |
| {% set value = states(entity) %} | |
| {{ entity.startswith('number.') | |
| and value not in failsafe_bad_states | |
| and is_number(value) }} | |
| failsafe_load_valid: >- | |
| {% set value = states(load_entity | trim) %} | |
| {{ load_entity | string | trim | length > 0 | |
| and value not in failsafe_bad_states | |
| and is_number(value) }} | |
| failsafe_gp_valid: >- | |
| {% set value = states(gp_entity | trim) %} | |
| {{ gp_entity | string | trim | length > 0 | |
| and value not in failsafe_bad_states | |
| and is_number(value) }} | |
| failsafe_pv_valid: >- | |
| {% set value = states(pv_entity | trim) %} | |
| {{ pv_entity | string | trim | length > 0 | |
| and value not in failsafe_bad_states | |
| and is_number(value) }} | |
| failsafe_soc_valid: >- | |
| {% set value = states(soc_entity | trim) %} | |
| {{ soc_entity | string | trim | length > 0 | |
| and value not in failsafe_bad_states | |
| and is_number(value) }} | |
| failsafe_min_soc_valid: >- | |
| {% set value = states(min_soc_entity | trim) %} | |
| {{ min_soc_entity | string | trim | length > 0 | |
| and value not in failsafe_bad_states | |
| and is_number(value) }} | |
| failsafe_max_soc_valid: >- | |
| {% set value = states(max_soc_entity | trim) %} | |
| {{ max_soc_entity | string | trim | length > 0 | |
| and value not in failsafe_bad_states | |
| and is_number(value) }} | |
| failsafe_meter_valid: >- | |
| {% set source = effective_meter_power_source | trim %} | |
| {% set bad = failsafe_bad_states %} | |
| {% if source in failsafe_signed_sources %} | |
| {% set value = states(meter_entity | trim) %} | |
| {{ meter_entity | string | trim | length > 0 | |
| and value not in bad | |
| and is_number(value) }} | |
| {% elif source == 'ecotracker_attribute_power' %} | |
| {% set value = states(meter_entity | trim) %} | |
| {% set power = state_attr(meter_entity | trim, meter_power_attribute) %} | |
| {{ meter_entity | string | trim | length > 0 | |
| and value not in bad | |
| and power is not none | |
| and is_number(power) }} | |
| {% elif source in failsafe_phase_sources %} | |
| {{ meter_l1_entity | string | trim | length > 0 | |
| and meter_l2_entity | string | trim | length > 0 | |
| and meter_l3_entity | string | trim | length > 0 | |
| and states(meter_l1_entity | trim) not in bad | |
| and states(meter_l2_entity | trim) not in bad | |
| and states(meter_l3_entity | trim) not in bad | |
| and is_number(states(meter_l1_entity | trim)) | |
| and is_number(states(meter_l2_entity | trim)) | |
| and is_number(states(meter_l3_entity | trim)) }} | |
| {% elif source == 'import_export_pair' %} | |
| {{ meter_import_entity | string | trim | length > 0 | |
| and meter_export_entity | string | trim | length > 0 | |
| and states(meter_import_entity | trim) not in bad | |
| and states(meter_export_entity | trim) not in bad | |
| and is_number(states(meter_import_entity | trim)) | |
| and is_number(states(meter_export_entity | trim)) }} | |
| {% elif source in failsafe_phase_pair_sources %} | |
| {{ meter_l1_import_entity | string | trim | length > 0 | |
| and meter_l2_import_entity | string | trim | length > 0 | |
| and meter_l3_import_entity | string | trim | length > 0 | |
| and meter_l1_export_entity | string | trim | length > 0 | |
| and meter_l2_export_entity | string | trim | length > 0 | |
| and meter_l3_export_entity | string | trim | length > 0 | |
| and states(meter_l1_import_entity | trim) not in bad | |
| and states(meter_l2_import_entity | trim) not in bad | |
| and states(meter_l3_import_entity | trim) not in bad | |
| and states(meter_l1_export_entity | trim) not in bad | |
| and states(meter_l2_export_entity | trim) not in bad | |
| and states(meter_l3_export_entity | trim) not in bad | |
| and is_number(states(meter_l1_import_entity | trim)) | |
| and is_number(states(meter_l2_import_entity | trim)) | |
| and is_number(states(meter_l3_import_entity | trim)) | |
| and is_number(states(meter_l1_export_entity | trim)) | |
| and is_number(states(meter_l2_export_entity | trim)) | |
| and is_number(states(meter_l3_export_entity | trim)) }} | |
| {% else %} | |
| {{ false }} | |
| {% endif %} | |
| failsafe_meter_fresh: >- | |
| {% set source = effective_meter_power_source | trim %} | |
| {% set limit = failsafe_limit_s | float(30) %} | |
| {% if not (failsafe_meter_valid | bool(false)) %} | |
| {{ false }} | |
| {% elif source in failsafe_signed_sources or source == 'ecotracker_attribute_power' %} | |
| {% set age = as_timestamp(now()) - | |
| as_timestamp(states[meter_entity | trim].last_reported | |
| | default(states[meter_entity | trim].last_updated), 0) %} | |
| {{ age >= 0 and age <= limit }} | |
| {% elif source in failsafe_phase_sources %} | |
| {% set ages = [ | |
| as_timestamp(now()) - as_timestamp(states[meter_l1_entity | trim].last_reported | default(states[meter_l1_entity | trim].last_updated), 0), | |
| as_timestamp(now()) - as_timestamp(states[meter_l2_entity | trim].last_reported | default(states[meter_l2_entity | trim].last_updated), 0), | |
| as_timestamp(now()) - as_timestamp(states[meter_l3_entity | trim].last_reported | default(states[meter_l3_entity | trim].last_updated), 0)] %} | |
| {{ ages | select('ge', 0) | select('le', limit) | list | count > 0 }} | |
| {% elif source == 'import_export_pair' %} | |
| {% set import_age = as_timestamp(now()) - as_timestamp(states[meter_import_entity | trim].last_reported | default(states[meter_import_entity | trim].last_updated), 0) %} | |
| {% set export_age = as_timestamp(now()) - as_timestamp(states[meter_export_entity | trim].last_reported | default(states[meter_export_entity | trim].last_updated), 0) %} | |
| {{ import_age >= 0 and import_age <= limit | |
| and export_age >= 0 and export_age <= limit }} | |
| {% elif source in failsafe_phase_pair_sources %} | |
| {% set ages = [ | |
| as_timestamp(now()) - as_timestamp(states[meter_l1_import_entity | trim].last_reported | default(states[meter_l1_import_entity | trim].last_updated), 0), | |
| as_timestamp(now()) - as_timestamp(states[meter_l2_import_entity | trim].last_reported | default(states[meter_l2_import_entity | trim].last_updated), 0), | |
| as_timestamp(now()) - as_timestamp(states[meter_l3_import_entity | trim].last_reported | default(states[meter_l3_import_entity | trim].last_updated), 0), | |
| as_timestamp(now()) - as_timestamp(states[meter_l1_export_entity | trim].last_reported | default(states[meter_l1_export_entity | trim].last_updated), 0), | |
| as_timestamp(now()) - as_timestamp(states[meter_l2_export_entity | trim].last_reported | default(states[meter_l2_export_entity | trim].last_updated), 0), | |
| as_timestamp(now()) - as_timestamp(states[meter_l3_export_entity | trim].last_reported | default(states[meter_l3_export_entity | trim].last_updated), 0)] %} | |
| {{ ages | select('ge', 0) | select('le', limit) | list | count > 0 }} | |
| {% else %} | |
| {{ false }} | |
| {% endif %} | |
| - variables: | |
| failsafe_inputs_ok: >- | |
| {{ failsafe_gs_valid | bool(false) | |
| and failsafe_is_valid | bool(false) | |
| and failsafe_meter_valid | bool(false) | |
| and failsafe_meter_fresh | bool(false) | |
| and failsafe_gp_valid | bool(false) | |
| and failsafe_load_valid | bool(false) | |
| and failsafe_pv_valid | bool(false) | |
| and failsafe_soc_valid | bool(false) | |
| and failsafe_min_soc_valid | bool(false) | |
| and failsafe_max_soc_valid | bool(false) }} | |
| failsafe_reason: >- | |
| {% set ns = namespace(parts=[]) %} | |
| {% if not (failsafe_gs_valid | bool(false)) %}{% set ns.parts = ns.parts + ['GS ungültig'] %}{% endif %} | |
| {% if not (failsafe_is_valid | bool(false)) %}{% set ns.parts = ns.parts + ['IS ungültig'] %}{% endif %} | |
| {% if not (failsafe_meter_valid | bool(false)) %} | |
| {% set ns.parts = ns.parts + ['Netzzähler ungültig'] %} | |
| {% elif not (failsafe_meter_fresh | bool(false)) %} | |
| {% set ns.parts = ns.parts + ['Netzzähler veraltet; GS wird gehalten'] %} | |
| {% endif %} | |
| {% if not (failsafe_gp_valid | bool(false)) %} | |
| {% set ns.parts = ns.parts + ['Grid-Port ungültig'] %} | |
| {% endif %} | |
| {% if not (failsafe_load_valid | bool(false)) %} | |
| {% set ns.parts = ns.parts + ['Load-Port ungültig'] %} | |
| {% endif %} | |
| {% if not (failsafe_pv_valid | bool(false)) %} | |
| {% set ns.parts = ns.parts + ['PV ungültig'] %} | |
| {% endif %} | |
| {% if not (failsafe_soc_valid | bool(false)) %} | |
| {% set ns.parts = ns.parts + ['SOC ungültig'] %} | |
| {% endif %} | |
| {% if not (failsafe_min_soc_valid | bool(false)) %}{% set ns.parts = ns.parts + ['Min-SOC ungültig'] %}{% endif %} | |
| {% if not (failsafe_max_soc_valid | bool(false)) %}{% set ns.parts = ns.parts + ['Max-SOC ungültig'] %}{% endif %} | |
| {{ ns.parts | join(', ') }} | |
| failsafe_hard_failure_confirmed: >- | |
| {% set bad = failsafe_bad_states %} | |
| {% set confirm = failsafe_invalid_confirm_s | float(30) %} | |
| {% set ns = namespace(ages=[]) %} | |
| {% for entity in [is_entity, gp_entity, load_entity, pv_entity, soc_entity, | |
| min_soc_entity, max_soc_entity] %} | |
| {% set entity_id = entity | string | trim %} | |
| {% if entity_id | length > 0 | |
| and (states(entity_id) in bad or not is_number(states(entity_id))) %} | |
| {% if expand(entity_id) | count > 0 %} | |
| {% set age = as_timestamp(now()) - as_timestamp(states[entity_id].last_changed, 0) %} | |
| {% set ns.ages = ns.ages + [age] %} | |
| {% endif %} | |
| {% endif %} | |
| {% endfor %} | |
| {% if not (failsafe_meter_valid | bool(false)) %} | |
| {% set source = effective_meter_power_source | trim %} | |
| {% if source in failsafe_signed_sources or source == 'ecotracker_attribute_power' %} | |
| {% set meter_entities = [meter_entity] %} | |
| {% elif source in failsafe_phase_sources %} | |
| {% set meter_entities = [meter_l1_entity, meter_l2_entity, meter_l3_entity] %} | |
| {% elif source == 'import_export_pair' %} | |
| {% set meter_entities = [meter_import_entity, meter_export_entity] %} | |
| {% elif source in failsafe_phase_pair_sources %} | |
| {% set meter_entities = [meter_l1_import_entity, meter_l2_import_entity, | |
| meter_l3_import_entity, meter_l1_export_entity, | |
| meter_l2_export_entity, meter_l3_export_entity] %} | |
| {% else %} | |
| {% set meter_entities = [] %} | |
| {% endif %} | |
| {% for entity in meter_entities %} | |
| {% set entity_id = entity | string | trim %} | |
| {% if entity_id | length > 0 | |
| and expand(entity_id) | count > 0 | |
| and source == 'ecotracker_attribute_power' | |
| and (state_attr(entity_id, meter_power_attribute) is none | |
| or not is_number(state_attr(entity_id, meter_power_attribute))) %} | |
| {% set age = as_timestamp(now()) - as_timestamp(states[entity_id].last_updated, 0) %} | |
| {% set ns.ages = ns.ages + [age] %} | |
| {% elif entity_id | length > 0 | |
| and expand(entity_id) | count > 0 | |
| and (states(entity_id) in bad or not is_number(states(entity_id))) %} | |
| {% set age = as_timestamp(now()) - as_timestamp(states[entity_id].last_changed, 0) %} | |
| {% set ns.ages = ns.ages + [age] %} | |
| {% endif %} | |
| {% endfor %} | |
| {% endif %} | |
| {{ ns.ages | count > 0 and (ns.ages | min) >= confirm }} | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: "{{ not (failsafe_inputs_ok | bool(false)) }}" | |
| sequence: | |
| - variables: | |
| failsafe_should_reset_gs: >- | |
| {{ failsafe_gs_valid | bool(false) | |
| and (failsafe_hard_failure_confirmed | bool(false)) | |
| and (states(gs_entity | trim) | float(0) | abs) >= 0.5 }} | |
| failsafe_log_due: >- | |
| {% set level = diagnostic_log_level | string | trim %} | |
| {% set interval = [diagnostic_log_interval_s | int(60), 60] | max %} | |
| {{ level in ['events', 'write', 'decision', 'debug'] | |
| and (as_timestamp(now()) | int(0)) % interval == 0 }} | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: "{{ failsafe_should_reset_gs | bool(false) }}" | |
| sequence: | |
| - service: number.set_value | |
| continue_on_error: true | |
| target: | |
| entity_id: "{{ gs_entity | trim }}" | |
| data: | |
| value: 0 | |
| - delay: | |
| seconds: "{{ [gs_feedback_settle_s | float(5), 1] | max }}" | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: "{{ failsafe_log_due | bool(false) }}" | |
| sequence: | |
| - service: system_log.write | |
| data: | |
| level: warning | |
| message: >- | |
| SunEnergyXT Nulleinspeisung | Fail-safe aktiv, Regelrunde beendet | GS-Rücksetzung={{ failsafe_should_reset_gs | bool(false) }} | Grund: {{ failsafe_reason }} | |
| - stop: >- | |
| SunEnergyXT Fail-safe: ungültiger Eingang oder veralteter Zählerwert; Sollwerte werden gehalten. | |
| - variables: | |
| meter_signed_total_sources: | |
| - shelly_total_power | |
| - bitshake_tasmota_power | |
| - ecotracker_power | |
| - custom_signed_total_power | |
| meter_phase_sum_sources: | |
| - shelly_three_phase_sum | |
| - custom_three_phase_sum | |
| meter_phase_pair_sources: | |
| - shelly_three_phase_import_export | |
| - custom_three_phase_import_export | |
| meter_import_positive_presets: | |
| - shelly_total_power | |
| - shelly_three_phase_sum | |
| - bitshake_tasmota_power | |
| - ecotracker_power | |
| - ecotracker_attribute_power | |
| - custom_signed_total_power | |
| - custom_three_phase_sum | |
| meter_raw_power_before_multiplier_w: >- | |
| {% set source = effective_meter_power_source | trim %} | |
| {% if source in meter_signed_total_sources %} | |
| {{ states(meter_entity | trim) | float(0) }} | |
| {% elif source == 'ecotracker_attribute_power' %} | |
| {{ state_attr(meter_entity | trim, meter_power_attribute) | float(0) }} | |
| {% elif source in meter_phase_sum_sources %} | |
| {{ (states(meter_l1_entity | trim) | float(0)) | |
| + (states(meter_l2_entity | trim) | float(0)) | |
| + (states(meter_l3_entity | trim) | float(0)) }} | |
| {% elif source == 'import_export_pair' %} | |
| {{ (states(meter_export_entity | trim) | float(0)) | |
| - (states(meter_import_entity | trim) | float(0)) }} | |
| {% elif source in meter_phase_pair_sources %} | |
| {{ (states(meter_l1_export_entity | trim) | float(0)) | |
| + (states(meter_l2_export_entity | trim) | float(0)) | |
| + (states(meter_l3_export_entity | trim) | float(0)) | |
| - (states(meter_l1_import_entity | trim) | float(0)) | |
| - (states(meter_l2_import_entity | trim) | float(0)) | |
| - (states(meter_l3_import_entity | trim) | float(0)) }} | |
| {% else %} | |
| {{ 0 }} | |
| {% endif %} | |
| meter_raw_w: >- | |
| {{ (meter_raw_power_before_multiplier_w | float(0)) | |
| * (meter_power_multiplier | float(1)) }} | |
| meter_control_sample_ts: >- | |
| {% set source = effective_meter_power_source | trim %} | |
| {% if source in meter_signed_total_sources or source == 'ecotracker_attribute_power' %} | |
| {{ as_timestamp(states[meter_entity | trim].last_reported | default(states[meter_entity | trim].last_updated), 0) }} | |
| {% elif source in meter_phase_sum_sources %} | |
| {{ [as_timestamp(states[meter_l1_entity | trim].last_reported | default(states[meter_l1_entity | trim].last_updated), 0), | |
| as_timestamp(states[meter_l2_entity | trim].last_reported | default(states[meter_l2_entity | trim].last_updated), 0), | |
| as_timestamp(states[meter_l3_entity | trim].last_reported | default(states[meter_l3_entity | trim].last_updated), 0)] | max }} | |
| {% elif source == 'import_export_pair' %} | |
| {{ [as_timestamp(states[meter_import_entity | trim].last_reported | default(states[meter_import_entity | trim].last_updated), 0), | |
| as_timestamp(states[meter_export_entity | trim].last_reported | default(states[meter_export_entity | trim].last_updated), 0)] | max }} | |
| {% elif source in meter_phase_pair_sources %} | |
| {{ [as_timestamp(states[meter_l1_import_entity | trim].last_reported | default(states[meter_l1_import_entity | trim].last_updated), 0), | |
| as_timestamp(states[meter_l2_import_entity | trim].last_reported | default(states[meter_l2_import_entity | trim].last_updated), 0), | |
| as_timestamp(states[meter_l3_import_entity | trim].last_reported | default(states[meter_l3_import_entity | trim].last_updated), 0), | |
| as_timestamp(states[meter_l1_export_entity | trim].last_reported | default(states[meter_l1_export_entity | trim].last_updated), 0), | |
| as_timestamp(states[meter_l2_export_entity | trim].last_reported | default(states[meter_l2_export_entity | trim].last_updated), 0), | |
| as_timestamp(states[meter_l3_export_entity | trim].last_reported | default(states[meter_l3_export_entity | trim].last_updated), 0)] | max }} | |
| {% else %} | |
| {{ 0 }} | |
| {% endif %} | |
| effective_meter_sign_mode: >- | |
| {% set source = effective_meter_power_source | trim %} | |
| {% if source == 'import_export_pair' or source in meter_phase_pair_sources %} | |
| {{ 'export_positive' }} | |
| {% elif meter_sign_mode == 'auto_from_preset' %} | |
| {% if source in meter_import_positive_presets %} | |
| {{ 'import_positive' }} | |
| {% else %} | |
| {{ 'export_positive' }} | |
| {% endif %} | |
| {% else %} | |
| {{ meter_sign_mode }} | |
| {% endif %} | |
| meter_factor: >- | |
| {{ 1 if (effective_meter_sign_mode | trim) == 'export_positive' else -1 }} | |
| current_gs_w: "{{ states(gs_entity | trim) | float(0) }}" | |
| inverter_rated_w: 2400 | |
| has_soc: >- | |
| {{ soc_entity | string | length > 0 | |
| and states(soc_entity | trim) not in ['unknown', 'unavailable', 'none', ''] }} | |
| has_is: >- | |
| {{ is_entity | string | length > 0 | |
| and states(is_entity | trim) not in ['unknown', 'unavailable', 'none', ''] }} | |
| has_load: >- | |
| {{ load_entity | string | length > 0 | |
| and states(load_entity | trim) not in ['unknown', 'unavailable', 'none', ''] }} | |
| has_gp: >- | |
| {{ gp_entity | string | length > 0 | |
| and states(gp_entity | trim) not in ['unknown', 'unavailable', 'none', ''] }} | |
| has_pv: >- | |
| {{ pv_entity | string | length > 0 | |
| and states(pv_entity | trim) not in ['unknown', 'unavailable', 'none', ''] }} | |
| - variables: | |
| gs_entity_min_w: >- | |
| {% if gs_entity | string | trim | length > 0 %} | |
| {{ state_attr(gs_entity | trim, 'min') | float(gs_min_w | float(-2400)) }} | |
| {% else %} | |
| {{ gs_min_w | float(-2400) }} | |
| {% endif %} | |
| gs_entity_max_w: >- | |
| {% if gs_entity | string | trim | length > 0 %} | |
| {{ state_attr(gs_entity | trim, 'max') | float(gs_max_w | float(2400)) }} | |
| {% else %} | |
| {{ gs_max_w | float(2400) }} | |
| {% endif %} | |
| is_entity_min_w: >- | |
| {% if has_is | bool(false) %} | |
| {{ state_attr(is_entity | trim, 'min') | float(0) }} | |
| {% else %} | |
| {{ 0 }} | |
| {% endif %} | |
| is_entity_max_w: >- | |
| {% if has_is | bool(false) %} | |
| {{ state_attr(is_entity | trim, 'max') | float(inverter_rated_w | float(2400)) }} | |
| {% else %} | |
| {{ inverter_rated_w | float(2400) }} | |
| {% endif %} | |
| normalized_grid_w: >- | |
| {{ (meter_raw_w | float(0)) * (meter_factor | float(1)) }} | |
| soc_value: >- | |
| {% if has_soc | bool(false) %} | |
| {{ states(soc_entity | trim) | float(50) }} | |
| {% else %} | |
| {{ 50 }} | |
| {% endif %} | |
| current_is_w: >- | |
| {% if has_is | bool(false) %} | |
| {{ states(is_entity | trim) | float(0) }} | |
| {% else %} | |
| {{ 0 }} | |
| {% endif %} | |
| load_power_raw_w: >- | |
| {% if has_load | bool(false) %} | |
| {{ states(load_entity | trim) | float(0) }} | |
| {% else %} | |
| {{ 0 }} | |
| {% endif %} | |
| load_power_w: >- | |
| {% set raw = load_power_raw_w | float(0) %} | |
| {% if raw > 32767 %} | |
| {{ raw - 65536 }} | |
| {% else %} | |
| {{ raw }} | |
| {% endif %} | |
| gp_power_raw_w: >- | |
| {% if has_gp | bool(false) %} | |
| {{ states(gp_entity | trim) | float(0) }} | |
| {% else %} | |
| {{ 0 }} | |
| {% endif %} | |
| gp_power_w: >- | |
| {% set raw = gp_power_raw_w | float(0) %} | |
| {% if raw > 32767 %} | |
| {{ raw - 65536 }} | |
| {% else %} | |
| {{ raw }} | |
| {% endif %} | |
| pv_power_w: >- | |
| {% if has_pv | bool(false) %} | |
| {{ states(pv_entity | trim) | float(0) }} | |
| {% else %} | |
| {{ 0 }} | |
| {% endif %} | |
| effective_min_discharge_soc: >- | |
| {{ min_discharge_soc | float(10) }} | |
| effective_max_charge_soc: >- | |
| {{ max_charge_soc | float(100) }} | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ ((states(min_soc_entity | trim) | float(0)) - (min_discharge_soc | float(10))) | abs >= 1 }} | |
| sequence: | |
| - service: number.set_value | |
| continue_on_error: true | |
| target: | |
| entity_id: "{{ min_soc_entity | trim }}" | |
| data: | |
| value: "{{ min_discharge_soc }}" | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ ((states(max_soc_entity | trim) | float(0)) - (max_charge_soc | float(100))) | abs >= 1 }} | |
| sequence: | |
| - service: number.set_value | |
| continue_on_error: true | |
| target: | |
| entity_id: "{{ max_soc_entity | trim }}" | |
| data: | |
| value: "{{ max_charge_soc }}" | |
| - variables: | |
| soc_hysteresis_value: 5 | |
| has_full_hold_helper: >- | |
| {{ full_hold_entity | string | trim | length > 0 }} | |
| has_low_hold_helper: >- | |
| {{ low_hold_entity | string | trim | length > 0 }} | |
| current_full_hold_state: >- | |
| {{ has_full_hold_helper | bool(false) | |
| and is_state(full_hold_entity | trim, 'on') }} | |
| current_low_hold_state: >- | |
| {{ has_low_hold_helper | bool(false) | |
| and is_state(low_hold_entity | trim, 'on') }} | |
| state_mismatch_threshold_w: "{{ [small_error_w | float(30), 30] | max }}" | |
| full_hold_memory_band_percent: 1 | |
| full_hold_bypass_command_active: >- | |
| {% set gs_tolerance = [gs_resolution_w | float(1), 5] | max %} | |
| {% set is_tolerance = [is_resolution_w | float(10), 20] | max %} | |
| {% set bypass_is_target = [is_entity_max_w | float(inverter_rated_w | float(2400)), inverter_rated_w | float(2400)] | min %} | |
| {{ full_battery_mode == 'follow_pv' | |
| and has_is | bool(false) | |
| and ((current_gs_w | float(0)) | abs) <= (gs_tolerance | float(5)) | |
| and (current_is_w | float(0)) >= ((bypass_is_target | float(2400)) - (is_tolerance | float(20))) }} | |
| full_hold_bypass_memory_active: >- | |
| {% set soc = soc_value | float(50) %} | |
| {% set upper = effective_max_charge_soc | float(100) %} | |
| {{ full_hold_bypass_command_active | bool(false) | |
| and soc < upper | |
| and soc >= (upper - (full_hold_memory_band_percent | float(1))) }} | |
| full_hold_state: >- | |
| {% set soc = soc_value | float(50) %} | |
| {% set upper = effective_max_charge_soc | float(100) %} | |
| {% set release = upper - (soc_hysteresis_value | float(2)) %} | |
| {% if has_full_hold_helper | bool(false) %} | |
| {% if soc >= upper %} | |
| {{ true }} | |
| {% elif soc <= release %} | |
| {{ false }} | |
| {% else %} | |
| {{ current_full_hold_state | bool(false) }} | |
| {% endif %} | |
| {% else %} | |
| {{ soc >= upper or full_hold_bypass_memory_active | bool(false) }} | |
| {% endif %} | |
| low_hold_state_prelim: >- | |
| {% set soc = soc_value | float(50) %} | |
| {% set lower = effective_min_discharge_soc | float(10) %} | |
| {% set release = lower + (soc_hysteresis_value | float(2)) %} | |
| {% if has_low_hold_helper | bool(false) %} | |
| {% if soc <= lower %} | |
| {{ true }} | |
| {% elif soc >= release %} | |
| {{ false }} | |
| {% else %} | |
| {{ current_low_hold_state | bool(false) }} | |
| {% endif %} | |
| {% else %} | |
| {{ soc <= lower }} | |
| {% endif %} | |
| - variables: | |
| control_error_w: >- | |
| {{ (normalized_grid_w | float(0)) - (target_grid_power_w | float(0)) }} | |
| positive_load_power_w: >- | |
| {{ [load_power_w | float(0), 0] | max }} | |
| load_port_backfeed_w: >- | |
| {{ [(-1 * (load_power_w | float(0))), 0] | max }} | |
| effective_load_power_w: >- | |
| {{ load_power_w | float(0) if has_load | bool(false) else 0 }} | |
| hardware_output_limit_w: >- | |
| {% if full_battery_mode == 'follow_pv' | |
| and full_hold_state | bool(false) | |
| and has_pv | bool(false) %} | |
| {{ inverter_rated_w | float(2400) }} | |
| {% else %} | |
| {{ [current_is_w | float(0), inverter_rated_w | float(2400)] | min }} | |
| {% endif %} | |
| load_port_from_inverter_w: >- | |
| {% if full_battery_mode == 'follow_pv' | |
| and full_hold_state | bool(false) | |
| and has_pv | bool(false) %} | |
| {{ [positive_load_power_w | float(0), inverter_rated_w | float(2400)] | min }} | |
| {% else %} | |
| {{ [positive_load_power_w | float(0), [current_is_w | float(0), inverter_rated_w | float(2400)] | min] | min }} | |
| {% endif %} | |
| load_port_grid_supply_w: >- | |
| {% if full_battery_mode == 'follow_pv' | |
| and full_hold_state | bool(false) | |
| and has_pv | bool(false) %} | |
| {{ [(positive_load_power_w | float(0)) - ([positive_load_power_w | float(0), inverter_rated_w | float(2400)] | min), 0] | max }} | |
| {% else %} | |
| {{ [(positive_load_power_w | float(0)) - ([positive_load_power_w | float(0), [current_is_w | float(0), inverter_rated_w | float(2400)] | min] | min), 0] | max }} | |
| {% endif %} | |
| estimated_home_load_w: >- | |
| {{ (gp_power_w | float(0)) - (normalized_grid_w | float(0)) }} | |
| - variables: | |
| load_grid_supply_target_w: >- | |
| {% if (positive_load_power_w | float(0)) > 0 %} | |
| {{ [[-1 * ((target_grid_power_w | float(0)) + (estimated_home_load_w | float(0))), 0] | max, positive_load_power_w | float(0)] | min }} | |
| {% else %} | |
| {{ 0 }} | |
| {% endif %} | |
| raw_grid_port_target_w: >- | |
| {% if (load_port_backfeed_w | float(0)) > 0 %} | |
| {{ (target_grid_power_w | float(0)) + (estimated_home_load_w | float(0)) }} | |
| {% else %} | |
| {{ (target_grid_power_w | float(0)) + (estimated_home_load_w | float(0)) + (load_grid_supply_target_w | float(0)) }} | |
| {% endif %} | |
| desired_load_port_output_w: >- | |
| {% if (positive_load_power_w | float(0)) > 0 %} | |
| {{ (positive_load_power_w | float(0)) - (load_grid_supply_target_w | float(0)) }} | |
| {% else %} | |
| {{ 0 }} | |
| {% endif %} | |
| bypass_active: >- | |
| {{ full_battery_mode == 'follow_pv' | |
| and full_hold_state | bool(false) | |
| and has_pv | bool(false) }} | |
| bypass_grid_available_w: >- | |
| {% if full_battery_mode == 'follow_pv' | |
| and full_hold_state | bool(false) | |
| and has_pv | bool(false) %} | |
| {% set pv_after_load = [(pv_power_w | float(0)) - (positive_load_power_w | float(0)), 0] | max %} | |
| {{ [[pv_after_load + (load_port_backfeed_w | float(0)), 0] | max, gs_max_w | float(2400)] | min }} | |
| {% else %} | |
| {{ 0 }} | |
| {% endif %} | |
| bypass_only_candidate: >- | |
| {{ bypass_active | bool(false) | |
| and ([raw_grid_port_target_w | float(0), 0] | max) <= (bypass_grid_available_w | float(0)) }} | |
| bypass_feedback_missing: >- | |
| {% set threshold = state_mismatch_threshold_w | float(30) %} | |
| {% set required_gp = [raw_grid_port_target_w | float(0), 0] | max %} | |
| {{ bypass_active | bool(false) | |
| and required_gp > threshold | |
| and (gp_power_w | float(0)) < (required_gp - threshold) | |
| and (normalized_grid_w | float(0)) < ((target_grid_power_w | float(0)) - threshold) }} | |
| bypass_only: >- | |
| {{ bypass_only_candidate | bool(false) and not (bypass_feedback_missing | bool(false)) }} | |
| raw_is_target_w: >- | |
| {{ (desired_load_port_output_w | float(0)) + ([raw_grid_port_target_w | float(0), 0] | max) }} | |
| pre_soc_target_is_w: >- | |
| {% if bypass_only | bool(false) %} | |
| {{ inverter_rated_w | float(2400) }} | |
| {% else %} | |
| {{ raw_is_target_w | float(0) }} | |
| {% endif %} | |
| - variables: | |
| low_hold_state: "{{ low_hold_state_prelim | bool(false) }}" | |
| charge_blocked_by_soc: >- | |
| {{ full_hold_state | bool(false) }} | |
| discharge_blocked_by_soc: >- | |
| {{ low_hold_state | bool(false) }} | |
| needs_grid_absorption: >- | |
| {{ ((target_grid_power_w | float(0)) + (estimated_home_load_w | float(0))) < 0 }} | |
| lp_load_import_guard: >- | |
| {{ (positive_load_power_w | float(0)) > 0 | |
| and (normalized_grid_w | float(0)) < ((target_grid_power_w | float(0)) - (state_mismatch_threshold_w | float(30))) }} | |
| - variables: | |
| charge_limited_gs_min_w: >- | |
| {% set configured_min = gs_min_w | float(-2400) %} | |
| {% set entity_min = gs_entity_min_w | float(configured_min) %} | |
| {{ [configured_min, entity_min, -1 * (ac_couple_max_charge_power_w | float(2400))] | max }} | |
| effective_gs_min_w: >- | |
| {% set configured_min = charge_limited_gs_min_w | float(-2400) %} | |
| {% if bypass_only | bool(false) %} | |
| {{ 0 }} | |
| {% elif (load_port_backfeed_w | float(0)) > 0 and not (needs_grid_absorption | bool(false)) %} | |
| {{ [configured_min, 0] | max }} | |
| {% elif lp_load_import_guard | bool(false) %} | |
| {{ [configured_min, 0] | max }} | |
| {% elif charge_blocked_by_soc | bool(false) %} | |
| {{ [configured_min, 0] | max }} | |
| {% else %} | |
| {{ configured_min }} | |
| {% endif %} | |
| effective_gs_max_w: >- | |
| {% set configured_max = [gs_max_w | float(2400), gs_entity_max_w | float(2400)] | min %} | |
| {% if bypass_only | bool(false) %} | |
| {{ 0 }} | |
| {% elif discharge_blocked_by_soc | bool(false) %} | |
| {{ [configured_max, 0] | min }} | |
| {% else %} | |
| {{ configured_max }} | |
| {% endif %} | |
| - variables: | |
| unbounded_target_gs_w: >- | |
| {% if bypass_only | bool(false) %} | |
| {{ 0 }} | |
| {% else %} | |
| {{ raw_grid_port_target_w | float(0) }} | |
| {% endif %} | |
| target_gs_w: >- | |
| {% set low = effective_gs_min_w | float(-2400) %} | |
| {% set high = effective_gs_max_w | float(2400) %} | |
| {{ [[unbounded_target_gs_w | float(0), low] | max, high] | min }} | |
| unclamped_target_is_w: >- | |
| {% if bypass_only | bool(false) %} | |
| {{ inverter_rated_w | float(2400) }} | |
| {% elif discharge_blocked_by_soc | bool(false) %} | |
| {{ [pre_soc_target_is_w | float(0), pv_power_w | float(0)] | min }} | |
| {% else %} | |
| {{ pre_soc_target_is_w | float(0) }} | |
| {% endif %} | |
| target_is_w: >- | |
| {% set low = is_entity_min_w | float(0) %} | |
| {% set high = [is_entity_max_w | float(inverter_rated_w | float(2400)), inverter_rated_w | float(2400)] | min %} | |
| {{ [[unclamped_target_is_w | float(0), low] | max, high] | min }} | |
| gs_error_w: >- | |
| {{ (target_gs_w | float(0)) - (current_gs_w | float(0)) }} | |
| is_error_w: >- | |
| {{ (target_is_w | float(0)) - (current_is_w | float(0)) }} | |
| overall_abs_error_w: >- | |
| {{ [control_error_w | float(0) | abs, | |
| is_error_w | float(0) | abs, | |
| gs_error_w | float(0) | abs] | max }} | |
| required_interval_s: >- | |
| {% if (overall_abs_error_w | float(0)) >= (large_error_w | float(150)) %} | |
| {{ fast_interval_s | float(1) }} | |
| {% elif (overall_abs_error_w | float(0)) >= (small_error_w | float(30)) %} | |
| {{ medium_interval_s | float(5) }} | |
| {% else %} | |
| {{ slow_interval_s | float(15) }} | |
| {% endif %} | |
| proportional_delta_w: >- | |
| {{ (gs_error_w | float(0)) * (gain_percent | float(100)) / 100 }} | |
| next_is_w: >- | |
| {% set resolution = [is_resolution_w | float(10), 1] | max %} | |
| {% set rounded = (((target_is_w | float(0)) / resolution) | round(0) * resolution) | float(0) %} | |
| {% set low = is_entity_min_w | float(0) %} | |
| {% set high = [is_entity_max_w | float(inverter_rated_w | float(2400)), inverter_rated_w | float(2400)] | min %} | |
| {{ [[rounded, low] | max, high] | min | round(0) }} | |
| seconds_since_is_change: >- | |
| {% if has_is | bool(false) %} | |
| {{ as_timestamp(now()) - as_timestamp(states[is_entity | trim].last_changed, 0) }} | |
| {% else %} | |
| {{ 0 }} | |
| {% endif %} | |
| is_last_change_ts: >- | |
| {% if has_is | bool(false) %} | |
| {{ as_timestamp(states[is_entity | trim].last_changed, 0) }} | |
| {% else %} | |
| {{ 0 }} | |
| {% endif %} | |
| gs_last_change_ts: "{{ as_timestamp(states[gs_entity | trim].last_changed, 0) }}" | |
| control_last_change_ts: >- | |
| {{ [as_timestamp(states[gs_entity | trim].last_changed, 0), | |
| is_last_change_ts | float(0)] | max }} | |
| seconds_since_control_change: >- | |
| {{ as_timestamp(now()) - (control_last_change_ts | float(0)) }} | |
| control_gp_sample_after_change: >- | |
| {{ as_timestamp(states[gp_entity | trim].last_reported | default(states[gp_entity | trim].last_updated), 0) > (control_last_change_ts | float(0)) }} | |
| control_meter_sample_after_change: >- | |
| {{ (meter_control_sample_ts | float(0)) > (control_last_change_ts | float(0)) }} | |
| control_feedback_interval_s: >- | |
| {{ [required_interval_s | float(1), gs_feedback_settle_s | float(4), | |
| meter_stabilization_delay_s | float(0)] | max }} | |
| - variables: | |
| should_write_is: >- | |
| {{ (seconds_since_control_change | float(999)) >= (control_feedback_interval_s | float(8)) | |
| and (control_gp_sample_after_change | bool(false)) | |
| and (control_meter_sample_after_change | bool(false)) | |
| and ((next_is_w | float(0)) - (current_is_w | float(0))) | abs >= (is_resolution_w | float(10)) }} | |
| - variables: | |
| max_adjustment_w: >- | |
| {% if (overall_abs_error_w | float(0)) >= (large_error_w | float(150)) %} | |
| {{ fast_max_step_w | float(500) }} | |
| {% elif (overall_abs_error_w | float(0)) >= (small_error_w | float(30)) %} | |
| {{ medium_max_step_w | float(100) }} | |
| {% else %} | |
| {{ small_max_step_w | float(20) }} | |
| {% endif %} | |
| - variables: | |
| limited_delta_w: >- | |
| {% set delta = proportional_delta_w | float(0) %} | |
| {% set limit = max_adjustment_w | float(20) %} | |
| {{ [[delta, -limit] | max, limit] | min }} | |
| - variables: | |
| stepped_target_gs_w: "{{ (current_gs_w | float(0)) + (limited_delta_w | float(0)) }}" | |
| - variables: | |
| next_gs_w: >- | |
| {% set resolution = [gs_resolution_w | float(1), 1] | max %} | |
| {% set rounded = (((stepped_target_gs_w | float(0)) / resolution) | round(0) * resolution) | float(0) %} | |
| {% set low = effective_gs_min_w | float(-2400) %} | |
| {% set high = effective_gs_max_w | float(2400) %} | |
| {{ [[rounded, low] | max, high] | min | round(0) }} | |
| - variables: | |
| seconds_since_gs_change: >- | |
| {{ as_timestamp(now()) - as_timestamp(states[gs_entity | trim].last_changed, 0) }} | |
| - variables: | |
| should_write_gs: >- | |
| {{ (seconds_since_control_change | float(999)) >= (control_feedback_interval_s | float(8)) | |
| and (control_gp_sample_after_change | bool(false)) | |
| and (control_meter_sample_after_change | bool(false)) | |
| and (next_gs_w | float(0)) != (current_gs_w | float(0)) }} | |
| diagnostic_decision_log_due: >- | |
| {% set level = diagnostic_log_level | string | trim %} | |
| {% set interval = 5 if level == 'debug' else ([diagnostic_log_interval_s | float(60), 10] | max) %} | |
| {% set now_ts = as_timestamp(now()) | int(0) %} | |
| {{ level in ['decision', 'debug'] | |
| and (now_ts % (interval | int(60))) == 0 }} | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ has_full_hold_helper | bool(false) | |
| and full_hold_state | bool(false) | |
| and not (current_full_hold_state | bool(false)) }} | |
| sequence: | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ (diagnostic_log_level | string | trim) in ['events', 'write', 'decision', 'debug'] }} | |
| sequence: | |
| - service: system_log.write | |
| data: | |
| level: info | |
| message: >- | |
| SunEnergyXT zero feed-in | event=full_hold_on | soc={{ soc_value | float(0) | round(1) }}% | lower={{ effective_min_discharge_soc | float(0) }}% | upper={{ effective_max_charge_soc | float(0) }}% | meter={{ normalized_grid_w | float(0) | round(0) }}W | gp={{ gp_power_w | float(0) | round(0) }}W | lp={{ load_power_w | float(0) | round(0) }}W | pv={{ pv_power_w | float(0) | round(0) }}W | |
| - service: input_boolean.turn_on | |
| target: | |
| entity_id: "{{ full_hold_entity | trim }}" | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ has_full_hold_helper | bool(false) | |
| and not (full_hold_state | bool(false)) | |
| and current_full_hold_state | bool(false) }} | |
| sequence: | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ (diagnostic_log_level | string | trim) in ['events', 'write', 'decision', 'debug'] }} | |
| sequence: | |
| - service: system_log.write | |
| data: | |
| level: info | |
| message: >- | |
| SunEnergyXT zero feed-in | event=full_hold_off | soc={{ soc_value | float(0) | round(1) }}% | lower={{ effective_min_discharge_soc | float(0) }}% | upper={{ effective_max_charge_soc | float(0) }}% | meter={{ normalized_grid_w | float(0) | round(0) }}W | gp={{ gp_power_w | float(0) | round(0) }}W | lp={{ load_power_w | float(0) | round(0) }}W | pv={{ pv_power_w | float(0) | round(0) }}W | |
| - service: input_boolean.turn_off | |
| target: | |
| entity_id: "{{ full_hold_entity | trim }}" | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ has_low_hold_helper | bool(false) | |
| and low_hold_state | bool(false) | |
| and not (current_low_hold_state | bool(false)) }} | |
| sequence: | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ (diagnostic_log_level | string | trim) in ['events', 'write', 'decision', 'debug'] }} | |
| sequence: | |
| - service: system_log.write | |
| data: | |
| level: info | |
| message: >- | |
| SunEnergyXT zero feed-in | event=low_hold_on | soc={{ soc_value | float(0) | round(1) }}% | lower={{ effective_min_discharge_soc | float(0) }}% | upper={{ effective_max_charge_soc | float(0) }}% | meter={{ normalized_grid_w | float(0) | round(0) }}W | gp={{ gp_power_w | float(0) | round(0) }}W | lp={{ load_power_w | float(0) | round(0) }}W | pv={{ pv_power_w | float(0) | round(0) }}W | |
| - service: input_boolean.turn_on | |
| target: | |
| entity_id: "{{ low_hold_entity | trim }}" | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ has_low_hold_helper | bool(false) | |
| and not (low_hold_state | bool(false)) | |
| and current_low_hold_state | bool(false) }} | |
| sequence: | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ (diagnostic_log_level | string | trim) in ['events', 'write', 'decision', 'debug'] }} | |
| sequence: | |
| - service: system_log.write | |
| data: | |
| level: info | |
| message: >- | |
| SunEnergyXT zero feed-in | event=low_hold_off | soc={{ soc_value | float(0) | round(1) }}% | lower={{ effective_min_discharge_soc | float(0) }}% | upper={{ effective_max_charge_soc | float(0) }}% | meter={{ normalized_grid_w | float(0) | round(0) }}W | gp={{ gp_power_w | float(0) | round(0) }}W | lp={{ load_power_w | float(0) | round(0) }}W | pv={{ pv_power_w | float(0) | round(0) }}W | |
| - service: input_boolean.turn_off | |
| target: | |
| entity_id: "{{ low_hold_entity | trim }}" | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: "{{ should_write_is }}" | |
| sequence: | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ (diagnostic_log_level | string | trim) in ['write', 'decision', 'debug'] }} | |
| sequence: | |
| - service: system_log.write | |
| data: | |
| level: info | |
| message: >- | |
| SunEnergyXT zero feed-in | event=write_is | old={{ current_is_w | float(0) | round(0) }}W | new={{ next_is_w | float(0) | round(0) }}W | target={{ target_is_w | float(0) | round(0) }}W | meter={{ normalized_grid_w | float(0) | round(0) }}W | gp={{ gp_power_w | float(0) | round(0) }}W | lp={{ load_power_w | float(0) | round(0) }}W | pv={{ pv_power_w | float(0) | round(0) }}W | soc={{ soc_value | float(0) | round(1) }}% | full_hold={{ full_hold_state | bool(false) }} | low_hold={{ low_hold_state | bool(false) }} | |
| - service: number.set_value | |
| continue_on_error: true | |
| target: | |
| entity_id: "{{ is_entity | trim }}" | |
| data: | |
| value: "{{ next_is_w }}" | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: "{{ should_write_gs }}" | |
| sequence: | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: >- | |
| {{ (diagnostic_log_level | string | trim) in ['write', 'decision', 'debug'] }} | |
| sequence: | |
| - service: system_log.write | |
| data: | |
| level: info | |
| message: >- | |
| SunEnergyXT zero feed-in | event=write_gs | old={{ current_gs_w | float(0) | round(0) }}W | new={{ next_gs_w | float(0) | round(0) }}W | target={{ target_gs_w | float(0) | round(0) }}W | meter={{ normalized_grid_w | float(0) | round(0) }}W | gp={{ gp_power_w | float(0) | round(0) }}W | lp={{ load_power_w | float(0) | round(0) }}W | pv={{ pv_power_w | float(0) | round(0) }}W | soc={{ soc_value | float(0) | round(1) }}% | full_hold={{ full_hold_state | bool(false) }} | low_hold={{ low_hold_state | bool(false) }} | |
| - service: number.set_value | |
| target: | |
| entity_id: "{{ gs_entity | trim }}" | |
| data: | |
| value: "{{ next_gs_w }}" | |
| - choose: | |
| - conditions: | |
| - condition: template | |
| value_template: "{{ diagnostic_decision_log_due | bool(false) }}" | |
| sequence: | |
| - service: system_log.write | |
| data: | |
| level: info | |
| message: >- | |
| SunEnergyXT zero feed-in | event=decision | meter={{ normalized_grid_w | float(0) | round(0) }}W | target_grid={{ target_grid_power_w | float(0) | round(0) }}W | gp={{ gp_power_w | float(0) | round(0) }}W | lp={{ load_power_w | float(0) | round(0) }}W | pv={{ pv_power_w | float(0) | round(0) }}W | soc={{ soc_value | float(0) | round(1) }}% | gs={{ current_gs_w | float(0) | round(0) }}W->{{ next_gs_w | float(0) | round(0) }}W | is={{ current_is_w | float(0) | round(0) }}W->{{ next_is_w | float(0) | round(0) }}W | write_gs={{ should_write_gs | bool(false) }} | write_is={{ should_write_is | bool(false) }} | full_hold={{ full_hold_state | bool(false) }} | low_hold={{ low_hold_state | bool(false) }} | bypass={{ bypass_only | bool(false) }} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment