Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.14"

- name: Install ruff
run: pip install ruff

- name: Ruff check
run: |
set +e
output=$(ruff check . --statistics 2>&1)
exit_code=$?
echo "## Ruff Lint Report" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "$output" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
exit $exit_code

- name: Ruff format check
run: |
set +e
output=$(ruff format --check . 2>&1)
exit_code=$?
echo "## Ruff Format Report" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "$output" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
exit $exit_code

mypy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.14"

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
pip install -e ".[fastapi]"

- name: mypy
run: |
set +e
output=$(mypy open_ess 2>&1)
exit_code=$?
echo "## mypy Report" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "$output" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
exit $exit_code

pytest:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12", "3.13", "3.14"]

steps:
- uses: actions/checkout@v6

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"

- name: Run tests
run: pytest
15 changes: 12 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
repos:
- repo: https://github.com/psf/black
rev: 26.3.1
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: black
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.12
hooks:
- id: ruff # Linter
args: [ --fix ]
- id: ruff-format # Formatter
3 changes: 2 additions & 1 deletion default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ buildPythonPackage {
];

meta = with lib; {
description = "Open Energy Storage System - Charge/discharge schedule optimizer for day-ahead energy prices.";
description =
"Open Energy Storage System - Charge/discharge schedule optimizer for day-ahead energy prices.";
license = licenses.mit;
};
}
41 changes: 41 additions & 0 deletions docs/dev/getting started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
To set up a dev environment run;

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install -e .
pip install -e ".[dev]"

pre-commit install
pre-commit autoupdate
```

### pytest

```bash
# Run all tests:
pytest

# For a code coverage report:
pytest --cov=metricsqlite --cov-report=term-missing
```

### ruff

```bash
ruff check . # Lint
ruff check . --fix # Lint + auto-fix
ruff format . # Format

# pyproject.toml sets `output-format = "concise"`. To show more details run;
ruff check --output-format=full .
```

### mypy

```bash
mypy open_ess

mypy --install-types
```
2 changes: 2 additions & 0 deletions open_ess/battery_system/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
from .battery_system import BatterySystem, VictronBatterySystem
from .config import BatterySystemConfig

__all__ = ["BatterySystem", "BatterySystemConfig", "VictronBatterySystem"]
11 changes: 6 additions & 5 deletions open_ess/battery_system/battery_system.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import logging
from abc import ABC, abstractmethod
from datetime import datetime, timedelta, timezone
from datetime import UTC, datetime, timedelta

from open_ess.victron_modbus import VictronClient

from .config import BatterySystemConfig

logger = logging.getLogger(__name__)
Expand All @@ -17,15 +18,15 @@ def config(self) -> BatterySystemConfig:
return self._config

@property
def name(self) -> str:
def name(self) -> str | None:
return self._config.name

@property
@abstractmethod
def id(self) -> str | None: ...

@abstractmethod
def set_ess_setpoint(self, power: float, until: datetime | None = None): ...
def set_ess_setpoint(self, power: float, until: datetime | None = None) -> None: ...


class VictronBatterySystem(BatterySystem):
Expand All @@ -40,8 +41,8 @@ def id(self) -> str | None:
return None
return f"victron/{self._victron_client.serial}"

def set_ess_setpoint(self, power: float, until: datetime | None = None):
def set_ess_setpoint(self, power: float, until: datetime | None = None) -> None:
if until is None:
until = datetime.now(tz=timezone.utc) + timedelta(hours=1)
until = datetime.now(tz=UTC) + timedelta(hours=1)
logger.info(f"{self.name}: Set setpoint to {power} W")
self._victron_client.set_ess_setpoint(power, until)
10 changes: 5 additions & 5 deletions open_ess/battery_system/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Annotated, Literal

from pydantic import BaseModel, Field, model_validator, computed_field
from pydantic import BaseModel, Field, computed_field, model_validator

from open_ess.victron_modbus import VictronConfig

Expand Down Expand Up @@ -39,8 +39,8 @@ class BatterySystemConfig(BaseModel):
control: Annotated[VictronConfig | MqttControl, Field(discriminator="type")]
metrics: MetricsConfig = MetricsConfig()

@computed_field
@property
@computed_field
def id(self) -> str:
if isinstance(self.control, VictronConfig):
return f"victron/vebus/{self.control.vebus_id}"
Expand All @@ -52,7 +52,7 @@ def is_victron(self) -> bool:
return isinstance(self.control, VictronConfig)

@model_validator(mode="after")
def check_power_limits(self):
def check_power_limits(self) -> "BatterySystemConfig":
if not self.monitor_only:
if self.max_charge_power_kw is None:
raise ValueError(
Expand All @@ -65,11 +65,11 @@ def check_power_limits(self):
return self

@model_validator(mode="after")
def set_defaults(self):
def set_defaults(self) -> "BatterySystemConfig":
if self.name is None:
self.name = self.id

if self.is_victron:
if isinstance(self.control, VictronConfig):
vebus_prefix = self.control.vebus_prefix
bms_prefix = self.control.battery_prefix

Expand Down
2 changes: 1 addition & 1 deletion open_ess/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import yaml
from pydantic import BaseModel

from open_ess.battery_system import BatterySystemConfig
from open_ess.database import DatabaseConfig
from open_ess.frontend import FrontendConfig
from open_ess.battery_system import BatterySystemConfig
from open_ess.pricing import PriceConfig

# TODO: Validate config. If a battery defines mqtt control, require mqtt config.
Expand Down
2 changes: 1 addition & 1 deletion open_ess/database/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from .config import DatabaseConfig
from .database import Database, DatabaseConnection
from .service import DatabaseService
from .util import ms_to_dt, dt_to_ms
from .util import dt_to_ms, ms_to_dt

__all__ = [
"Database",
Expand Down
Loading