Thanks for considering a contribution! This integration is maintained by volunteers, and improvements of any size - bug reports, documentation fixes, new device support, new features - are very welcome.
This document explains how to get set up, how to propose changes, and what to expect from the review process. If anything is unclear, open a Discussion or draft issue and we'll help out.
- Code of conduct
- Ways to contribute
- Your first code contribution
- Development setup
- Making a pull request
- Style guide
- Project layout
Be kind, patient, and constructive. Assume good intent, give concrete feedback, and remember that most contributors are hobbyists working in their spare time. Harassment, personal attacks, or hostility toward other contributors are not tolerated.
All three of the flows below open through dedicated issue forms in
.github/ISSUE_TEMPLATE/ - blank issues are disabled, so
pick the template that matches what you're reporting.
For crashes, disconnects, errors in the log, or anything broken that isn't specifically a sensor value problem, use the Bug report template. Before opening it:
- make sure you're on the latest release and that the bug still reproduces
- search existing issues to avoid duplicates - add a 👍 to an existing report rather than opening a new one
- fill in every field the template asks for - integration and Home Assistant versions, device model, Bluetooth connection type, any changed settings, and relevant log output. Bug reports without this information are much harder to act on
- if the bug is sensor-value-shaped rather than crash-shaped, use the sensor template below instead
If a sensor is missing, shows the wrong value, or a value the device exposes isn't surfaced in Home Assistant, use the New / missing / incorrect sensor template. Almost all sensor reports need a diagnostics dump to identify the right protobuf field - see Providing Diagnostics Data for how to capture one. Diagnostics are safe to share publicly - all potentially sensitive data is AES encrypted. Capture the dump while the value you care about is non-zero (sun on the panels, load on the output, charger plugged in) so the relevant field is actually populated.
If you own an EcoFlow device that isn't listed in the README's Supported Devices section, use the New device support template. It mirrors the flow described on the Requesting Support for New Devices wiki page:
- add the unsupported device through the integration; it will run in a diagnostic collection mode
- exercise different ports / features so the captured traffic covers the functionality you care about
- attach the diagnostics file to the issue along with the device model, firmware, and SN prefix the template asks for
Some devices can't provide useful diagnostics over BLE. If your device won't connect for collection, open the issue anyway with the model and firmware version so we know there's demand.
Before opening a pull request for a non-trivial change, please open an issue first describing the motivation and proposed approach. This avoids wasted effort on changes that don't fit the integration's scope or conflict with other ongoing work.
Small, focused suggestions (wording fixes, obvious bug fixes, sensor defaults) don't need a pre-discussion issue - just send the PR.
For usage questions, setup troubleshooting, or general discussion, use GitHub Discussions. The issue templates link to Discussions directly, and blank issues are disabled on the tracker - it's only for actionable bug reports and feature requests.
Good starting points:
- issues labelled
good first issueorhelp wanted - documentation and translation fixes - typos, clarifications, missing sensor entries in the README device tables
- adding tests for existing device modules under
tests/eflib/
- Python 3.13 or newer. The project also runs on 3.14.
uvfor dependency management and tooling. Install via the official instructions, e.g.curl -LsSf https://astral.sh/uv/install.sh | shon Linux/macOS.- git, and ideally a running Home Assistant instance to test the integration end-to-end.
git clone https://github.com/rabits/ha-ef-ble.git
cd ha-ef-ble
uv sync --all-groupsThis creates a .venv/ with the runtime dependencies plus the dev, test, and lint
groups - everything needed for the device-library tests and the code-style hooks.
uv run pytest tests/eflibUse uv run pytest -k <name> to run a single test and
uv run pytest --cov=custom_components.ef_ble if you want coverage locally. Add tests
for new device modules - tests/eflib/test_powerstream.py and
tests/eflib/test_river3.py are reasonable templates.
Style and lint rules are enforced with prek (a fast
Rust reimplementation of pre-commit) running the hooks defined in
.pre-commit-config.yaml. The same hooks run in CI, so if
prek is happy locally, CI will be too.
The simplest way to run prek is through uvx, which fetches and runs it in an
ephemeral environment - no separate install step needed:
uvx prek install # enable the pre-commit git hook (one-time)
uvx prek run # check files staged for the next commit
uvx prek run --all-files # check the whole repo (matches CI)If you run these often and want to skip the per-invocation resolve, install prek as a
persistent tool once with uv tool install prek and drop the uvx prefix.
The hooks include ruff for linting and formatting
(configured under [tool.ruff] in pyproject.toml),
rumdl for Markdown, and a handful of generic hygiene
checks (trailing whitespace, line endings, YAML/TOML parsing, accidentally-committed
private keys).
The simplest loop for manual testing is to symlink the component into a local Home Assistant config directory:
ln -s "$(pwd)/custom_components/ef_ble" /path/to/ha-config/custom_components/ef_bleRestart Home Assistant after changes to Python code; translation and manifest changes may also require a restart rather than a hot-reload.
- Fork the repo and create a feature branch off
main:git checkout -b fix-device-sensor. - Keep each PR focused on a single concern. If your branch fixes a bug and refactors something nearby, prefer two PRs (or two commits that can be reviewed independently).
- Write commit messages in the imperative mood ("Fix PV2 mapping on STREAM Ultra")
Using LLMs / AI coding assistants is fine - most of us do nowadays. What matters is that you own the code you submit: you've read every line, understand why it's there, tested that it works, and can respond to review feedback without going back to the model for every answer.
Adding support for a new device is the one place where fully vibe-coded PRs are actively welcome. It's genuinely easier to start from a rough LLM draft plus a diagnostics dump than from a blank module - even an imperfect draft gives us a concrete starting point, your field mappings, and proof that the device is reachable. Open the PR, link the diagnostics file, describe what you tested on the hardware, and be honest about which parts you didn't verify.
That said: we will not merge a vibe-coded new-device PR as-is. Expect us to use it
as a reference and rewrite the parts that need to match project patterns - renaming
fields to line up with existing device modules, consolidating duplicated logic into
shared transforms, dropping invented APIs, adjusting controls to the conventions in
eflib/entity/, adding tests, and so on. The final commit that lands usually won't
look much like your original branch. If that's not how you want your contribution
handled, open an issue with the diagnostics dump instead and we'll pick it up from
there.
For everything else (bug fixes, refactors, changes to existing devices, protocol / packet handling), the bar is higher: unread LLM output, code the author can't explain, or changes that ignore existing patterns won't be reviewed in place. Re-read your diff top-to-bottom before opening the PR.
Most style is enforced by ruff, so the full rule set lives in pyproject.toml. A few
non-obvious conventions:
-
Python≥3.13 features are fair game: pattern matching, PEP 695 generics,
typestatements, f-strings, the walrus operator, dataclasses. -
Type hints everywhere. Public functions, class attributes, and fields declared via
pb_field/raw_fieldshould have meaningful types. The shared transforms incustom_components/ef_ble/eflib/props/transforms.pypreserveNoneintentionally - prefer them over inlinelambdahelpers. -
American English for identifiers, comments, and user-facing strings.
-
Logging: use lazy
%sformatting (_LOGGER.debug("got %s", value)), no trailing periods, no sensitive data, and don't repeat the integration name - it's added automatically. -
Async-safe everywhere: no blocking I/O on the event loop, no
time.sleep, don't reuseBleakClientinstances across connects. Useasyncio.gatherinstead of sequentialawaits where possible. -
Exceptions: raise the most specific type that fits (
ConfigEntryAuthFailed,ConfigEntryNotReady,HomeAssistantError,ServiceValidationError,UpdateFailed).Keeptryblocks minimal - only wrap the call that can raise, not the data processing that follows. Bareexcept Exception:is reserved for config flows and background tasks. -
Docstrings: NumPy style without types in the docstring (
numpy-notypesvariant - argument and return types belong in the signature, not duplicated in the docstring). Use a one-line docstring for short descriptions; when a longer explanation is needed, start the summary on the second line (matches Ruff'sD213) and useParameters/Returnssections for non-obvious arguments and return values. The summary line does not end with a period. Document why, not what - skip docstrings that only restate the function name.class Device(DeviceBase, ProtobufProps): """STREAM AC""" def pb_field( attr: Any, transform: Callable[[Any], Any] | None = None, ) -> "ProtobufField[Any]": """ Create field that allows value assignment from protocol buffer messages Parameters ---------- attr Protobuf field attribute of instance returned from `proto_attr_mapper` transform, optional Function that is applied to raw protobuf value """
custom_components/ef_ble/
├── __init__.py # HA integration entry point
├── config_flow.py # Config flow for pairing
├── sensor.py / switch.py # Entity platforms
├── translations/ # User-facing strings
└── eflib/ # Home Assistant-independent device library
├── devices/ # One module per EcoFlow device family
├── pb/ # Generated protobuf bindings (do not edit)
├── props/ # Field descriptors, transforms, prop mixins
├── entity/ # Control / dynamic entity helpers
└── ...
tests/
├── eflib/ # Library-level tests (no Home Assistant)
└── ha/ # Integration tests using Home Assistant
New device support typically means:
- a new module in
custom_components/ef_ble/eflib/devices/with aDeviceclass and aSN_PREFIXtuple. - field definitions using
pb_field/raw_fieldand shared transforms fromeflib/props/transforms.py - controls defined with the
controls.*decorators where applicable. - tests under
tests/eflib/test_<device>.py - a new
<details>block in the README listing the exposed sensors and controls
Thanks again for contributing. When in doubt, open an issue or draft PR early - it's much easier to course-correct at the design stage than at review time.