{"id":49883480,"url":"https://github.com/haraldschilly/solmate-optimizer","last_synced_at":"2026-05-15T16:35:17.840Z","repository":{"id":350433537,"uuid":"1206815912","full_name":"haraldschilly/solmate-optimizer","owner":"haraldschilly","description":"Dynamically adjusts EET SolMate injection profile based on hourly electricity price and weather forecast","archived":false,"fork":false,"pushed_at":"2026-04-21T12:39:01.000Z","size":225,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-21T14:37:28.366Z","etag":null,"topics":["eet","electricity","solar-energy","solmate"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/haraldschilly.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-10T09:27:58.000Z","updated_at":"2026-04-21T12:35:34.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/haraldschilly/solmate-optimizer","commit_stats":null,"previous_names":["haraldschilly/solmate-optimizer"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/haraldschilly/solmate-optimizer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haraldschilly%2Fsolmate-optimizer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haraldschilly%2Fsolmate-optimizer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haraldschilly%2Fsolmate-optimizer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haraldschilly%2Fsolmate-optimizer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/haraldschilly","download_url":"https://codeload.github.com/haraldschilly/solmate-optimizer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haraldschilly%2Fsolmate-optimizer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33072612,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-15T11:35:32.926Z","status":"ssl_error","status_checked_at":"2026-05-15T11:35:31.362Z","response_time":103,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["eet","electricity","solar-energy","solmate"],"created_at":"2026-05-15T16:35:16.936Z","updated_at":"2026-05-15T16:35:17.830Z","avatar_url":"https://github.com/haraldschilly.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SolMate Optimizer\n\n[![PyPI version](https://img.shields.io/pypi/v/solmate-optimizer)](https://pypi.org/project/solmate-optimizer/)\n[![CI](https://github.com/haraldschilly/solmate-optimizer/actions/workflows/ci.yml/badge.svg)](https://github.com/haraldschilly/solmate-optimizer/actions/workflows/ci.yml)\n[![Python 3.13+](https://img.shields.io/python/required-version-toml?tomlFilePath=https://raw.githubusercontent.com/haraldschilly/solmate-optimizer/main/pyproject.toml)](https://pypi.org/project/solmate-optimizer/)\n[![License](https://img.shields.io/pypi/l/solmate-optimizer)](https://github.com/haraldschilly/solmate-optimizer/blob/main/LICENSE)\n\nDynamically adjusts [EET SolMate](https://www.eet.energy/) solar battery injection profiles based on real-time electricity prices and weather data.\n\nRun as a one-shot script once per hour — locally via cron, on [GCP Cloud Run](DEPLOYMENT.md), or any other scheduler.\n\n## Data sources\n\n### Electricity prices: aWATTar\n\n[aWATTar Austria](https://www.awattar.at/) provides a **free public API** with hourly day-ahead electricity prices for the Austrian market (EPEX spot). No API key or registration needed.\n\n- Endpoint: `GET https://api.awattar.at/v1/marketdata`\n- Returns prices in EUR/MWh for the next ~24 hours\n- Currently only the Austrian market is supported. Other hourly price providers (Tibber, ENTSO-E, etc.) could be added in the future.\n\n### Weather: OpenWeatherMap\n\n[OpenWeatherMap](https://openweathermap.org/) provides current weather and a 5-day/3-hour forecast. Used to determine cloud coverage (current and forecast).\n\n- **You need a free API key** — sign up at [openweathermap.org/api](https://openweathermap.org/api), the free tier is sufficient (current weather + 5-day forecast)\n- The forecast is used to decide whether the battery can recharge via solar (sun expected vs. persistent overcast)\n\n### SolMate: solmate-sdk\n\nThe [solmate-sdk](https://github.com/eet-energy/solmate-sdk) connects to your EET SolMate via WebSocket (cloud API). Used to read battery state, read/write injection profiles, and activate the optimized profile.\n\n## What it does\n\nEvery run:\n\n1. Fetches hourly electricity prices from aWATTar (public API, no auth)\n2. Fetches current weather and forecast from OpenWeatherMap (free API key)\n3. Connects to your SolMate via solmate-sdk (cloud API, serial + password)\n4. Reads the current battery state and existing injection profiles\n5. Computes an optimized 24-hour injection profile based on price quantiles, weather, and time of day\n6. Compares with the current profile — only writes if something changed\n7. Writes and activates the profile on the SolMate (existing profiles are preserved)\n\n## Decision logic\n\nThe optimizer is **price-driven** — electricity prices already encode weather, demand, and time-of-day patterns.\n\n### Injection levels\n\nNamed power levels (all configurable via env / CLI):\n\n| Level | Default | Used for |\n|-------|---------|----------|\n| **zero** | 0 / 0 W | No injection (negative or cheap prices) |\n| **night** | 30 / 80 W | Nighttime baseload |\n| **low** | 0 / 50 W | Battery protection, daytime PV charging |\n| **evening** | 50 / 120 W | Household evening consumption |\n| **medium** | 100 / 200 W | High price, no sun / evening moderate battery |\n| **high** | 200 / 400 W | High price, sun expected |\n\n### Priority table\n\n| Priority | Condition | Level | Reasoning |\n|----------|-----------|-------|-----------|\n| 1 | Price \u003c 0 (negative) | zero | Grid pays consumers to take power — never inject |\n| 2 | Price below P25 of 24 h | zero | Electricity is cheap, save battery for when it matters |\n| 3 | Battery \u003c 25 % | low | Protect battery regardless of price |\n| 4 | Price above P75 + battery OK + sun expected + not nighttime | high | Inject hard when it pays off and battery can recharge |\n| 4 | Price above P75 + battery OK + no sun + not nighttime | medium | Price is high but can't recharge — be cautious |\n| 4 | Price above P75 + battery 25–75 % + evening | medium | High price but below high threshold — moderate injection, spread over time |\n| 5 | Middle prices, night (default 23:00–07:59) | night | Baseload (fridge, standby); no solar production |\n| 5 | Middle prices, daytime (default 08:00–17:59) | low | Let PV charge the battery |\n| 5 | Middle prices, evening (default 18:00–22:59) | evening | Cover active household consumption |\n\nPriority 4 is intentionally skipped during nighttime: there is no solar production overnight, so injecting aggressively would drain the battery before the sun rises. The nighttime window defaults to 23:00–07:59 and is configurable via `--nighttime` / `NIGHTTIME`. The evening start defaults to 18:00 and is configurable via `--evening-start` / `EVENING_START`.\n\nDuring **evening hours** priority 4 distinguishes two battery bands. If the battery is at or above `BATTERY_HIGH_THRESHOLD` (default 75 %), the full power level applies. If the battery is between `BATTERY_LOW_THRESHOLD` (25 %) and `BATTERY_HIGH_THRESHOLD` (75 %), the **medium** level is used — the high price still warrants more than pure baseload, but without solar recharging available it makes sense not to drain the battery too aggressively.\n\nPrice-based rules (priorities 1 and 2) always win over battery protection: even a low battery should not inject when prices are negative or very cheap.\n\nFor the reasoning behind these rules — why relative prices, why the sun-expected heuristic works the way it does, what the optimizer deliberately does *not* do — see [DESIGN.md](DESIGN.md).\n\n## Install\n\nRequires Python 3.13+.\n\n### Option A — run without installing (recommended for quick use)\n\nWith [uv](https://docs.astral.sh/uv/) installed, run the latest version directly — no clone, no virtualenv:\n\n```bash\nuvx --from solmate-optimizer@latest solmate      # run optimizer\nuvx --from solmate-optimizer@latest status       # read-only status view\n```\n\nThe `--from` flag is required because the package name (`solmate-optimizer`) differs from the executable names (`solmate`, `status`). `uvx` fetches the package from PyPI into an ephemeral environment on first run and caches it for later invocations. Pin to a specific version with e.g. `solmate-optimizer@0.2.0`.\n\n### Option B — install via pip\n\n```bash\npip install solmate-optimizer\nsolmate           # run optimizer\nstatus            # read-only status view\n```\n\n### Option C — from source (for development)\n\n```bash\ngit clone https://github.com/haraldschilly/solmate-optimizer.git\ncd solmate-optimizer\nuv sync\nuv run solmate\n```\n\n## Configuration\n\nAll configuration is via environment variables and/or CLI options. CLI options override environment variables.\n\n| Env variable | CLI option | Default | Description |\n|-------------|-----------|---------|-------------|\n| `SOLMATE_SERIAL` | `--serial` | — | Your SolMate's serial number (required) |\n| `SOLMATE_PASSWORD` | `--password` | — | Your SolMate's user password (required) |\n| `OWM_API_KEY` | `--owm-api-key` | — | OpenWeatherMap API key ([free tier](https://openweathermap.org/api) works) |\n| `LOCATION_LATLON` | `--location` | `48.2:16.32` | Latitude and longitude as `lat:lon` (default: Vienna) |\n| `TIMEZONE` | `--timezone` | `Europe/Vienna` | Timezone for price/weather hour matching and display (use IANA names, e.g. `Europe/Berlin`) |\n| `SOLMATE_PROFILE_NAME` | `--profile-name` | `dynamic` | Name of the injection profile to create/update |\n| `BATTERY_LOW_THRESHOLD` | `--battery-low` | `0.25` | Battery fraction (0–1) below which injection is throttled |\n| `BATTERY_HIGH_THRESHOLD` | `--battery-high` | `0.75` | Battery fraction (0–1) required for high-price injection during evening hours |\n| `CLOUD_SUN_THRESHOLD` | `--cloud-sun-threshold` | `60` | Forecast cloud % below which \"sun expected\" for recharging |\n| `MAX_WATTS` | `--max-watts` | `800` | SolMate max injection capacity in watts |\n| `NIGHTTIME` | `--nighttime` | `23,8` | Nighttime window as `start,end` (inclusive start, exclusive end, wraps midnight) |\n| `EVENING_START` | `--evening-start` | `18` | First evening hour (inclusive). Evening runs from here to nighttime start. |\n| `LEVEL_NIGHT` | `--level-night` | `30,80` | Night/baseload injection level as `min,max` watts |\n| `LEVEL_LOW` | `--level-low` | `0,50` | Low injection level as `min,max` watts (battery protection, daytime) |\n| `LEVEL_EVENING` | `--level-evening` | `50,120` | Evening consumption injection level as `min,max` watts |\n| `LEVEL_MEDIUM` | `--level-medium` | `100,200` | Medium injection level as `min,max` watts (high price, no sun) |\n| `LEVEL_HIGH` | `--level-high` | `200,400` | High injection level as `min,max` watts (high price, sun expected) |\n\nLevel values are validated: min must be ≤ max, min ≥ 0, max ≤ `MAX_WATTS`, and `EVENING_START` ≤ nighttime start.\n\n## Run\n\nSet the required credentials in the environment, then invoke either the installed entry point (Option A/B above) or `uv run` when working from source (Option C):\n\n```bash\nexport SOLMATE_SERIAL=\"your-serial\"\nexport SOLMATE_PASSWORD=\"your-password\"\nexport OWM_API_KEY=\"your-owm-key\"\n\nsolmate                         # run optimizer (default)\nsolmate optimize --dry-run      # compute profile, don't write\nsolmate optimize --no-activate  # write but don't activate\nstatus                          # read-only status view\nstatus --graph                  # status with plotext profile graphs\nhistory                         # plot last 7 days of PV/injection/battery\n```\n\nWhen using `uvx`, prefix every command with `uvx --from solmate-optimizer@latest` (e.g. `uvx --from solmate-optimizer@latest solmate optimize --dry-run`). When working from a checkout, prefix with `uv run`.\n\n### Commands\n\n| Command | Description |\n|---------|-------------|\n| `solmate` | Run the optimizer (default, no subcommand needed) |\n| `solmate optimize` | Explicit optimizer subcommand |\n| `solmate optimize --dry-run` | Compute and display profile, but don't write or activate it |\n| `solmate optimize --no-activate` | Write the profile to SolMate, but don't activate it |\n| `status` | Show live values and injection profiles (read-only, no OWM/aWATTar needed) |\n| `status --graph` | Same, with ASCII art visualization of each profile |\n| `status --max-watts 600` | Override max watts for display (also via `MAX_WATTS` env) |\n| `history` | Plot the last 7 days of PV, injection and battery with a dual y-axis (watts left, battery % right). Fills the terminal width and ~2/3 of its height (min 30 lines) |\n| `history --days 2` | Use a different time window |\n| `history --raw` | Dump the full JSON response (with all numeric arrays) to stdout instead of plotting |\n| `history --no-plot` | Print the response structure summary instead of plotting |\n| `history --dump logs.json` | Also write the full JSON response to a file (plot is still shown) |\n| `history --from-file logs.json` | Re-plot a previously dumped response without hitting the cloud |\n\n### `history` screenshot\n\n`solmate history` renders the last 7 days of PV production, grid injection and battery state on a dual-axis ASCII chart that fills the terminal:\n\n![solmate history — 7 days of PV, injection, battery](docs/solmate-history.png)\n\n### Example output\n\n```\n======================================================================\nSolMate Optimizer — 2026-04-13 18:21 CEST\n======================================================================\naWATTar: 24 hourly prices loaded\nOpenWeatherMap: clouds 0%, 24h forecast\nSolMate: PV=16W, inject=95W, battery=97%\nPrice now: 15.1 ct/kWh (P25=12.0, P75=14.7, range: 11.5 – 16.5 ct/kWh)\nBattery: 97%\nClouds now: 0%\n\nHourly profile 'dynamic':\n  Hour  ct/kWh  Cloud   MinW   MaxW  Reason\n  ----  ------  -----  -----  -----  ----------------------------------------\n     0    12.0    97%     30     80  Night/baseload\n     1    11.9    97%      0      0  Price low (11.9 ct \u003c= P25=12.0 ct)\n     2    12.0    97%      0      0  Price low (12.0 ct \u003c= P25=12.0 ct)\n     3    11.8    98%      0      0  Price low (11.8 ct \u003c= P25=12.0 ct)\n     4    12.0    99%     30     80  Night/baseload\n     5    12.5   100%     30     80  Night/baseload\n     6    14.7   100%     30     80  Night/baseload\n     7    16.3   100%     30     80  Night/baseload\n     8    16.0   100%    100    200  Price high (16.0 ct \u003e= P75=14.7 ct), no sun expected\n     9    14.5   100%      0     50  Daytime, let PV charge\n    10    12.6    99%      0     50  Daytime, let PV charge\n    11    12.9    99%      0     50  Daytime, let PV charge\n    12    11.9    98%      0      0  Price low (11.9 ct \u003c= P25=12.0 ct)\n    13    11.5    96%      0      0  Price low (11.5 ct \u003c= P25=12.0 ct)\n    14    11.9    95%      0      0  Price low (11.9 ct \u003c= P25=12.0 ct)\n    15    12.6    97%      0     50  Daytime, let PV charge\n    16    13.5    98%      0     50  Daytime, let PV charge\n    17    14.8   100%    100    200  Price high (14.8 ct \u003e= P75=14.7 ct), no sun expected\n*   18    15.1    78%    100    200  Price high (15.1 ct \u003e= P75=14.7 ct), no sun expected\n    19    16.5    55%    100    200  Price high (16.5 ct \u003e= P75=14.7 ct), no sun expected\n    20    15.8    33%    100    200  Price high (15.8 ct \u003e= P75=14.7 ct), no sun expected\n    21    13.8    43%     50    120  Evening consumption\n    22    13.3    53%     50    120  Evening consumption\n    23    12.3    63%     30     80  Night/baseload\n\n                                Profile 'dynamic'                             \n   ┌────────────────────────────────────────────────────────┬────────────────┐\n400┤                                                        │                │\n   │                                                        │                │\n200┤                         ▖                           ▗▄▄▄▄▄▄▄▄▄▖         │\n   │                        ▞▝▖                         ▗▘  │      ▝▚▖       │\n   │                       ▞▄▚▝▚                       ▗▘▞▀▀▀▀▀▀▀▀▀▚▄▝▀▀▀▀▄▖ │\n  0┤▚▄▄▄▄▄▄▄▄▄▄▄▞▀▀▀▀▀▀▀▀▀▀▘  ▝▚▀▀▀▀▀▀▀▄▄▄▄▄▄▄▄▄▄▄▄▞▀▀▀▘▘   │        ▀▀▀▀▀▀▝▀│\n   └┬────────┬─────────┬────────┬─────────┬────────┬────────┴─────────┬──────┘\n    0        3         6        9        12       15       18        21       \n```\n\n## How injection profiles work\n\nThe SolMate stores named injection profiles, each containing two 24-element arrays:\n- `min[24]` — minimum injection per hour (fraction 0.0–1.0 of 800W max)\n- `max[24]` — maximum injection per hour\n\nIndex 0 = midnight, index 23 = 11 PM. The optimizer creates/updates a profile (name configurable via `SOLMATE_PROFILE_NAME`, default `\"dynamic\"`) and activates it, leaving your existing profiles (\"Sonnig\", \"Schlechtwetter\", etc.) untouched. You can switch back to any profile via the EET app at any time.\n\n## Deployment\n\nSee [DEPLOYMENT.md](DEPLOYMENT.md) for instructions on running this on GCP Cloud Run with Cloud Scheduler (hourly cron).\n\n## Dependencies\n\n- [solmate-sdk](https://github.com/eet-energy/solmate-sdk) — EET SolMate WebSocket API client\n- [httpx](https://www.python-httpx.org/) — HTTP client for aWATTar and OpenWeatherMap\n\n## License\n\nApache 2.0 — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fharaldschilly%2Fsolmate-optimizer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fharaldschilly%2Fsolmate-optimizer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fharaldschilly%2Fsolmate-optimizer/lists"}