Skip to content

Commit

Permalink
Selettore del tipo di contratto
Browse files Browse the repository at this point in the history
Selettore per selezionare il proprio contratto tra: tri-orario, bi-orario e mono-orario
Verrano creati solo i sensori riguardanti tale contratto.
  • Loading branch information
Gariam-1 authored Dec 22, 2024
1 parent 8f5a81a commit e49a8f4
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 81 deletions.
11 changes: 9 additions & 2 deletions custom_components/pzo_sensor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from homeassistant.helpers.event import async_call_later, async_track_point_in_time
import homeassistant.util.dt as dt_util

from .const import CONF_ACTUAL_DATA_ONLY, CONF_SCAN_HOUR, CONF_ZONE, CONF_MONTH_AVG, DOMAIN, WEB_RETRIES_MINUTES
from .const import CONF_ACTUAL_DATA_ONLY, CONF_SCAN_HOUR, ZONE_CODES, CONF_ZONE, CONTRACTS, CONF_CONTRACT, CONF_MONTH_AVG, DOMAIN, WEB_RETRIES_MINUTES
from .coordinator import PricesDataUpdateCoordinator

if AwesomeVersion(HA_VERSION) >= AwesomeVersion("2024.5.0"):
Expand Down Expand Up @@ -79,7 +79,14 @@ async def update_listener(hass: HomeAssistant, config: ConfigEntry) -> None:
config.options[CONF_ZONE] != coordinator.zone
):
# Modificata la zona geografica nelle opzioni
coordinator.scan_hour = config.options[CONF_SCAN_HOUR]
coordinator.zone = ZONE_CODES[config.options[CONF_ZONE]]
reload = True

if (CONF_CONTRACT in config.options) and (
config.options[CONF_CONTRACT] != coordinator.contract
):
# Modificata il tipo di contratto nelle opzioni
coordinator.contract = CONTRACTS[config.options[CONF_CONTRACT]]
reload = True

