Skip to content

Commit 064cf41

Browse files
Merge release v2.0.0
2 parents 80eca80 + 934fa81 commit 064cf41

173 files changed

Lines changed: 28845 additions & 3693 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.dockerignore

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1+
.git
2+
.github
3+
.venv
14
__pycache__
2-
*.pyc
3-
*.pyo
4-
*.pyd
5-
.Python
5+
*.py[cod]
6+
*.egg-info
7+
dist
8+
build
9+
.mypy_cache
10+
.ruff_cache
11+
.pytest_cache
12+
tests
13+
docs
14+
config.ini
15+
.DS_Store
616
.env
7-
.git
8-
**/*_test.py

.github/ISSUE_TEMPLATE/issue_report.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ assignees: []
1010
<!-- Describe what's happening or what you need help with -->
1111

1212
## Your setup
13-
- **Installation method**: <!-- Home Assistant Add-on / Docker / Python / Node-RED -->
13+
- **Installation method**: <!-- Home Assistant App / Docker / Python -->
1414
- **Storage system**: <!-- e.g. B2500, Jupiter, Venus + firmware version if known -->
1515
- **Power meter source**: <!-- e.g. Shelly 3EM, Home Assistant sensor, etc. -->
1616

.github/workflows/build-image.yml

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@ on:
1111
required: true
1212
type: string
1313
description: "Platform to build for (e.g. linux/amd64)"
14-
base:
15-
required: false
14+
cache_scope:
15+
required: true
1616
type: string
17-
description: "Base image to use (e.g. distroless, alpine)"
18-
default: ""
17+
description: "Unique GHA cache scope for this platform/image (avoid matrix collisions)"
1918
context:
2019
required: true
2120
type: string
@@ -57,6 +56,15 @@ jobs:
5756
uses: docker/metadata-action@v5
5857
with:
5958
images: ${{ inputs.registry }}/${{ steps.lower-repo.outputs.IMAGE_NAME }}
59+
tags: |
60+
type=ref,event=branch
61+
type=ref,event=tag
62+
type=ref,event=pr
63+
type=sha
64+
type=raw,value=next,enable=${{ github.ref == 'refs/heads/develop' }}
65+
type=semver,pattern={{version}},enable=${{ github.ref_type == 'tag' }}
66+
type=semver,pattern={{major}}.{{minor}},enable=${{ github.ref_type == 'tag' }}
67+
type=semver,pattern={{major}},enable=${{ github.ref_type == 'tag' }}
6068
flavor: |
6169
latest=${{ github.ref == 'refs/heads/main' }}
6270
- name: Set up Docker Buildx
@@ -71,7 +79,7 @@ jobs:
7179
- name: Build and push by digest
7280
id: build
7381
if: inputs.push
74-
uses: docker/build-push-action@v5
82+
uses: docker/build-push-action@v6
7583
with:
7684
context: ${{ inputs.context }}
7785
platforms: ${{ inputs.platform }}
@@ -81,13 +89,13 @@ jobs:
8189
labels: ${{ steps.meta.outputs.labels }}
8290
build-args: |
8391
${{ inputs.build-args }}
84-
BASE=${{ inputs.base }}
85-
cache-from: type=gha
86-
cache-to: type=gha,mode=max
92+
GIT_COMMIT_SHA=${{ github.sha }}
93+
cache-from: type=gha,scope=${{ inputs.cache_scope }}
94+
cache-to: type=gha,mode=max,scope=${{ inputs.cache_scope }}
8795
- name: Build (no push)
8896
id: build-no-push
8997
if: inputs.push == false
90-
uses: docker/build-push-action@v5
98+
uses: docker/build-push-action@v6
9199
with:
92100
context: ${{ inputs.context }}
93101
platforms: ${{ inputs.platform }}
@@ -96,9 +104,8 @@ jobs:
96104
labels: ${{ steps.meta.outputs.labels }}
97105
build-args: |
98106
${{ inputs.build-args }}
99-
BASE=${{ inputs.base }}
100-
cache-from: type=gha
101-
cache-to: type=gha,mode=max
107+
GIT_COMMIT_SHA=${{ github.sha }}
108+
cache-from: type=gha,scope=${{ inputs.cache_scope }}
102109
- name: Export digest
103110
if: inputs.push
104111
run: |
@@ -118,4 +125,4 @@ jobs:
118125
name: ${{ inputs.digest-prefix }}${{ steps.platform.outputs.name }}
119126
path: /tmp/digests/*
120127
if-no-files-found: error
121-
retention-days: 1
128+
retention-days: 1

.github/workflows/ci.yml

Lines changed: 87 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,107 +2,166 @@ name: CI Pipeline
22

33
on:
44
push:
5-
branches: [ main ]
5+
branches: [ "main", "develop" ]
66
tags: [ "*.*.*" ]
77
pull_request:
8-
branches: [ main ]
8+
branches: [ "main", "develop" ]
9+
10+
concurrency:
11+
group: ci-${{ github.workflow }}-${{ github.ref }}
12+
cancel-in-progress: true
913

1014
permissions:
11-
packages: write
1215
contents: read
1316

1417
jobs:
18+
lint:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Checkout repository
22+
uses: actions/checkout@v4
23+
24+
- name: Install uv
25+
uses: astral-sh/setup-uv@v5
26+
with:
27+
enable-cache: true
28+
29+
- name: Set up Python
30+
uses: actions/setup-python@v5
31+
with:
32+
python-version: "3.13"
33+
34+
- name: Install dependencies
35+
run: uv sync --frozen --extra dev
36+
37+
- name: Ruff check
38+
run: uv run ruff check .
39+
40+
- name: Ruff format
41+
run: uv run ruff format --check .
42+
43+
- name: Mypy
44+
run: uv run mypy src/
45+
1546
validate:
47+
needs: [ lint ]
1648
runs-on: ubuntu-latest
1749
strategy:
1850
fail-fast: false
1951
matrix:
20-
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
52+
python-version: [ "3.10", "3.11", "3.12", "3.13" ]
2153
steps:
2254
- name: Checkout repository
23-
uses: actions/checkout@v2
55+
uses: actions/checkout@v4
56+
57+
- name: Install uv
58+
uses: astral-sh/setup-uv@v5
59+
with:
60+
enable-cache: true
61+
cache-suffix: ${{ matrix.python-version }}
2462

2563
- name: Set up Python
26-
uses: actions/setup-python@v2
64+
uses: actions/setup-python@v5
2765
with:
2866
python-version: ${{ matrix.python-version }}
2967

68+
- name: Install mosquitto (for MQTT integration tests)
69+
run: sudo apt-get update -qq && sudo apt-get install -y -qq mosquitto
70+
3071
- name: Install dependencies
31-
run: |
32-
pip install pipenv
33-
pipenv install --dev
72+
run: uv sync --frozen --extra dev
3473

3574
- name: Run tests
36-
run: pipenv run python3 -m pytest --junitxml=test-results.xml
37-
38-
- name: Validate Python code with flake8
39-
run: pipenv run python3 -m flake8 --select BLK **/*.py
75+
run: uv run pytest --cov=astrameter --cov-report=xml --junitxml=test-results.xml
4076

