batpred icon indicating copy to clipboard operation
batpred copied to clipboard

solax has no hold charge freeze charge or freeze discharge

Open adamrhunt opened this issue 8 months ago • 15 comments

when predbat goes into holdcharge mode on the plan it switches a solax inverter into charge mode causing it to charge on higher rate slots that you dont want it to. Im not sure if there there is a way to make the inverter hold charge on solax could be added ? as the only commands solax have are is “force charge” (from grid) “force discharge” (to grid) “stop charge and discharge” (stops using battery) “back up mode” (using battery or charging battery from solar) “Feed in priority” ( prioritizes exporting excess solar energy to the grid, even if the battery could be charged,) Could there be a way to use these commands on apps yaml? I have the charge start service using “force charge” and then discharge service using “force discharge” and the stop services using “back up”. But for solax this does not make hold charge work it just makes the inverter charge switching to “force charge”

ive pasted my apps.yaml which all works apart from the hold service is there a way wit solax to make the hold service work?


 pred_bat:
  module: predbat
  class: PredBat

  # Sets the prefix for all created entities in HA - only change if you want to run more than once instance
  prefix: predbat

  # Timezone to work in
  timezone: Europe/London

  # XXX: Template configuration, delete this line once you have set up for your system
  

  # If you are using Predbat outside of HA then set the HA URL and Key (long lived access token here)
  #ha_url: 'http://homeassistant.local:8123'
  #ha_key: 'xxx'

  # Currency, symbol for main currency second symbol for 1/100s e.g. $ c or £ p or e c
  currency_symbols:
    - '£'
    - 'p'

  # Number of threads to use in plan calculation
  # Can be auto for automatic, 0 for off or values 1-N for a fixed number
  threads: auto

  #
  # Sensors, currently more than one can be specified and they will be summed up automatically
  # however if you have two inverters only set one of them as they will both read the same.
  #

  inverter_type: "SX4"
  num_inverters: 1
  #
  # Controls/status - must by 1 per inverter
  #

  # Max inverter power from battery
  battery_rate_max:
    - 8000
  # Battery capacity in kWh
  soc_max:
    - 14.87

  # Solis specific parameters (these are based on https://github.com/wills106/homeassistant-solax-modbus)

  load_today:
    - sensor.solax_today_house_load
  import_today:
    - sensor.solax_today_s_import_energy
  export_today:
    - sensor.solax_today_s_export_energy
  pv_today:
    - sensor.solax_today_s_solar_energy

  battery_voltage:
    - sensor.solax_battery_voltage_charge

   # This is disabled by default in the Solax integration so it must be manually enabled.
  inverter_time:
    - sensor.solax_rtc

  # This is disabled by default in the Solax integration so it must be manually enabled.
  battery_power:
    - sensor.solax_battery_power_charge
  pv_power:
    - sensor.solax_pv_power_total

  # For Solis inverters we need to add the bypass (backup) load to the main house load. If not used the load_power_1 can be commented out
  load_power:
    - sensor.solax_house_load
  #load_power_1:
  #  - sensor.solis_bypass_load

  soc_percent:
    - sensor.solax_battery_capacity

  reserve:
    - number.solax_backup_discharge_min_soc

  battery_min_soc:
    - number.solax_feedin_discharge_min_soc

  # With the Solax integration we need to press a button to send the time to
  charge_discharge_update_button:
    - button.solax_battery_awaken

  #timed_charge_current:
  #  - number.solis_timed_charge_current
  #charge_start_hour:
  #  - number.solis_timed_charge_start_hours
  #charge_start_minute:
  #  - number.solis_timed_charge_start_minutes
  #charge_end_hour:
  #  - number.solis_timed_charge_end_hours
  #charge_end_minute:
  #  - number.solis_timed_charge_end_minutes


  #timed_discharge_current:
  #  - number.solis_timed_discharge_current

  #discharge_start_hour:
  #  - number.solis_timed_discharge_start_hours
  #discharge_start_minute:
  #  - number.solis_timed_discharge_start_minutes
  #discharge_end_hour:
  #  - number.solis_timed_discharge_end_hours
  #discharge_end_minute:
  #  - number.solis_timed_discharge_end_minutes


  energy_control_switch:
    - select.solax_remotecontrol_power_control