if (CONF_SCAN_HOUR in config.options) and (
Expand Down
9 changes: 8 additions & 1 deletion custom_components/pzo_sensor/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from homeassistant.data_entry_flow import FlowResult
import homeassistant.helpers.config_validation as cv

from .const import CONF_ACTUAL_DATA_ONLY, CONF_SCAN_HOUR, CONF_ZONE, CONF_MONTH_AVG, ZONE_CODES, DEFAULT_ZONE, DOMAIN
from .const import CONF_ACTUAL_DATA_ONLY, CONF_SCAN_HOUR, CONF_ZONE, DEFAULT_ZONE, CONF_CONTRACT, DEFAULT_CONTRACT, CONF_MONTH_AVG, ZONE_CODES, CONTRACTS, DOMAIN


class OptionsFlow(config_entries.OptionsFlow):
Expand All @@ -32,6 +32,12 @@ async def async_step_init(self, user_input=None) -> FlowResult:
CONF_ZONE, self.config_entry.data[CONF_ZONE]
)
): vol.In(ZONE_CODES.keys()),
vol.Required(
CONF_CONTRACT,
default=self.config_entry.options.get(
CONF_CONTRACT, self.config_entry.data[CONF_CONTRACT]
)
): vol.In(CONTRACTS.keys()),
vol.Required(
CONF_SCAN_HOUR,
default=self.config_entry.options.get(
Expand Down Expand Up @@ -86,6 +92,7 @@ async def async_step_user(self, user_input=None):
# Schema dati di configurazione (con default fissi)
data_schema = {
vol.Required(CONF_ZONE, default=DEFAULT_ZONE): vol.In(ZONE_CODES.keys()),
vol.Required(CONF_CONTRACT, default=DEFAULT_CONTRACT): vol.In(CONTRACTS.keys()),
vol.Required(CONF_SCAN_HOUR, default=1): vol.All(
cv.positive_int, vol.Range(min=0, max=23)
),
Expand Down
9 changes: 6 additions & 3 deletions custom_components/pzo_sensor/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@

# Parametri configurabili da configuration.yaml
CONF_ZONE = "zone"
CONF_CONTRACT = "contract"
CONF_SCAN_HOUR = "scan_hour"
CONF_ACTUAL_DATA_ONLY = "actual_data_only"
CONF_MONTH_AVG = "month_average"

# Parametri interni
CONF_SCAN_MINUTE = "scan_minute"

CONTRACTS = {"Tri-orario": 3, "Bi-orario": 2, "Mono-orario": 1}
DEFAULT_CONTRACT = "Tri-orario"

# Traduce i nomi delle zone nei rispettivi codici presenti nell'xml
ZONE_CODES = {
#"Austria": "AUST",
Expand All @@ -35,7 +39,7 @@
#"Grecia": "GREC",
#"Malta": "MALT",
#"Montenegro": "MONT",
"Italia (senza vincoli)": "NAT",
"Italia": "NAT",
"Nord": "NORD",
"Sardegna": "SARD",
"Sicilia": "SICI",
Expand All @@ -47,6 +51,5 @@
#"Grecia Coupling": "XGRE",
}

DEFAULT_ZONE = "NAT"

DEFAULT_ZONE = "Italia"
PHYSICAL_ZONES = ["CALA", "CNOR", "CSUD", "NORD", "SARD", "SICI", "SUD"]
19 changes: 12 additions & 7 deletions custom_components/pzo_sensor/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
CONF_ZONE,
ZONE_CODES,
DEFAULT_ZONE,
CONTRACTS,
CONF_CONTRACT,
DEFAULT_CONTRACT,
CONF_ACTUAL_DATA_ONLY,
CONF_MONTH_AVG,
CONF_SCAN_HOUR,
Expand Down Expand Up @@ -62,7 +65,10 @@ def __init__(self, hass: HomeAssistant, config: ConfigEntry) -> None:

# Inizializza i valori di configurazione (dalle opzioni o dalla configurazione iniziale)
self.zone = ZONE_CODES[config.options.get(
CONF_ZONE, config.data.get(CONF_ZONE, DEFAULT_ZONE)
CONF_ZONE, config.data.get(CONF_ZONE, ZONE_CODES[DEFAULT_ZONE])
)]
self.contract = CONTRACTS[config.options.get(
CONF_CONTRACT, config.data.get(CONF_CONTRACT, CONTRACTS[DEFAULT_CONTRACT])
)]
self.actual_data_only = config.options.get(
CONF_ACTUAL_DATA_ONLY, config.data.get(CONF_ACTUAL_DATA_ONLY, False)
Expand Down Expand Up @@ -205,13 +211,12 @@ async def _async_update_data(self):
# Calcola le fasce facendo la media dei prezzi orari che le compongono
if fascia == Fascia.ORARIA:
self.pz_values.value[fascia] = pz_list[datetime.now().hour]
self.pz_values.value[Fascia.MONO] = mean(self.pz_data.data[fascia])
else:
self.pz_values.value[fascia] = mean(self.pz_data.data[fascia])

# Calcola la fascia F23 e il mono-fascia facendo la media delle fasce che le compongono ponderata sul numero di ore che coprono
ore_fasce = {Fascia.F1: len(self.pz_data.data[Fascia.F1]), Fascia.F2: len(self.pz_data.data[Fascia.F2]), Fascia.F3: len(self.pz_data.data[Fascia.F3])}
self.pz_values.value[Fascia.MONO] = (self.pz_values.value[Fascia.F1] * ore_fasce[Fascia.F1] + self.pz_values.value[Fascia.F2] * ore_fasce[Fascia.F2] + self.pz_values.value[Fascia.F3] * ore_fasce[Fascia.F3]) / 24
self.pz_values.value[Fascia.F23] = (self.pz_values.value[Fascia.F2] * ore_fasce[Fascia.F2] + self.pz_values.value[Fascia.F3] * ore_fasce[Fascia.F3]) / (ore_fasce[Fascia.F2] + ore_fasce[Fascia.F3])
# Calcola la fascia F23
self.pz_values.value[Fascia.F23] = self.pz_values.value[Fascia.F2] * 0.46 + self.pz_values.value[Fascia.F3] * 0.54

# Logga i dati
_LOGGER.debug(
Expand Down Expand Up @@ -265,12 +270,12 @@ async def update_fascia(self, now=None):

# Ottiene la fascia oraria corrente e il prossimo aggiornamento
self.fascia_corrente, self.prossimo_cambio_fascia = get_fascia(
dt_util.now(time_zone=time_zone)
dt_util.now(time_zone=time_zone), self.contract
)

# Calcola la fascia futura ri-applicando lo stesso algoritmo
self.fascia_successiva, self.termine_prossima_fascia = get_fascia(
self.prossimo_cambio_fascia
self.prossimo_cambio_fascia, self.contract
)
_LOGGER.info(
"Nuova fascia corrente: %s (prossima: %s alle %s)",
Expand Down
2 changes: 1 addition & 1 deletion custom_components/pzo_sensor/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
"loggers": ["pzo_sensor"],
"requirements": [],
"single_config_entry": true,
"version": "0.1.1"
"version": "0.1.2"
}
148 changes: 130 additions & 18 deletions custom_components/pzo_sensor/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,23 @@ async def async_setup_entry(

# Crea i sensori dei valori dei prezzi zonali (legati al coordinator)
entities: list[SensorEntity] = []
entities.extend(
PriceSensorEntity(coordinator, fascia) for fascia in PricesValues().value
)
match coordinator.contract:
case 3:
entities.append(PrezzoSensorEntity(coordinator, Fascia.F1))
entities.append(PrezzoSensorEntity(coordinator, Fascia.F2))
entities.append(PrezzoSensorEntity(coordinator, Fascia.F3))
case 2:
entities.append(PrezzoSensorEntity(coordinator, Fascia.F1))
entities.append(PrezzoSensorEntity(coordinator, Fascia.F23))
case 1:
entities.append(PrezzoSensorEntity(coordinator, Fascia.MONO))

# Crea sensori aggiuntivi
entities.append(FasciaSensorEntity(coordinator))
entities.append(PrezzoFasciaSensorEntity(coordinator))
if coordinator.contract != 1:
entities.append(FasciaSensorEntity(coordinator))
entities.append(PrezzoFasciaSensorEntity(coordinator))

entities.append(PrezzoOrarioSensorEntity(coordinator))

# Aggiunge i sensori ma non aggiorna automaticamente via web
# per lasciare il tempo ad Home Assistant di avviarsi
Expand All @@ -80,7 +90,7 @@ def fmt_float(num: float) -> float:
return round(num, 6)


class PriceSensorEntity(CoordinatorEntity, SensorEntity, RestoreEntity):
class PrezzoSensorEntity(CoordinatorEntity, SensorEntity, RestoreEntity):
"""Sensore relativo al prezzo per fasce."""

def __init__(self, coordinator: PricesDataUpdateCoordinator, fascia: Fascia) -> None:
Expand All @@ -94,17 +104,15 @@ def __init__(self, coordinator: PricesDataUpdateCoordinator, fascia: Fascia) ->
# ID univoco sensore basato su un nome fisso
match self.fascia:
case Fascia.MONO:
self.entity_id = ENTITY_ID_FORMAT.format(f"prezzo_zonale_mono_orario")
self.entity_id = ENTITY_ID_FORMAT.format("prezzo_zonale_mono_orario")
case Fascia.F1:
self.entity_id = ENTITY_ID_FORMAT.format(f"prezzo_zonale_fascia_f1")
self.entity_id = ENTITY_ID_FORMAT.format("prezzo_zonale_fascia_f1")
case Fascia.F2:
self.entity_id = ENTITY_ID_FORMAT.format(f"prezzo_zonale_fascia_f2")
self.entity_id = ENTITY_ID_FORMAT.format("prezzo_zonale_fascia_f2")
case Fascia.F3:
self.entity_id = ENTITY_ID_FORMAT.format(f"prezzo_zonale_fascia_f3")
self.entity_id = ENTITY_ID_FORMAT.format("prezzo_zonale_fascia_f3")
case Fascia.F23:
self.entity_id = ENTITY_ID_FORMAT.format(f"prezzo_zonale_fascia_f23")
case Fascia.ORARIA:
self.entity_id = ENTITY_ID_FORMAT.format(f"prezzo_zonale_orario")
self.entity_id = ENTITY_ID_FORMAT.format("prezzo_zonale_fascia_f23")
case _:
self.entity_id = None
self._attr_unique_id = self.entity_id
Expand Down Expand Up @@ -247,13 +255,16 @@ def device_class(self) -> SensorDeviceClass | None:
@property
def options(self) -> list[str] | None:
"""Possibili stati del sensore."""
return [Fascia.F1.value, Fascia.F2.value, Fascia.F3.value]
match self.coordinator.contract:
case 3: return [Fascia.F1.value, Fascia.F2.value, Fascia.F3.value]
case 2: return [Fascia.F1.value, Fascia.F23.value]

@property
def native_value(self) -> str | None:
"""Restituisce la fascia corrente come stato."""
if not self.coordinator.fascia_corrente:
return None

return self.coordinator.fascia_corrente.value

@property
Expand Down Expand Up @@ -289,7 +300,7 @@ def __init__(self, coordinator: PricesDataUpdateCoordinator) -> None:
self.coordinator = coordinator

# ID univoco sensore basato su un nome fisso
self.entity_id = ENTITY_ID_FORMAT.format(f"prezzo_zonale_fascia_corrente")
self.entity_id = ENTITY_ID_FORMAT.format("prezzo_zonale_fascia_corrente")
self._attr_unique_id = self.entity_id
self._attr_has_entity_name = True

Expand All @@ -298,20 +309,21 @@ def __init__(self, coordinator: PricesDataUpdateCoordinator) -> None:
self._attr_suggested_display_precision = 6
self._available = False
self._native_value = 0
self._friendly_name = f"Prezzo zonale fascia corrente"
self._friendly_name = "Prezzo zonale fascia corrente"

def _handle_coordinator_update(self) -> None:
"""Gestisce l'aggiornamento dei dati dal coordinator."""

if self.coordinator.fascia_corrente is not None and self.coordinator.pz_values.value[self.coordinator.fascia_corrente] > 0:

self._available = len(self.coordinator.pz_data.data[self.coordinator.fascia_corrente]) > 0
self._available = self.coordinator.pz_values.value[self.coordinator.fascia_corrente] > 0
self._native_value = self.coordinator.pz_values.value[self.coordinator.fascia_corrente]
self._friendly_name = f"Prezzo zonale fascia corrente ({self.coordinator.fascia_corrente.value})"

else:
self._available = False
self._native_value = 0
self._friendly_name = f"Prezzo zonale fascia corrente"
self._friendly_name = "Prezzo zonale fascia corrente"

self.async_write_ha_state()

Expand Down Expand Up @@ -378,3 +390,103 @@ def extra_state_attributes(self) -> dict[str, Any]:
# Nelle versioni precedenti di Home Assistant
# restituisce un valore arrotondato come attributo
return {ATTR_ROUNDED_DECIMALS: str(format(round(self.native_value, 3), ".3f"))}

class PrezzoOrarioSensorEntity(CoordinatorEntity, SensorEntity, RestoreEntity):
"""Sensore che rappresenta il prezzo zonale della fascia corrente."""

def __init__(self, coordinator: PricesDataUpdateCoordinator) -> None:
"""Inizializza il sensore."""
super().__init__(coordinator)

# Inizializza coordinator
self.coordinator = coordinator

# ID univoco sensore basato su un nome fisso
self.entity_id = ENTITY_ID_FORMAT.format("prezzo_zonale_orario")
self._attr_unique_id = self.entity_id
self._attr_has_entity_name = True

# Inizializza le proprietà comuni
self._attr_state_class = SensorStateClass.MEASUREMENT
self._attr_suggested_display_precision = 6
self._available = False
self._native_value = 0
self._friendly_name = "Prezzo zonale orario"

def _handle_coordinator_update(self) -> None:
"""Gestisce l'aggiornamento dei dati dal coordinator."""

if self.coordinator.pz_values.value[Fascia.ORARIA] > 0:
self._available = len(self.coordinator.pz_data.data[Fascia.ORARIA]) > 0
self._native_value = self.coordinator.pz_values.value[Fascia.ORARIA]
self._friendly_name = f"Prezzo zonale orario ({datetime.now().hour})"
else:
self._available = False
self._native_value = 0
self._friendly_name = "Prezzo zonale orario"

self.async_write_ha_state()

@property
def extra_restore_state_data(self) -> ExtraStoredData:
"""Determina i dati da salvare per il ripristino successivo."""
return RestoredExtraData(
{
"native_value": self._native_value if self._available else None,
"friendly_name": self._friendly_name if self._available else None,
}
)

async def async_added_to_hass(self) -> None:
"""Entità aggiunta ad Home Assistant."""
await super().async_added_to_hass()

# Recupera lo stato precedente, se esiste
if (old_data := await self.async_get_last_extra_data()) is not None:
if (old_native_value := old_data.as_dict().get("native_value")) is not None:
self._available = True
self._native_value = old_native_value
if (
old_friendly_name := old_data.as_dict().get("friendly_name")
) is not None:
self._friendly_name = old_friendly_name

@property
def should_poll(self) -> bool:
"""Determina l'aggiornamento automatico."""
return False

@property
def available(self) -> bool:
"""Determina se il valore è disponibile."""
return self._available

@property
def native_value(self) -> float:
"""Restituisce il prezzo della fascia corrente."""
return fmt_float(self._native_value)

@property
def native_unit_of_measurement(self) -> str:
"""Unita' di misura."""
return f"{CURRENCY_EURO}/{UnitOfEnergy.KILO_WATT_HOUR}"

@property
def icon(self) -> str:
"""Icona da usare nel frontend."""
return "mdi:currency-eur"

@property
def name(self) -> str:
"""Restituisce il nome del sensore."""
return self._friendly_name

@property
def extra_state_attributes(self) -> dict[str, Any]:
"""Restituisce gli attributi di stato."""
if CommonSettings.has_suggested_display_precision:
return {}

# Nelle versioni precedenti di Home Assistant
# restituisce un valore arrotondato come attributo
return {ATTR_ROUNDED_DECIMALS: str(format(round(self.native_value, 3), ".3f"))}
2 changes: 2 additions & 0 deletions custom_components/pzo_sensor/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"title": "Impostazioni scraping prezzi zonali GME",
"data": {
"zone": "Zona geografica",
"contract": "Tipo di contratto",
"scan_hour": "Ora inizio download dati (0-23)",
"month_average": "Calcola la media del mese corrente (altrimenti fornisce solo il dato odierno)",
"actual_data_only": "Usa solo dati reali ad inizio mese"
Expand All @@ -24,6 +25,7 @@
"title": "Modifica impostazioni scraping prezzi zonali GME",
"data": {
"zone": "Zona geografica",
"contract": "Tipo di contratto",
"scan_hour": "Ora inizio download dati (0-23)",
"month_average": "Calcola la media del mese corrente (altrimenti fornisce solo il dato odierno)",
"actual_data_only": "Usa solo dati reali ad inizio mese"
Expand Down
Loading

0 comments on commit e49a8f4

Please sign in to comment.