Controlling Enphase AC Battery (ACB)

Controlling Enphase AC Battery (ACB) sleep mode via Home Assistant - has anyone done this?

I have an Enphase system with an older AC Battery (ACB - part 800-00930-r03), which is NOT the same as the newer IQ Battery. Getting this confused with IQ Battery support is a common pitfall so worth stating upfront.

The problem

The Enphase tariff scheduler has no concept of “hold” - once the overnight grid charge window closes at 06:00, the battery immediately starts self-consumption discharge regardless of any peak window you configure. There is no way to prevent this via the Enlighten UI or Installer Toolkit.

Integrations I have tried - none give ACB control

All of these either don’t expose the ACB at all or only expose it as a read-only sensor.

What I have found

Thanks to Matthew1471’s excellent Enphase API documentation project, the IQ Gateway has a local API endpoint specifically for the ACB:

/admin/lib/acb_config.json

Documented here: Enphase-API/Documentation/IQ Gateway API/Admin/Lib/ACB_Config.adoc at 85e3ec86e73be4837ee447d459408886327873b5 · Matthew1471/Enphase-API · GitHub

This supports GET, PUT and DELETE to get, set and cancel sleep mode on the ACB. Confirmed working on firmware D8.3.5167 with Bearer token auth (token from entrez.enphaseenergy.com/tokens).

To put the battery to sleep (stop discharge):

PUT /admin/lib/acb_config.json
{"acb_sleep": [{"serial_num": YOUR_SERIAL, "sleep_min_soc": 0, "sleep_max_soc": 100}]}

To cancel sleep (allow discharge):

DELETE /admin/lib/acb_config.json
{"acb_sleep": [{"serial_num": YOUR_SERIAL}]}

What I am trying to do

Build HA automations using rest_command to call these endpoints on a schedule - e.g. enable sleep at 06:00, cancel at 11:00 - so the battery holds its overnight charge until the peak tariff window begins.

Has anyone else done something similar with an ACB? I want to avoid reinventing the wheel if a working rest_command/automation config already exists. Happy to share back whatever I end up with.

Enhancement request

I have also raised this as a feature request against the official Enphase Envoy integration to get proper ACB sleep mode control added natively: Enphase Envoy: Add manual AC Battery (ACB) control via sleep mode · home-assistant · Discussion #3316 · GitHub

Also see my reply in the feature request.

Below custom integration can be used next to the core integration, uses the same library as the core integration and provides 2 actions/services to read or write Envoy endpoints.

Before we can extend the core integration with the requested functions, we need more detailed data. Above custom integration enables you to request Envoy data from /admin/lib/acb_config.json.

Once configured you can use settings / developers tools / actions and configure something like

action: enphase_envoy_raw_data.read_data
data:
  endpoint: /admin/lib/acb_config.json
  config_entry_id: 01KMZMGA4EF2C68G9AFX6P3ZBM

which will return the data in the output pane.

Curious about what data is returned during various setups you use. Does it always shows all existing ACB serial-nums? We currently have no individual ACB data, only aggregated. So coming up with serials might not be straight forward.

Other question is if the .json is needed in the endpoint spec of if /admin/lib/acb_config works as well.

The custom integration also allows to send PUT data to the Envoy. I also added a DELETE choice as that seems needed as well. Needless to say that sending data to the Envoy is “handle with care” and can be risky. I have no experience with that endpoint, so any risk is yours.

1 Like

Just doing some testing (custom integration) with my non-metered Envoy-s EU, no batteries,

action: enphase_envoy_raw_data.read_data
data:
  endpoint: /admin/lib/acb_config.json
  config_entry_id: 01KN4RVZEMYBH1RP1A2MFND2K8

returns

/admin/lib/acb_config.json:
  acb_sleep: []

Trying it without .json works as well. So the endpoint exists, but obviously useless for my config without batteries.

Did some backlog cleanup on the enphase_envow_raw_data custom integration and added a devcontainer setup using the custom integration blueprint.

To test you ACB Sleep with a running HA instance, install the custom integration by adding it as a custom library in HACS. Then configure the custom integration and add your Envoy. The custom integration does NOT replace the core one or any other cusom intgeration, but runs as a separate one named enphase_envow_raw_data.

To see if your ACB seep mode actually works go to Settings / Developers tools / Actions. Select GUI or yaml mode as you prefer. On your very first one, best start in GUI mode to easily select the config_id of the Envoy. Once you have that id you can use either mode.