4177
- name: Upload test results
78+
if: ${{ always() }}
4279
uses: actions/upload-artifact@v4
4380
with:
4481
name: test-results-${{ matrix.python-version }}
4582
path: test-results.xml
4683

84+
- name: Upload coverage XML
85+
if: ${{ always() }}
86+
uses: actions/upload-artifact@v4
87+
with:
88+
name: coverage-${{ matrix.python-version }}
89+
path: coverage.xml
90+
4791
build:
92+
needs: [ validate ]
93+
permissions:
94+
contents: read
95+
packages: write
4896
strategy:
4997
fail-fast: false
5098
matrix:
5199
config:
52100
- platform: linux/amd64
53-
base: distroless
54-
- platform: linux/arm/v6
55-
base: alpine
101+
cache_scope: base-linux-amd64
56102
- platform: linux/arm/v7
57-
base: alpine
103+
cache_scope: base-linux-arm-v7
58104
- platform: linux/arm64
59-
base: distroless
105+
cache_scope: base-linux-arm64
60106
uses: ./.github/workflows/build-image.yml
61107
with:
62108
registry: ghcr.io
63109
platform: ${{ matrix.config.platform }}
64-
base: ${{ matrix.config.base }}
110+
cache_scope: ${{ matrix.config.cache_scope }}
65111
context: .
66112
dockerfile: ./Dockerfile
67113
image-suffix: ""
68114
digest-prefix: "digests-base-"
69115
push: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }}
70116

71117
merge:
72-
needs: [build]
118+
needs: [ build ]
73119
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
120+
permissions:
121+
contents: read
122+
packages: write
74123
uses: ./.github/workflows/merge-manifests.yml
75124
with:
76125
registry: ghcr.io
77126
image-suffix: ""
78127
digest-prefix: "digests-base-"
128+
legacy-image-name: tomquist/b2500-meter
79129

80130
build-addon:
131+
needs: [ validate ]
132+
permissions:
133+
contents: read
134+
packages: write
81135
strategy:
82136
fail-fast: false
83137
matrix:
84-
platform:
85-
- linux/amd64
86-
- linux/arm64
87-
- linux/arm/v7
88-
- linux/arm/v6
138+
include:
139+
- platform: linux/amd64
140+
cache_scope: addon-linux-amd64
141+
- platform: linux/arm64
142+
cache_scope: addon-linux-arm64
89143
uses: ./.github/workflows/build-image.yml
90144
with:
91145
registry: ghcr.io
92146
platform: ${{ matrix.platform }}
147+
cache_scope: ${{ matrix.cache_scope }}
93148
context: .
94149
dockerfile: ./ha_addon/Dockerfile
95150
build-args: |
96-
BUILD_FROM=ghcr.io/hassio-addons/base:14.2.2
151+
BUILD_FROM=ghcr.io/hassio-addons/base:17.0.1
97152
image-suffix: "-addon"
98153
digest-prefix: "digests-addon-"
99154
push: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }}
100155

101156
merge-addon:
102-
needs: [build-addon]
157+
needs: [ build-addon ]
103158
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
159+
permissions:
160+
contents: read
161+
packages: write
104162
uses: ./.github/workflows/merge-manifests.yml
105163
with:
106164
registry: ghcr.io
107165
image-suffix: "-addon"
108-
digest-prefix: "digests-addon-"
166+
digest-prefix: "digests-addon-"
167+
legacy-image-name: tomquist/b2500-meter

.github/workflows/merge-manifests.yml

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ on:
1313
digest-prefix:
1414
required: true
1515
type: string
16+
legacy-image-name:
17+
required: false
18+
type: string
19+
default: ""
20+
description: "Optional legacy image name (e.g. owner/repo) to publish the same manifest under for backward compatibility"
1621

