Skip to content

Commit

Permalink
Merge pull request #17 from mchwalisz/energy-support
Browse files Browse the repository at this point in the history
Added energy support
  • Loading branch information
mchwalisz authored Jan 10, 2022
2 parents b15843d + 0d22240 commit 3bedfd8
Show file tree
Hide file tree
Showing 10 changed files with 220 additions and 122 deletions.
18 changes: 17 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@ repos:
rev: 21.12b0
hooks:
- id: black
args:
- --line-length=100
# these folders wont be formatted by black
- --exclude="""\.git |
\.__pycache__|
\.hg|
\.mypy_cache|
\.tox|
\.venv|
_build|
buck-out|
build|
dist"""
- repo: https://github.com/PyCQA/isort
rev: '5.10.1'
hooks:
Expand All @@ -18,4 +31,7 @@ repos:
require_serial: true
language: python
types: [python]
args: [--multi-line=3, --line-length=88]
args:
- --multi-line=3
- --line-length=100
- --profile=black
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,15 @@ Add custom integration using the web interface and follow instruction on screen.
- Go to `Configuration -> Integrations` and add "Senec" integration
- Provide name for the device and it's address (hostname or IP)
- Provide area where the battery is located

## Home Assistant Energy Dashboard

This integration supports Home Assistant's [Energy Management](https://www.home-assistant.io/docs/energy/)

Example setup:

![Energy Dashboard Setup](images/energy_dashboard.png)

Resulting energy distribution card:

![Energy Distribution](images/energy_distribution.png)
56 changes: 27 additions & 29 deletions custom_components/senec/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from pysenec import Senec

Expand Down Expand Up @@ -43,9 +43,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
hass.data[DOMAIN][entry.entry_id] = coordinator

for platform in PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, platform)
)
hass.async_create_task(hass.config_entries.async_forward_entry_setup(entry, platform))

return True

Expand All @@ -58,10 +56,9 @@ def __init__(self, hass, session, entry):
self._host = entry.data[CONF_HOST]
self.senec = Senec(self._host, websession=session)
self.name = entry.title
self._entry = entry

super().__init__(
hass, _LOGGER, name=DOMAIN, update_interval=timedelta(seconds=60)
)
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL)

async def _async_update_data(self):
"""Update data via library."""
Expand All @@ -88,38 +85,40 @@ async def async_unload_entry(hass, entry):
class SenecEntity(Entity):
"""Defines a base Senec entity."""

def __init__(self, coordinator: SenecDataUpdateCoordinator, sensor: str) -> None:
_attr_should_poll = False

def __init__(
self, coordinator: SenecDataUpdateCoordinator, description: EntityDescription
) -> None:
"""Initialize the Atag entity."""
self.coordinator = coordinator
self._sensor = sensor
self._name = DOMAIN.title()
self._name = coordinator._entry.title
self._state = None

self.entity_description = description

@property
def device_info(self) -> dict:
"""Return info for device registry."""
device = self._name
return {
"identifiers": {(DOMAIN, device)},
"name": "Senec Home Battery ",
"name": "Senec Home Battery",
"model": "Senec",
"sw_version": None,
"manufacturer": "Senec",
}

@property
def name(self) -> str:
"""Return the name of the entity."""
return self._name

@property
def should_poll(self) -> bool:
"""Return the polling requirement of the entity."""
return False

# @property
# def unit_of_measurement(self):
# """Return the unit of measurement of this entity, if any."""
# return self.coordinator.atag.climate.temp_unit
def state(self):
"""Return the current state."""
sensor = self.entity_description.key
value = getattr(self.coordinator.senec, sensor)
try:
rounded_value = round(float(value), 2)
return rounded_value
except ValueError:
return value

@property
def available(self):
Expand All @@ -129,14 +128,13 @@ def available(self):
@property
def unique_id(self):
"""Return a unique ID to use for this entity."""
return f"{self._name}_{self._sensor}"
sensor = self.entity_description.key
return f"{self._name}_{sensor}"

async def async_added_to_hass(self):
"""Connect to dispatcher listening for entity data notifications."""
self.async_on_remove(
self.coordinator.async_add_listener(self.async_write_ha_state)
)
self.async_on_remove(self.coordinator.async_add_listener(self.async_write_ha_state))

async def async_update(self):
"""Update Atag entity."""
"""Update entity."""
await self.coordinator.async_request_refresh()
16 changes: 4 additions & 12 deletions custom_components/senec/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@
@callback
def senec_entries(hass: HomeAssistant):
"""Return the hosts already configured."""
return {
entry.data[CONF_HOST] for entry in hass.config_entries.async_entries(DOMAIN)
}
return {entry.data[CONF_HOST] for entry in hass.config_entries.async_entries(DOMAIN)}


class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
Expand Down Expand Up @@ -63,9 +61,7 @@ async def async_step_user(self, user_input=None):
self._errors[CONF_HOST] = "already_configured"
else:
if await self._test_connection(host_entry):
return self.async_create_entry(
title=name, data={CONF_HOST: host_entry}
)
return self.async_create_entry(title=name, data={CONF_HOST: host_entry})
else:
user_input = {}
user_input[CONF_NAME] = DEFAULT_NAME
Expand All @@ -75,12 +71,8 @@ async def async_step_user(self, user_input=None):
step_id="user",
data_schema=vol.Schema(
{
vol.Required(
CONF_NAME, default=user_input.get(CONF_NAME, DEFAULT_NAME)
): str,
vol.Required(
CONF_HOST, default=user_input.get(CONF_HOST, DEFAULT_HOST)
): str,
vol.Required(CONF_NAME, default=user_input.get(CONF_NAME, DEFAULT_NAME)): str,
vol.Required(CONF_HOST, default=user_input.get(CONF_HOST, DEFAULT_HOST)): str,
}
),
errors=self._errors,
Expand Down
148 changes: 135 additions & 13 deletions custom_components/senec/const.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,151 @@
"""Constants for the Senec integration."""
from collections import namedtuple
from datetime import timedelta
from typing import Final

from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import ENERGY_KILO_WATT_HOUR, PERCENTAGE, POWER_WATT

DOMAIN = "senec"


"""Default config for Senec."""
DEFAULT_HOST = "senec"
DEFAULT_HOST = "Senec"
DEFAULT_NAME = "senec"

"""Fixed constants."""
SCAN_INTERVAL = timedelta(seconds=60)

"""Supported sensor types."""

SENSOR_TYPES = {
"system_state": ["", "mdi:solar-power"],
"solar_generated_power": [POWER_WATT, "mdi:solar-power"],
"house_power": [POWER_WATT, "mdi:home-import-outline"],
"battery_state_power": [POWER_WATT, "mdi:ev-station"],
"battery_charge_power": [POWER_WATT, "mdi:ev-station"],
"battery_discharge_power": [POWER_WATT, "mdi:ev-station"],
"battery_charge_percent": [PERCENTAGE, "mdi:ev-station"],
"grid_state_power": [POWER_WATT, "mdi:transmission-tower"],
"grid_imported_power": [POWER_WATT, "mdi:transmission-tower"],
"grid_exported_power": [POWER_WATT, "mdi:transmission-tower"],
}
SENSOR_TYPES = [
SensorEntityDescription(
key="system_state",
name="System State",
icon="mdi:solar-power",
),
SensorEntityDescription(
key="solar_generated_power",
name="Solar Generated Power",
native_unit_of_measurement=POWER_WATT,
icon="mdi:solar-power",
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key="house_power",
name="House Power",
native_unit_of_measurement=POWER_WATT,
icon="mdi:home-import-outline",
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key="battery_state_power",
name="Battery State Power",
native_unit_of_measurement=POWER_WATT,
icon="mdi:home-battery",
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key="battery_charge_power",
name="Battery Charge Power",
native_unit_of_measurement=POWER_WATT,
icon="mdi:home-battery",
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key="battery_discharge_power",
name="Battery Discharge Power",
native_unit_of_measurement=POWER_WATT,
icon="mdi:home-battery-outline",
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key="battery_charge_percent",
name="Battery Charge Percent",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:home-battery",
# device_class=SensorDeviceClass.BATTERY,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key="grid_state_power",
name="Grid State Power",
native_unit_of_measurement=POWER_WATT,
icon="mdi:transmission-tower",
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key="grid_imported_power",
name="Grid Imported Power",
native_unit_of_measurement=POWER_WATT,
icon="mdi:transmission-tower-import",
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key="grid_exported_power",
name="Grid Exported Power",
native_unit_of_measurement=POWER_WATT,
icon="mdi:transmission-tower-export",
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key="house_total_consumption",
name="House consumed",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
icon="mdi:home-import-outline",
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
SensorEntityDescription(
key="solar_total_generated",
name="Solar generated",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
icon="mdi:solar-power",
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
SensorEntityDescription(
key="battery_total_charged",
name="Battery charged",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
icon="mdi:home-battery",
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
SensorEntityDescription(
key="battery_total_discharged",
name="Battery discharged",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
icon="mdi:home-battery-outline",
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
SensorEntityDescription(
key="grid_total_import",
name="Grid Imported",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
icon="mdi:transmission-tower-import",
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
SensorEntityDescription(
key="grid_total_export",
name="Grid Exported",
native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
icon="mdi:transmission-tower-export",
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
]
4 changes: 2 additions & 2 deletions custom_components/senec/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
"documentation": "https://github.com/mchwalisz/home-assistant-senec/blob/master/README.md",
"issue_tracker": "https://github.com/mchwalisz/home-assistant-senec/issues",
"requirements": [
"pysenec==0.1.0"
"pysenec==0.2.0"
],
"dependencies": [],
"codeowners": [
"@mchwalisz"
],
"iot_class": "local_polling",
"version": "1.0.1"
"version": "2.0.0"
}
Loading

0 comments on commit 3bedfd8

Please sign in to comment.