# Service for start/stop
  charge_start_service:
    - service: select.select_option
      entity_id: select.solax_charger_use_mode
      option: "Manual Mode"
    - service: select.select_option
      entity_id: select.solax_manual_mode_select
      option: "Force Charge"

 
  charge_stop_service:
    service: select.select_option
    entity_id: select.solax_charger_use_mode
    option: "Back Up Mode" 
 
  discharge_start_service:
    - service: select.select_option
      entity_id: select.solax_charger_use_mode
      option: "Manual Mode"
    - service: select.select_option
      entity_id: select.solax_manual_mode_select
      option: "Force Discharge"
 
  discharge_stop_service:
    service: select.select_option
    entity_id: select.solax_charger_use_mode
    option: "Back Up Mode"
 
 
  

  # Inverter max AC limit (one per inverter)
  # If you have a second inverter for PV only please add the two values together
  inverter_limit: 8000

  # Export limit is a software limit set on your inverter that prevents exporting above a given level
  # When enabled Predbat will model this limit
  #export_limit:
  #  - 3600
  #  - 3600
  #
  # The maximum rate the inverter can charge and discharge the battery can be overwritten, this will change
  # the register programming and thus cap the max rates. The default is to use the maximum supported rates (recommended)
  #
  #inverter_limit_charge:
  #  - 2000
  #inverter_limit_discharge:
  #  - 2600


  # Some inverters don't turn off when the rate is set to 0, still charge or discharge at around 200w
  # The value can be set here in watts to model this (doesn't change operation)
  #inverter_battery_rate_min:
  #  - 100

  # Some batteries tail off their charge rate at high soc%
  # enter the charging curve here as a % of the max charge rate for each soc percentage.
  # the default is 1.0 (full power)
  #battery_charge_power_curve:
  #  91 : 0.91
  #  92 : 0.81
  #  93 : 0.71
  #  94 : 0.62
  #  95 : 0.52
  #  96 : 0.43
  #  97 : 0.33
  #  98 : 0.24
  #  99 : 0.24
  #  100 : 0.24

  # Inverter clock skew in minutes, e.g. 1 means it's 1 minute fast and -1 is 1 minute slow
  # Separate start and end options are applied to the start and end time windows, mostly as you want to start late (not early) and finish early (not late)
  # Separate discharge skew for discharge windows only
  inverter_clock_skew_start: 0
  inverter_clock_skew_end: 0
  inverter_clock_skew_discharge_start: 0
  inverter_clock_skew_discharge_end: 0

  # Clock skew adjusts the Appdaemon time
  # This is the time that Predbat takes actions like starting discharge/charging
  # Only use this for workarounds if your inverter time is correct but Predbat is somehow wrong (AppDaemon issue)
  # 1 means add 1 minute to AppDaemon time, -1 takes it away
  clock_skew: 0

  # Solcast cloud interface, set this or the local interface below
  #solcast_host: 'https://api.solcast.com.au/'
  #solcast_api_key: 'xxxx'
  #solcast_poll_hours: 8

  # Set these to match solcast sensor names if not using the cloud interface
  # The regular expression (re:) makes the solcast bit optional
  # If these don't match find your own names in Home Assistant
  pv_forecast_today: re:(sensor.(solcast_|)(pv_forecast_|)forecast_today)
  pv_forecast_tomorrow: re:(sensor.(solcast_|)(pv_forecast_|)forecast_tomorrow)
  pv_forecast_d3: re:(sensor.(solcast_|)(pv_forecast_|)forecast_(day_3|d3))
  pv_forecast_d4: re:(sensor.(solcast_|)(pv_forecast_|)forecast_(day_4|d4))

  # car_charging_energy defines an incrementing sensor which measures the charge added to your car
  # is used for car_charging_hold feature to filter out car charging from the previous load data
  # Automatically set to detect Wallbox and Zappi, if it doesn't match manually enter your sensor name
  # Also adjust car_charging_energy_scale if it's not in kwH to fix the units
  car_charging_energy: 
   're:(sensor.myenergi_zappi_[0-9a-z]+_charge_added_session)'
   
  
  num_cars: 1

  # car_charging_planned is set to a sensor which when positive indicates the car will charged in the upcoming low rate slots
  # This should not be needed if you use Octopus Intelligent Slots which will take priority if enabled
  # The list of possible values is in car_charging_planned_response
  # Auto matches Zappi and Wallbox, or change it for your own
  car_charging_planned:
     - 're:(sensor.myenergi_zappi_[0-9a-z]+_plug_status)'
     - 'connected'

  car_charging_planned_response:
     - 'yes'
     - 'on'
     - 'true'
     - 'connected'
     - 'ev connected'
     - 'charging'
     - 'paused'
     - 'waiting for car demand'
     - 'waiting for ev'
     - 'scheduled'
     - 'enabled'
     - 'latched'
     - 'locked'
     - 'plugged in'

  # To make planned car charging more accurate, either using car_charging_planned or Octopus Intelligent
  # specify your battery size in kwh, charge limit % and current car battery soc % sensors/values
  # If you have intelligent the battery size and limit will be extracted from Intelligent directly
  # Set the car SOC% if you have it to give an accurate forecast of the cars battery levels
  # One entry per car if you have multiple cars
  car_charging_battery_size:
     - 64
  # car_charging_limit:
  #   - 're:number.tsunami_charge_limit'
  # car_charging_soc:
  #   - 're:sensor.tsunami_battery'

  # If you have Octopus intelligent, enable the intelligent slot information to add to pricing
  # Will automatically disable if not found, or comment out to disable fully
  # When enabled it overrides the 'car_charging_planned' feature and predict the car charging based on the intelligent plan (unless octopus intelligent charging is False)
  # This matches either the intelligent slot from the Octopus Plugin or from the Intelligent plugin
  # octopus_intelligent_slot: 're:(binary_sensor.octopus_intelligent_slot|re:binary_sensor.octopus_energy_intelligent_dispatching)'
  # octopus_intelligent_slot: 're:binary_sensor.octopus_energy_a_02a2c7d0_intelligent_dispatching'
  # octopus_ready_time: 're:time.octopus_energy_a_02a2c7d0_intelligent_target_time'
  # octopus_charge_limit: 're:number.octopus_energy_a_02a2c7d0_intelligent_charge_target'

  # Energy rates
  # Please set one of these three, if multiple are set then Octopus is used first, second rates_import/rates_export and latest basic metric

  # Set import and export entity to point to the Octopus Energy plugin
  # automatically matches your meter number assuming you have only one
  # Will be ignored if you don't have the sensor
  # Or manually set it to the correct sensor names e.g:
  # sensor.octopus_energy_electricity_xxxxxxxxxx_xxxxxxxxxxxxx_current_rate
  # sensor.octopus_energy_electricity_xxxxxxxxxx_xxxxxxxxxxxxx_export_current_rate
  metric_octopus_import: 're:(sensor.octopus_energy_electricity_22e1034737_1100002737690_current_rate)'
  metric_octopus_export: 're:(sensor.octopus_energy_electricity_22e1034737_1170001721258_export_current_rate)'

  # Standing charge can be set to a sensor (e.g. Octopus) or manually entered in pounds here (e.g. 0.50 is 50p)
  metric_standing_charge: 're:(sensor.octopus_energy_electricity_22e1034737_1100002737690_current_standing_charge)'

  # Or set your actual rates across time for import and export
  # If start/end is missing it's assumed to be a fixed rate
  # Gaps are filled with 0
  # rates_import:
  #   - start: "23:30:00"
  #     end: "05:30:00"
  #     rate: 7.5
  #   - start: "05:30:00"
  #     end: "23:30:00"
  #     rate: 30.0

  # rates_export:
  #   - rate: 15.0

  # Can be used instead of the plugin to get import rates directly online
  # Overrides metric_octopus_import and rates_import
  # rates_import_octopus_url : "https://api.octopus.energy/v1/products/FLUX-IMPORT-23-02-14/electricity-tariffs/E-1R-FLUX-IMPORT-23-02-14-A/standard-unit-rates"
  # rates_import_octopus_url : "https://api.octopus.energy/v1/products/AGILE-FLEX-BB-23-02-08/electricity-tariffs/E-1R-AGILE-FLEX-BB-23-02-08-A/standard-unit-rates"

  # Overrides metric_octopus_export and rates_export
  # rates_export_octopus_url: "https://api.octopus.energy/v1/products/FLUX-EXPORT-BB-23-02-14/electricity-tariffs/E-1R-FLUX-EXPORT-BB-23-02-14-A/standard-unit-rates"
  # rates_export_octopus_url: "https://api.octopus.energy/v1/products/AGILE-OUTGOING-BB-23-02-28/electricity-tariffs/E-1R-AGILE-OUTGOING-BB-23-02-28-A/standard-unit-rates/"
  # rates_export_octopus_url: "https://api.octopus.energy/v1/products/OUTGOING-FIX-12M-BB-23-02-09/electricity-tariffs/E-1R-OUTGOING-FIX-12M-BB-23-02-09-A/standard-unit-rates/"

  # Import rates can be overridden with rate_import_override
  # Export rates can be overridden with rate_export_override
  # Use the same format as above, but a date can be included if it just applies for a set day (e.g. Octopus power ups)
  # This will override even the Octopus plugin rates if enabled
  #
  #rates_import_override:
  #  - date: '2023-09-10'
  #    start: '14:00:00'
  #    end: '14:30:00'
  #    rate: 5

  # For pv estimate, leave blank for central estimate, or add 10 for 10% curve (worst case) or 90 or 90% curve (best case)
  # If you use 10 then disable pv_metric10_weight below
  # pv_estimate: 10

  # Days previous is the number of days back to find historical load data
  # Recommended is 7 to capture day of the week but 1 can also be used
  # if you have more history you could use 7 and 14 (in a list) but the standard data in HA only lasts 10 days
  days_previous:
    - 7


  # Days previous weight can be used to control the weighting of the previous load points, the values are multiplied by their
  # weights and then divided through by the total weight. E.g. if you used 1 and 0.5 then the first value would have 2/3rd of the weight and the second 1/3rd
  days_previous_weight:
    - 1

  # Number of hours forward to forecast, best left as-is unless you have specific reason
  forecast_hours: 48

  # The number of hours ahead to count in charge planning (for cost estimates)
  # It's best to set this on your charge window repeat cycle (24) but you may want to set it higher for more variable
  # tariffs like Agile
  forecast_plan_hours: 24

  # Specify the devices that notifies are sent to, the default is 'notify' which goes to all
  #notify_devices:
  #  - mobile_app_treforsiphone12_2

  # Set the frequency in minutes that this plugin is run
  # recommend something that divides by 60 (5, 10 or 15) or you won't trigger at the start of energy price slots
  run_every: 5

  # Battery scaling makes the battery smaller (e.g. 0.9) or bigger than its reported
  # If you have an 80% DoD battery that falsely reports it's kwh then set it to 0.8 to report the real figures
  battery_scaling: 1.0

  # Can be used to scale import and export data, used for workarounds
  import_export_scaling: 1.0

  # Export triggers:
  # For each trigger give a name, the minutes of export needed and the energy required in that time
  # Multiple triggers can be set at once so in total you could use too much energy if all run
  # Creates an entity called 'binary_sensor.predbat_export_trigger_<name>' which will be turned On when the condition is valid
  # connect this to your automation to start whatever you want to trigger
  export_triggers:
    - name: 'large'
      minutes: 60
      energy: 1.0
    - name: 'small'
      minutes: 15
      energy: 0.25

  # If you have a sensor that gives the energy consumed by your solar diverter then add it here
  # this will make the predictions more accurate. It should be an incrementing sensor, it can reset at midnight or not
  # It's assumed to be in Kwh but scaling can be applied if need be
  #iboost_energy_today: 'sensor.xxxxx'
  #iboost_energy_scaling: 1.0

adamrhunt avatar Apr 06 '25 07:04 adamrhunt

Did you mean to close this issue without comment?

The code is meant to change between charging and discharging based on the current SOC level, can you share a logfile where it doesn't do this?

springfall2008 avatar Apr 06 '25 08:04 springfall2008

sorry no i didnt mean to close it

predbat.log

adamrhunt avatar Apr 06 '25 09:04 adamrhunt

Additonal config for investigation. Mine uses solax_modbus in modbus remote control mode (no EEPROM writes, instant control) and I think the implementations of the freeze service calls work when called.

 ## Need expert mode enabled to configure battery cycle cost and self-sufficiency bias, which are then set through the UI
  expert_mode: True

  # Inverter
  # JT - combined our two as one; trying to have two was tricky and we can't ignore the non-battery one else predbat doesn't see the true load
  inverter_type: "SX4"
  num_inverters: 1
  output_charge_control: "power"

  # solax_modbus_new: true

  ##JT shouldn't need this if using direct control rather than setting time periods, but it errors without it
  inverter_time: sensor.solax_rtc

  #
  # Controls/status - must by 1 per inverter
  #

  # Max inverter power from battery
  battery_rate_max:
  # per Solax docs for the T58
    - 4000
  # Battery capacity in kWh
  soc_max:
    - 5.8

  # Solis specific parameters (these are based on https://github.com/wills106/homeassistant-solax-modbus)

  load_today:
    - sensor.house_power_consumed_today
  import_today:
    - sensor.solax_today_s_import_energy
  export_today:
    - sensor.solax_today_s_export_energy
  pv_today:
    - sensor.solar_yield_today

  battery_voltage:
    - sensor.solax_battery_voltage_charge

  inverter_time:
    - sensor.solax_rtc

  battery_power:
    - sensor.solax_battery_power_charge
  pv_power:
    - sensor.solax_pv_power_total

  # For Solis inverters we need to add the bypass (backup) load to the main house load. If not used the load_power_1 can be commented out
  load_power:
    - sensor.solar_ac_power

  soc_percent:
    - sensor.solax_battery_capacity
  reserve:
    - number.solax_backup_discharge_min_soc
  battery_min_soc:
    - number.solax_feedin_discharge_min_soc

## JT - Predbat errors without this, so we have a dummy one when using services
#  charge_discharge_update_button:
#     - input_button.predbat_dummy_button

## JT - Services for start/stop - needed for SolaX as the supposed SX4 controls don't seem to achieve anything
  charge_start_service:
  # force charge from grid + solar
    service: input_select.select_option
    entity_id: input_select.solar_control_mode
    option: "Charge"
  charge_stop_service:
  # return to normal operation (self-use mode)
    service: input_select.select_option
    entity_id: input_select.solar_control_mode
    option: "Self-use"
  charge_freeze_service:
  # Charge freeze: NO discharge, NO grid charge, ALLOW solar to charge battery.
    service: input_select.select_option
    entity_id: input_select.solar_control_mode
    option: "Charge freeze"
  discharge_freeze_service:
  # Export freeze: ALLOW discharge to load (but don't force export), NO grid charge, NO solar charge.
    service: input_select.select_option
    entity_id: input_select.solar_control_mode
    option: "Discharge freeze"
  discharge_start_service:
  # force discharge (i.e., force export)
    service: input_select.select_option
    entity_id: input_select.solar_control_mode
    option: "Discharge"
  discharge_stop_service:
  # return to normal operation (self-use mode)
    service: input_select.select_option
    entity_id: input_select.solar_control_mode
    option: "Self-use"

  # Inverter max AC limit (one per inverter)
  # If you have a second inverter for PV only please add the two values together
  inverter_limit:
  ## JT - 5k + 2k
    - 7000

  # Export limit is a software limit set on your inverter that prevents exporting above a given level
  # When enabled Predbat will model this limit
  export_limit:
  ## JT - 7200 is our G99 export limit from the grid/DNO
    - 7200

  # Some inverters don't turn off when the rate is set to 0, still charge or discharge at around 200w
  # The value can be set here in watts to model this (doesn't change operation)
#  inverter_battery_rate_min:
#    - 100

  ## JT - this is a rough observation; need to put it into forced charge and watch to get a better curve
  ## this may taper much earlier - at 92%, I'm seeing 2.7kW which is ~55%!
  ## It's supposed to calculate this from historical data, so let's leave it out and see how it fares!
  # battery_charge_power_curve:
  #  95 : 0.80
  #  96 : 0.60
  #  97 : 0.40
  #  98 : 0.30
  #  99 : 0.20
  #  100 : 0.10

  # Battery min SoC
  set_reserve_min: 10

  # Inverter efficiency
  ## JT 5% vs default 4%, battery figures are default 3%
  battery_loss: 0.03
  battery_loss_discharge: 0.03
  inverter_loss: 0.05

  # Octoplus saving sessions & free energy events - predbat will decide whether to charge and dump
  octopus_saving_session: binary_sensor.octopus_energy_a_39001cba_octoplus_saving_sessions
  octopus_free_session: 're:(event.octopus_energy_([0-9a-z_]+|)_octoplus_free_electricity_session_events)'

  # Energy rates

  # Set import and export entity to point to the Octopus Energy plugin
  # automatically matches your meter number assuming you have only one
  # Or manually set it to the correct sensor names e.g:
  # sensor.octopus_energy_electricity_xxxxxxxxxx_xxxxxxxxxxxxx_current_rate
  # sensor.octopus_energy_electricity_xxxxxxxxxx_xxxxxxxxxxxxx_export_current_rate
  metric_octopus_import: 're:(sensor.(octopus_energy_|)electricity_[0-9a-z]+_[0-9a-z]+_current_rate)'
  metric_octopus_export: 're:(sensor.(octopus_energy_|)electricity_[0-9a-z]+_[0-9a-z]+_export_current_rate)'

  # Standing charge can be set to a sensor (e.g. Octopus) or manually entered in pounds here (e.g. 0.50 is 50p)
  metric_standing_charge: 're:(sensor.(octopus_energy_|)electricity_[0-9a-z]+_[0-9a-z]+_current_standing_charge)'

  # For pv estimate, leave blank for central estimate, or add 10 for 10% curve (worst case) or 90 or 90% curve (best case)
  # If you use 10 then disable pv_metric10_weight below
  # pv_estimate: 10

  # Days previous is the number of days back to find historical load data
  # Recommended is 7 to capture day of the week but 1 can also be used
  # if you have more history you could use 7 and 14 (in a list) but the standard data in HA only lasts 10 days
  ## JT - tried averaging this day last week (usage pattern) and yesterday (weather/time of year effects), but that caused Monday afternoon to look rather high because of Sunday laundry.
  ##      So, back to just same day last week.
  days_previous:
    - 1
    - 7
    - 14

  # Days previous weight can be used to control the weighting of the previous load points, the values are multiplied by their
  # weights and then divided through by the total weight. E.g. if you used 1 and 0.5 then the first value would have 2/3rd of the weight and the second 1/3rd
  ## JT - bias to same day last week, with a smattering of earlier weeks and the last 2 days
  days_previous_weight:
    - 35
    - 50
    - 15

  # Number of hours forward to forecast, best left as-is unless you have specific reason (default is 48)
  forecast_hours: 48

  # The number of hours ahead to count in charge planning (for cost estimates)
  # It's best to set this on your charge window repeat cycle (24) but you may want to set it higher for more variable
  # tariffs like Agile
  ## JT - Agile. 36 hrs is about the most we can ever see rates for and covers overnight into the next day
  forecast_plan_hours: 36

  ## How often to reevaulate the plan
  calculate_plan_every: 10

  # Specify the devices that notifies are sent to, the default is 'notify' which goes to all
  #notify_devices:
  #  - mobile_app_treforsiphone12_2

  # Set the frequency in minutes that this plugin is run
  # recommend something that divides by 60 (5, 10 or 15) or you won't trigger at the start of energy price slots
  ## JT - default 5, I think 10 is often enough. Not sure this does anything as we're running predbat in a docker not through the HA plugin
  run_every: 10

  # Solcast cloud interface, set this or the local interface below
  #solcast_host: 'https://api.solcast.com.au/'
  #solcast_api_key: '7F6SvLJMOIinITEfqHFdG7eqC0PcIrsJ'
  #solcast_poll_hours: 8

  # Set these to match solcast sensor names if not using the cloud interface
  # The regular expression (re:) makes the solcast bit optional
  # If these don't match find your own names in Home Assistant
  pv_forecast_today: re:(sensor.(solcast_|)(pv_forecast_|)forecast_today)
  pv_forecast_tomorrow: re:(sensor.(solcast_|)(pv_forecast_|)forecast_tomorrow)
  pv_forecast_d3: re:(sensor.(solcast_|)(pv_forecast_|)forecast_(day_3|d3))
  pv_forecast_d4: re:(sensor.(solcast_|)(pv_forecast_|)forecast_(day_4|d4))
  # Battery scaling makes the battery smaller (e.g. 0.9) or bigger than its reported
  # If you have an 80% DoD battery that falsely reports it's kwh then set it to 0.8 to report the real figures
  battery_scaling: 1.0

  # Can be used to scale import and export data, used for workarounds
  import_export_scaling: 1.0

  # Inverter clock skew in minutes, e.g. 1 means it's 1 minute fast and -1 is 1 minute slow
  # Separate start and end options are applied to the start and end time windows, mostly as you want to start late (not early) and finish early (not late)
  # Separate discharge skew for discharge windows only
  inverter_clock_skew_start: 0
  inverter_clock_skew_end: 0
  inverter_clock_skew_discharge_start: 0
  inverter_clock_skew_discharge_end: 0

  # Clock skew adjusts the Appdaemon time
  # This is the time that Predbat takes actions like starting discharge/charging
  # Only use this for workarounds if your inverter time is correct but Predbat is somehow wrong (AppDaemon issue)
  # 1 means add 1 minute to AppDaemon time, -1 takes it away
  clock_skew: 0

  #
  # Car charging
  #
  # How many cars?
  num_cars: 1
  car_charging_exclusive:
    - True
  # JT - set switch.predbat_car_charging_hold ON in HA (or its equivalent in the predbat UI config page) to remove car charging from the house load data
  car_charging_energy:
    - sensor.evc_charge_added
    # might need to be the cumulative one, not sure.
    # EVC bounces to zero quite a bit during charging. Possible need to smooth this out? See how it fares.
  car_charging_battery_size:
    - 58
  car_charging_planned:
  # A sensor that says 'we'd like to charge'
  # Could be simply, is there something attached to the charger... or some composite of the charger mode and the car 'charger connected' sensor?
  # The EVC seems to have nothing to say it's connected, so going with the car for now.
    - binary_sensor.id_3_charging_cable_connected
  car_charging_planned_reponse:
     - 'on'
     - 'true'
     # ID.3 state value is on/off, although shows up in UI as 'Plugged in'/'Unplugged', include just in case
     - 'Plugged in'
  car_charging_limit:
    - sensor.id_3_battery_target_charge_level
  car_charging_soc:
    - sensor.id_3_battery_level