1722
jobs:
1823
merge:
@@ -32,11 +37,26 @@ jobs:
3237
- id: lower-repo
3338
run: |
3439
echo "IMAGE_NAME=${GITHUB_REPOSITORY@L}${{ inputs.image-suffix }}" >> $GITHUB_OUTPUT
40+
if [ -n "${{ inputs.legacy-image-name }}" ]; then
41+
LEGACY_LOWER=$(echo "${{ inputs.legacy-image-name }}" | tr '[:upper:]' '[:lower:]')
42+
echo "LEGACY_IMAGE_NAME=${LEGACY_LOWER}${{ inputs.image-suffix }}" >> $GITHUB_OUTPUT
43+
fi
3544
- name: Extract metadata
3645
id: meta
3746
uses: docker/metadata-action@v5
3847
with:
39-
images: ${{ inputs.registry }}/${{ steps.lower-repo.outputs.IMAGE_NAME }}
48+
images: |
49+
${{ inputs.registry }}/${{ steps.lower-repo.outputs.IMAGE_NAME }}
50+
${{ inputs.legacy-image-name != '' && format('{0}/{1}', inputs.registry, steps.lower-repo.outputs.LEGACY_IMAGE_NAME) || '' }}
51+
tags: |
52+
type=ref,event=branch
53+
type=ref,event=tag
54+
type=ref,event=pr
55+
type=sha
56+
type=raw,value=next,enable=${{ github.ref == 'refs/heads/develop' }}
57+
type=semver,pattern={{version}},enable=${{ github.ref_type == 'tag' }}
58+
type=semver,pattern={{major}}.{{minor}},enable=${{ github.ref_type == 'tag' }}
59+
type=semver,pattern={{major}},enable=${{ github.ref_type == 'tag' }}
4060
flavor: |
4161
latest=${{ github.ref == 'refs/heads/main' }}
4262
- name: Log in to Container registry

.gitignore

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,15 @@
11
**/__pycache__/*
2-
.venv
2+
*.py[cod]
3+
.venv/
4+
*.egg-info/
5+
dist/
6+
build/
7+
.mypy_cache/
8+
.ruff_cache/
9+
.pytest_cache/
10+
.coverage
11+
htmlcov/
12+
test-results.xml
13+
coverage.xml
14+
config.ini
15+
.DS_Store

AGENTS.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Agent notes
2+
3+
Resolved versions live in **`uv.lock`**. Install dev dependencies the same way CI does:
4+
5+
```bash
6+
uv sync --extra dev
7+
```
8+
9+
Before finishing Python changes, run (from repo root, with dev deps):
10+
11+
```bash
12+
uv run ruff format .
13+
uv run ruff check .
14+
uv run mypy src/
15+
uv run pytest
16+
```
17+
18+
CI runs the same steps (see `.github/workflows/ci.yml`).
19+
20+
## Changelog
21+
22+
For user-facing work on a branch, keep **one bullet under `## Next`** that summarizes the **overall** outcome of that branch. **Add** it when you first document the change; on **later iterations** on the same branch, **edit that same bullet** if the scope or wording shifts—do **not** append extra bullets for each follow-up. Skip `CHANGELOG.md` entirely when nothing users would notice changes (refactors, tests-only, etc.).
23+
24+
Do **not** expand `CHANGELOG.md` with every internal or tooling-only follow-up. If the branch bullet already states the high-level theme, leave it unless the **user-visible** story changes.
25+
26+
## Adding a powermeter
27+
28+
1. **Implementation** — Add `src/astrameter/powermeter/<module>.py` with a class subclassing `Powermeter`; implement `get_powermeter_watts()` (and `wait_for_message()` only if the base default is wrong for your source).
29+
2. **Exports** — Import and re-export the class from `src/astrameter/powermeter/__init__.py`.
30+
3. **Config** — In `src/astrameter/config/config_loader.py`: import the class, define a `*_SECTION` string, add a `section.startswith(...)` branch in `create_powermeter()`, and a `create_*_powermeter()` factory that reads options from the section. `POWER_OFFSET` / `POWER_MULTIPLIER`, `THROTTLE_INTERVAL`, and `NETMASK` are handled globally for any section that returns a powermeter — no extra wiring unless you need something custom.
31+
4. **Examples, docs & changelog** — Add a commented example to `config.ini.example` and a subsection under **Configuration** in `README.md`, plus one **`## Next`** bullet for the powermeter (add once, then update that bullet on follow-up iterations if needed—see **Changelog** above).
32+
5. **Tests** — Add `src/astrameter/powermeter/<module>_test.py` (or extend existing tests) and run the commands above before finishing.

0 commit comments

Comments
 (0)