Home Assistant integration — HACS mirror repo & release runbook
The battery_soc Home Assistant integration lives in this monorepo under
integrations/homeassistant/custom_components/battery_soc/. It is distributed
to users through a separate, public GitHub repo wired for HACS. That mirror
repo is a derived artifact: it is assembled from this monorepo by
scripts/publish_mirror.sh and never edited by hand.
- Source of truth: this monorepo.
- Mirror repo:
Developer-Simon/ha-battery-soc— the public, HACS-facing repo, assembled from here byscripts/publish_mirror.sh. - Shared core:
libs/battery_soc_core/→ vendored into the integration byscripts/vendor_core.py(drift-guarded by the CI job Vendored artefacts in sync andintegrations/homeassistant/tests/test_vendor_sync.py). - Test venvs: the plain suites run in
.venv; the Home Assistant suite needs its own.venv-ha(HA’s pytest plugins conflict with the plain suite). Build it once:python3.14 -m venv .venv-ha && .venv-ha/bin/pip install -e ./libs/battery_soc_core -r integrations/homeassistant/requirements-test.txt. - Public identifiers:
integrations/homeassistant/mirror/release.env—OWNER=Developer-Simon,REPO=ha-battery-soc,HA_MIN_VERSION. - Real HACS/hassfest validation: runs as GitHub Actions in the mirror
repo (
.github/workflows/validate.yml), not here.scripts/check_mirror_manifest.pyis only a fast offline pre-check.
Local mirror checkout used below: ../ha-battery-soc (a sibling of this
repository’s working copy).
One-time — create the public repo (fresh, no history)
Consistent with this project’s release model (open-source as a fork without history, no git remote in the monorepo), the mirror is a brand-new repo — no history is imported from here.
mkdir -p ../ha-battery-soc && cd ../ha-battery-soc && git init && git branch -m main- On GitHub: create an empty public repo
Developer-Simon/ha-battery-soc(no README, no license, no .gitignore —publish_mirror.shprovides them). git -C ../ha-battery-soc remote add origin git@github.com:Developer-Simon/ha-battery-soc.git- Confirm
integrations/homeassistant/mirror/release.envhas the realOWNER(Developer-Simon). This is the only place it lives. - In the base manifest
integrations/homeassistant/custom_components/battery_soc/manifest.json, replace theOWNER/@OWNERplaceholders indocumentation,issue_trackerandcodeownerswith the real values, then commit in the monorepo. (publish_mirror.shalso rewrites these for the mirror, but the base file should not ship placeholders.) - First publish (assembles into the sibling
../ha-battery-socby default; override with--mirror-path):- the very first time the branch does not exist on
originyet, so push it by hand:scripts/publish_mirror.sh --version 0.1.0(a bare sync commit, no tag) thengit -C ../ha-battery-soc push -u origin main --follow-tagsandgh release create v0.1.0 --repo Developer-Simon/ha-battery-soc --title v0.1.0 --notes-file <(...). - every later release:
scripts/publish_mirror.sh --release(version taken frommanifest.json) regenerates the changelog, commits + tags the mirror, pushes branch + tag and creates the GitHub Release in one step. A plainscripts/publish_mirror.sh --push(no--release) only syncs the tree and pushes the branch — no tag, no Release.
- the very first time the branch does not exist on
- Set GitHub repo topics on the mirror (HACS rejects a repo with none):
gh repo edit Developer-Simon/ha-battery-soc --add-topic home-assistant --add-topic hacs --add-topic home-assistant-integration --add-topic lifepo4 --add-topic battery - Watch the Validate workflow (hassfest + HACS) in the mirror repo. Fix anything it flags in the monorepo, then re-publish (bump the version).
A GitHub Release (not just a tag) is mandatory, not optional polish: with
zero Releases HACS runs the repo in commit mode — the update entity shows a
bare commit SHA instead of the version and the “Read release announcement” link
just opens the repo homepage. --release creates the Release so this cannot be
forgotten.
Brand assets ship in-tree at custom_components/battery_soc/brand/icon.png
(+ icon@2x.png; icon.svg is the editable source). That local brand/ folder
covers the native Home Assistant UI (config-flow card, device page — HA
2025.x+ serves it via /api/brands/integration/...) and the hassfest / HACS
brands validation check. It does not cover the HACS store list or the
HACS update-entity dialog — both fetch https://brands.home-assistant.io/
by domain from the browser, so the icon is blank there until battery_soc is in
home-assistant/brands (see the last section). mirror/AI-DISCLAIMER.md and
mirror/.github/pull_request_template.md are assembled into the mirror too.
One-time — make it installable in Home Assistant
- HACS → ⋮ → Custom repositories → add
https://github.com/Developer-Simon/ha-battery-soc, category Integration. - HACS → search Battery SoC → Download → restart Home Assistant.
- Settings → Devices & Services → Add Integration → “Battery SoC (LiFePO4 coulomb-counting)”.
- Smoke test: add one battery pointing at real power/voltage sensors; confirm
the
SoCsensor and the diagnostic entities appear; callbattery_soc.set_state_of_chargewithstate_of_charge: 60and confirm the SoC sensor jumps to 60.
Recurring — cut a release
- Land the change in
libs/battery_soc_core/(or directly in the integration), then.venv/bin/python scripts/vendor_core.pyand stage the result. cd integrations/homeassistant && ../../.venv-ha/bin/pytest -q(whole HA suite, including the drift and mirror-template checks)..venv/bin/python scripts/check_mirror_manifest.pyversioninintegrations/homeassistant/custom_components/battery_soc/manifest.json(bare semver, no leadingv) has its patch bumped on the PR branch by theVersion bumpworkflow when the PR touchesintegrations/homeassistant/(scripts/version/bump-patch.sh, sameCOMPONENTSmechanism asdashboard/VERSION/services/VERSION— seescripts/version/components.sh); major/minor stay hand-edited.CHANGELOG.mdis generated the same way as the other components, grouped by major.minor into## vX.Y.Z (date)sections.publish_mirror.sh --releaserunsscripts/generate_changelog.sh ha-integrationfor you and stages the result in the monorepo (you commit it with the version bump), then slices the Release notes out of the matching section — no need to run it by hand first.v0.1.0-v0.1.4predate this automation and were written by hand as 5 separate sections sharing minor0.1; a permanent floor baked intogenerate_changelog.sh(min_freeze="v0.2"forha-integration) keeps that history frozen exactly as written on every regeneration, even without passing--freeze-before— do not lower it.scripts/publish_mirror.sh --release(regenerates + stages the changelog, commits + tags the mirror, pushesmain+ the tag, thengh release createsvX.Y.Zwith the changelog section as the body). The version defaults to themanifest.jsonversionfield from step 4, so no--versionis needed once the bump has been committed; pass--version X.Y.Zonly to override (bootstrap, or a manual major/minor jump before the commit lands). Then commit the stagedintegrations/homeassistant/CHANGELOG.mdin the monorepo.- HACS shows the update to users within a few hours (or immediately on a manual “Redownload”). An install that was made before the first Release existed is pinned to commit mode until its next Redownload in HACS — that one manual redownload flips it to version tracking.
--dry-run on publish_mirror.sh stops after assembling the tree and prints
git status --porcelain plus the rewritten manifest.json — use it to review
before the real run; it cannot be combined with --push or --release.
Without --release the script only syncs the tree into the mirror as a plain
commit (no tag, no changelog, no Release); add --push to also push the
branch. --release implies publishing, so --push is redundant with it.