The issue for me, is that I have a plan showing 'hold chrg', with a target to reduce the battery SoC, but predbat is calling the 'charge' service! I wonder whether using the SX4 inverter type (Solax in the other control mode has no 'freeze' modes) stops it recognising that I have the freeze services available...

jefft4 avatar Apr 06 '25 09:04 jefft4

The issue is there is no support charge hold option its always assumed to be working.

Can you add a logfile of the problem?

springfall2008 avatar Apr 06 '25 09:04 springfall2008

The issue is there is no support charge hold option its always assumed to be working.

Can you add a logfile of the problem? The situation I posted on the FB group around 20:20 on 05/04/25 (calling charge when it needs to be 'charge stop' to select self-use mode to power the house until target SoC is reached), should be in this log file. Plan debug wasn't on. If you'd like a log with plan debug, I'll create one next time I see a hold with a target SoC <100%

predbat.log

jefft4 avatar Apr 06 '25 11:04 jefft4

So in the logfile it says '2025-04-05 20:20:03.625381: Current SOC 100% is greater than Target SOC 10. Grid Charge disabled.' At this point it should set timed_charge_current to a value of 0 which should hold the charge, but for some reason this is commented out in the template so it does nothing

springfall2008 avatar Apr 06 '25 18:04 springfall2008

So in the logfile it says '2025-04-05 20:20:03.625381: Current SOC 100% is greater than Target SOC 10. Grid Charge disabled.' At this point it should set timed_charge_current to a value of 0 which should hold the charge, but for some reason this is commented out in the template so it does nothing

If timed_charge_current is a parameter to be sent to the inverter, then that would make sense - Solax in standard control mode doesn't support that. I'll have a look at whether I can adapt the SX4 to create a SX4-modbus-RC; the modbus remote control mode can set the power (not current).

jefft4 avatar Apr 07 '25 07:04 jefft4

So, I've set up my SX4 like this: config.py:

    "SX4": {
        "name": "Solax Gen4 (Modbus Remote Control JT)",
        "has_rest_api": False,
        "has_mqtt_api": False,
#NEW
        "has_service_api": True,
#
        "output_charge_control": "power",
        "charge_control_immediate": True,
        "has_charge_enable_time": False,
        "has_discharge_enable_time": False,
        "has_target_soc": False,
        "has_reserve_soc": False,
        "has_timed_pause": False,
        "charge_time_format": "S",
        "charge_time_entity_is_option": False,
        "soc_units": "%",
        "num_load_entities": 1,
        "has_ge_inverter_mode": False,
        "time_button_press": True,
        "clock_time_format": "%Y-%m-%d %H:%M:%S",
        "write_and_poll_sleep": 4,
        "has_time_window": False,
#CHANGE
        "support_charge_freeze": True,
        "support_discharge_freeze": True,
#
        "has_idle_time": False,
        "can_span_midnight": True,
        "charge_discharge_with_rate": False,
    },

and apps.yaml:

## JT - Services for start/stop - needed for SolaX as the supposed SX4 controls don't seem to achieve anything
  charge_start_service:
  # force charge from grid + solar
    service: input_select.select_option
    entity_id: input_select.solar_control_mode
    option: "Charge"
    repeat: true
  charge_stop_service:
  # return to normal operation (self-use mode)
    service: input_select.select_option
    entity_id: input_select.solar_control_mode
    option: "Self-use"
    repeat: true
  charge_freeze_service:
  # Charge freeze: per Trefor, NO discharge to load or export, NO grid charge, ALLOW solar to charge battery.
    service: input_select.select_option
    entity_id: input_select.solar_control_mode
    option: "Charge freeze"
    repeat: true
  discharge_freeze_service:
  # Export freeze: per Trefor, ALLOW discharge to load (but don't force export), NO grid charge, NO solar charge.
    service: input_select.select_option
    entity_id: input_select.solar_control_mode
    option: "Discharge freeze"
    repeat: true
  discharge_start_service:
  # force discharge (i.e., force export)
    service: input_select.select_option
    entity_id: input_select.solar_control_mode
    option: "Discharge"
    repeat: true
  discharge_stop_service:
  # return to normal operation (self-use mode)
    service: input_select.select_option
    entity_id: input_select.solar_control_mode
    option: "Self-use"
    repeat: true


The input_select changing triggers a HA script that sets the multiple modbus parameters needed to make things happen. If this works, I'll move the code of that script into the service call definitions in apps.yaml.

