diff --git a/README.md b/README.md index 3714c8d..9c25e8b 100644 --- a/README.md +++ b/README.md @@ -16,17 +16,15 @@ Platform | Description `sensor` | Most data available from the PVS system including per-panel data. ## Installation - 1. Click install. -1. In the HA UI go to "Configuration" -> "Integrations" click "+" and search for "Sunpower". - - +2. In the HA UI go to "Configuration" -> "Integrations" click "+" and search for "Sunpower". ## Configuration is done in the UI * it will ask for a host (ip works) * hint: most sunpower systems are at 172.27.153.1 - +## Use descriptive entity names +When selected during installation, entities added for each device will have the device descriptor added onto the front of their name. The device descriptor is a combination of device type (e.g., Inverter) and serial number. This guarantees unique entity IDs and names at the expense of making said names very long. ## Network Setup This integration requires connectivity to the management interface used for installing the system. The PVS systems have a second lan interface in the box. *DO NOT PLUG THIS INTO YOUR LAN!!!* it is running its own DHCP server which will cause all sorts of IP addressing issues. I run a Linux router with a spare ethernet port and route to the sunpower interface and allow my home assistant system to connect directly to the PVS. Also note that the command used to dump data 'device list' is very slow and sometimes times out. I've built in some retry logic so setup passes pretty reliably. Sometimes you may see data go blank if the fetch times out. diff --git a/custom_components/sunpower/__init__.py b/custom_components/sunpower/__init__.py index 656c082..00df8df 100644 --- a/custom_components/sunpower/__init__.py +++ b/custom_components/sunpower/__init__.py @@ -10,7 +10,6 @@ from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import ( @@ -20,7 +19,6 @@ SUNPOWER_COORDINATOR, SUNPOWER_HOST, SETUP_TIMEOUT_MIN, - PVS_DEVICE_TYPE, ) _LOGGER = logging.getLogger(__name__) @@ -34,7 +32,7 @@ def sunpower_fetch(sunpower_monitor): """Basic data fetch routine to get and reformat sunpower data to a dict of device type and serial #""" try: sunpower_data = sunpower_monitor.device_list() - _LOGGER.info("got data %s", sunpower_data) + _LOGGER.debug("got data %s", sunpower_data) data = {} # Convert data into indexable format data[device_type][serial] for device in sunpower_data["devices"]: @@ -74,13 +72,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_update_data(): """Fetch data from API endpoint, used by coordinator to get mass data updates""" - _LOGGER.info("Updating all sunpower data") + _LOGGER.debug("Updating SunPower data") return await hass.async_add_executor_job(sunpower_fetch, sunpower_monitor) coordinator = DataUpdateCoordinator( hass, _LOGGER, - name="Sunpower PVS", + name="SunPower PVS", update_method=async_update_data, update_interval=timedelta(seconds=UPDATE_INTERVAL), ) @@ -93,7 +91,7 @@ async def async_update_data(): start = time.time() # Need to make sure this data loads on setup, be aggressive about retries while not coordinator.data: - _LOGGER.info("Config Update Attempt") + _LOGGER.debug("Config Update Attempt") await coordinator.async_refresh() if (time.time() - start) > (SETUP_TIMEOUT_MIN * 60): _LOGGER.error("Failed to update data") diff --git a/custom_components/sunpower/binary_sensor.py b/custom_components/sunpower/binary_sensor.py index 9cb2c95..bb795d9 100644 --- a/custom_components/sunpower/binary_sensor.py +++ b/custom_components/sunpower/binary_sensor.py @@ -6,8 +6,7 @@ from .const import ( DOMAIN, SUNPOWER_COORDINATOR, - # SUNPOWER_DATA, - # SUNPOWER_OBJECT, + SUNPOWER_DESCRIPTIVE_NAMES, PVS_DEVICE_TYPE, INVERTER_DEVICE_TYPE, METER_DEVICE_TYPE, @@ -24,7 +23,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Sunpower sensors.""" sunpower_state = hass.data[DOMAIN][config_entry.entry_id] - _LOGGER.error("Sunpower_state: %s", sunpower_state) + _LOGGER.debug("Sunpower_state: %s", sunpower_state) + + do_descriptive_names = config_entry.data[SUNPOWER_DESCRIPTIVE_NAMES] coordinator = sunpower_state[SUNPOWER_COORDINATOR] sunpower_data = coordinator.data @@ -34,19 +35,19 @@ async def async_setup_entry(hass, config_entry, async_add_entities): else: pvs = next(iter(sunpower_data[PVS_DEVICE_TYPE].values())) - entities = [SunPowerPVSState(coordinator, pvs)] + entities = [SunPowerPVSState(coordinator, pvs, do_descriptive_names)] if METER_DEVICE_TYPE not in sunpower_data: _LOGGER.error("Cannot find any power meters") else: for data in sunpower_data[METER_DEVICE_TYPE].values(): - entities.append(SunPowerMeterState(coordinator, data, pvs)) + entities.append(SunPowerMeterState(coordinator, data, pvs, do_descriptive_names)) if INVERTER_DEVICE_TYPE not in sunpower_data: _LOGGER.error("Cannot find any power inverters") else: for data in sunpower_data[INVERTER_DEVICE_TYPE].values(): - entities.append(SunPowerInverterState(coordinator, data, pvs)) + entities.append(SunPowerInverterState(coordinator, data, pvs, do_descriptive_names)) async_add_entities(entities, True) @@ -54,10 +55,17 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class SunPowerPVSState(SunPowerPVSEntity): """Representation of SunPower PVS Working State""" + def __init__(self, coordinator, pvs_info, do_descriptive_names): + super().__init__(coordinator, pvs_info) + self._do_descriptive_names = do_descriptive_names + @property def name(self): """Device Name.""" - return "System State" + if self._do_descriptive_names: + return "PVS System State" + else: + return "System State" @property def device_class(self): @@ -83,10 +91,17 @@ def is_on(self): class SunPowerMeterState(SunPowerMeterEntity): """Representation of SunPower Meter Working State""" + def __init__(self, coordinator, meter_info, pvs_info, do_descriptive_names): + super().__init__(coordinator, meter_info, pvs_info) + self._do_descriptive_names = do_descriptive_names + @property def name(self): """Device Name.""" - return "System State" + if self._do_descriptive_names: + return f"{self._meter_info['DESCR']} System State" + else: + return "System State" @property def device_class(self): @@ -112,10 +127,17 @@ def is_on(self): class SunPowerInverterState(SunPowerInverterEntity): """Representation of SunPower Inverter Working State""" + def __init__(self, coordinator, inverter_info, pvs_info, do_descriptive_names): + super().__init__(coordinator, inverter_info, pvs_info) + self._do_descriptive_names = do_descriptive_names + @property def name(self): """Device Name.""" - return "System State" + if self._do_descriptive_names: + return f"{self._inverter_info['DESCR']} System State" + else: + return "System State" @property def device_class(self): diff --git a/custom_components/sunpower/config_flow.py b/custom_components/sunpower/config_flow.py index d0bdc50..1372345 100644 --- a/custom_components/sunpower/config_flow.py +++ b/custom_components/sunpower/config_flow.py @@ -4,12 +4,18 @@ import voluptuous as vol from homeassistant import config_entries, core, exceptions +from homeassistant.const import CONF_HOST -from .const import DOMAIN, SUNPOWER_HOST # pylint:disable=unused-import +from .const import DOMAIN, SUNPOWER_HOST, SUNPOWER_DESCRIPTIVE_NAMES _LOGGER = logging.getLogger(__name__) -DATA_SCHEMA = vol.Schema({"host": str}) +DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_HOST): str, + vol.Required(SUNPOWER_DESCRIPTIVE_NAMES, default=False): bool, + } +) async def validate_input(hass: core.HomeAssistant, data): @@ -22,7 +28,7 @@ async def validate_input(hass: core.HomeAssistant, data): name = "PVS {}".format(data["host"]) try: response = await hass.async_add_executor_job(spm.network_status) - _LOGGER.info("Got from %s %s", data["host"], response) + _LOGGER.debug("Got from %s %s", data["host"], response) except ConnectionException as error: raise CannotConnect from error diff --git a/custom_components/sunpower/const.py b/custom_components/sunpower/const.py index 31db244..46b94f3 100644 --- a/custom_components/sunpower/const.py +++ b/custom_components/sunpower/const.py @@ -14,13 +14,12 @@ DOMAIN = "sunpower" - +SUNPOWER_DESCRIPTIVE_NAMES = "use_descriptive_names" SUNPOWER_OBJECT = "sunpower" SUNPOWER_HOST = "host" SUNPOWER_COORDINATOR = "coordinator" UPDATE_INTERVAL = 120 SETUP_TIMEOUT_MIN = 5 -# SUNPOWER_DATA = "data" PVS_DEVICE_TYPE = "PVS" INVERTER_DEVICE_TYPE = "Inverter" @@ -75,7 +74,7 @@ "t_htsnk_degc", "Temperature", TEMP_CELSIUS, - "mdi:temperature-celsius", + "mdi:thermometer", ], "INVERTER_FREQUENCY": ["freq_hz", "Frequency", FREQUENCY_HERTZ, "mdi:flash"], } diff --git a/custom_components/sunpower/manifest.json b/custom_components/sunpower/manifest.json index b821826..0578dc5 100644 --- a/custom_components/sunpower/manifest.json +++ b/custom_components/sunpower/manifest.json @@ -1,5 +1,5 @@ { - "version": "0.0.9", + "version": "0.0.10", "domain": "sunpower", "name": "sunpower", "config_flow": true, diff --git a/custom_components/sunpower/sensor.py b/custom_components/sunpower/sensor.py index c875fb6..4f7b625 100644 --- a/custom_components/sunpower/sensor.py +++ b/custom_components/sunpower/sensor.py @@ -6,8 +6,7 @@ from .const import ( DOMAIN, SUNPOWER_COORDINATOR, - # SUNPOWER_DATA, - # SUNPOWER_OBJECT, + SUNPOWER_DESCRIPTIVE_NAMES, PVS_DEVICE_TYPE, INVERTER_DEVICE_TYPE, METER_DEVICE_TYPE, @@ -23,7 +22,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Sunpower sensors.""" sunpower_state = hass.data[DOMAIN][config_entry.entry_id] - _LOGGER.error("Sunpower_state: %s", sunpower_state) + _LOGGER.debug("Sunpower_state: %s", sunpower_state) + + do_descriptive_names = config_entry.data[SUNPOWER_DESCRIPTIVE_NAMES] coordinator = sunpower_state[SUNPOWER_COORDINATOR] sunpower_data = coordinator.data @@ -35,11 +36,15 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities = [] for sensor in PVS_SENSORS: + if do_descriptive_names: + title = f"{pvs['DEVICE_TYPE']} {PVS_SENSORS[sensor][1]}" + else: + title = PVS_SENSORS[sensor][1] spb = SunPowerPVSBasic( coordinator, pvs, PVS_SENSORS[sensor][0], - PVS_SENSORS[sensor][1], + title, PVS_SENSORS[sensor][2], PVS_SENSORS[sensor][3], ) @@ -54,12 +59,16 @@ async def async_setup_entry(hass, config_entry, async_add_entities): else: for data in sunpower_data[METER_DEVICE_TYPE].values(): for sensor in METER_SENSORS: + if do_descriptive_names: + title = f"{data['DESCR']} {METER_SENSORS[sensor][1]}" + else: + title = METER_SENSORS[sensor][1] smb = SunPowerMeterBasic( coordinator, data, pvs, METER_SENSORS[sensor][0], - METER_SENSORS[sensor][1], + title, METER_SENSORS[sensor][2], METER_SENSORS[sensor][3], ) @@ -74,12 +83,16 @@ async def async_setup_entry(hass, config_entry, async_add_entities): else: for data in sunpower_data[INVERTER_DEVICE_TYPE].values(): for sensor in INVERTER_SENSORS: + if do_descriptive_names: + title = f"{data['DESCR']} {INVERTER_SENSORS[sensor][1]}" + else: + title = INVERTER_SENSORS[sensor][1] sib = SunPowerInverterBasic( coordinator, data, pvs, INVERTER_SENSORS[sensor][0], - INVERTER_SENSORS[sensor][1], + title, INVERTER_SENSORS[sensor][2], INVERTER_SENSORS[sensor][3], ) @@ -102,7 +115,7 @@ def __init__(self, coordinator, pvs_info, field, title, unit, icon): self._field = field self._unit = unit self._icon = icon - + @property def unit_of_measurement(self): """Return the unit of measurement.""" diff --git a/custom_components/sunpower/strings.json b/custom_components/sunpower/strings.json index 63b29de..02a0a40 100644 --- a/custom_components/sunpower/strings.json +++ b/custom_components/sunpower/strings.json @@ -4,8 +4,10 @@ "step": { "user": { "data": { - "host": "host" - } + "host": "Host", + "use_descriptive_names": "Use descriptive entity names" + }, + "description": "If 'Use descriptive entity names' is selected, device names\nwill be prepended on all entity names." } }, "error": { diff --git a/custom_components/sunpower/sunpower.py b/custom_components/sunpower/sunpower.py index fd97f85..93873ca 100644 --- a/custom_components/sunpower/sunpower.py +++ b/custom_components/sunpower/sunpower.py @@ -30,4 +30,4 @@ def device_list(self): def network_status(self): """Get a list of network interfaces on the PVS""" - return self.generic_command("Get_Comm") \ No newline at end of file + return self.generic_command("Get_Comm") diff --git a/custom_components/sunpower/translations/en.json b/custom_components/sunpower/translations/en.json index 9116abc..b63ec86 100644 --- a/custom_components/sunpower/translations/en.json +++ b/custom_components/sunpower/translations/en.json @@ -10,10 +10,12 @@ "step": { "user": { "data": { - "host": "host" - } + "host": "Host", + "use_descriptive_names": "Use descriptive entity names" + }, + "description": "If 'Use descriptive entity names' is selected, device names\nwill be prepended on all entity names." } } }, "title": "SunPower" -} \ No newline at end of file +} diff --git a/hacs.json b/hacs.json index 1c802e6..9d5aefb 100644 --- a/hacs.json +++ b/hacs.json @@ -1,5 +1,5 @@ { - "name": "HACS Sunpower Integration", + "name": "SunPower", "country": "US", "domains": ["binary_sensor", "sensor"], "homeassistant": "2021.8.0",