Skip to content

Commit

Permalink
Adopt givenergy_modbus pre-release library for increased compatibilit…
Browse files Browse the repository at this point in the history
…y and future proofing (#74)
  • Loading branch information
cdpuk authored Jan 29, 2024
1 parent 4d2771e commit 896345c
Show file tree
Hide file tree
Showing 51 changed files with 3,625 additions and 607 deletions.
4 changes: 4 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ about: Suggest an idea for this project
---

**Is your feature request related to a problem? Please describe.**

A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**

A clear and concise description of what you want to happen.

**Describe alternatives you've considered**

A clear and concise description of any alternative solutions or features you've considered.

**Additional context**

Add any other context or screenshots about the feature request here.
28 changes: 15 additions & 13 deletions .github/ISSUE_TEMPLATE/issue.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,32 @@ Issues not containing the minimum requirements will be closed:
- Issues without a description (using the header is not good enough) will be closed.
- Issues without debug logging will be closed.
- Issues without configuration will be closed
-->

## Version of the custom_component
<!-- If you are not using the newest version, download and try that before opening an issue
If you are unsure about the version check the const.py file.
## Hardware and versions
<!-- If you are not using the newest version, download and try that before opening an issue.
Check the version you're running under the HACS UI.
Inverter and battery firmware versions can be found via the HA Devices page, or via the GivEnergy portal.
-->

## Configuration

```yaml

Add your logs here.

```
* Inverter model & generation:
* Inverter firmware version:
* Battery firmware version:
* Home Assistant integration version:

## Describe the bug
A clear and concise description of what the bug is.

A clear and concise description of what the bug is.

## Debug log

<!-- To enable debug logs check this https://www.home-assistant.io/components/logger/ -->
<!--
There are two ways to capture debug logs:
1. Via the UI, see https://www.home-assistant.io/docs/configuration/troubleshooting/#enabling-debug-logging
2. Via the configuration file, see https://www.home-assistant.io/components/logger/
-->

```text
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pull.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ jobs:
- name: Setup Python
uses: "actions/setup-python@v4"
with:
python-version: "3.10"
python-version: "3.11"
- name: Install requirements
run: python3 -m pip install -r requirements_test.txt
run: python3 -m pip install -r requirements.txt -r requirements_test.txt
- name: Run tests
run: |
pytest \
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/push.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ jobs:
- name: Setup Python
uses: "actions/setup-python@v4"
with:
python-version: "3.10"
python-version: "3.11"
- name: Install requirements
run: python3 -m pip install -r requirements_test.txt
run: python3 -m pip install -r requirements.txt -r requirements_test.txt
- name: Run tests
run: |
pytest \
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
__pycache__
config/
pythonenv*
venv
.venv
Expand Down
25 changes: 13 additions & 12 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,40 +1,41 @@
---
exclude: '^custom_components/givenergy_local/givenergy_modbus/.*'
repos:
- repo: https://github.com/asottile/pyupgrade
rev: v3.3.1
rev: v3.15.0
hooks:
- id: pyupgrade
args: [--py310-plus]
args: [--py311-plus]
stages: [manual]
- repo: https://github.com/psf/black
rev: 23.11.0
rev: 24.1.1
hooks:
- id: black
args:
- --quiet
files: ^((custom_components|tests)/.+)?[^/]+\.py$
- repo: https://github.com/codespell-project/codespell
rev: v2.2.2
rev: v2.2.6
hooks:
- id: codespell
args:
- --ignore-words-list=hass,alot,datas,dof,dur,ether,farenheit,hist,iff,iif,ines,ist,lightsensor,mut,nd,pres,referer,rime,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing,iam,incomfort,ba,haa,pullrequests
- --ignore-words-list=hass
- --skip="./.*,*.csv,*.json"
- --quiet-level=2
exclude_types: [csv, json]
- repo: https://github.com/PyCQA/flake8
rev: 6.1.0
rev: 7.0.0
hooks:
- id: flake8
additional_dependencies:
- flake8-docstrings==1.7.0
- pydocstyle==6.3.0
- flake8-comprehensions==3.14.0
- flake8-noqa==1.3.2
- flake8-noqa==1.4.0
- mccabe==0.7.0
files: ^(custom_components|tests)/.+\.py$
- repo: https://github.com/PyCQA/bandit
rev: 1.7.5
rev: 1.7.7
hooks:
- id: bandit
args:
Expand All @@ -43,7 +44,7 @@ repos:
- --configfile=tests/bandit.yaml
files: ^(custom_components|tests)/.+\.py$
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
rev: 5.13.2
hooks:
- id: isort
- repo: https://github.com/pre-commit/pre-commit-hooks
Expand All @@ -54,11 +55,11 @@ repos:
- id: check-json
exclude: (.vscode|.devcontainer)
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.32.0
rev: v1.33.0
hooks:
- id: yamllint
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.0.3
rev: v3.1.0
hooks:
- id: prettier
stages: [manual]
Expand All @@ -76,7 +77,7 @@ repos:
- --keep-updates
files: ^(custom_components|tests|script)/.+\.py$
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.6.1
rev: v1.8.0
hooks:
- id: mypy
args:
Expand Down
23 changes: 14 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ This custom component provides local access to GivEnergy inverters via Modbus. T

While the risk of something going wrong is low, bear in mind the use of this integration is entirely at your own risk.

## Device Support

Modbus support is provided by the [`givenergy-modbus`][givenergy-modbus] library. While this works well for the vast majority of GivEnergy inverters and batteries, inevitably there will be edge cases and new kit that requires updates to either this integration or the underlying library. See the [Limitations](#limitations) section.

## Installation

This integration is delivered as a HACS custom repository.
Expand All @@ -27,7 +23,7 @@ You need to know the hostname or IP address of your inverter, which you can norm
* Go to **Configuration** > **Devices & Services** > **Add Integration**, then find **GivEnergy Local** in the list.
* Enter the inverter address when prompted.

If your Home Assistant instance is in a different VLAN or network than inverter, ensure it can reach the inverter via TCP port 8899.
Your Home Assistant instance must be able to establish a TCP connection to your inverter on port 8899.

## Documentation

Expand All @@ -37,13 +33,21 @@ If your Home Assistant instance is in a different VLAN or network than inverter,

## Limitations

This integration uses the latest public release of the `givenergy_modbus` library. Unfortunately, this hasn't kept pace with GivEnergy product updates, so may be incompatible with communication methods and features found in newer systems.
GivEnergy are continually releasing new equipment and firmware. There is a risk that new devices won't work, or that firmware updates may suddenly prevent the integration being able to talk to your inverter.

If this happens, bear in mind the maintainers of this integration do not have access to your equipment, so debugging such issues can be challenging. Raise an issue with as much detail as possible to make it easier to help you.

## Acknowledgements

### givenergy_modbus

The Modbus protocol implementation for GivEnergy systems was originally created by the [`givenergy-modbus`][givenergy-modbus] project. Huge thanks goes to the author and contributors for unpicking the non-standard low level technical details of the protocol.

The current `givenergy_modbus` uses a communication method that is known to be slightly unreliable. Your Home Assistant logs are likely to be filled with a gradual stream of errors from the library. However, due to the fairly high update rate, the odd missed update is rarely an issue.
Since the project was paused, the current implementation of this integration uses an embedded forked version of the library so that further bugfixes and updates can be made without an external dependency.

Other community projects such as GivTCP have chosen to copy & modify the `givenergy_modbus` library to resolve some of these issues, however those modifications have not been released as a standalone project that can easily be reused by this integration.
### GivTCP

Don't be offended if you bug report is closed if it relates to the above limitations.
[GivTCP][givtcp] is an alternative to this integration, which runs as a Home Assistant add-on, and therefore may not be suitable for all installations. However, it sees frequent updates and several compatibility updates made to that project have been reused here.

## Contributing

Expand All @@ -57,5 +61,6 @@ If you want to contribute to this please read the [Contribution Guidelines](CONT
[releases-shield]: https://img.shields.io/github/release/cdpuk/givenergy-local.svg?style=for-the-badge
[releases]: https://github.com/cdpuk/givenergy-local/releases
[givenergy-modbus]: https://github.com/dewet22/givenergy-modbus
[givtcp]: https://github.com/britkat1980/giv_tcp
[hacs-download]: https://hacs.xyz/docs/setup/download
[hacs-custom]: https://hacs.xyz/docs/faq/custom_repositories
30 changes: 4 additions & 26 deletions custom_components/givenergy_local/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"""The GivEnergy integration."""

from __future__ import annotations

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady

from .const import CONF_HOST, CONF_NUM_BATTERIES, DOMAIN, LOGGER
from .const import CONF_HOST, DOMAIN
from .coordinator import GivEnergyUpdateCoordinator
from .services import async_setup_services, async_unload_services

Expand All @@ -21,13 +21,9 @@
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up GivEnergy from a config entry."""
host = entry.data.get(CONF_HOST)
num_batteries = entry.data.get(CONF_NUM_BATTERIES)

coordinator = GivEnergyUpdateCoordinator(hass, host, num_batteries)
await coordinator.async_refresh()

if not coordinator.last_update_success:
raise ConfigEntryNotReady
coordinator = GivEnergyUpdateCoordinator(hass, host)
await coordinator.async_config_entry_first_refresh()

hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
await hass.config_entries.async_forward_entry_setups(entry, _PLATFORMS)
Expand All @@ -54,21 +50,3 @@ async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Reload config entry."""
await async_unload_entry(hass, entry)
await async_setup_entry(hass, entry)


async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Migrates old config versions to the latest."""

LOGGER.debug("Migrating from version %s", entry.version)

if entry.version == 1:
new = {**entry.data}
new[CONF_NUM_BATTERIES] = 0
entry.version = 2
hass.config_entries.async_update_entry(entry, data=new)

LOGGER.info("Migration to version %s successful", entry.version)
return True
else:
LOGGER.error("Existing schema version %s is not supported", entry.version)
return False
22 changes: 12 additions & 10 deletions custom_components/givenergy_local/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Binary sensor platform."""

from __future__ import annotations

from datetime import datetime, time, timedelta
Expand All @@ -18,6 +19,7 @@
from .const import DOMAIN, LOGGER, Icon
from .coordinator import GivEnergyUpdateCoordinator
from .entity import InverterEntity
from .givenergy_modbus.model import TimeSlot

_CHARGE_SLOT_BINARY_SENSORS = [
BinarySensorEntityDescription(
Expand Down Expand Up @@ -72,9 +74,7 @@ def __init__(
) -> None:
"""Initialize a sensor based on an entity description."""
super().__init__(coordinator, config_entry)
self._attr_unique_id = (
f"{self.data.inverter_serial_number}_{entity_description.key}"
)
self._attr_unique_id = f"{self.data.serial_number}_{entity_description.key}"
self.entity_description = entity_description

async def async_added_to_hass(self) -> None:
Expand Down Expand Up @@ -110,8 +110,8 @@ def _schedule_next_update(self) -> None:

# Get slot details
current_time = now.time()
start = self.slot[0]
end = self.slot[1]
start = self.slot.start
end = self.slot.end

# We don't need to be notified about entering/leaving an undefined slot
if start == end:
Expand Down Expand Up @@ -146,20 +146,22 @@ def _handle_coordinator_update(self) -> None:
self.async_write_ha_state()

@property
def slot(self) -> tuple[time, time]:
def slot(self) -> TimeSlot:
"""Get the slot definition."""
return self.data.dict().get(self.entity_description.key) # type: ignore
slot: TimeSlot = self.data.dict().get(self.entity_description.key)
return slot

@property
def is_on(self) -> bool | None:
"""Determine whether we're currently within the slot."""
now: time = dt.now().time()
return self.slot[0] <= now < self.slot[1]
is_on: bool = self.slot.start <= now < self.slot.end
return is_on

@property
def extra_state_attributes(self) -> Mapping[str, Any] | None:
"""Attach charge slot configuration."""
return {
"start": self.slot[0].strftime("%H:%M"),
"end": self.slot[1].strftime("%H:%M"),
"start": self.slot.start.strftime("%H:%M"),
"end": self.slot.end.strftime("%H:%M"),
}
Loading

0 comments on commit 896345c

Please sign in to comment.