Skip to content

Commit

Permalink
Merge pull request #10 from crkochan/descriptive_entity_names
Browse files Browse the repository at this point in the history
Add descriptive entity names feature.
  • Loading branch information
krbaker authored Sep 19, 2021
2 parents 33657f3 + 73e3314 commit 794063d
Show file tree
Hide file tree
Showing 11 changed files with 81 additions and 41 deletions.
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 4 additions & 6 deletions custom_components/sunpower/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -20,7 +19,6 @@
SUNPOWER_COORDINATOR,
SUNPOWER_HOST,
SETUP_TIMEOUT_MIN,
PVS_DEVICE_TYPE,
)

_LOGGER = logging.getLogger(__name__)
Expand All @@ -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"]:
Expand Down Expand Up @@ -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),
)
Expand All @@ -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")
Expand Down
40 changes: 31 additions & 9 deletions custom_components/sunpower/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -34,30 +35,37 @@ 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)


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):
Expand All @@ -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):
Expand All @@ -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):
Expand Down
12 changes: 9 additions & 3 deletions custom_components/sunpower/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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

Expand Down
5 changes: 2 additions & 3 deletions custom_components/sunpower/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -75,7 +74,7 @@
"t_htsnk_degc",
"Temperature",
TEMP_CELSIUS,
"mdi:temperature-celsius",
"mdi:thermometer",
],
"INVERTER_FREQUENCY": ["freq_hz", "Frequency", FREQUENCY_HERTZ, "mdi:flash"],
}
Expand Down
2 changes: 1 addition & 1 deletion custom_components/sunpower/manifest.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "0.0.9",
"version": "0.0.10",
"domain": "sunpower",
"name": "sunpower",
"config_flow": true,
Expand Down
27 changes: 20 additions & 7 deletions custom_components/sunpower/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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],
)
Expand All @@ -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],
)
Expand All @@ -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],
)
Expand All @@ -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."""
Expand Down
6 changes: 4 additions & 2 deletions custom_components/sunpower/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion custom_components/sunpower/sunpower.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
return self.generic_command("Get_Comm")
8 changes: 5 additions & 3 deletions custom_components/sunpower/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
2 changes: 1 addition & 1 deletion hacs.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "HACS Sunpower Integration",
"name": "SunPower",
"country": "US",
"domains": ["binary_sensor", "sensor"],
"homeassistant": "2021.8.0",
Expand Down

0 comments on commit 794063d

Please sign in to comment.