-
-
Notifications
You must be signed in to change notification settings - Fork 33.8k
Add Enphase Envoy component #15081
8000New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Enphase Envoy component #15081
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
too many blank lines (2)
|
||
|
||
|
||
class Envoy(Entity): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
too many blank lines (3)
#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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
line too long (94 > 79 characters)
add_devices([Envoy(ip_address, DEFAULT_NAMES[i], \ | ||
SENSOR_TYPES[i])], True) | ||
|
||
class Envoy(Entity): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expected 2 blank lines, found 1
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
block comment should start with '# '
import requests | ||
|
||
try: | ||
response = requests.get(self._url, timeout=5) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
line too long (82 > 79 characters)
self._name = name | ||
|
||
|
||
if sensor_type == 'production' or sensor_type == 'consumption': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to descri F438 be this comment to others. Learn more.
too many blank lines (2)
import json | ||
import voluptuous as vol | ||
|
||
from homeassistant.components.sensor.rest import RestData |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
10000 The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
continuation line over-indented for visual indent
Docs PR spotted. Removed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The requests and parsing should be broken out into standalone library published on pypi.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove this blank line.
""" | ||
|
||
import logging | ||
import voluptuous as vol |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just iterate monitored_conditions
.
"Envoy Last Seven Days Energy Consumption", | ||
"Envoy Lifetime Energy Consumption"] | ||
|
||
SENSOR_TYPES = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
@@ -1451,3 +1454,5 @@ zigpy-xbee==0.1.1 | |||
|
|||
# homeassistant.components.zha | |||
zigpy==0.1.0 | |||
|
|||
# homeassistant.components.sensor.enphase_envoy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be a blank line between logging and voluptuous.
Yep, that was it. Thanks. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great!
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:
REQUIREMENTS
variable (example).requirements_all.txt
by runningscript/gen_requirements_all.py
..coveragerc
.If the code does not interact with devices: