A: No, that's normal. The emulator waits for your storage system to ask for data, and only polls when asked. Until your Marstek device sends a request, you won't see any further activity.
A: Common causes include:
- Firmware issues: See the firmware requirements in the Device and firmware section below
- Network setup: Put both devices on the same subnet (255.255.255.0)
- Bluetooth interference: Disconnect any Bluetooth connections while you set up
- Docker configuration: With Docker, set
network_mode: hostso the container can receive UDP broadcasts - CT002/CT003 pairing flow: For managed fake CTs, refresh the CT device list
(or log out and back in). Then pick
AstraMeter CT002/AstraMeter CT003, switch battery mode to automatic, and select that CT. You can select it as soon as it appears in the device list. The fake CT shows as offline in the CT list (expected). - Config source confusion: If you use
custom_configin the Home Assistant app, it overrides the credentials and options from the app UI.
A: Yes. The emulator implements only the minimal protocol that Marstek storage systems need. It is not a complete Shelly device emulation.
A: Use systemd to create a service:
- Create a unit file (e.g.,
/etc/systemd/system/astrameter.service) - Set
ExecStartto your startup command - Enable and start:
sudo systemctl enable astrameter && sudo systemctl start astrameter
A: Yes. Define several sections in config.ini (e.g., [SHELLY_1],
[SHELLY_2]), then use the NETMASK setting to assign each one to specific
client IPs. See Multiple Powermeters.
A: Power from grid to house (import): positive Power from house to grid (export): negative
A: Create a template sensor in Home Assistant:
{{ states('sensor.power_in_kilowatts') | float * 1000 }}A: Use comma-separated entity IDs:
sensor.phase1,sensor.phase2,sensor.phase3
A:
CURRENT_POWER_ENTITY: For a single bidirectional sensor (positive/negative values)POWER_INPUT_ALIAS/POWER_OUTPUT_ALIAS: Entity IDs for separate import/export sensors (withPOWER_CALCULATE = True)
A: One sensor, if you have a single signed one (positive for import, negative
for export). In the Home Assistant App, put it in POWER_INPUT_ALIAS (or
CURRENT_POWER_ENTITY) only, and leave POWER_OUTPUT_ALIAS empty. Separate
import/export sensors can update at different moments and get read out of sync,
which causes drift and oscillation. A single signed value avoids that.
A: Prefer CT002/CT003 (set DEVICE_TYPE = ct002 or ct003) for multi-battery
setups. With Shelly emulation, each battery reacts on its own, and they tend to
fight each other — one charges while another discharges. The CT emulation
coordinates one shared target across the fleet, so the load is spread more
evenly and stays stable. See CT002 / CT003 steering.
A:
- Venus: Firmware 120+ for Shelly support, 152+ for improved regulation
- B2500: Firmware 108+ (HMJ devices) or 224+ (all others)
A: Use one of these device types:
shellypro3em_old: Port 1010 (B2500 firmware ≤224 or Jupiter & Venus)shellypro3em_new: Port 2220 (B2500 firmware ≥226)shellypro3em: Both ports (most compatible)
A: No, this project is Marstek-specific. For other brands, see uni-meter.
A: Ports below 1024 need root privileges on Linux. Solutions:
- Use Docker or Home Assistant App (recommended)
- Use
setcapto grant permissions - Run as root (not recommended)
Note: the Docker image runs as a non-root user. So binding port 1010 (used by
shellypro3em_old and the combined shellypro3em, which starts both listeners)
still fails with PermissionError: [Errno 13] under network_mode: host. Port
2220 (shellypro3em_new) is unaffected. Either lower the host's
privileged-port range (sudo sysctl -w net.ipv4.ip_unprivileged_port_start=1010,
persist via /etc/sysctl.d/) or run the container as root (user: "0:0" in
compose). Publishing the port via bridge networking does not work: the
Marstek discovery packets are UDP broadcasts to the subnet address, and Docker's
port mapping doesn't forward them.
A: Common causes:
- Incorrect entity IDs or API access
- Memory limitations (especially on RPi 2 or similar devices)
- Check logs for specific error messages
A: Only partly — you can verify the initial configuration. Full testing needs a Marstek device in "self-adaptation" mode to request data. For local end-to-end testing without hardware, use the simulator.
A: This usually happens when your battery asks AstraMeter for a new reading more often than your meter has a fresh one. The battery keeps reacting to stale numbers, overshoots, and swings back and forth. The fix is to slow things down and smooth out the readings. Try these one at a time. After each change, watch how the battery behaves for a few minutes before moving on:
-
Prefer a fresh reading. This setting matters most here. Your battery adds each answer to its current output, so the same reading twice makes it apply the same correction twice — and that is what an overshoot looks like. Leave
WAIT_FOR_NEXT_MESSAGE = true(the default) so AstraMeter waits briefly, up to 2 seconds, for a new reading before it answers. If that wait times out it still replies from the cache, so this improves the odds rather than guaranteeing that every reply is fresh. If your meter is polled rather than pushed, setTHROTTLE_INTERVAL = 1so AstraMeter re-reads it at most once a second.Reach for
DEDUPE_TIME_WINDOWonly if a battery really floods you with repeat polls. It leaves the extra polls unanswered, so raising it does not slow a battery down smoothly — the answered rate can only drop to half, a third, a quarter of the battery's own poll rate. That is why nudging the value often changes nothing, and then changes a lot. -
Ignore tiny wobbles. Raise
DEADBANDto around10–20(watts). Small fluctuations near zero then count as "close enough" and don't trigger a correction. -
Smooth the changes. Set
SMOOTH_TARGET_ALPHAto around0.2–0.4andMAX_SMOOTH_STEPto around40–60, so the reported power moves in gentle steps instead of jumping.
If it still swings after that, the most effective option is the
PID Controller — a smart helper that
gently nudges the reading toward zero and calms down a battery that tends to
over- or under-react. To get started, just set PID_KP = 0.5 and
PID_MODE = bias, and leave the other PID_* settings alone. A few more
optional filters (including one that throws out occasional bad spikes) are
described under
Per-powermeter options if you want to
fine-tune further.
A: MIN_EFFICIENT_POWER governs this: it decides how many batteries are engaged
for a given demand. It's meant for AC batteries that can hold a precise
setpoint; pure DC battery pools can't be steered to exactly zero the same way.
If a second unit won't engage, lower MIN_EFFICIENT_POWER. For DC-only setups,
set it to 0. See
Battery efficiency optimization.
A: By default AstraMeter splits the load equally, so a 5 kWh battery gets the same share as a 15 kWh one and saturates first. Three settings work together to fix this:
MIN_EFFICIENT_POWERis a per-battery threshold, not a total. Set it to 900 W, say, and AstraMeter won't activate a second battery until demand is high enough for each battery to handle at least 900 W. For asymmetric packs, lower this value. Both batteries then run together across a wider demand range, instead of the smaller one absorbing everything alone.- Distribution Weight (the per-battery slider exposed via MQTT Insights)
splits load in proportion to capacity, once both batteries are active
simultaneously. For example, a 15 kWh battery paired with a 5 kWh one might
use weights of
3.0and1.0for a 75:25 split. - Efficiency Window Weight (also a per-battery slider via MQTT Insights,
shown only when
MIN_EFFICIENT_POWER > 0) controls what fraction of the efficiency-rotation active time each battery holds. When demand is low, only one battery runs at a time, so a larger battery should hold a proportionally bigger slice of the rotation window — e.g.75% and25% for the same 3:1 capacity ratio — and handle more of the cumulative energy over time. It applies only while demand is low enough to run one battery at a time, which is what it describes: how long each battery holds the single rotating slot. Once demand runs several at once, every turn is a full interval again, so only0% still has an effect (it parks a battery whenever the others can cover) — use Distribution Weight to divide load between batteries that run together.
See CT002 / CT003 steering for details on all three settings.
A: This is expected for purely local operation. The emulated meter usually fills
in only one phase, and the app won't show your raw readings because each battery
is handed only its share of the target (so the totals steer toward zero). It
does not mean the integration is failing. If you do want live readings in the
Marstek app, configure the [MARSTEK] section together with
hame-relay (≥ 1.3.5). AstraMeter can
then answer the app's polls via MQTT. See
MQTT Insights.
A: AstraMeter exposes a Distribution Weight entity for every battery in a CT002/CT003 fleet (requires MQTT Insights with HA discovery enabled). Raise the weight on a battery and it receives a larger share of the charging or discharging target. You can adjust these weights dynamically from a Home Assistant automation, so emptier batteries are prioritised and fuller ones are throttled back.
-
In Home Assistant go to Settings → Devices & Services → MQTT and open the Devices tab.
-
Look for devices named AstraMeter Consumer … (one per battery). Open each one.
-
Under Controls you will find a Distribution Weight slider. Note its entity ID — it looks like
number.astrameter_consumer_<mac>_distribution_weight, where<mac>is the battery's MAC address lowercased with all non-alphanumeric characters removed (e.g. a battery with MACAA:BB:CC:DD:EE:FFproducesnumber.astrameter_consumer_aabbccddeeff_distribution_weight).You can also find the entity ID on the entity's detail page: click the gear icon, and the entity ID is shown at the top of the settings dialog.
The SoC sensor comes from your battery's native integration (e.g. hm2mqtt,
hame-relay, or any other source). Open the battery device in Home Assistant,
find the State of Charge sensor, and note its entity ID
(e.g. sensor.marstek_b2500_aabbccddeeff_soc).
The formula below maps SoC to weight linearly: an empty battery (0 %) gets weight 2.0, and a full battery (100 %) gets weight 0.0. Adjust the formula to taste — for example, clamp the minimum above 0 if you never want a battery fully excluded.
Go to Settings → Automations & Scenes → Create Automation → Start with an empty automation and paste the following YAML (switch to YAML mode with the three-dot menu):
alias: AstraMeter – SoC-based distribution weights
description: >
Adjust each battery's distribution weight inversely proportional to its SoC
so that the emptiest battery is charged first.
triggers:
- trigger: state
entity_id:
- sensor.marstek_b2500_aabbccddeeff_soc # battery 1 SoC — replace with yours
- sensor.marstek_b2500_112233445566_soc # battery 2 SoC — replace with yours
for:
seconds: 10
actions:
- action: number.set_value
target:
entity_id: number.astrameter_consumer_aabbccddeeff_distribution_weight
data:
value: >
{{ [0, 2.0 * (1 - states('sensor.marstek_b2500_aabbccddeeff_soc') | float(100) / 100)] | max | round(1) }}
- action: number.set_value
target:
entity_id: number.astrameter_consumer_112233445566_distribution_weight
data:
value: >
{{ [0, 2.0 * (1 - states('sensor.marstek_b2500_112233445566_soc') | float(100) / 100)] | max | round(1) }}
mode: queued
max: 2Replace the four entity IDs with the real ones you found in steps 1 and 2. Add
one number.set_value action block per additional battery.
Tip:
mode: queuedwithmax: 2keeps a burst of rapid SoC updates from piling up, and the 10-secondfor:delay further debounces short-lived spikes. Increasemaxif you have more than two batteries, so that a concurrent trigger for every battery can queue safely.
A: Powermeters typically report import as positive and export as negative (see What's the correct power value convention? above). The Shelly and CT002/CT003 emulators forward those signed watts into the Marstek protocols. What happens on the battery side depends on your firmware and device type.