For action select enphase_envoy_raw_data.send_data. For endpoint use the endpoint you mentioned: /admin/lib/acb_config.json. For data use what you want to test for this sleep mode. The example shows {"acb_sleep": [{"serial_num": 12345, "sleep_min_soc": 0, "sleep_max_soc": 100}]}. I assume you need to add one serial_num line for each battery you have, or send these in separate actions.

The full yaml for the example would be:

action: enphase_envoy_raw_data.send_data
data:
  risk_acknowledged: false
  method: PUT
  test_mode: true
  config_entry_id: 01KN7PTDNZN3S6DVR47NB4KBA0
  endpoint: /admin/lib/acb_config.json
  data: >-
    {"acb_sleep": [{"serial_num": 12345, "sleep_min_soc": 0, "sleep_max_soc": 100}]}

The config_entry_id should of course be yours, mind the risk_acknowledge setting safety guard. should be yes or it won’t execute. Also mind the test_method setting, if true it will not actually send the data but just return the data you want to send. Simple test method for mistakes in data formats and alike.

For the Delete you also need, change the method to DELETE and the data to what is required for the delete action.

GUI format screenshot for this:

Fantastic, catsmanac I will have a try of this and report back. Thank you.

For ref, I have also created a Enphase AC Battery Enlighten Capture Summary with the cloud endpoints here: [Device support]: AC Battery (ACB): Add sleep mode control entities · Issue #464 · barneyonline/ha-enphase-energy · GitHub

It’s just for refrence, as i’m more intrested in local polling.

I did some more trial and error on this locally and I think there is enough here to support ACBs properly.

On my Envoy, the ACB page is under /home#acb/manage, and GET /inventory.json?deleted=1 returns the AC Batteries individually under the ACB group. In my case I have two entries there: one working battery and one faulted battery. So at least locally, they are not exposed as one single combined battery value.

For the working ACB, the inventory data includes fields like:

  • serial_num
  • sleep_enabled
  • sleep_min_soc
  • sleep_max_soc
  • charge_status
  • percentFull
  • producing
  • operating

The local control endpoint is /admin/lib/acb_config.json.

  • GET shows the current configured sleep request
  • PUT sets sleep for a specific ACB serial
  • DELETE clears that request again

I was able to control the working ACB through the ha_enphase_envoy_raw_data commands here: https://github.com/catsmanac/ha_enphase_envoy_raw_data.

I tested both PUT and DELETE through that raw-data integration and both return message: success.

The interesting bit is that the local “wake” action does not seem to be a separate endpoint. It looks like waking is just DELETE /admin/lib/acb_config.json for that battery serial.

From the state changes I saw, these two endpoints seem to tell different parts of the story:

  • /admin/lib/acb_config.json shows the requested sleep state
  • /inventory.json?deleted=1 shows what the battery is actually doing

For the working battery, this is what I observed:

  • Normal: sleep_enabled: false, device_status: ["envoy.global.ok"]
  • Going to sleep: sleep_enabled: true, device_status: ["envoy.global.ok"]
  • Asleep: sleep_enabled: true, device_status: ["envoy.cond_flags.pcu_ctrl.sleep-mode"]
  • Waking: sleep_enabled: false, device_status: ["envoy.cond_flags.pcu_ctrl.sleep-mode"]

So from what I can tell:

  • ACBs are exposed individually in local inventory
  • sleep is writable locally
  • wake is the DELETE form of the same endpoint
  • sleep_enabled tracks the requested sleep state
  • device_status is what shows normal vs asleep vs waking

That seems like enough for per-device ACB support locally, rather than only exposing one aggregate storage value.

Local Inventory Findings

Observed page:

  • GET /home#acb/manage

Observed inventory endpoint:

  • GET /inventory.json?deleted=1

The important point for integration work is that the ACB group contains a devices array with separate per-battery objects.

Observed ACB Records

ACB 1: Working battery

  • part_num: 800-00930-r03
  • serial_num: <working_acb_serial>
  • device_status: envoy.global.ok in normal mode
  • admin_state: 2
  • communicating: true
  • provisioned: true
  • producing: true when awake, false when asleep
  • operating: true when awake, false when asleep
  • sleep_enabled: false when normal, true while entering sleep and while asleep, false while waking
  • percentFull: 57 while entering sleep, 48 while asleep and waking in the captured examples
  • maxCellTemp: 24 while entering sleep, 25 while asleep and waking in the captured examples
  • sleep_min_soc: normal value observed as 25, commanded values observed as 65 and 45
  • sleep_max_soc: normal value observed as 30, commanded values observed as 70 and 50
  • charge_status: discharging while awake and while entering sleep, idle while asleep and waking
  • img_pnum_running: 520-00092-r01-v02.13.02
  • ptpn: 540-00155-r01-v02.13.01

