I wanted something that sounds trivial: to boost my AC·THOR 9s — the my-PV heating element that turns PV surplus into hot water — to a target temperature from Home Assistant whenever the evening didn't bring enough sun. Reading over Modbus TCP had worked for ages: boiler temperature, power, all sitting cleanly as sensors in HA. So I figured the write path was one line of YAML away.
It wasn't. Every Modbus write to the AC·THOR ended in a connection refused. No timeout, no silent failure — the device actively rejects write attempts. It's in no documentation, and it cost me an evening of debugging to understand: on the AC·THOR 9s, Modbus TCP is read-only. Full stop.
The real problem: Modbus reads yes, writes no
The split is hard and reproducible. Monitoring works permanently over Modbus, control doesn't at all. If you, like me, build the read sensors first and feel pleased with yourself, you hit a wall the moment the first set command lands. Here's the inventory I noted down after the debugging session:
BLOCKED: Modbus-TCP write operations (device-side restriction, 'connection refused' on every write attempt)
WORKING: Control via Cloud API PUT /setup
WORKING: Temperature monitoring via Modbus-TCP still works (read-only)
Tested parameters (PUT /setup, JSON body):
bstmode Assurance/boost mode 0=Off, 1=On
ww1boost Target temperature 550 = 55.0°C (tenths °C)
ww1target Max temperature, solar mode 650 = 65.0°C (tenths °C)
bstton1 Boost window 1 start Hour 0-23
bsttof1 Boost window 1 end Hour 0-23
Gotchas:
- Propagation delay: a change only takes effect after 4-6 s on the device.
- The API replies immediately with 'ok' (no proof the value is set yet).
- Wait before the GET /setup verification.The rescue is the my-PV Cloud API. It's barely documented, but it accepts exactly the write commands that local Modbus refuses. So the path doesn't go over the LAN straight to the device — it takes the detour through the my-PV cloud, which the device then syncs from itself.
The solution: the my-PV Cloud API via PUT /setup
The API has a single control endpoint that matters: PUT /api/v1/device/<serial>/setup. The JSON body carries exactly the fields you want to change — everything else stays untouched. You authenticate with an Authorization header carrying your cloud API token. That's the whole transport mechanism: a PUT with a few keys in the body.
An important framing: this post only covers the transport layer — that is, how the write command reaches the device at all. The actual surplus logic (when to boost, boost-stop at 55 °C) belongs in a separate automation and is the subject of the AC·THOR surplus post.
The shell_command.yaml — set boost and target temperature
Since there's no native HA integration for the my-PV cloud, I build the write commands as shell_command entries using curl. Each entry is a single PUT. Replace DEVICE_SERIAL with your my-PV serial and YOUR_MYPV_API_TOKEN with your cloud API token (ideally pulled from !secret — inline here for readability):
# AC·THOR 9s Cloud API commands (direct REST calls)
# Uses the my-PV Cloud API, since Modbus-TCP write is blocked device-side.
# Replace placeholders:
# DEVICE_SERIAL = your my-PV device serial number
# YOUR_MYPV_API_TOKEN = your my-PV Cloud API authorization token
acthor_enable_boost: 'curl -s -X PUT "https://api.my-pv.com/api/v1/device/DEVICE_SERIAL/setup" -H "Authorization: YOUR_MYPV_API_TOKEN" -H "Content-Type: application/json" -d "{\"bstmode\": 1}"'
acthor_disable_boost: 'curl -s -X PUT "https://api.my-pv.com/api/v1/device/DEVICE_SERIAL/setup" -H "Authorization: YOUR_MYPV_API_TOKEN" -H "Content-Type: application/json" -d "{\"bstmode\": 0}"'
# Target temperature (in tenths °C: 550=55°C, 600=60°C, 650=65°C)
acthor_set_target_temp_55: 'curl -s -X PUT "https://api.my-pv.com/api/v1/device/DEVICE_SERIAL/setup" -H "Authorization: YOUR_MYPV_API_TOKEN" -H "Content-Type: application/json" -d "{\"ww1boost\": 550}"'
# Max solar temperature
acthor_set_max_temp_65: 'curl -s -X PUT "https://api.my-pv.com/api/v1/device/DEVICE_SERIAL/setup" -H "Authorization: YOUR_MYPV_API_TOKEN" -H "Content-Type: application/json" -d "{\"ww1target\": 650}"'
# Boost window (start/end as hour 0-23)
acthor_enable_boost_with_time: 'curl -s -X PUT "https://api.my-pv.com/api/v1/device/DEVICE_SERIAL/setup" -H "Authorization: YOUR_MYPV_API_TOKEN" -H "Content-Type: application/json" -d "{\"bstmode\": 1, \"bstton1\": {{ start_hour }}, \"bsttof1\": {{ end_hour }}}"'After a reload of the shell commands, shell_command.acthor_enable_boost and the others are available as services in HA and can be called from any automation or from the Developer Tools service tab. That restores the write path that Modbus refused.
The parameters that actually work
I tested the fields one by one, because the my-PV docs are silent here. These four (plus the two time-window fields) are enough for full boost control. bstmode is the switch: 1 starts the boost, 0 stops it. ww1boost is the boost's target temperature, ww1target the maximum temperature in normal solar operation — both in tenths of a degree, so 550 means 55.0 °C. bstton1 and bsttof1 define a boost window as a whole hour (0–23).
The most common beginner mistake is the unit: send 55 instead of 550 and you set the device to a 5.5 °C target — effectively off. The tenths-of-a-degree convention isn't documented cleanly anywhere, but it's consistent across all temperature fields.
The gotcha: the propagation delay
This is where I lost the most time. The API answers every PUT instantly with 'ok' — but that only means the cloud accepted the command, not that the device has applied the value yet. It takes 4 to 6 seconds before the change actually reaches the AC·THOR.
In practice that means: if you verify with GET /setup immediately after writing, you'll still read the old value and conclude the command failed. So build in a short wait after the PUT before triggering the verification or a follow-up action. In an HA automation I do this with a delay of a few seconds between the shell_command and the next step — that eliminated every phantom failure.
Monitoring stays on Modbus
A pleasant side effect: you don't have to give up the working Modbus read. On my setup HA keeps reading the boiler temperature and power locally over Modbus TCP — no cloud dependency, no rate limit. Only the write path goes through the cloud. This hybrid (read locally, write via cloud) is robust: if the internet drops you lose control, not the display. If you wire up the inverter behind it over Modbus too, the basics are in the Modbus cache post.
Frequently asked questions
Why doesn't the AC·THOR 9s allow Modbus writes?
It's a device-side restriction from my-PV: the Modbus TCP server on the AC·THOR is designed read-only, and every write attempt is rejected with 'connection refused'. Control is intended exclusively via the official app, the local web interface, or the cloud API. It's not a bug in your configuration — it's the manufacturer's intent.
Do I strictly need the cloud, or can I do it locally?
For API write access, the documented path goes through the my-PV cloud (api.my-pv.com). Some firmware versions additionally expose local HTTP endpoints, but that's version-dependent and not guaranteed. The cloud path shown here works reliably across devices — the cost is an internet dependency for control. Monitoring stays local and unaffected.
Why does my verification fail right after writing?
Almost certainly the propagation delay. The API acknowledges instantly with 'ok', but the device only applies the value after 4–6 seconds. If you query GET /setup immediately afterward, you still read the old state. Wait a few seconds between writing and reading and the result is correct.
How do I hide the serial number and token in the config?
Put both in secrets.yaml and reference them with !secret in the shell_command. That keeps neither the device serial nor the cloud API token in versioned YAML. Be especially careful that the token never appears in logs, screenshots, or a Git repo — it grants full write access to your device.