I switched predbat from monitor into control charge/discharge mode. Initially it went to 'HoldChrg' and made a 'start_charge' service call, which didn't look good. Then it recalculated and is now in self-use (stop charge), which seems reasonable for 4:40pm with a full battery and sunshine. I've got a mix of 'HoldChrg' with a reducing SoC target, 'FrzChrg' at min SoC and 'HoldChrg' at min SoC (what's the difference?!) late this evening. Most of the 'holdchrg' slots look like they really could be 'demand' mode - discharge to the load, use solar if there's any going. I've set the script to alert me to changes, so we'll see what it does! I shouldn't see 'charge' mode for real until 05:30 tomorrow.

jefft4 avatar Apr 07 '25 15:04 jefft4

Okay, I'm pretty confused now! All evening until about 21:00, I've had a plan saying 'hold charge' with a reducing SoC target. Every 5 minutes, predbat has called first start_charge then immediately stop_charge (or possibly stop_discharge, can't which). Now we're in the first of the 'freeze charge' periods - not sure why, we're already at min SoC so it makes no difference - and it called charge_freeze then immediately one of the stop_* services. I'm not sure how many calls it's actually making - just noticed that the log has 40+ instances of 'script already running' errors too.

@springfall2008 Any idea what's causing that? I would only expect one service to be called each time, not two different ones.

Image

Image

Image

jefft4 avatar Apr 07 '25 20:04 jefft4

I beleive the Solis inverters use essentially the same software, certainly solax_modbus talks to my Solis S6 99% of the registers.

On the Solis and I believe the Solax will be the same, "Charge Hold" is achieved by requesting "charge" and setting the target SOC to a value less than or equal to the current SOC, this causes the inverter to stop charging the battery and the house load will be fed from the grid.

rszemeti avatar Apr 09 '25 10:04 rszemeti

So, I've set up my SX4 like this: config.py:

The input_select changing triggers a HA script that sets the multiple modbus parameters needed to make things happen. If this works, I'll move the code of that script into the service call definitions in apps.yaml.

That looks useful, what is in the HA script you used to set the modbus parameters?

I had managed to control it so far using these service calls to the modbus API's:

  energy_control_switch:
    - select.solax_remotecontrol_power_control

  charge_start_service:
    - service: input_number.set_value
      entity_id: input_number.battery_state
      value: "2"
    - service: number.set_value
      entity_id: number.solax_remotecontrol_active_power
      value: "4000"
    - service: select.select_option
      entity_id: select.solax_remotecontrol_power_control
      option: Enabled Battery Control
    - service: number.set_value
      entity_id: number.solax_remotecontrol_duration
      value: "300"
    - service: number.set_value
      entity_id: number.solax_remotecontrol_autorepeat_duration
      value: "28800"
    - service: button.press
      entity_id: button.solax_remotecontrol_trigger
      data: {}

  charge_stop_service:
    - service: number.set_value
      entity_id: input_number.battery_state
      value: "1"
    - service: number.set_value
      entity_id: number.solax_remotecontrol_active_power
      value: "4000"
    - service: select.select_option
      entity_id: select.solax_remotecontrol_power_control
      option: Disabled
    - service: number.set_value
      entity_id: number.solax_remotecontrol_duration
      value: "300"
    - service: number.set_value
      entity_id: number.solax_remotecontrol_autorepeat_duration
      value: "28800"
    - service: button.press
      entity_id: button.solax_remotecontrol_trigger
      data: {}

  discharge_start_service:
    - service: number.set_value
      entity_id: input_number.battery_state
      value: "3"
    - service: number.set_value
      entity_id: number.solax_remotecontrol_active_power
      value: "-4000"
    - service: select.select_option
      entity_id: select.solax_remotecontrol_power_control
      option: Enabled Battery Control
    - service: number.set_value
      entity_id: number.solax_remotecontrol_duration
      value: "300"
    - service: number.set_value
      entity_id: number.solax_remotecontrol_autorepeat_duration
      value: "28800"
    - service: button.press
      entity_id: button.solax_remotecontrol_trigger
      data: {}

Though when I try to use the {power} attribute the services fail to run so it is using an arbitrary value at the moment and I had to create a service to disable the charge if it reaches 100% so that the solar panels do not stop operating.

jimbaxter avatar Apr 17 '25 22:04 jimbaxter

So, I've set up my SX4 like this: config.py:

The input_select changing triggers a HA script that sets the multiple modbus parameters needed to make things happen. If this works, I'll move the code of that script into the service call definitions in apps.yaml.

That looks useful, what is in the HA script you used to set the modbus parameters?

I had managed to control it so far using these service calls to the modbus API's:

  energy_control_switch:
    - select.solax_remotecontrol_power_control

  charge_start_service:
    - service: input_number.set_value
      entity_id: input_number.battery_state
      value: "2"
    - service: number.set_value
      entity_id: number.solax_remotecontrol_active_power
      value: "4000"
    - service: select.select_option
      entity_id: select.solax_remotecontrol_power_control
      option: Enabled Battery Control
    - service: number.set_value
      entity_id: number.solax_remotecontrol_duration
      value: "300"
    - service: number.set_value
      entity_id: number.solax_remotecontrol_autorepeat_duration
      value: "28800"
    - service: button.press
      entity_id: button.solax_remotecontrol_trigger
      data: {}

  charge_stop_service:
    - service: number.set_value
      entity_id: input_number.battery_state
      value: "1"
    - service: number.set_value
      entity_id: number.solax_remotecontrol_active_power
      value: "4000"
    - service: select.select_option
      entity_id: select.solax_remotecontrol_power_control
      option: Disabled
    - service: number.set_value
      entity_id: number.solax_remotecontrol_duration
      value: "300"
    - service: number.set_value
      entity_id: number.solax_remotecontrol_autorepeat_duration
      value: "28800"
    - service: button.press
      entity_id: button.solax_remotecontrol_trigger
      data: {}

  discharge_start_service:
    - service: number.set_value
      entity_id: input_number.battery_state
      value: "3"
    - service: number.set_value
      entity_id: number.solax_remotecontrol_active_power
      value: "-4000"
    - service: select.select_option
      entity_id: select.solax_remotecontrol_power_control
      option: Enabled Battery Control
    - service: number.set_value
      entity_id: number.solax_remotecontrol_duration
      value: "300"
    - service: number.set_value
      entity_id: number.solax_remotecontrol_autorepeat_duration
      value: "28800"
    - service: button.press
      entity_id: button.solax_remotecontrol_trigger
      data: {}

Though when I try to use the {power} attribute the services fail to run so it is using an arbitrary value at the moment and I had to create a service to disable the charge if it reaches 100% so that the solar panels do not stop operating.

My script looks like this:

alias: Solar modbus control
sequence:
  - choose:
      - conditions:
          - condition: state
            entity_id: input_select.solar_control_mode
            state: Charge
            for:
              hours: 0
              minutes: 0
              seconds: 0
        sequence:
          - action: select.select_option
            metadata: {}
            data:
              option: Enabled Power Control
            target:
              entity_id: select.solax_remotecontrol_power_control
          - action: number.set_value
            metadata: {}
            data:
              value: "5000"
            target:
              entity_id: number.solax_remotecontrol_active_power
          - action: number.set_value
            metadata: {}
            data:
              value: "10000"
            target:
              entity_id: number.solax_remotecontrol_import_limit
      - conditions:
          - condition: state
            entity_id: input_select.solar_control_mode
            state: Self-use
            for:
              hours: 0
              minutes: 0
              seconds: 0
        sequence:
          - action: select.select_option
            metadata: {}
            data:
              option: Enabled Self Use
            target:
              entity_id: select.solax_remotecontrol_power_control
          - action: number.set_value
            metadata: {}
            data:
              value: "5000"
            target:
              entity_id: number.solax_remotecontrol_active_power
          - action: number.set_value
            metadata: {}
            data:
              value: "10000"
            target:
              entity_id: number.solax_remotecontrol_import_limit
      - conditions:
          - condition: state
            entity_id: input_select.solar_control_mode
            state: Discharge
            for:
              hours: 0
              minutes: 0
              seconds: 0
        sequence:
          - action: select.select_option
            metadata: {}
            data:
              option: Enabled Power Control
            target:
              entity_id: select.solax_remotecontrol_power_control
          - action: number.set_value
            metadata: {}
            data:
              value: "-5000"
            target:
              entity_id: number.solax_remotecontrol_active_power
      - conditions:
          - condition: state
            entity_id: input_select.solar_control_mode
            state: Charge freeze
            for:
              hours: 0
              minutes: 0
              seconds: 0
        sequence:
          - action: select.select_option
            metadata: {}
            data:
              option: Enabled Power Control
            target:
              entity_id: select.solax_remotecontrol_power_control
          - action: number.set_value
            metadata: {}
            data:
              value: "0"
            target:
              entity_id: number.solax_remotecontrol_active_power
          - action: number.set_value
            metadata: {}
            data:
              value: "10000"
            target:
              entity_id: number.solax_remotecontrol_import_limit
      - conditions:
          - condition: state
            entity_id: input_select.solar_control_mode
            state: Discharge freeze
            for:
              hours: 0
              minutes: 0
              seconds: 0
        sequence:
          - action: select.select_option
            metadata: {}
            data:
              option: Enabled Battery Control
            target:
              entity_id: select.solax_remotecontrol_power_control
          - action: number.set_value
            metadata: {}
            data:
              value: "0"
            target:
              entity_id: number.solax_remotecontrol_active_power
          - action: number.set_value
            metadata: {}
            data:
              value: "10000"
            target:
              entity_id: number.solax_remotecontrol_import_limit
      - conditions:
          - condition: state
            entity_id: input_select.solar_control_mode
            state: "OFF"
            for:
              hours: 0
              minutes: 0
              seconds: 0
        sequence:
          - action: select.select_option
            metadata: {}
            data:
              option: Enabled Battery Control
            target:
              entity_id: select.solax_remotecontrol_power_control
          - action: button.press
            metadata: {}
            data: {}
            target:
              entity_id: button.solax_system_off
  - action: number.set_value
    metadata: {}
    data:
      value: "30"
    target:
      entity_id: number.solax_remotecontrol_duration
  - action: number.set_value
    metadata: {}
    data:
      value: "660"
    target:
      entity_id: number.solax_remotecontrol_autorepeat_duration
  - delay:
      hours: 0
      minutes: 0
      seconds: 1
      milliseconds: 0
  - action: button.press
    metadata: {}
    data: {}
    target:
      entity_id: button.solax_remotecontrol_trigger
description: Reads solar control mode helper, sets solax_modbus controls accordingly.
icon: mdi:sun-wireless-outline
mode: queued
max: 2

apps.yaml uses them thus:

## JT - Services for start/stop - needed for SolaX as the supposed SX4 controls don't seem to achieve what we need
  charge_start_service:
  # force charge from grid + solar
    service: input_select.select_option
    entity_id: input_select.solar_control_mode
    option: "Charge"
    repeat: true
  charge_stop_service:
  # return to normal operation (self-use mode)
    service: input_select.select_option
    entity_id: input_select.solar_control_mode
    option: "Self-use"
    repeat: true
  charge_freeze_service:
  # Charge freeze: NO discharge, NO grid charge, ALLOW solar to charge battery.
    service: input_select.select_option
    entity_id: input_select.solar_control_mode
    option: "Charge freeze"
    repeat: true
  discharge_freeze_service:
  # Export freeze: ALLOW discharge to load (but don't force export), NO grid charge, NO solar charge.
    service: input_select.select_option
    entity_id: input_select.solar_control_mode
    option: "Discharge freeze"
    repeat: true
  discharge_start_service:
  # force discharge (i.e., force export)
    service: input_select.select_option
    entity_id: input_select.solar_control_mode
    option: "Discharge"
    repeat: true
  discharge_stop_service:
  # return to normal operation (self-use mode)
    service: input_select.select_option
    entity_id: input_select.solar_control_mode
    option: "Self-use"
    repeat: true

I think I have the right modbus actions for each predbat service call, although it may be that for some, predbat really wants to specify a SoC. That would need more work; target SoC isn't implemented in the modbus code so it might need work there or a combination of the script and a HA automation to monitor SoC vs target and change state.

What's bugging me, though, is that predbat appears to be calling the wrong service for what the plan says it wants to achieve!

jefft4 avatar Apr 18 '25 12:04 jefft4

I did begin looking through PredBat's plan class with the idea of hooking it up to some dummy data and looking at the plan output and seeing if I could optimise the code further as it is proposing some weird things at times for me ... but I got lost in the code ... there are some incredibly loooooooong methods in the class, my mum always told me any method longer than a page needed refactoring, so I've made a start on that, but I really do need to hang a few more unit tests around it.

rszemeti avatar Apr 18 '25 17:04 rszemeti

Another sequence of madness... Predbat seems to issue multiple service calls, some of them completely contrary to what it plans to be doing. Perhaps the inverters it was written for, need to be told 'stop this, do that' rather than just 'now do that', but some of these sequences of service calls don't make sense even then!

This is it trying to set charge mode:

Image

And this is coming out of charge, back into demand/self-use:

Image

Both are just way too many calls. The first wants to be simply 'charge' and the second, 'stop charge'.

jefft4 avatar Apr 21 '25 15:04 jefft4

Another sequence of madness... Predbat seems to issue multiple service calls, some of them completely contrary to what it plans to be doing. Perhaps the inverters it was written for, need to be told 'stop this, do that' rather than just 'now do that', but some of these sequences of service calls don't make sense even then! Both are just way too many calls. The first wants to be simply 'charge' and the second, 'stop charge'.

I noticed the same thing, it results in the predbat Plan stating charge but the battery not charging. I guess it is a bug.

It also shows in the logs:

2025-05-03 16:09:10.751486: Adjust demand (idle) time computed is 16:00:00-23:59:00
2025-05-03 16:09:10.751671: Inverter 0 current discharge rate is 2600.0W and new target is 5000W
2025-05-03 16:09:10.751739: Setting charging SOC to 100 as per target
2025-05-03 16:09:10.751809: Inverter 0 adjust target soc for charge to 100.0% based on requested all inverter soc 100%
2025-05-03 16:09:10.751882: Inverter 0 Current Target SOC is 100%, already at target
2025-05-03 16:09:10.751992: Current SOC 91% is less than Target SOC 100. Grid charging enabled with charge current set to 65.00
2025-05-03 16:09:10.752198: Inverter 0 Calling service discharge_stop_service domain discharge service_name input_select/select_option with data {'entity_id': 'input_select.solar_control_mode', 'option': 'Self-use'}
2025-05-03 16:09:10.822819: Inverter 0 Calling service charge_start_service domain charge service_name input_select/select_option with data {'entity_id': 'input_select.solar_control_mode', 'option': 'Charge'}
2025-05-03 16:09:10.870794: Inverter 0 Calling service discharge_stop_service domain discharge service_name input_select/select_option with data {'entity_id': 'input_select.solar_control_mode', 'option': 'Self-use'}
2025-05-03 16:09:10.935046: Inverter 0 count register writes 0
2025-05-03 16:09:11.092165: Total inverter register writes now 0
2025-05-03 16:09:41.273027: Wrote debug yaml to /addon_configs/6adb4f0d_predbat/debug/predbat_debug_15_55_00.yaml
2025-05-03 16:09:41.274036: Completed run status Charging
2025-05-03 16:09:41.320800: Info: record_status Charging target 91%-100%

jimbaxter avatar May 03 '25 15:05 jimbaxter

I'm going to close this. There hasn't been any movement on it for 6+ months and #2839 is actively being progressed for refinements and improvements to Solax with Predbat, so any remaining discussion can carry on over there

gcoan avatar Nov 25 '25 21:11 gcoan