Add Enphase Envoy component#15081
Add Enphase Envoy component#15081MartinHjelmare merged 19 commits intohome-assistant:devfrom jesserizzo:dev
Conversation
|
Hi @jesserizzo, It seems you haven't yet signed a CLA. Please do so here. Once you do that we will be able to review and accept this pull request. Thanks! |
| """Initialize the sensor.""" | ||
|
|
||
|
|
||
| self._url = "http://{}/production.json".format(ip_address) |
|
|
||
|
|
||
|
|
||
| class Envoy(Entity): |
| #out of the config, or that the given sensor is in the monitored conditions list | ||
| for i in range(8): | ||
| if monitored_conditions == {} or SENSOR_TYPES[i] in monitored_conditions: | ||
| add_devices([Envoy(ip_address, DEFAULT_NAMES[i], SENSOR_TYPES[i])], True) |
There was a problem hiding this comment.
line too long (85 > 79 characters)
| #Iterate through the list of sensors, adding it if either monitored conditions has been left | ||
| #out of the config, or that the given sensor is in the monitored conditions list | ||
| for i in range(8): | ||
| if monitored_conditions == {} or SENSOR_TYPES[i] in monitored_conditions: |
There was a problem hiding this comment.
line too long (81 > 79 characters)
| monitored_conditions = config.get(CONF_MONITORED_CONDITIONS, {}) | ||
|
|
||
| #Iterate through the list of sensors, adding it if either monitored conditions has been left | ||
| #out of the config, or that the given sensor is in the monitored conditions list |
There was a problem hiding this comment.
block comment should start with '# '
line too long (84 > 79 characters)
|
|
||
| DEFAULT_NAMES = ["Envoy Current Energy Production", "Envoy Today's Energy Production", "Envoy Last Seven Days Energy Production", | ||
| "Envoy Lifetime Energy Production", "Envoy Current Energy Consumption", "Envoy Today's Energy Consumption", | ||
| "Envoy Last Seven Days Energy Consumption", "Envoy Lifetime Energy Consumption"] |
There was a problem hiding this comment.
continuation line under-indented for visual indent
line too long (80 > 79 characters)
| CONF_MONITORED_CONDITIONS = 'monitored_conditions' | ||
|
|
||
| DEFAULT_NAMES = ["Envoy Current Energy Production", "Envoy Today's Energy Production", "Envoy Last Seven Days Energy Production", | ||
| "Envoy Lifetime Energy Production", "Envoy Current Energy Consumption", "Envoy Today's Energy Consumption", |
There was a problem hiding this comment.
continuation line under-indented for visual indent
line too long (107 > 79 characters)
| CONF_IP_ADDRESS = 'ip' | ||
| CONF_MONITORED_CONDITIONS = 'monitored_conditions' | ||
|
|
||
| DEFAULT_NAMES = ["Envoy Current Energy Production", "Envoy Today's Energy Production", "Envoy Last Seven Days Energy Production", |
There was a problem hiding this comment.
line too long (129 > 79 characters)
| from homeassistant.helpers.entity import Entity | ||
| from homeassistant.components.sensor import PLATFORM_SCHEMA | ||
| import homeassistant.helpers.config_validation as cv | ||
| from homeassistant.const import CONF_NAME |
There was a problem hiding this comment.
'homeassistant.const.CONF_NAME' imported but unused
| @@ -0,0 +1,126 @@ | |||
| """ | |||
| Support for monitoring energy usage and solar panel energy production using the Enphase Envoy. | |||
There was a problem hiding this comment.
line too long (94 > 79 characters)
| add_devices([Envoy(ip_address, DEFAULT_NAMES[i], \ | ||
| SENSOR_TYPES[i])], True) | ||
|
|
||
| class Envoy(Entity): |
| if monitored_conditions == {} or \ | ||
| SENSOR_TYPES[i] in monitored_conditions: | ||
| add_devices([Envoy(ip_address, DEFAULT_NAMES[i], \ | ||
| SENSOR_TYPES[i])], True) |
There was a problem hiding this comment.
continuation line under-indented for visual indent
| for i in range(8): | ||
| if monitored_conditions == {} or \ | ||
| SENSOR_TYPES[i] in monitored_conditions: | ||
| add_devices([Envoy(ip_address, DEFAULT_NAMES[i], \ |
There was a problem hiding this comment.
the backslash is redundant between brackets
| #is in the monitored conditions list | ||
| for i in range(8): | ||
| if monitored_conditions == {} or \ | ||
| SENSOR_TYPES[i] in monitored_conditions: |
There was a problem hiding this comment.
continuation line missing indentation or outdented
|
|
||
| #Iterate through the list of sensors, adding it if either monitored | ||
| #conditions has been left out of the config, or that the given sensor | ||
| #is in the monitored conditions list |
There was a problem hiding this comment.
block comment should start with '# '
| ip_address = config.get(CONF_IP_ADDRESS) | ||
| monitored_conditions = config.get(CONF_MONITORED_CONDITIONS, {}) | ||
|
|
||
| #Iterate through the list of sensors, adding it if either monitored |
There was a problem hiding this comment.
block comment should start with '# '
|
|
||
| SENSOR_TYPES = ["production", "daily_production", "7_days_production", | ||
| "lifetime_production", "consumption", "daily_consumption", | ||
| "7_days_consumption", "lifetime_consumption"] |
There was a problem hiding this comment.
continuation line under-indented for visual indent
| "Envoy Lifetime Energy Consumption"] | ||
|
|
||
| SENSOR_TYPES = ["production", "daily_production", "7_days_production", | ||
| "lifetime_production", "consumption", "daily_consumption", |
There was a problem hiding this comment.
continuation line under-indented for visual indent
| DEFAULT_NAMES = ["Envoy Current Energy Production", | ||
| "Envoy Today's Energy Production", | ||
| "Envoy Last Seven Days Energy Production", | ||
| "Envoy Lifetime Energy Production", |
There was a problem hiding this comment.
continuation line under-indented for visual indent
|
|
||
| DEFAULT_NAMES = ["Envoy Current Energy Production", | ||
| "Envoy Today's Energy Production", | ||
| "Envoy Last Seven Days Energy Production", |
There was a problem hiding this comment.
continuation line under-indented for visual indent
| if monitored_conditions == {} or \ | ||
| SENSOR_TYPES[i] in monitored_conditions: | ||
| add_devices([Envoy(ip_address, DEFAULT_NAMES[i], | ||
| SENSOR_TYPES[i])], True) |
There was a problem hiding this comment.
continuation line under-indented for visual indent
| # is in the monitored conditions list | ||
| for i in range(8): | ||
| if monitored_conditions == {} or \ | ||
| SENSOR_TYPES[i] in monitored_conditions: |
There was a problem hiding this comment.
continuation line with same indent as next logical line
| from homeassistant.helpers.entity import Entity | ||
| from homeassistant.components.sensor import PLATFORM_SCHEMA | ||
| import homeassistant.helpers.config_validation as cv | ||
| #from homeassistant.const import CONF_NAME |
There was a problem hiding this comment.
block comment should start with '# '
| import requests | ||
|
|
||
| try: | ||
| response = requests.get(self._url, timeout=5) |
There was a problem hiding this comment.
Why not use the RestData class (components/sensor/rest.py) here? May save a bit of code duplication.
Or maybe even use one instance of RestData and share across all sensors?
|
I have an Envoy-S myself and so far have simply been using |
|
I'd also like to suggest to add some unit tests. The JSON format used by the Envoy is not a public API, and having a few current sample JSON responses to test your code against would help just in case Enphase decides to change the format; you would then just update the test JSON and modify the code until the tests work again. |
| if type == 'production' or type == 'consumption': | ||
| self._unit_of_measurement = 'watts' | ||
| else: | ||
| self._unit_of_measurement = "watt hours" |
There was a problem hiding this comment.
I would prefer W and Wh here. Looking at homeassistant/const.py it seems to be the consensus for other units of measurement to use the abbreviated version. Maybe these two could be added there?
There was a problem hiding this comment.
A couple of really good points, thank you. I'm not really sure how to do unit tests. Is there somewhere you can suggest to learn more about that?
There was a problem hiding this comment.
Maybe the easiest way is by looking at the existing unit tests. Most unit tests focus on two areas - setting up the component, and then testing the various functionalities, transformation, error handling, etc. You can load a static JSON file with load_fixture.
The developer documentation tells you how to run tests in your environment.
| elif self._type == "daily_consumption": | ||
| self._state = int(response_parsed["consumption"][0]["whToday"]) | ||
| elif self._type == "7_days_consumption": | ||
| self._state = int(response_parsed["consumption"][0]["whLastSevenDays"]) |
There was a problem hiding this comment.
line too long (83 > 79 characters)
| elif self._type == "daily_production": | ||
| self._state = int(response_parsed["production"][1]["whToday"]) | ||
| elif self._type == "7_days_production": | ||
| self._state = int(response_parsed["production"][1]["whLastSevenDays"]) |
There was a problem hiding this comment.
line too long (82 > 79 characters)
| self._name = name | ||
|
|
||
|
|
||
| if sensor_type == 'production' or sensor_type == 'consumption': |
| import json | ||
| import voluptuous as vol | ||
|
|
||
| from homeassistant.components.sensor.rest import RestData |
There was a problem hiding this comment.
'homeassistant.components.sensor.rest.RestData' imported but unused
| self._state = int(response_parsed["consumption"][0]["whToday"]) | ||
| elif self._type == "7_days_consumption": | ||
| self._state = \ | ||
| int(response_parsed["consumption"][0]["whLastSevenDays"]) |
There was a problem hiding this comment.
continuation line missing indentation or outdented
| self._state = int(response_parsed["production"][1]["whToday"]) | ||
| elif self._type == "7_days_production": | ||
| self._state = \ | ||
| int(response_parsed["production"][1]["whLastSevenDays"]) |
There was a problem hiding this comment.
continuation line missing indentation or outdented
| if monitored_conditions == {} or \ | ||
| SENSOR_TYPES[i] in monitored_conditions: | ||
| add_devices([Envoy(ip_address, DEFAULT_NAMES[i], | ||
| SENSOR_TYPES[i])], True) |
There was a problem hiding this comment.
continuation line under-indented for visual indent
| if monitored_conditions == {} or \ | ||
| SENSOR_TYPES[i] in monitored_conditions: | ||
| add_devices([Envoy(ip_address, DEFAULT_NAMES[i], | ||
| SENSOR_TYPES[i])], True) |
There was a problem hiding this comment.
continuation line over-indented for visual indent
|
Docs PR spotted. Removed |
MartinHjelmare
left a comment
There was a problem hiding this comment.
The requests and parsing should be broken out into standalone library published on pypi.
MartinHjelmare
left a comment
There was a problem hiding this comment.
Nice! See further comments below.
| For more details about this platform, please refer to the documentation at | ||
| https://home-assistant.io/components/sensor.enphase_envoy/ | ||
| """ | ||
|
|
There was a problem hiding this comment.
Please remove this blank line.
| """ | ||
|
|
||
| import logging | ||
| import voluptuous as vol |
There was a problem hiding this comment.
Please add a blank line between standard library and 3rd party imports and 3rd party and homeassistant imports.
| REQUIREMENTS = ['envoy_reader==0.1'] | ||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
| CONF_IP_ADDRESS = 'ip' |
There was a problem hiding this comment.
Please import and use CONF_IP_ADDRESS from const.py.
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
| CONF_IP_ADDRESS = 'ip' | ||
| CONF_MONITORED_CONDITIONS = 'monitored_conditions' |
There was a problem hiding this comment.
Import and use CONF_MONITORED_CONDITIONS from const.py.
|
|
||
| PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ | ||
| vol.Required(CONF_IP_ADDRESS): cv.string, | ||
| vol.Optional(CONF_MONITORED_CONDITIONS): vol.All(cv.ensure_list) |
There was a problem hiding this comment.
Make sure the selected conditions are in the SENSOR_TYPES list and default to the whole list.
vol.Optional(CONF_MONITORED_CONDITIONS, default=SENSOR_TYPES): vol.All(
cv.ensure_list, [vol.In(SENSOR_TYPES)])| # Iterate through the list of sensors, adding it if either monitored | ||
| # conditions has been left out of the config, or that the given sensor | ||
| # is in the monitored conditions list | ||
| for i in range(8): |
There was a problem hiding this comment.
Just iterate monitored_conditions.
| "Envoy Last Seven Days Energy Consumption", | ||
| "Envoy Lifetime Energy Consumption"] | ||
|
|
||
| SENSOR_TYPES = [ |
There was a problem hiding this comment.
It's cleaner to make this a list of lists or tuples, where the items in the list or tuple are the sensor type and the corresponding sensor name. Then we don't have to keep track of that the order in the list of types matches the order of the list of names. See eg:
https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/sensor/airvisual.py#L43-L47
| # conditions has been left out of the config, or that the given sensor | ||
| # is in the monitored conditions list | ||
| for i in range(8): | ||
| if monitored_conditions == {} or \ |
There was a problem hiding this comment.
Validation should already be done in the config schema, so we don't need to check that here.
| """Initialize the sensor.""" | ||
| self._ip_address = ip_address | ||
| self._name = name | ||
| if sensor_type == 'production' or sensor_type == 'consumption': |
There was a problem hiding this comment.
Move the unit of measurement type to the nested list of sensor types. If no unit should be used, put None in the list.
requirements_all.txt
Outdated
| # homeassistant.components.zha | ||
| zigpy==0.1.0 | ||
|
|
||
| # homeassistant.components.sensor.enphase_envoy |
There was a problem hiding this comment.
Run script/gen_requirements.py to update the requirements file correctly.
|
Ok @MartinHjelmare I did most of the changes you suggested. Thanks for being so detailed. The only thing I did differently is I made the sensors a dictionary with value a tuple of the default name and unit of measurement. SENSORS = {
"production": ("Envoy Current Energy Production", 'W'),
"daily_production": ("Envoy Today's Energy Production", "Wh"),
"7_days_production": ("Envoy Last Seven Days Energy Production", "Wh"),
"lifetime_production": ("Envoy Lifetime Energy Production", "Wh"),
"consumption": ("Envoy Current Energy Consumption", "W"),
"daily_consumption": ("Envoy Today's Energy Consumption", "Wh"),
"7_days_consumption": ("Envoy Last Seven Days Energy Consumption", "Wh"),
"lifetime_consumption": ("Envoy Lifetime Energy Consumption", "Wh")
}I think this accomplishes what you wanted so we aren't trying to keep multiple lists synced up. Also, I can't get the gen_requirements_all.py to run. I'm getting
Any suggestions? Thanks. |
|
Did you run |
| from homeassistant.components.sensor import PLATFORM_SCHEMA | ||
| import homeassistant.helpers.config_validation as cv | ||
| from homeassistant.const import (CONF_IP_ADDRESS) | ||
| from homeassistant.const import (CONF_MONITORED_CONDITIONS) |
There was a problem hiding this comment.
Group imports from the same module. I use isort to do this automatically for new modules. Most advanced editors have plugins that can do this.
from homeassistant.const import (
CONF_IP_ADDRESS, CONF_MONITORED_CONDITIONS)| ip_address = config[CONF_IP_ADDRESS] | ||
| monitored_conditions = config[CONF_MONITORED_CONDITIONS] | ||
|
|
||
| # Iterate through the list of sensors |
There was a problem hiding this comment.
Indent the comment with the code that the comment is aimed at.
| https://home-assistant.io/components/sensor.enphase_envoy/ | ||
| """ | ||
| import logging | ||
| import voluptuous as vol |
There was a problem hiding this comment.
There should be a blank line between logging and voluptuous.
Yep, that was it. Thanks. |
|
Error with envoy which does not measure the Consumption (Envoy s standard). I Left the last 4 lines away from the sensor configuration. Log Details (ERROR) Invalid config for [sensor.enphase_envoy]: required key not provided @ data['ip_address']. Got None. (See ?, line ?). Please check the docs at https://home-assistant.io/components/sensor.enphase_envoy |
|
Please open an issue. |
Description:
Add component to get energy consumption and solar panel energy production from an Enphase Envoy
Related issue (if applicable): fixes #
Pull request in home-assistant.github.io with documentation (if applicable): home-assistant/home-assistant.io#5638
Example entry for
configuration.yaml(if applicable):Checklist:
tox. Your PR cannot be merged unless tests passIf user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
REQUIREMENTSvariable (example).requirements_all.txtby runningscript/gen_requirements_all.py..coveragerc.If the code does not interact with devices: