-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
#10 multiple inverters+async
- Loading branch information
Showing
17 changed files
with
533 additions
and
187 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
*.pyc | ||
|
||
.vscode | ||
example.json | ||
|
||
.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,91 +1,110 @@ | ||
# Goodwe SEMS integration for Home Assistant | ||
# GoodWe SEMS API integration for Home Assistant | ||
|
||
## Easiest install method via HACS | ||
[![Paypal-shield]](https://www.paypal.com/donate?business=9NWEEX4P6998J¤cy_code=EUR) | ||
<a href="https://www.buymeacoffee.com/TimSoethout" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="20"></a> | ||
|
||
Integration for Home Assistant that retrieves PV data from GoodWe SEMS API. | ||
|
||
{% if prerelease %} | ||
### NB!: This is a Beta version! | ||
{% endif %} | ||
|
||
## Setup | ||
|
||
### Easiest install method via HACS | ||
|
||
[![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg?style=for-the-badge)](https://github.com/custom-components/hacs) | ||
|
||
The repository folder structure is compatible with [HACS](https://hacs.xyz) and is included by default in HACS. | ||
|
||
Install HACS via: https://hacs.xyz/docs/installation/manual. | ||
Then search for "SEMS" in the Integrations tab (under Community). | ||
|
||
## Setup | ||
### Manual Setup | ||
|
||
Crude sensor for Home Assistant that scrapes from GoodWe SEMS portal. Copy all the files in `custom_components/sems/` to `custom_components/sems/` your Home Assistant config dir. | ||
|
||
Crude sensor for Home Assistant that scrapes from GoodWe SEMS portal. Copy all the files in `custom_components/sems/` in your Home Assistant config dir: | ||
- `sensor.py` | ||
- `__init__.py` | ||
- `manifest.json` | ||
## Configure integration | ||
|
||
And update configuration. The ID of your Power Station can be retrieved by logging in to the SEMS Portal with your credentials: | ||
The required ID of your Power Station can be retrieved by logging in to the SEMS Portal with your credentials: | ||
https://www.semsportal.com | ||
|
||
After login you'll see the ID in your URL. E.g.: | ||
https://www.semsportal.com/powerstation/powerstatussnmin/12345678-1234-1234-1234-123456789012 | ||
After login you'll see the ID in your URL, e.g.: | ||
https://semsportal.com/PowerStation/PowerStatusSnMin/12345678-1234-1234-1234-123456789012 | ||
|
||
In this example the ID of the Power Station is: 12345678-1234-1234-1234-123456789012 | ||
|
||
Example entry in `configuration.yaml`: | ||
In the home assistant GUI, go to `Configuration` > `Integrations` and click the `Add Integration` button. Search for `GoodWe SEMS API`. | ||
|
||
``` | ||
sensor: | ||
- platform: sems | ||
username: 'XXXX' | ||
password: 'XXXX' | ||
station_id : '12345678-1234-1234-1234-123456789012' | ||
scan_interval: 60 | ||
# Optional/example | ||
# A template to ease access to the data as "sensor.pv_outputpower" etc. | ||
Fill in the required configuration and it should find your inverters. | ||
|
||
Note that changed to `configuration.yaml` are no longer necessary and can be removed. | ||
|
||
|
||
### Extra (optional) templates to easy access data as sensors | ||
Replace `$NAME` with your inverter name. | ||
```yaml | ||
- platform: template | ||
sensors: | ||
pv_outputpower: | ||
value_template: '{{ states.sensor.sems_portal.attributes.outputpower }}' | ||
value_template: '{{ states.sensor.inverter_$NAME.attributes.outputpower }}' | ||
unit_of_measurement: 'W' | ||
friendly_name: "PV Power output" | ||
pv_temperature: | ||
value_template: '{{ states.sensor.sems_portal.attributes.tempperature }}' | ||
value_template: '{{ states.sensor.inverter_$NAME.attributes.tempperature }}' | ||
unit_of_measurement: 'C' | ||
friendly_name: "PV Temperature" | ||
pv_eday: | ||
value_template: '{{ states.sensor.sems_portal.attributes.eday }}' | ||
value_template: '{{ states.sensor.inverter_$NAME.attributes.eday }}' | ||
unit_of_measurement: 'kWh' | ||
friendly_name: "PV energy day" | ||
pv_etotal: | ||
value_template: '{{ states.sensor.sems_portal.attributes.etotal }}' | ||
value_template: '{{ states.sensor.inverter_$NAME.attributes.etotal }}' | ||
unit_of_measurement: 'kWh' | ||
friendly_name: "PV energy total" | ||
pv_iday: | ||
value_template: '{{ states.sensor.sems_portal.attributes.iday }}' | ||
value_template: '{{ states.sensor.inverter_$NAME.attributes.iday }}' | ||
unit_of_measurement: '€' | ||
friendly_name: "PV income day" | ||
pv_itotal: | ||
value_template: '{{ states.sensor.sems_portal.attributes.itotal }}' | ||
value_template: '{{ states.sensor.inverter_$NAME.attributes.itotal }}' | ||
unit_of_measurement: '€' | ||
friendly_name: "PV income total" | ||
pv_excess: | ||
value_template: '{{ states.sensor.sems_portal.attributes.pmeter }}' | ||
value_template: '{{ states.sensor.inverter_$NAME.attributes.pmeter }}' | ||
unit_of_measurement: 'W' | ||
friendly_name: "PV spare" | ||
# battery soc | ||
pv_soc: | ||
value_template: '{{ states.sensor.sems_portal.attributes.soc }}' | ||
value_template: '{{ states.sensor.inverter_$NAME.attributes.soc }}' | ||
unit_of_measurement: '%' | ||
friendly_name: "Battery power" | ||
# PV output power only | ||
pv_outputpower: | ||
value_template: '{{ states.sensor.sems_portal.attributes.outputpower }}' | ||
unit_of_measurement: 'W' | ||
friendly_name: "PV Power output" | ||
``` | ||
|
||
Use the credentials you use to login to https://www.semsportal.com/. | ||
## Screenies | ||
|
||
`scan_interval` controls how often the sensor updates/scrapes. By default this seems to be every 60 seconds. | ||
![Detail window](images/sems-details.webp) | ||
|
||
## Screenies | ||
![Detail window](images/search-integration.webp) | ||
|
||
![Detail window](images/integration-flow.webp) | ||
|
||
## Debug info | ||
|
||
Add the last line in `configuration.yaml` in the relevant part of `logger`: | ||
|
||
```yaml | ||
logger: | ||
default: info | ||
logs: | ||
custom_components.sems: debug | ||
``` | ||
|
||
![Overview icon](images/sems-icon.png) | ||
## Notes | ||
|
||
![Detail window](images/sems-details.png) | ||
* Sometimes the SEMS API is a bit slow, so time-out messages may occur in the log as `[ERROR]`. The component should continue to work normally and try fetch again the next minute. | ||
|
||
## Credits | ||
|
||
Reuses code from https://github.com/Sprk-nl/goodwe_sems_portal_scraper. | ||
Inspired by https://github.com/Sprk-nl/goodwe_sems_portal_scraper and https://github.com/bouwew/sems2mqtt . | ||
Also supported by generous contributions by various helpful community members. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,54 @@ | ||
"""The sems integration.""" | ||
from __future__ import annotations | ||
|
||
import asyncio | ||
|
||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.core import HomeAssistant | ||
|
||
from .const import DOMAIN | ||
from .sems_api import SemsApi | ||
|
||
# TODO List the platforms that you want to support. | ||
# For your initial PR, limit it to 1 platform. | ||
PLATFORMS = ["sensor"] | ||
|
||
|
||
async def async_setup(hass: HomeAssistant, config: dict): | ||
"""Set up the sems component.""" | ||
# Ensure our name space for storing objects is a known type. A dict is | ||
# common/preferred as it allows a separate instance of your class for each | ||
# instance that has been created in the UI. | ||
hass.data.setdefault(DOMAIN, {}) | ||
|
||
return True | ||
|
||
|
||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
"""Set up sems from a config entry.""" | ||
hass.data[DOMAIN][entry.entry_id] = SemsApi( | ||
hass, entry.data["username"], entry.data["password"] | ||
) | ||
|
||
for platform in PLATFORMS: | ||
hass.async_create_task( | ||
hass.config_entries.async_forward_entry_setup(entry, platform) | ||
) | ||
|
||
return True | ||
|
||
|
||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
"""Unload a config entry.""" | ||
unload_ok = all( | ||
await asyncio.gather( | ||
*[ | ||
hass.config_entries.async_forward_entry_unload(entry, platform) | ||
for platform in PLATFORMS | ||
] | ||
) | ||
) | ||
if unload_ok: | ||
hass.data[DOMAIN].pop(entry.entry_id) | ||
|
||
return unload_ok |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
"""Config flow for sems integration.""" | ||
from __future__ import annotations | ||
|
||
import logging | ||
from typing import Any | ||
|
||
from homeassistant import config_entries | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.exceptions import HomeAssistantError | ||
|
||
from .const import DOMAIN, SEMS_CONFIG_SCHEMA | ||
from .sems_api import SemsApi | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]: | ||
"""Validate the user input allows us to connect. | ||
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user. | ||
""" | ||
# TODO validate the data can be used to set up a connection. | ||
|
||
# If your PyPI package is not built with async, pass your methods | ||
# to the executor: | ||
# await hass.async_add_executor_job( | ||
# your_validate_func, data["username"], data["password"] | ||
# ) | ||
|
||
api = SemsApi(hass, data["username"], data["password"]) | ||
|
||
authenticated = await hass.async_add_executor_job(api.test_authentication) | ||
if not authenticated: | ||
raise InvalidAuth | ||
|
||
# If you cannot connect: | ||
# throw CannotConnect | ||
# If the authentication is wrong: | ||
# InvalidAuth | ||
|
||
# Return info that you want to store in the config entry. | ||
return { | ||
"powerstation_id": data["powerstation_id"], | ||
"username": data["username"], | ||
"password": data["password"], | ||
} | ||
|
||
|
||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): | ||
"""Handle a config flow for sems.""" | ||
|
||
VERSION = 1 | ||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL | ||
|
||
async def async_step_user( | ||
self, user_input: dict[str, Any] | None = None | ||
) -> dict[str, Any]: | ||
"""Handle the initial step.""" | ||
if user_input is None: | ||
return self.async_show_form(step_id="user", data_schema=SEMS_CONFIG_SCHEMA) | ||
|
||
errors = {} | ||
|
||
try: | ||
info = await validate_input(self.hass, user_input) | ||
except CannotConnect: | ||
errors["base"] = "cannot_connect" | ||
except InvalidAuth: | ||
errors["base"] = "invalid_auth" | ||
except Exception: # pylint: disable=broad-except | ||
_LOGGER.exception("Unexpected exception") | ||
errors["base"] = "unknown" | ||
else: | ||
return self.async_create_entry(title=info["powerstation_id"], data=user_input) | ||
|
||
return self.async_show_form( | ||
step_id="user", data_schema=SEMS_CONFIG_SCHEMA, errors=errors | ||
) | ||
|
||
|
||
class CannotConnect(HomeAssistantError): | ||
"""Error to indicate we cannot connect.""" | ||
|
||
|
||
class InvalidAuth(HomeAssistantError): | ||
"""Error to indicate there is invalid auth.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
"""Constants for the sems integration.""" | ||
|
||
DOMAIN = "sems" | ||
|
||
import voluptuous as vol | ||
from homeassistant.const import ( | ||
CONF_PASSWORD, | ||
CONF_USERNAME, | ||
DEVICE_CLASS_POWER, | ||
POWER_WATT, | ||
) | ||
|
||
CONF_STATION_ID = "powerstation_id" | ||
|
||
# Validation of the user's configuration | ||
SEMS_CONFIG_SCHEMA = vol.Schema( | ||
{ | ||
vol.Required(CONF_USERNAME): str, | ||
vol.Required(CONF_PASSWORD): str, | ||
vol.Required(CONF_STATION_ID): str, | ||
} | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,11 @@ | ||
{ | ||
"domain": "sems", | ||
"name": "Goodwe SEMS API", | ||
"documentation": "https://github.com/TimSoethout/goodwe-sems-home-assistant", | ||
"dependencies": [], | ||
"codeowners": ["@TimSoethout"], | ||
"domain": "sems", | ||
"name": "GoodWe SEMS API", | ||
"config_flow": true, | ||
"documentation": "https://github.com/TimSoethout/goodwe-sems-home-assistant", | ||
"issue_tracker": "https://github.com/TimSoethout/goodwe-sems-home-assistant/issues", | ||
"requirements": [], | ||
"version": "1.0.1" | ||
} | ||
"dependencies": [], | ||
"codeowners": ["@TimSoethout"], | ||
"version": "3.1.0-beta" | ||
} |
Oops, something went wrong.