Skip to content

Commit

Permalink
New features (#17)
Browse files Browse the repository at this point in the history
* New features

Support for multiple instances;
Possibility to select which sensor to integrate;
Changed "iot_class" to "cloud_polling";
Added binary sensors for power flows (Solar to Grid, Solar to House, Solar to Battery, etc);
Removed some unused sensors (pMaxVenduta, pMaxPannelli, pMaxBatteria, pMaxComprata, pGrid);
Entities names, icons and various attributes modified:
"State of battery" renamed to "Batery level" and limit its value to a maximum of 100;
"Date" renamed to "Last update" and addes various attributes;
"Instant grid power" uses the value from "pRete" not "pGrid";
"Daily battery energy" renamed to "Daily self consumed energy";
added "Daily consumed energy";
added "Self sufficiency"

* minor updates

Import/export icons swap;
Device name changed to username (was "Fotovoltaico"+username).
  • Loading branch information
mauriziosacca authored Dec 3, 2023
1 parent 322e5ad commit bf83e7a
Show file tree
Hide file tree
Showing 7 changed files with 473 additions and 213 deletions.
7 changes: 6 additions & 1 deletion custom_components/atonstorage/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
CONF_PASSWORD,
CONF_SCAN_INTERVAL,
CONF_USERNAME,
CONF_MONITORED_VARIABLES,
Platform,
)
from homeassistant.core import HomeAssistant
Expand All @@ -20,13 +21,14 @@
from homeassistant.helpers.debounce import Debouncer
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import DEFAULT_SCAN_INTERVAL, DOMAIN
from .const import DEFAULT_SCAN_INTERVAL, DOMAIN, AVAILABLE_SENSORS
from .controller import Controller as AtonStorage

_LOGGER = logging.getLogger(__name__)

PLATFORMS = [
Platform.SENSOR,
Platform.BINARY_SENSOR,
]
TIMEOUT = 10

Expand All @@ -46,6 +48,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
password = entry.data.get(CONF_PASSWORD)
serial_number = entry.data.get(CONF_DEVICE_ID)
scan_interval = entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
sensors_selected = entry.data.get(CONF_MONITORED_VARIABLES, AVAILABLE_SENSORS)

try:
opts = {
Expand All @@ -61,6 +64,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
hass.data[DOMAIN][entry.entry_id] = {
"coordinator": coordinator,
"controller": controller,
"username": user,
"sensors_selected": sensors_selected,
}

except Exception as exc:
Expand Down
151 changes: 151 additions & 0 deletions custom_components/atonstorage/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
"""AtonStorage integration."""
import logging
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any

from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import DOMAIN
from .controller import Controller as AtonStorage

_LOGGER = logging.getLogger(__name__)


@dataclass
class AtonStorageBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Class to describe a AtonStorage sensor entity."""

value_calc_function: Callable[[AtonStorage], Any] = None


INVERTER_BINARY_SENSOR_DESCRIPTIONS = (
AtonStorageBinarySensorEntityDescription(
key="grid_to_house",
translation_key="grid_to_house",
name="Grid to House",
value_calc_function=lambda controller: controller.grid_to_house
),
AtonStorageBinarySensorEntityDescription(
key="solar_to_battery",
translation_key="solar_to_battery",
name="Solar to Battery",
value_calc_function=lambda controller: controller.solar_to_battery
),
AtonStorageBinarySensorEntityDescription(
key="solar_to_grid",
translation_key="solar_to_grid",
name="Solar to Grid",
value_calc_function=lambda controller: controller.solar_to_grid
),
AtonStorageBinarySensorEntityDescription(
key="battery_to_house",
translation_key="battery_to_house",
name="Battery to House",
value_calc_function=lambda controller: controller.battery_to_house
),
AtonStorageBinarySensorEntityDescription(
key="solar_to_house",
translation_key="solar_to_house",
name="Solar to House",
value_calc_function=lambda controller: controller.solar_to_house
),
AtonStorageBinarySensorEntityDescription(
key="grid_to_battery",
translation_key="grid_to_battery",
name="Grid to Battery",
value_calc_function=lambda controller: controller.grid_to_battery
),
AtonStorageBinarySensorEntityDescription(
key="battery_to_grid",
translation_key="battery_to_grid",
name="Batter to Grid",
value_calc_function=lambda controller: controller.battery_to_grid
),
)


async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the AtonStorage sensors."""
_LOGGER.debug("Set up the AtonStorage sensors")
entities = _create_entities(hass, entry)
async_add_entities(entities, True)


def _create_entities(hass: HomeAssistant, entry: dict):
entities = []

controller = hass.data[DOMAIN][entry.entry_id]["controller"]
coordinator = hass.data[DOMAIN][entry.entry_id]["coordinator"]
username = hass.data[DOMAIN][entry.entry_id]["username"]
sensors_selected = hass.data[DOMAIN][entry.entry_id]["sensors_selected"]

if "BINARY SENSORS" in sensors_selected:
for entity_description in INVERTER_BINARY_SENSOR_DESCRIPTIONS:
entities.append(
AtonStorageBinarySensorEntity(
entry=entry,
controller=controller,
coordinator=coordinator,
description=entity_description,
username=username,
)
)

return entities


class AtonStorageBinarySensorEntity(CoordinatorEntity, BinarySensorEntity):
"""AtonStorage Sensor which receives its data via an DataUpdateCoordinator."""

entity_description: AtonStorageBinarySensorEntityDescription
# _attr_has_entity_name = True

def __init__(
self,
entry: ConfigEntry,
controller: AtonStorage,
coordinator,
description: AtonStorageBinarySensorEntityDescription,
username,
# device_info,
):
"""Batched AtonStorage Sensor Entity constructor."""
super().__init__(coordinator)

self.controller = controller
self.entity_description = description

# self._entry = entry
# self._name = self.entity_description.name
# self._attr_name = f"{controller.serial_number}_{self.entity_description.name}"
# self._attr_translation_key = self.entity_description.key
self._attr_name = f"{username} {self.entity_description.name}"
self._attr_unique_id = f"{controller.serial_number}_{self.entity_description.key}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, "AtonStorage " + username)},
name=username,
manufacturer="AtonStorage",
)

self._register_key = self.entity_description.key
if "#" in self._register_key:
self._register_key = self._register_key[0 : self._register_key.find("#")]

@property
def is_on(self):
"""Return true if the binary sensor is on."""

return self.entity_description.value_calc_function(self.controller)
16 changes: 12 additions & 4 deletions custom_components/atonstorage/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@
from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow
from homeassistant.const import (
CONF_DEVICE_ID,
CONF_NAME,
CONF_PASSWORD,
CONF_SCAN_INTERVAL,
CONF_USERNAME,
CONF_MONITORED_VARIABLES,
)
from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util import slugify

from .const import DEFAULT_NAME, DEFAULT_SCAN_INTERVAL, DOMAIN
from homeassistant.helpers import selector

from .const import *
from .controller import AtonStorageConnectionError
from .controller import Controller as AtonStorage
from .controller import SerialNumberRequiredError, UsernameAndPasswordRequiredError
Expand All @@ -27,8 +29,14 @@
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
vol.Required(CONF_DEVICE_ID): str,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): str,
vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): int,
vol.Required(CONF_MONITORED_VARIABLES, default=AVAILABLE_SENSORS): selector.SelectSelector(
selector.SelectSelectorConfig(
options=AVAILABLE_SENSORS,
multiple=True,
mode=selector.SelectSelectorMode.LIST,
),
),
}
)

Expand All @@ -49,7 +57,7 @@ async def async_step_user(self, user_input=None):
user = user_input.get(CONF_USERNAME, None)
password = user_input.get(CONF_PASSWORD, None)
serial_number = user_input.get(CONF_DEVICE_ID, None)
name = user_input.get(CONF_NAME, DEFAULT_NAME)
name = f"{DEFAULT_NAME} {user}"
interval = user_input.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)

opts = {
Expand Down
36 changes: 36 additions & 0 deletions custom_components/atonstorage/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,39 @@
DEFAULT_NAME = "AtonStorage"

DEFAULT_SCAN_INTERVAL = 30

AVAILABLE_SENSORS = [
"Last update",
"Self sufficiency",
"Instant solar power",
"Instant user power",
"Instant battery power",
"Instant battery power input",
"Instant battery power output",
"Instant grid power",
"Instant grid power input",
"Instant grid power output",
"Battery level",
"Battery voltage",
"Battery current",
"Battery charged current",
"Battery discharged current",
"Battery charged energy",
"Battery discharged energy",
"Daily bought energy",
"Daily sold energy",
"Daily solar energy",
"Daily consumed energy",
"Daily self consumed energy",
"Grid voltage",
"Grid frequency",
"Utilities voltage",
"Utilities current",
"String1 voltage",
"String1 current",
"String2 voltage",
"String2 current",
"Inverter temperature",
"Temperature 2",
"BINARY SENSORS",
]
59 changes: 48 additions & 11 deletions custom_components/atonstorage/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
+ "set_request.php?request=MONITOR&intervallo={interval}&sn={serial_number}"
)
# _ENDPOINT = "https://www.atonstorage.com/atonTC/get_monitor.php?sn={serialNumber}&_={timestamp}"
# https://www.atonstorage.com/atonTC/set_request.php?sn=T19DE000868&request=MONITOR&intervallo=15&_={timestamp}
# https://www.atonstorage.com/atonTC/getAlarmDesc.php?sn=T19DE000868&_={timestamp}
# https://www.atonstorage.com/atonTC/set_request.php?sn={serialNumber}&request=MONITOR&intervallo=15&_={timestamp}
# https://www.atonstorage.com/atonTC/getAlarmDesc.php?sn={serialNumber}&_={timestamp}
# https://www.atonstorage.com/atonTC/hasExternalEV.php?id_impianto=151762966&_={timestamp}
# https://www.atonstorage.com/atonTC/get_monitorToday.php?&sn=T19DE000868&_={timestamp}
# https://www.atonstorage.com/atonTC/get_energy.php?anno=2022&mese=11&giorno=9&idImpianto=151762966&intervallo=d&potNom=3500&batNom=3500&sn=T19DE000868&_={timestamp}
# https://www.atonstorage.com/atonTC/get_vbib.php?anno=2022&mese=11&sn=T19DE000868&_={timestamp}
# https://www.atonstorage.com/atonTC/get_allarmi_oggi.php?sn=T19DE000868&idImpianto=151762966&tipoUtente=1&_={timestamp}
# https://www.atonstorage.com/atonTC/checkTShift.php?sn=T19DE000868&_={timestamp}
# https://www.atonstorage.com/atonTC/getTShift.php?sn=T19DE000868&_={timestamp}
# https://www.atonstorage.com/atonTC/get_monitorToday.php?&sn={serialNumber}&_={timestamp}
# https://www.atonstorage.com/atonTC/get_energy.php?anno=2022&mese=11&giorno=9&idImpianto=151762966&intervallo=d&potNom=3500&batNom=3500&sn={serialNumber}&_={timestamp}
# https://www.atonstorage.com/atonTC/get_vbib.php?anno=2022&mese=11&sn={serialNumber}&_={timestamp}
# https://www.atonstorage.com/atonTC/get_allarmi_oggi.php?sn={serialNumber}&idImpianto=151762966&tipoUtente=1&_={timestamp}
# https://www.atonstorage.com/atonTC/checkTShift.php?sn={serialNumber}&_={timestamp}
# https://www.atonstorage.com/atonTC/getTShift.php?sn={serialNumber}&_={timestamp}

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -130,13 +130,41 @@ async def refresh(self) -> None:

def get_raw_data(self, __name: str):
return self.data[__name]

@property
def grid_to_house(self) -> bool:
return int(self.data["status"]) & 1 == 1

@property
def solar_to_battery(self) -> bool:
return int(self.data["status"]) & 2 == 2

@property
def solar_to_grid(self) -> bool:
return int(self.data["status"]) & 4 == 4

@property
def battery_to_house(self) -> bool:
return int(self.data["status"]) & 8 == 8

@property
def solar_to_house(self) -> bool:
return int(self.data["status"]) & 16 == 16

@property
def grid_to_battery(self) -> bool:
return int(self.data["status"]) & 32 == 32

@property
def battery_to_grid(self) -> bool:
return int(self.data["status"]) & 64 == 64

@property
def serial_number(self) -> str:
return self.data["serialNumber"]

@property
def current_date(self) -> str:
def last_update(self) -> str:
return self.data["data"]

@property
Expand Down Expand Up @@ -272,12 +300,16 @@ def pannel_energy(self) -> int:
return self.data["ePannelli"]

@property
def battery_energy(self) -> int:
def self_consumed_energy(self) -> int:
return self.data["eBatteria"]

@property
def bought_energy(self) -> int:
return self.data["eComprata"]

@property
def consumed_energy(self) -> int:
return int(self.bought_energy) + int(self.self_consumed_energy)

# "ingressi1": "0",
# "ingressi2": "160",
Expand Down Expand Up @@ -339,6 +371,11 @@ def temperature2(self) -> float:
return self.data["temperatura2"]

# "dataAllarme": "07/11/2022 07:11:28",

@property
def update_delay(self) -> int:
return self.data["DiffDate"]

# "DiffDate": "829",
# "timestampScheda": "07/11/2022 11:13:13",

Expand Down Expand Up @@ -391,4 +428,4 @@ class InvalidUsernameOrPasswordError(Exception):


class SerialNumberRequiredError(Exception):
"""Error to serial number required."""
"""Error to serial number required."""
2 changes: 1 addition & 1 deletion custom_components/atonstorage/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"config_flow": true,
"dependencies": ["integration"],
"documentation": "https://github.com/wilds/hass-atonstorage",
"iot_class": "local_polling",
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/wilds/hass-atonstorage/issues",
"requirements": [],
"version": "1.0.3"
Expand Down
Loading

0 comments on commit bf83e7a

Please sign in to comment.