ACB 2: Non-working or faulted AC Battery

  • part_num: 800-00930-r03
  • serial_num: <faulted_acb_serial>
  • device_status: envoy.cond_flags.acb_ctrl.cellminvoltagewarning, envoy.cond_flags.acb_ctrl.bmusenseerror
  • admin_state: 3
  • img_pnum_running: 520-00092-r01-v02.13.02
  • ptpn: 540-00155-r01-v02.13.01

Fields such as sleep_enabled, percentFull, charge_status, producing, and operating were present on the healthy battery record and absent from the faulted battery record in the provided payload.

Confirmed Local Control Endpoint Behavior

Observed control endpoint:

  • GET /admin/lib/acb_config.json
  • PUT /admin/lib/acb_config.json
  • DELETE /admin/lib/acb_config.json

Normal mode read

Observed HA action:

action: enphase_envoy_raw_data.read_data
data:
  config_entry_id: 01KNPGAK95DTK3950B33FG5JE1
  endpoint: /admin/lib/acb_config.json

Observed response:

/admin/lib/acb_config.json:
  acb_sleep: []

This indicates that no active sleep request is configured in normal mode.

Command battery to go to sleep

Observed HA action:

action: enphase_envoy_raw_data.send_data
data:
  config_entry_id: 01KNPGAK95DTK3950B33FG5JE1
  endpoint: /admin/lib/acb_config.json
  method: PUT
  risk_acknowledged: true
  test_mode: false
  data:
    acb_sleep:
      - serial_num: "<working_acb_serial>"
        sleep_min_soc: 65
        sleep_max_soc: 70

Observed response:

/admin/lib/acb_config.json:
  message: success

Observed immediate follow-up read:

/admin/lib/acb_config.json:
  acb_sleep:
    - serial_num: "<working_acb_serial>"
      sleep_min_soc: 65
      sleep_max_soc: 70

This confirms:

  • the endpoint is writable through the HA raw-data integration
  • the gateway accepts per-battery sleep requests by serial number
  • the GET endpoint reflects the configured per-battery sleep request immediately after the PUT
  • the working ACB can be put into sleep through the ha_enphase_envoy_raw_data commands

Cancel pending sleep or wake battery

Observed HA action:

action: enphase_envoy_raw_data.send_data
data:
  config_entry_id: 01KNPGAK95DTK3950B33FG5JE1
  endpoint: /admin/lib/acb_config.json
  method: DELETE
  risk_acknowledged: true
  test_mode: false
  data:
    acb_sleep:
      - serial_num: "<working_acb_serial>"

Observed response:

/admin/lib/acb_config.json:
  message: success

Observed read behavior around wake:

{"acb_sleep":[{"serial_num":"<working_acb_serial>"}]}

followed by:

{"acb_sleep":[]}

This confirms:

  • DELETE is accepted by the local endpoint
  • the same DELETE path is used both to cancel a pending sleep request and to wake a sleeping battery
  • the local gateway does not require a separate wake endpoint for ACBs
  • the working ACB can be woken through the ha_enphase_envoy_raw_data commands

Confirmed State Transitions

The most useful result from the trial-and-error data is that the battery state can be inferred by combining /admin/lib/acb_config.json with /inventory.json?deleted=1.

1. Normal mode

Observed GET /admin/lib/acb_config.json:

/admin/lib/acb_config.json:
  acb_sleep: []

Observed GET /inventory.json?deleted=1 for the healthy battery:

{
  "serial_num": "<working_acb_serial>",
  "device_status": ["envoy.global.ok"],
  "admin_state": 2,
  "producing": true,
  "communicating": true,
  "provisioned": true,
  "operating": true,
  "sleep_enabled": false,
  "percentFull": 57,
  "maxCellTemp": 24,
  "sleep_min_soc": 25,
  "sleep_max_soc": 30,
  "charge_status": "discharging"
}

