Skip to content

Instantly share code, notes, and snippets.

@tomquist
Created January 10, 2025 18:06
Show Gist options
  • Save tomquist/53bea769b8db13cf160ac84ac0122dbf to your computer and use it in GitHub Desktop.
Save tomquist/53bea769b8db13cf160ac84ac0122dbf to your computer and use it in GitHub Desktop.
alias: "Zero Power Solar Control"
description: "Adjusts solar output to achieve zero grid consumption with safety features"
# Control parameters for easy tuning
variables:
# Entity Configuration
GRID_POWER_SENSOR: "sensor.lesekopf_sml_power_curr" # Grid power sensor
HELPER_LAST_COMMAND: "input_text.last_solar_command" # Helper storing last command
HELPER_FALLBACK_POWER: "input_number.solar_fallback_power" # Helper storing fallback power
HELPER_LAST_NOTIFICATION: "input_datetime.last_zero_power_notification" # Helper for notification timing
# MQTT Configuration
MQTT_TOPIC: "hame_energy/HMA-1/App/1234567/ctrl" # Your MQTT topic
# Control parameters
CONTROL_DEADBAND: 10 # ±W around zero where no adjustment is made
CONTROL_PROPORTION: 0.7 # Adjustment factor (0.7 = 70% of measured offset)
MIN_ADJUSTMENT: 20 # Minimum power adjustment threshold (W)
MAX_RATE_OF_CHANGE: 100 # Maximum change per adjustment (W)
SENSOR_TIMEOUT: 30 # Seconds before considering sensor failed
trigger:
- platform: state
entity_id: "{{ variables.GRID_POWER_SENSOR }}"
for: "0:00:10"
condition:
# Only run during daytime (optional, remove if 24/7 operation desired)
- condition: time
after: "07:00:00"
before: "21:00:00"
# Sensor value sanity check
- condition: template
value_template: >
{% set power = states(variables.GRID_POWER_SENSOR) | float(-9999) %}
{% set age = (now() - states[variables.GRID_POWER_SENSOR].last_updated).total_seconds() %}
{{ power > -5000 and power < 5000 and age < variables.SENSOR_TIMEOUT }}
variables:
# Get current grid power consumption
current_grid_power: >
{% set power = states(variables.GRID_POWER_SENSOR) | float(default=0) %}
{{ power }}
# Extract current solar output from stored command with enhanced validation
current_solar_output: >
{% set stored = states(variables.HELPER_LAST_COMMAND) | default('') %}
{% if stored and 'v1=' in stored %}
{% set matches = stored | regex_findall('v1=(\d+)') %}
{% if matches and matches[0] | int(-1) >= 0 %}
{{ matches[0] | int }}
{% else %}
{% set fallback = states(variables.HELPER_FALLBACK_POWER) | int(0) %}
{{ fallback }}
{% endif %}
{% else %}
{% set fallback = states(variables.HELPER_FALLBACK_POWER) | int(0) %}
{{ fallback }}
{% endif %}
# Check if adjustment is needed (implements deadband)
needs_adjustment: >
{% set grid = states(variables.GRID_POWER_SENSOR) | float(default=0) %}
{{ grid < -variables.CONTROL_DEADBAND or grid > variables.CONTROL_DEADBAND }}
# Calculate new solar output with rate limiting
new_solar_output: >
{% set grid = states(variables.GRID_POWER_SENSOR) | float(default=0) %}
{% set current_output = variables.current_solar_output | int %}
{% if not variables.needs_adjustment %}
{# Within deadband - maintain current output #}
{{ current_output }}
{% else %}
{# Calculate adjustment with proportion factor #}
{% set raw_adjustment = (grid * variables.CONTROL_PROPORTION) | round(0) %}
{# Limit rate of change #}
{% set max_change = variables.MAX_RATE_OF_CHANGE %}
{% set limited_adjustment = [raw_adjustment, max_change, -max_change]
| sort(reverse=false) [1] %}
{% set new_value = current_output + limited_adjustment %}
{# Clamp between 0 and 800 #}
{{ [new_value | round(0), 0, 800] | sort(reverse=false) [1] }}
{% endif %}
action:
# Only act if significant change needed
- condition: template
value_template: >
{% set current = variables.current_solar_output | int %}
{% set new = variables.new_solar_output | int %}
{% set significant_change = (current - new) | abs >= variables.MIN_ADJUSTMENT %}
{{ significant_change and variables.needs_adjustment }}
# Comprehensive logging
- service: system_log.write
data:
message: >-
Zero Power: Grid={{ variables.current_grid_power }}W,
Current Solar={{ variables.current_solar_output }}W,
New Solar={{ variables.new_solar_output }}W,
Needs Adjustment={{ variables.needs_adjustment }},
Delta={{ (variables.new_solar_output - variables.current_solar_output) }}W
level: info
# Send MQTT command with error handling
- service: mqtt.publish
data:
topic: "{{ variables.MQTT_TOPIC }}"
payload: >-
cd=20,md=0,a1=1,b1=0:00,e1=23:59,v1={{ variables.new_solar_output }}
response_variable: mqtt_result
# Log MQTT failures
- if:
- condition: template
value_template: "{{ not mqtt_result }}"
then:
- service: system_log.write
data:
message: "Zero Power: Failed to publish MQTT message!"
level: warning
# Store last known good value as fallback
- service: input_number.set_value
target:
entity_id: "{{ variables.HELPER_FALLBACK_POWER }}"
data:
value: "{{ variables.current_solar_output }}"
# Store successful command
- service: input_text.set_value
target:
entity_id: "{{ variables.HELPER_LAST_COMMAND }}"
data:
value: "cd=20,md=0,a1=1,b1=0:00,e1=23:59,v1={{ variables.new_solar_output }}"
# Monitor for persistent issues (hourly check)
- condition: template
value_template: >
{% set avg_power = float(states(variables.GRID_POWER_SENSOR)) | float(0) %}
{% set last_notification = states(variables.HELPER_LAST_NOTIFICATION)
| default('2020-01-01') %}
{% set hours_since = (now() - strptime(last_notification, '%Y-%m-%d %H:%M:%S'))
.total_seconds() / 3600 %}
{{ (avg_power | abs > 50) and hours_since > 1 }}
- service: notify.notify
data:
message: >
Zero Power Warning: Grid power has been {{ states(variables.GRID_POWER_SENSOR) }}W
for over an hour. Check system status.
- service: input_datetime.set_datetime
target:
entity_id: "{{ variables.HELPER_LAST_NOTIFICATION }}"
data:
datetime: "{{ now() }}"
mode: queued
max: 5
# Helper entities for Zero Power Control
input_text:
last_solar_command:
name: Last Solar Command
initial: ""
max: 255
input_number:
solar_fallback_power:
name: Solar Fallback Power
min: 0
max: 800
step: 1
unit_of_measurement: "W"
icon: mdi:solar-power
input_datetime:
last_zero_power_notification:
name: Last Zero Power Notification
has_date: true
has_time: true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment