diff --git a/README.md b/README.md
index 4a34c15..87abf2b 100644
--- a/README.md
+++ b/README.md
@@ -175,3 +175,107 @@ cards:
name: Ajusta ICarga
icon: mdi:current-ac
```
+
+OR USING pvpc control ( you need to configure pvpc on the integration config ):
+```
+type: vertical-stack
+cards:
+ - type: markdown
+ content:
CARGADOR COCHE
+ - type: horizontal-stack
+ cards:
+ - type: gauge
+ entity: sensor.v2c_trydan_sensor_fvpower
+ severity:
+ green: 0
+ yellow: 8000
+ red: 9500
+ needle: true
+ min: 0
+ max: 10000
+ name: Producción FV
+ - type: gauge
+ entity: sensor.v2c_trydan_sensor_housepower
+ name: Consumo hogar
+ severity:
+ green: -10000
+ yellow: 0
+ red: 0
+ needle: true
+ min: -10000
+ max: 10000
+ - type: gauge
+ entity: sensor.v2c_trydan_sensor_chargepower
+ name: Cargando en el coche
+ needle: true
+ min: 0
+ max: 8000
+ severity:
+ green: 0
+ yellow: 7200
+ red: 7700
+ - type: entities
+ entities:
+ - entity: sensor.v2c_trydan_sensor_chargestate
+ name: Estado
+ secondary_info: none
+ - entity: switch.v2c_trydan_switch_dynamic
+ name: Control dinámico
+ secondary_info: last-changed
+ - entity: switch.v2c_trydan_switch_paused
+ name: En pausa
+ icon: mdi:play
+ secondary_info: last-changed
+ - entity: switch.v2c_trydan_switch_locked
+ name: Bloquedado
+ icon: mdi:play
+ secondary_info: last-changed
+ - entity: sensor.v2c_trydan_sensor_intensity
+ name: Intensidad de carga
+ secondary_info: last-changed
+ - entity: sensor.v2c_trydan_sensor_minintensity
+ name: Mínimo dinámica
+ secondary_info: last-changed
+ - entity: sensor.v2c_trydan_sensor_maxintensity
+ name: Máximo dinámica
+ secondary_info: last-changed
+ - entity: sensor.v2c_trydan_sensor_chargeenergy
+ name: Energía cargada
+ secondary_info: none
+ - entity: sensor.v2c_trydan_sensor_chargekm
+ name: Km Cargados
+ secondary_info: none
+ - entity: sensor.v2c_trydan_sensor_chargetime
+ name: Tiempo de carga
+ - entity: number.v2c_km_to_charge
+ name: Km a Cargar
+ secondary_info: last-changed
+ icon: mdi:car-arrow-right
+ - entity: sensor.v2c_precio_luz
+ - entity: switch.v2c_trydan_switch_v2c_carga_pvpc
+ name: Carga por Precio
+ - entity: number.v2c_maxprice
+ - type: conditional
+ conditions:
+ - entity: switch.v2c_trydan_switch_dynamic
+ state: 'on'
+ card:
+ type: entities
+ entities:
+ - entity: number.v2c_min_intensity
+ name: Ajusta Imin dinámica
+ icon: mdi:current-ac
+ - entity: number.v2c_max_intensity
+ name: Ajusta Imax dinámica
+ icon: mdi:current-ac
+ - type: conditional
+ conditions:
+ - entity: switch.v2c_trydan_switch_dynamic
+ state: 'off'
+ card:
+ type: entities
+ entities:
+ - entity: number.v2c_intensity
+ name: Ajusta ICarga
+ icon: mdi:current-ac
+```
diff --git a/custom_components/v2c_trydan/config_flow.py b/custom_components/v2c_trydan/config_flow.py
index d4788d3..ffaca62 100644
--- a/custom_components/v2c_trydan/config_flow.py
+++ b/custom_components/v2c_trydan/config_flow.py
@@ -3,7 +3,7 @@
from homeassistant.core import HomeAssistant
from homeassistant.const import CONF_IP_ADDRESS
-from .const import DOMAIN, CONF_KWH_PER_100KM
+from .const import DOMAIN, CONF_KWH_PER_100KM, CONF_PRECIO_LUZ
DATA_SCHEMA = vol.Schema(
{
@@ -46,6 +46,7 @@ class V2CtrydanOptionsFlowHandler(config_entries.OptionsFlow):
def __init__(self, config_entry: config_entries.ConfigEntry):
self.config_entry = config_entry
self.current_kwh_per_100km = config_entry.options.get(CONF_KWH_PER_100KM, 20.8)
+ self.current_precio_luz = config_entry.options.get(CONF_PRECIO_LUZ, "sensor.precio_luz")
async def async_step_init(self, user_input=None):
if user_input is not None:
@@ -56,6 +57,9 @@ async def async_step_init(self, user_input=None):
vol.Required(
CONF_KWH_PER_100KM, description={"suggested_value": self.current_kwh_per_100km}
): vol.Coerce(float),
+ vol.Optional(
+ CONF_PRECIO_LUZ, description={"suggested_value": self.current_precio_luz}
+ ): str,
}
)
diff --git a/custom_components/v2c_trydan/const.py b/custom_components/v2c_trydan/const.py
index e555281..6880648 100644
--- a/custom_components/v2c_trydan/const.py
+++ b/custom_components/v2c_trydan/const.py
@@ -1,4 +1,5 @@
DOMAIN = "v2c_trydan"
CONF_IP_ADDRESS = "ip_address"
CONF_KWH_PER_100KM = "kwh_per_100km"
-CONF_KM_TO_CHARGE = "km_to_charge"
\ No newline at end of file
+CONF_KM_TO_CHARGE = "km_to_charge"
+CONF_PRECIO_LUZ = "precio_luz"
\ No newline at end of file
diff --git a/custom_components/v2c_trydan/manifest.json b/custom_components/v2c_trydan/manifest.json
index 87c5371..6ad4447 100644
--- a/custom_components/v2c_trydan/manifest.json
+++ b/custom_components/v2c_trydan/manifest.json
@@ -11,6 +11,6 @@
"quality_scale": "internal",
"requirements": ["aiohttp"],
"ssdp": [],
- "version": "2.9.1",
+ "version": "2.9.2",
"zeroconf": []
}
\ No newline at end of file
diff --git a/custom_components/v2c_trydan/number.py b/custom_components/v2c_trydan/number.py
index b8edfaa..e092afc 100644
--- a/custom_components/v2c_trydan/number.py
+++ b/custom_components/v2c_trydan/number.py
@@ -7,10 +7,12 @@
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config_entry, async_add_entities):
+
async_add_entities([MaxIntensityNumber(hass)])
async_add_entities([MinIntensityNumber(hass)])
async_add_entities([KmToChargeNumber(hass)])
- async_add_entities([IntensityNumber(hass)])
+ async_add_entities([IntensityNumber(hass)])
+ async_add_entities([MaxPrice(hass)])
class MaxIntensityNumber(NumberEntity):
def __init__(self, hass):
@@ -175,3 +177,42 @@ async def async_set_native_value(self, value):
else:
_LOGGER.error("v2c_intensity must be between {} and {}".format(self.native_min_value, self.native_max_value))
+class MaxPrice(NumberEntity):
+ def __init__(self, hass):
+ self._hass = hass
+ self._state = 0
+
+ @property
+ def unique_id(self):
+ return "v2c_MaxPrice"
+
+ @property
+ def name(self):
+ return "v2c_MaxPrice"
+
+ @property
+ def icon(self):
+ return "mdi:currency-eur"
+
+ @property
+ def native_value(self):
+ return self._state
+
+ @property
+ def native_step(self) -> float | None:
+ return 0.001
+
+ @property
+ def native_max_value(self):
+ return 1.000
+
+ @property
+ def native_min_value(self):
+ return 0.000
+
+ async def async_set_native_value(self, value):
+ if 0 <= value <= 1.0:
+ self._state = value
+ self.async_write_ha_state()
+ else:
+ _LOGGER.error("v2c_MaxPrice must be between 0 and 1")
\ No newline at end of file
diff --git a/custom_components/v2c_trydan/sensor.py b/custom_components/v2c_trydan/sensor.py
index 90d730e..58fb43d 100644
--- a/custom_components/v2c_trydan/sensor.py
+++ b/custom_components/v2c_trydan/sensor.py
@@ -1,28 +1,43 @@
import logging
+import asyncio
from datetime import timedelta, datetime
import aiohttp
import async_timeout
import voluptuous as vol
-from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity, SensorDeviceClass
-from homeassistant.const import CONF_IP_ADDRESS
-from homeassistant.core import HomeAssistant
-from homeassistant.core import callback
+from homeassistant.const import (
+ CONF_NAME,
+ STATE_UNKNOWN,
+ CONF_IP_ADDRESS,
+)
+from homeassistant.core import HomeAssistant, callback
+from homeassistant.components.sensor import (
+ PLATFORM_SCHEMA,
+ SensorEntity,
+ SensorDeviceClass,
+)
from homeassistant.exceptions import PlatformNotReady
+from homeassistant.helpers import entity_registry
from homeassistant.helpers.aiohttp_client import async_get_clientsession
+from homeassistant.helpers.entity import Entity
+from homeassistant.helpers.event import (
+ async_track_time_interval,
+ async_track_state_change,
+ async_call_later,
+)
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
UpdateFailed,
)
-from homeassistant.helpers.event import async_track_time_interval
-from homeassistant.helpers.event import async_track_state_change
-from .const import DOMAIN, CONF_KWH_PER_100KM
+from .const import DOMAIN, CONF_KWH_PER_100KM, CONF_PRECIO_LUZ
from .coordinator import V2CtrydanDataUpdateCoordinator
from .number import KmToChargeNumber
+DEPENDENCIES = ["switch"]
+
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
@@ -55,6 +70,8 @@
"MaxIntensity": "A"
}
+UPDATE_INTERVAL = timedelta(minutes=1)
+
async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entities):
ip_address = config_entry.data[CONF_IP_ADDRESS]
kwh_per_100km = config_entry.options.get(CONF_KWH_PER_100KM, 15)
@@ -67,6 +84,21 @@ async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entitie
]
sensors.append(ChargeKmSensor(coordinator, ip_address, kwh_per_100km))
sensors.append(NumericalStatus(coordinator))
+
+ await asyncio.sleep(10)
+
+ precio_luz_entity = hass.states.get(config_entry.options[CONF_PRECIO_LUZ]) if CONF_PRECIO_LUZ in config_entry.options else None
+
+ async def async_update_precio_luz(now):
+ nonlocal precio_luz_entity
+ if precio_luz_entity is not None:
+ precio_luz_entity = hass.states.get(CONF_PRECIO_LUZ)
+
+ if all([precio_luz_entity]):
+ precio_luz_entity_instance = PrecioLuzEntity(coordinator, precio_luz_entity, ip_address)
+ sensors.append(precio_luz_entity_instance)
+ _LOGGER.debug("PrecioLuzEntity instance added to sensors list")
+
async_add_entities(sensors)
class V2CtrydanSensor(CoordinatorEntity, SensorEntity):
@@ -241,16 +273,12 @@ async def async_added_to_hass(self):
async def check_and_pause_charging(self, now):
paused_switch = self.hass.states.get("switch.v2c_trydan_switch_paused")
if paused_switch is not None and paused_switch.state == "on":
- #_LOGGER.debug("Charging is paused, skipping check_and_pause_charging")
return
- #_LOGGER.debug("Checking if it's necessary to pause charging")
km_to_charge = self.hass.states.get("number.v2c_km_to_charge")
if km_to_charge is not None:
km_to_charge = float(km_to_charge.state)
- #_LOGGER.debug(f"Current km_to_charge value: {km_to_charge}")
if self.state >= km_to_charge and km_to_charge != 0:
- #_LOGGER.debug("Pausing charging and resetting km to charge")
await self.hass.services.async_call("switch", "turn_on", {"entity_id": "switch.v2c_trydan_switch_paused"})
await self.async_set_km_to_charge(0)
self.hass.bus.async_fire("v2c_trydan.charging_complete")
@@ -308,4 +336,87 @@ def state(self):
@property
def state_class(self):
- return "measurement"
\ No newline at end of file
+ return "measurement"
+
+class PrecioLuzEntity(CoordinatorEntity, SensorEntity):
+ def __init__(self, coordinator, precio_luz_entity, ip_address):
+ super().__init__(coordinator)
+ self.v2c_precio_luz_entity = precio_luz_entity
+ self.ip_address = ip_address
+
+ @property
+ def unique_id(self):
+ return f"v2c_precio_luz_entity"
+
+ @property
+ def name(self):
+ return "v2c Precio Luz"
+
+ @property
+ def state(self):
+ if self.v2c_precio_luz_entity is not None:
+ return self.v2c_precio_luz_entity.state
+ else:
+ return None
+
+ @property
+ def extra_state_attributes(self):
+ if self.v2c_precio_luz_entity is not None:
+ return self.v2c_precio_luz_entity.attributes
+ else:
+ return None
+
+ @property
+ def state_class(self):
+ if self.v2c_precio_luz_entity is not None:
+ return self.v2c_precio_luz_entity.attributes.get("state_class", "measurement")
+ else:
+ return "measurement"
+
+ @property
+ def native_unit_of_measurement(self):
+ if self.v2c_precio_luz_entity is not None:
+ return self.v2c_precio_luz_entity.attributes.get("unit_of_measurement", "€/kWh")
+ else:
+ return "€/kWh"
+
+ async def async_added_to_hass(self):
+ """Register update callback when added to hass."""
+ paused_switch_id = f"{self.ip_address}_Paused"
+ v2c_carga_pvpc_switch_id = f"v2c_carga_pvpc"
+ max_price_entity_id = "v2c_MaxPrice"
+
+ async def update_state(event_time):
+ precio_luz_entity = self.v2c_precio_luz_entity
+
+ entity_registry_instance = entity_registry.async_get(self.hass)
+ for entity_id, entity_entry in entity_registry_instance.entities.items():
+ if entity_entry.unique_id == paused_switch_id:
+ paused_switch = self.hass.data["switch"].get_entity(entity_id)
+ if entity_entry.unique_id == v2c_carga_pvpc_switch_id:
+ v2c_carga_pvpc_switch = self.hass.data["switch"].get_entity(entity_id)
+ if entity_entry.unique_id == max_price_entity_id:
+ max_price_entity = self.hass.states.get(entity_id)
+
+ if all([precio_luz_entity, paused_switch, v2c_carga_pvpc_switch, max_price_entity]):
+ max_price = float(max_price_entity.state)
+ if v2c_carga_pvpc_switch.is_on:
+ # Comprueba si el precio está entre max_price
+ if float(self.state) <= max_price:
+ self.hass.async_create_task(paused_switch.async_turn_off())
+ else:
+ self.hass.async_create_task(paused_switch.async_turn_on())
+ else:
+ _LOGGER.debug("Hay entidades aun no creadas")
+ if precio_luz_entity is None:
+ _LOGGER.debug(f"1 V2C precio_luz_entity: {precio_luz_entity}")
+ if paused_switch is None:
+ _LOGGER.debug(f"1 V2C paused_switch: {paused_switch}")
+ if v2c_carga_pvpc_switch is None:
+ _LOGGER.debug(f"1 V2C v2c_carga_pvpc_switch: {v2c_carga_pvpc_switch}")
+ if max_price_entity is None:
+ _LOGGER.debug(f"1 V2C max_price_entity: {max_price_entity}")
+
+ await update_state(None)
+
+ async_track_time_interval(self.hass, update_state, timedelta(seconds=60))
\ No newline at end of file
diff --git a/custom_components/v2c_trydan/switch.py b/custom_components/v2c_trydan/switch.py
index ae2ade8..df1a541 100644
--- a/custom_components/v2c_trydan/switch.py
+++ b/custom_components/v2c_trydan/switch.py
@@ -17,7 +17,7 @@
)
from .coordinator import V2CtrydanDataUpdateCoordinator
-from .const import DOMAIN
+from .const import DOMAIN, CONF_PRECIO_LUZ
_LOGGER = logging.getLogger(__name__)
@@ -32,10 +32,16 @@ async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entitie
coordinator = V2CtrydanDataUpdateCoordinator(hass, ip_address)
await coordinator.async_config_entry_first_refresh()
+ # Obtén precio_luz_entity
+ precio_luz_entity = hass.states.get(config_entry.options[CONF_PRECIO_LUZ]) if CONF_PRECIO_LUZ in config_entry.options else None
+
switches = [
V2CtrydanSwitch(coordinator, ip_address, key)
for key in ["Paused", "Dynamic", "Locked"]
]
+
+ switches.append(V2CCargaPVPCSwitch(precio_luz_entity))
+
async_add_entities(switches)
class V2CtrydanSwitch(CoordinatorEntity, SwitchEntity):
@@ -75,3 +81,29 @@ async def async_turn_off(self, **kwargs):
await self.coordinator.async_request_refresh()
except Exception as e:
_LOGGER.error(f"Error turning off switch: {e}")
+
+class V2CCargaPVPCSwitch(SwitchEntity):
+ def __init__(self, precio_luz_entity):
+ self._is_on = False
+ self.precio_luz_entity = precio_luz_entity
+
+ @property
+ def unique_id(self):
+ return f"v2c_carga_pvpc"
+
+ @property
+ def name(self):
+ return "V2C trydan Switch v2c_carga_pvpc"
+
+ @property
+ def is_on(self):
+ return self._is_on
+
+ async def async_turn_on(self, **kwargs):
+ if self.precio_luz_entity is not None:
+ self._is_on = True
+ else:
+ self._is_on = False
+
+ async def async_turn_off(self, **kwargs):
+ self._is_on = False