Normal-mode markers:

  • sleep_enabled: false
  • device_status: ["envoy.global.ok"]
  • producing: true
  • operating: true
  • charge_status: "discharging"

2. Entering sleep mode

Observed GET /admin/lib/acb_config.json while entering sleep:

{"acb_sleep": [{"serial_num": "<working_acb_serial>","sleep_min_soc": 65,"sleep_max_soc": 70}]}

Observed GET /inventory.json?deleted=1 while entering sleep:

{
  "serial_num": "<working_acb_serial>",
  "device_status": ["envoy.global.ok"],
  "admin_state": 2,
  "producing": true,
  "communicating": true,
  "provisioned": true,
  "operating": true,
  "sleep_enabled": true,
  "percentFull": 57,
  "maxCellTemp": 24,
  "sleep_min_soc": 65,
  "sleep_max_soc": 70,
  "charge_status": "discharging"
}

Entering-sleep markers:

  • sleep_enabled: true
  • device_status still remains envoy.global.ok
  • producing and operating can still remain true
  • charge_status can still remain discharging

The only immediately visible state change in the captured entering-sleep example was sleep_enabled switching from false to true.

3. Fully asleep

Observed GET /admin/lib/acb_config.json while asleep:

{"acb_sleep": [{"serial_num": "<working_acb_serial>","sleep_min_soc": 45,"sleep_max_soc": 50}]}

Observed GET /inventory.json?deleted=1 while asleep:

{
  "serial_num": "<working_acb_serial>",
  "device_status": ["envoy.cond_flags.pcu_ctrl.sleep-mode"],
  "admin_state": 2,
  "producing": false,
  "communicating": true,
  "provisioned": true,
  "operating": false,
  "sleep_enabled": true,
  "percentFull": 48,
  "maxCellTemp": 25,
  "sleep_min_soc": 45,
  "sleep_max_soc": 50,
  "charge_status": "idle"
}

Asleep markers:

  • sleep_enabled: true
  • device_status: ["envoy.cond_flags.pcu_ctrl.sleep-mode"]
  • producing: false
  • operating: false
  • charge_status: "idle"

4. Waking or editing sleep mode

Observed GET /inventory.json?deleted=1 after DELETE /admin/lib/acb_config.json while the battery was still transitioning:

{
  "serial_num": "<working_acb_serial>",
  "device_status": ["envoy.cond_flags.pcu_ctrl.sleep-mode"],
  "admin_state": 2,
  "producing": false,
  "communicating": true,
  "provisioned": true,
  "operating": false,
  "sleep_enabled": false,
  "percentFull": 48,
  "maxCellTemp": 25,
  "sleep_min_soc": 25,
  "sleep_max_soc": 30,
  "charge_status": "idle"
}

Waking markers:

  • sleep_enabled: false
  • device_status can still remain envoy.cond_flags.pcu_ctrl.sleep-mode
  • producing and operating can still remain false
  • charge_status can still remain idle

This shows that sleep_enabled clears before device_status returns to normal. That is the clearest evidence in the current dataset that waking is a distinct transition state rather than an instantaneous return to envoy.global.ok.

How To Tell What The Battery Is Doing

Based on the captured data, the local state can be inferred like this:

Awake

  • sleep_enabled: false
  • device_status: ["envoy.global.ok"]

Going to sleep

  • sleep_enabled: true
  • device_status: ["envoy.global.ok"]

Asleep

  • sleep_enabled: true
  • device_status: ["envoy.cond_flags.pcu_ctrl.sleep-mode"]

Waking

  • sleep_enabled: false
  • device_status: ["envoy.cond_flags.pcu_ctrl.sleep-mode"]

Interpretation

  • The local gateway inventory response can identify ACB units individually by serial number.
  • The local response exposes per-battery health and operational fields, not only aggregate storage totals.
  • A healthy ACB record includes battery-control-related fields such as sleep_enabled, sleep_min_soc, sleep_max_soc, charge_status, producing, and operating.
  • The faulted ACB still appears in inventory as a distinct AC Battery, which is useful for discovery and diagnostics.
  • PUT /admin/lib/acb_config.json returns success and persists per-battery sleep requests.
  • GET /admin/lib/acb_config.json reflects configured sleep requests, not the full live operating state by itself.
  • DELETE /admin/lib/acb_config.json returns success and acts as the local wake or cancel path.
  • /inventory.json?deleted=1 is the better source for the actual exposed battery state.
  • sleep_enabled appears to represent the requested sleep state.
  • device_status appears to represent the operating state transition more directly.
  • The combined state machine is now visible enough to support richer local integration logic.

Remaining Unknowns

The main remaining unknowns are narrower now:

  • how long it takes after DELETE for device_status to return from envoy.cond_flags.pcu_ctrl.sleep-mode to envoy.global.ok
  • whether there are any additional intermediate device_status values during wake on other firmware versions
  • whether invalid serial numbers or invalid SoC ranges return useful validation errors
  • whether the gateway ever removes sleeping entries from /admin/lib/acb_config.json automatically without a DELETE

Relevant ACB Payload Excerpts

Normal ACB excerpt

{
  "type": "ACB",
  "devices": [
    {
      "part_num": "800-00930-r03",
      "installed": "1769471139",
      "serial_num": "<working_acb_serial>",
      "device_status": [
        "envoy.global.ok"
      ],
      "last_rpt_date": "1775654518",
      "admin_state": 2,
      "dev_type": 11,
      "created_date": "1769471139",
      "img_load_date": "1625324297",
      "img_pnum_running": "520-00092-r01-v02.13.02",
      "ptpn": "540-00155-r01-v02.13.01",
      "chaneid": 1795162641,
      "device_control": [
        {
          "gficlearset": false
        }
      ],
      "producing": true,
      "communicating": true,
      "provisioned": true,
      "operating": true,
      "sleep_enabled": false,
      "percentFull": 57,
      "maxCellTemp": 24,
      "sleep_min_soc": 25,
      "sleep_max_soc": 30,
      "charge_status": "discharging"
    },
    {
      "part_num": "800-00930-r03",
      "installed": "1769471087",
      "serial_num": "<faulted_acb_serial>",
      "device_status": [
        "envoy.cond_flags.acb_ctrl.cellminvoltagewarning",
        "envoy.cond_flags.acb_ctrl.bmusenseerror"
      ],
      "last_rpt_date": "1770429494",
      "admin_state": 3,
      "dev_type": 11,
      "created_date": "1769471087",
      "img_load_date": "1662811078",
      "img_pnum_running": "520-00092-r01-v02.13.02",
      "ptpn": "540-00155-r01-v02.13.01",
      "chaneid": 1795162385,
      "device_control": [
        {
          "gficlearset": false
        }
      ]
    }
  ]
}

Sleeping ACB excerpt

{
  "type": "ACB",
  "devices": [
    {
      "part_num": "800-00930-r03",
      "installed": "1769471139",
      "serial_num": "<working_acb_serial>",
      "device_status": [
        "envoy.cond_flags.pcu_ctrl.sleep-mode"
      ],
      "last_rpt_date": "1775659175",
      "admin_state": 2,
      "dev_type": 11,
      "created_date": "1769471139",
      "img_load_date": "1625324297",
      "img_pnum_running": "520-00092-r01-v02.13.02",
      "ptpn": "540-00155-r01-v02.13.01",
      "chaneid": 1795162641,
      "device_control": [
        {
          "gficlearset": false
        }
      ],
      "producing": false,
      "communicating": true,
      "provisioned": true,
      "operating": false,
      "sleep_enabled": true,
      "percentFull": 48,
      "maxCellTemp": 25,
      "sleep_min_soc": 45,
      "sleep_max_soc": 50,
      "charge_status": "idle"
    },
    {
      "part_num": "800-00930-r03",
      "installed": "1769471087",
      "serial_num": "<faulted_acb_serial>",
      "device_status": [
        "envoy.cond_flags.acb_ctrl.cellminvoltagewarning",
        "envoy.cond_flags.acb_ctrl.bmusenseerror"
      ],
      "last_rpt_date": "1775658742",
      "admin_state": 3,
      "dev_type": 11,
      "created_date": "1769471087",
      "img_load_date": "1662811078",
      "img_pnum_running": "520-00092-r01-v02.13.02",
      "ptpn": "540-00155-r01-v02.13.01",
      "chaneid": 1795162385,
      "device_control": [
        {
          "gficlearset": false
        }
      ]
    }
  ]
}

Notes:

  • Local polling can already see ACBs individually
  • A healthy ACB exposes battery-specific sleep and charge state fields
  • Faulted ACBs still appear as distinct AC Batteries
  • The local control endpoint is confirmed writable for both sleep and wake or cancel actions
  • DELETE /admin/lib/acb_config.json is the local wake path
  • The battery state machine can be inferred from sleep_enabled plus device_status
  • The remaining gap is not discovery or control availability, but only the finer timing semantics during wake transitions

One more thing I noticed from a later inventory dump.

I now have an example where both AC Batteries show up in the local ACB device list with battery fields, even though one of them is faulted.

In this capture, the faulted ACB still shows fields like:

  • percentFull
  • maxCellTemp
  • sleep_enabled
  • sleep_min_soc
  • sleep_max_soc
  • charge_status

So even though that battery is faulted, it is still not just a blank placeholder entry. The local inventory is still exposing battery-specific state for both ACBs.

For example, in this dump:

  • the faulted ACB reports percentFull: 0, maxCellTemp: 18, and charge_status: "idle"
  • the working ACB reports percentFull: 43, maxCellTemp: 25, and charge_status: "discharging"

That makes me think the local API is returning useful per-battery data for both ACBs, even when one is faulted.

Sanitized ACB excerpt from that later inventory dump:

{
  "type": "ACB",
  "devices": [
    {
      "part_num": "800-00930-r03",
      "installed": "1769471087",
      "serial_num": "<faulted_acb_serial>",
      "device_status": [
        "envoy.cond_flags.acb_ctrl.cellminvoltagewarning",
        "envoy.cond_flags.acb_ctrl.bmusenseerror"
      ],
      "last_rpt_date": "1775661447",
      "admin_state": 2,
      "dev_type": 11,
      "created_date": "1769471087",
      "img_load_date": "1662811078",
      "img_pnum_running": "520-00092-r01-v02.13.02",
      "ptpn": "540-00155-r01-v02.13.01",
      "chaneid": 1795162385,
      "device_control": [
        {
          "gficlearset": false
        }
      ],
      "producing": false,
      "communicating": true,
      "provisioned": true,
      "operating": false,
      "sleep_enabled": false,
      "percentFull": 0,
      "maxCellTemp": 18,
      "sleep_min_soc": 25,
      "sleep_max_soc": 30,
      "charge_status": "idle"
    },
    {
      "part_num": "800-00930-r03",
      "installed": "1769471139",
      "serial_num": "<working_acb_serial>",
      "device_status": [
        "envoy.global.ok"
      ],
      "last_rpt_date": "1775660876",
      "admin_state": 2,
      "dev_type": 11,
      "created_date": "1769471139",
      "img_load_date": "1625324297",
      "img_pnum_running": "520-00092-r01-v02.13.02",
      "ptpn": "540-00155-r01-v02.13.01",
      "chaneid": 1795162641,
      "device_control": [
        {
          "gficlearset": false
        }
      ],
      "producing": true,
      "communicating": true,
      "provisioned": true,
      "operating": true,
      "sleep_enabled": false,
      "percentFull": 43,
      "maxCellTemp": 25,
      "sleep_min_soc": 25,
      "sleep_max_soc": 30,
      "charge_status": "discharging"
    }
  ]
}

One other detail I forgot to mention is the sleep bounds.

Locally, with /admin/lib/acb_config.json, we can send both sleep_min_soc and sleep_max_soc.

The local GUI itself uses a dropdown with 5% steps for this.

The examples I tested locally were things like:

  • 25 and 30

  • 45 and 50

  • 65 and 70

And based on the local GUI format, the edge examples are 0 to 5 and 95 to 100.

So the local API clearly accepts both min and max values for the ACB sleep window.

What I have NOT tested yet is what happens if I send values that do not match the format used by the local GUI.

I also have NOT tested larger ranges like 0 to 20.

So at the moment my assumption is that we should probably keep the bounds in the same 5% steps used by the local GUI.

In other words, I am assuming the safe format is:

  • sleep_min_soc and sleep_max_soc in 5% increments

  • with sleep_max_soc being the next 5% step above sleep_min_soc

I have not yet checked whether the local endpoint will accept something outside that pattern, like 23 to 29, or whether it would round it, reject it, or silently accept it.

Are you accessing the Enphase system locally?

I recently had installed an IQ6C combiner and IQ Battery 10C. The only thing I don’t like is that access to the systems is through the Enphase cloud. Thus my question.

I have reached my first milestone: 24 contiguous hours of net zero or less of import. My electric energy provider pays almost nothing for the export, but charges USD$0.36/kWh just for delivery plus a flat rate of USD$0.78/day. The goal is every rate period be net 0 or less energy import.

Being able to access the monitoring (and control?) capabilities of the Enphase system locally will be helpful.

Please note that we are talking purely about the AC Battery, the IQ battery is completely different product with different endpoints and controls.

But yes I’m trying to build a workaround for enabling control as the AC battery system has no controls. Whereas the IQ system does have controls.

Nice testing.

In other words, I am assuming the safe format is:

  • sleep_min_soc and sleep_max_soc in 5% increments
  • with sleep_max_soc being the next 5% step above sleep_min_soc

Does this imply that when you want to switch the battery to sleep mode, you first need to obtain the current soc, calculate the fitting 5% range around that value and put it to sleep using

{"acb_sleep": [{"serial_num": "<working_acb_serial>","sleep_min_soc": lower_5%_boundary,"sleep_max_soc": upper_5%_boundary}]}

?

As for the enphase_envow_raw_data integration, you can use the actions you tested also in automations. In settings / automations & scenes / automations you can configure the actions needed to execute go-sleep or wake command. That will provide options to see how this best can be used.

Monitoring with the enphase_envoy core integration is possible as I think the IQ6C shows up as an Envoy? Local control probably not as Enphase removed those options. Unless you know of endpoints that still allow this.

I would not take it in that direction yet.

What I am trying to do here is not build a precise “sleep at the current SoC bucket” control. I am trying to use ACB sleep mode as a workaround for the lack of local charge, discharge, and hold controls.

So my current working is not “read current SoC first, then calculate the matching 5% range around it”.

Instead, the practical question is whether the sleep request just needs a valid pair of bounds that the gateway will accept, and whether those bounds can be used as a coarse control to make the battery stop participating until I clear the request again.

That is what I want to test next. For example, I want to see whether something broad or high such as sleep_min_soc: 80 and sleep_max_soc: 100 works just as well for this purpose, rather than having to calculate a narrow range around the live SoC every time.

If that works, then the automation model becomes much simpler:

  • PUT /admin/lib/acb_config.json to force the battery into the sleep or hold behavior
  • DELETE /admin/lib/acb_config.json later to wake it and allow normal discharge again

So from an automation point of view, I do not yet think this needs to start with a read of the current SoC.

I already have one quick Home Assistant automation that behaved well with a fixed high range. In my case it let the battery charge overnight on cheap energy and then hold that energy until I explicitly woke it later.

These two screenshots show the difference in observed SoC behavior over time:

Old (undesired behavior)

New good behavior (using sleep):

That means the immediate unknown is not just whether 5% adjacent ranges are the “correct” format, but whether the gateway actually cares much about the exact range at all as long as it accepts the request.

If later testing shows that only very specific adjacent 5% pairs work, then reading current SoC first may become necessary. But I do not think we should assume that up front.

Sanitized HA Automation Example

This is the basic pattern I tested locally:

alias: ACB Battery Charge Hold
description: ""
triggers:
  - trigger: time
    at: "00:00:00"
    id: ChargeHold
  - trigger: time
    at: "11:00:00"
    id: Release
conditions: []
actions:
  - choose:
      - conditions:
          - condition: trigger
            id:
              - ChargeHold
        sequence:
          - action: enphase_envoy_raw_data.send_data
            data:
              config_entry_id: <config_entry_id>
              endpoint: /admin/lib/acb_config.json
              method: PUT
              risk_acknowledged: true
              test_mode: false
              data:
                acb_sleep:
                  - serial_num: "<working_acb_serial>"
                    sleep_min_soc: 95
                    sleep_max_soc: 100
      - conditions:
          - condition: trigger
            id:
              - Release
        sequence:
          - action: enphase_envoy_raw_data.send_data
            data:
              config_entry_id: <config_entry_id>
              endpoint: /admin/lib/acb_config.json
              method: DELETE
              risk_acknowledged: true
              test_mode: false
              data:
                acb_sleep:
                  - serial_num: "<working_acb_serial>"
mode: single

In my case that was enough to get the battery to charge overnight, hold instead of discharging on its usual schedule, and then resume normal behavior once I sent the DELETE.

So for now I think the right framing is:

  • sleep mode can be used as a crude local control workaround
  • PUT and DELETE are already enough to automate that behavior
  • the remaining question is how strict the gateway really is about the sleep_min_soc and sleep_max_soc values

Ah, I see, thanks for educating me. Sounds as for now you have all the tools you need to research it.

1 Like