From 949e02fa246bec76c47a481404a75113fcc69c04 Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Sun, 28 Apr 2019 15:36:34 +0200 Subject: [PATCH 01/20] Initial commit for Essent --- .coveragerc | 1 + homeassistant/components/essent/__init__.py | 1 + homeassistant/components/essent/manifest.json | 8 + homeassistant/components/essent/sensor.py | 139 ++++++++++++++++++ requirements_all.txt | 3 + 5 files changed, 152 insertions(+) create mode 100644 homeassistant/components/essent/__init__.py create mode 100644 homeassistant/components/essent/manifest.json create mode 100644 homeassistant/components/essent/sensor.py diff --git a/.coveragerc b/.coveragerc index 3aeb2b5c1874f5..a574c2ead43429 100644 --- a/.coveragerc +++ b/.coveragerc @@ -173,6 +173,7 @@ omit = homeassistant/components/esphome/light.py homeassistant/components/esphome/sensor.py homeassistant/components/esphome/switch.py + homeassistant/components/essent/sensor.py homeassistant/components/etherscan/sensor.py homeassistant/components/eufy/* homeassistant/components/everlights/light.py diff --git a/homeassistant/components/essent/__init__.py b/homeassistant/components/essent/__init__.py new file mode 100644 index 00000000000000..42e867c6d21448 --- /dev/null +++ b/homeassistant/components/essent/__init__.py @@ -0,0 +1 @@ +"""The Essent component.""" diff --git a/homeassistant/components/essent/manifest.json b/homeassistant/components/essent/manifest.json new file mode 100644 index 00000000000000..cba51534f775b8 --- /dev/null +++ b/homeassistant/components/essent/manifest.json @@ -0,0 +1,8 @@ +{ + "domain": "essent", + "name": "Essent", + "documentation": "https://www.home-assistant.io/components/essent", + "requirements": ["requests==2.21.0"], + "dependencies": [], + "codeowners": ["@TheLastProject"] +} diff --git a/homeassistant/components/essent/sensor.py b/homeassistant/components/essent/sensor.py new file mode 100644 index 00000000000000..92cae5569bec81 --- /dev/null +++ b/homeassistant/components/essent/sensor.py @@ -0,0 +1,139 @@ +from homeassistant.const import ENERGY_KILO_WATT_HOUR, STATE_UNKNOWN +from homeassistant.helpers.entity import Entity + +import xml.etree.ElementTree as ET +import requests + +DOMAIN = 'essent' + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the Essent platform.""" + username = config['username'] + password = config['password'] + for meter in EssentBase(username, password).retrieve_meters(): + for tariff in ['L', 'N', 'H']: + add_devices([EssentMeter(username, password, meter, tariff)]) + + +class EssentBase(): + """Essent Base info.""" + + def __init__(self, username, password): + """Initialize the Essent API.""" + self._session = requests.session() + + # First, auth ourselves + authentication_xml = """ + + + + + false + + """ + + auth_request = self._session.post('https://api.essent.nl/selfservice/user/authenticateUser', data=authentication_xml.format(username, password)) + auth_request.raise_for_status() # Throw exception if auth fails + + def get_session(self): + return self._session + + def retrieve_meters(self): + meters = [] + + # Get customer details + customer_details_request = self._session.get('https://api.essent.nl/selfservice/customer/getCustomerDetails', params={'GetContracts': 'false'}) + customer_details_request.raise_for_status() # Throw exception if getting customer details fails + + # Parse our agreement ID + agreement_id = ET.fromstring(customer_details_request.text).find('response').find('Partner').find('BusinessAgreements').find('BusinessAgreement').findtext('AgreementID') + + # Prepare retrieving business partner details + business_partner_details_xml = """ + + {} + true + + """ + + # Get business partner details + business_details_request = self._session.post('https://api.essent.nl/selfservice/customer/getBusinessPartnerDetails', data=business_partner_details_xml.format(agreement_id)) + business_details_request.raise_for_status() # Throw exception if getting business partner details fails + + # Parse out our meters + contracts = ET.fromstring(business_details_request.text).find('response').find('Partner').find('BusinessAgreements').find('BusinessAgreement').find('Connections').find('Connection').find('Contracts').findall('Contract') + for contract in contracts: + meters.append(contract.findtext('ConnectEAN')) + + return meters + +class EssentMeter(Entity): + """Representation of Essent measurements.""" + + def __init__(self, username, password, meter, tariff): + """Initialize the sensor.""" + self._state = None + self._username = username + self._password = password + self._meter = meter + self._tariff = tariff + self._meter_type = STATE_UNKNOWN + self._meter_unit = None + + @property + def name(self): + """Return the name of the sensor.""" + return "Essent {} ({})".format(self._meter_type, self._tariff) + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + if self._meter_unit and self._meter_unit.lower() == "kwh": + return ENERGY_KILO_WATT_HOUR + + return self._meter_unit + + def update(self): + """Fetch the energy usage.""" + # Retrieve an authenticated session + session = EssentBase(self._username, self._password).get_session() + + # Get current datetime according to server + datetime_request = session.get('https://api.essent.nl/generic/getDateTime') + datetime_request.raise_for_status() # Throw exception if getting datetime fails + datetime = ET.fromstring(datetime_request.text).findtext('Timestamp') + + # Prepare reading the meter + meter_reading_xml = """ + + + + {} + + + true2000-01-01T00:00:00+02:00{} + """ + + # Request meter info + meter_request = session.post('https://api.essent.nl/selfservice/customer/getMeterReadingHistory', data=meter_reading_xml.format(self._meter, datetime)) + meter_request.raise_for_status() # Throw exception if getting meter history fails + + # Parse out into the root of our data + info_base = ET.fromstring(meter_request.text).find('response').find('Installations').find('Installation') + + # Set meter type now that it's known + self._meter_type = info_base.find('EnergyType').get('text') + + # Retrieve the current status + for register in info_base.find('Meters').find('Meter').find('Registers').findall('Register'): + if register.findtext('MeteringDirection') != 'LVR' or register.findtext('TariffType') != self._tariff: + continue + + # Set unit of measurement now that it's known + self._meter_unit = register.findtext('MeasureUnit') + self._state = register.find('MeterReadings').find('MeterReading').findtext('ReadingResultValue') diff --git a/requirements_all.txt b/requirements_all.txt index b510ebbc0de9cb..acf24353fba5bb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1509,6 +1509,9 @@ recollect-waste==1.0.1 # homeassistant.components.rainmachine regenmaschine==1.4.0 +# homeassistant.components.essent +requests==2.21.0 + # homeassistant.components.python_script restrictedpython==4.0b8 From b3d2609c5a6a44845c09f807612856a664a78f04 Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Sun, 28 Apr 2019 16:15:25 +0200 Subject: [PATCH 02/20] Cleanup Essent component --- homeassistant/components/essent/sensor.py | 96 +++++++++++++++++------ 1 file changed, 74 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/essent/sensor.py b/homeassistant/components/essent/sensor.py index 92cae5569bec81..a5e4428a8ebd11 100644 --- a/homeassistant/components/essent/sensor.py +++ b/homeassistant/components/essent/sensor.py @@ -1,13 +1,19 @@ -from homeassistant.const import ENERGY_KILO_WATT_HOUR, STATE_UNKNOWN -from homeassistant.helpers.entity import Entity +"""Support for Essent API.""" import xml.etree.ElementTree as ET + import requests +from homeassistant.const import ENERGY_KILO_WATT_HOUR, STATE_UNKNOWN +from homeassistant.helpers.entity import Entity + DOMAIN = 'essent' +API_BASE = 'https://api.essent.nl/selfservice/' + + def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the Essent platform.""" + """Set up the Essent platform.""" username = config['username'] password = config['password'] for meter in EssentBase(username, password).retrieve_meters(): @@ -16,7 +22,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class EssentBase(): - """Essent Base info.""" + """Essent Base.""" def __init__(self, username, password): """Initialize the Essent API.""" @@ -32,21 +38,34 @@ def __init__(self, username, password): """ - auth_request = self._session.post('https://api.essent.nl/selfservice/user/authenticateUser', data=authentication_xml.format(username, password)) - auth_request.raise_for_status() # Throw exception if auth fails + auth_request = self._session.post( + API_BASE + 'user/authenticateUser', + data=authentication_xml.format(username, password)) + # Throw exception if auth fails + auth_request.raise_for_status() def get_session(self): + """Return the active session.""" return self._session def retrieve_meters(self): + """Retrieve the IDs of the meters used by Essent.""" meters = [] # Get customer details - customer_details_request = self._session.get('https://api.essent.nl/selfservice/customer/getCustomerDetails', params={'GetContracts': 'false'}) - customer_details_request.raise_for_status() # Throw exception if getting customer details fails + customer_details_request = self._session.get( + API_BASE + 'customer/getCustomerDetails', + params={'GetContracts': 'false'}) + # Throw exception if getting customer details fails + customer_details_request.raise_for_status() # Parse our agreement ID - agreement_id = ET.fromstring(customer_details_request.text).find('response').find('Partner').find('BusinessAgreements').find('BusinessAgreement').findtext('AgreementID') + agreement_id = ET.fromstring(customer_details_request.text) \ + .find('response') \ + .find('Partner') \ + .find('BusinessAgreements') \ + .find('BusinessAgreement') \ + .findtext('AgreementID') # Prepare retrieving business partner details business_partner_details_xml = """ @@ -57,16 +76,29 @@ def retrieve_meters(self): """ # Get business partner details - business_details_request = self._session.post('https://api.essent.nl/selfservice/customer/getBusinessPartnerDetails', data=business_partner_details_xml.format(agreement_id)) - business_details_request.raise_for_status() # Throw exception if getting business partner details fails + business_details_request = self._session.post( + API_BASE + 'customer/getBusinessPartnerDetails', + data=business_partner_details_xml.format(agreement_id)) + # Throw exception if getting business partner details fails + business_details_request.raise_for_status() # Parse out our meters - contracts = ET.fromstring(business_details_request.text).find('response').find('Partner').find('BusinessAgreements').find('BusinessAgreement').find('Connections').find('Connection').find('Contracts').findall('Contract') + contracts = ET.fromstring(business_details_request.text) \ + .find('response') \ + .find('Partner') \ + .find('BusinessAgreements') \ + .find('BusinessAgreement') \ + .find('Connections') \ + .find('Connection') \ + .find('Contracts') \ + .findall('Contract') + for contract in contracts: meters.append(contract.findtext('ConnectEAN')) return meters + class EssentMeter(Entity): """Representation of Essent measurements.""" @@ -93,7 +125,7 @@ def state(self): @property def unit_of_measurement(self): """Return the unit of measurement.""" - if self._meter_unit and self._meter_unit.lower() == "kwh": + if self._meter_unit and self._meter_unit.lower() == 'kwh': return ENERGY_KILO_WATT_HOUR return self._meter_unit @@ -104,8 +136,10 @@ def update(self): session = EssentBase(self._username, self._password).get_session() # Get current datetime according to server - datetime_request = session.get('https://api.essent.nl/generic/getDateTime') - datetime_request.raise_for_status() # Throw exception if getting datetime fails + datetime_request = session.get( + API_BASE + 'generic/getDateTime') + # Throw exception if getting datetime fails + datetime_request.raise_for_status() datetime = ET.fromstring(datetime_request.text).findtext('Timestamp') # Prepare reading the meter @@ -116,24 +150,42 @@ def update(self): {} - true2000-01-01T00:00:00+02:00{} + true + + 2000-01-01T00:00:00+02:00 + {} + + """ # Request meter info - meter_request = session.post('https://api.essent.nl/selfservice/customer/getMeterReadingHistory', data=meter_reading_xml.format(self._meter, datetime)) - meter_request.raise_for_status() # Throw exception if getting meter history fails + meter_request = session.post( + API_BASE + 'customer/getMeterReadingHistory', + data=meter_reading_xml.format(self._meter, datetime)) + # Throw exception if getting meter history fails + meter_request.raise_for_status() # Parse out into the root of our data - info_base = ET.fromstring(meter_request.text).find('response').find('Installations').find('Installation') + info_base = ET.fromstring(meter_request.text) \ + .find('response') \ + .find('Installations') \ + .find('Installation') # Set meter type now that it's known self._meter_type = info_base.find('EnergyType').get('text') # Retrieve the current status - for register in info_base.find('Meters').find('Meter').find('Registers').findall('Register'): - if register.findtext('MeteringDirection') != 'LVR' or register.findtext('TariffType') != self._tariff: + registers = info_base.find('Meters') \ + .find('Meter') \ + .find('Registers') \ + .findall('Register') + for register in registers: + if (register.findtext('MeteringDirection') != 'LVR' or + register.findtext('TariffType') != self._tariff): continue # Set unit of measurement now that it's known self._meter_unit = register.findtext('MeasureUnit') - self._state = register.find('MeterReadings').find('MeterReading').findtext('ReadingResultValue') + self._state = register.find('MeterReadings') \ + .find('MeterReading') \ + .findtext('ReadingResultValue') From 489d7a09d11d7cf010a8a78505d263667b8cfab1 Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Sun, 28 Apr 2019 16:40:28 +0200 Subject: [PATCH 03/20] Update CODEOWNERS --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/CODEOWNERS b/CODEOWNERS index 38de5b1fe6f11e..103e6ec9a68ab6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -71,6 +71,7 @@ homeassistant/components/ephember/* @ttroy50 homeassistant/components/epsonworkforce/* @ThaStealth homeassistant/components/eq3btsmart/* @rytilahti homeassistant/components/esphome/* @OttoWinter +homeassistant/components/essent/* @TheLastProject homeassistant/components/file/* @fabaff homeassistant/components/filter/* @dgomes homeassistant/components/fitbit/* @robbiet480 From 82e1d1dcddd8bab0a082ca54d213b14f34be717e Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Sun, 28 Apr 2019 18:05:33 +0200 Subject: [PATCH 04/20] Move stuff to PyEssent --- homeassistant/components/essent/manifest.json | 2 +- homeassistant/components/essent/sensor.py | 82 +++---------------- 2 files changed, 12 insertions(+), 72 deletions(-) diff --git a/homeassistant/components/essent/manifest.json b/homeassistant/components/essent/manifest.json index cba51534f775b8..fe5a12485d3065 100644 --- a/homeassistant/components/essent/manifest.json +++ b/homeassistant/components/essent/manifest.json @@ -2,7 +2,7 @@ "domain": "essent", "name": "Essent", "documentation": "https://www.home-assistant.io/components/essent", - "requirements": ["requests==2.21.0"], + "requirements": ["PyEssent==0.1"], "dependencies": [], "codeowners": ["@TheLastProject"] } diff --git a/homeassistant/components/essent/sensor.py b/homeassistant/components/essent/sensor.py index a5e4428a8ebd11..f97087bafb53af 100644 --- a/homeassistant/components/essent/sensor.py +++ b/homeassistant/components/essent/sensor.py @@ -2,15 +2,13 @@ import xml.etree.ElementTree as ET -import requests +from pyessent import PyEssent from homeassistant.const import ENERGY_KILO_WATT_HOUR, STATE_UNKNOWN from homeassistant.helpers.entity import Entity DOMAIN = 'essent' -API_BASE = 'https://api.essent.nl/selfservice/' - def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the Essent platform.""" @@ -26,38 +24,18 @@ class EssentBase(): def __init__(self, username, password): """Initialize the Essent API.""" - self._session = requests.session() - - # First, auth ourselves - authentication_xml = """ - - - - - false - - """ - - auth_request = self._session.post( - API_BASE + 'user/authenticateUser', - data=authentication_xml.format(username, password)) - # Throw exception if auth fails - auth_request.raise_for_status() + self._essent = PyEssent(username, password) def get_session(self): """Return the active session.""" - return self._session + return self._essent def retrieve_meters(self): """Retrieve the IDs of the meters used by Essent.""" meters = [] # Get customer details - customer_details_request = self._session.get( - API_BASE + 'customer/getCustomerDetails', - params={'GetContracts': 'false'}) - # Throw exception if getting customer details fails - customer_details_request.raise_for_status() + customer_details_request = self._essent.Customer.get_customer_details() # Parse our agreement ID agreement_id = ET.fromstring(customer_details_request.text) \ @@ -67,23 +45,11 @@ def retrieve_meters(self): .find('BusinessAgreement') \ .findtext('AgreementID') - # Prepare retrieving business partner details - business_partner_details_xml = """ - - {} - true - - """ - # Get business partner details - business_details_request = self._session.post( - API_BASE + 'customer/getBusinessPartnerDetails', - data=business_partner_details_xml.format(agreement_id)) - # Throw exception if getting business partner details fails - business_details_request.raise_for_status() + business_partner_details_request = self._essent.Customer.get_business_partner_details(agreement_id) # Parse out our meters - contracts = ET.fromstring(business_details_request.text) \ + contracts = ET.fromstring(business_partner_details_request.text) \ .find('response') \ .find('Partner') \ .find('BusinessAgreements') \ @@ -133,37 +99,11 @@ def unit_of_measurement(self): def update(self): """Fetch the energy usage.""" # Retrieve an authenticated session - session = EssentBase(self._username, self._password).get_session() - - # Get current datetime according to server - datetime_request = session.get( - API_BASE + 'generic/getDateTime') - # Throw exception if getting datetime fails - datetime_request.raise_for_status() - datetime = ET.fromstring(datetime_request.text).findtext('Timestamp') - - # Prepare reading the meter - meter_reading_xml = """ - - - - {} - - - true - - 2000-01-01T00:00:00+02:00 - {} - - - """ - - # Request meter info - meter_request = session.post( - API_BASE + 'customer/getMeterReadingHistory', - data=meter_reading_xml.format(self._meter, datetime)) - # Throw exception if getting meter history fails - meter_request.raise_for_status() + essent = EssentBase(self._username, self._password).get_session() + + # Read the meter + meter_request = essent.Customer.get_meter_reading_history( + self._meter, only_last_meter_reading=True) # Parse out into the root of our data info_base = ET.fromstring(meter_request.text) \ From e1b57b833ad52a4b73795eee4be981c0bcdfb9b9 Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Sun, 28 Apr 2019 18:07:44 +0200 Subject: [PATCH 05/20] Update requirements_all --- requirements_all.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements_all.txt b/requirements_all.txt index acf24353fba5bb..22a835ea21747f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -41,6 +41,9 @@ Mastodon.py==1.3.1 # homeassistant.components.orangepi_gpio OPi.GPIO==0.3.6 +# homeassistant.components.essent +PyEssent==0.1 + # homeassistant.components.github PyGithub==1.43.5 @@ -1509,9 +1512,6 @@ recollect-waste==1.0.1 # homeassistant.components.rainmachine regenmaschine==1.4.0 -# homeassistant.components.essent -requests==2.21.0 - # homeassistant.components.python_script restrictedpython==4.0b8 From ee2fca8638535ce8c29e5863d7e3f013d0341654 Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Sun, 28 Apr 2019 20:00:33 +0200 Subject: [PATCH 06/20] Fix PyEssent --- homeassistant/components/essent/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/essent/manifest.json b/homeassistant/components/essent/manifest.json index fe5a12485d3065..f5fdcb2bf15007 100644 --- a/homeassistant/components/essent/manifest.json +++ b/homeassistant/components/essent/manifest.json @@ -2,7 +2,7 @@ "domain": "essent", "name": "Essent", "documentation": "https://www.home-assistant.io/components/essent", - "requirements": ["PyEssent==0.1"], + "requirements": ["PyEssent==0.8"], "dependencies": [], "codeowners": ["@TheLastProject"] } From 5fe1ca4556f48f9d6ffce154727892fe786939bb Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Sun, 28 Apr 2019 20:12:43 +0200 Subject: [PATCH 07/20] Move meter list to PyEssent library --- homeassistant/components/essent/manifest.json | 2 +- homeassistant/components/essent/sensor.py | 32 +------------------ 2 files changed, 2 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/essent/manifest.json b/homeassistant/components/essent/manifest.json index f5fdcb2bf15007..8c95c391443461 100644 --- a/homeassistant/components/essent/manifest.json +++ b/homeassistant/components/essent/manifest.json @@ -2,7 +2,7 @@ "domain": "essent", "name": "Essent", "documentation": "https://www.home-assistant.io/components/essent", - "requirements": ["PyEssent==0.8"], + "requirements": ["PyEssent==0.9"], "dependencies": [], "codeowners": ["@TheLastProject"] } diff --git a/homeassistant/components/essent/sensor.py b/homeassistant/components/essent/sensor.py index f97087bafb53af..cab2dd1cb9df28 100644 --- a/homeassistant/components/essent/sensor.py +++ b/homeassistant/components/essent/sensor.py @@ -32,37 +32,7 @@ def get_session(self): def retrieve_meters(self): """Retrieve the IDs of the meters used by Essent.""" - meters = [] - - # Get customer details - customer_details_request = self._essent.Customer.get_customer_details() - - # Parse our agreement ID - agreement_id = ET.fromstring(customer_details_request.text) \ - .find('response') \ - .find('Partner') \ - .find('BusinessAgreements') \ - .find('BusinessAgreement') \ - .findtext('AgreementID') - - # Get business partner details - business_partner_details_request = self._essent.Customer.get_business_partner_details(agreement_id) - - # Parse out our meters - contracts = ET.fromstring(business_partner_details_request.text) \ - .find('response') \ - .find('Partner') \ - .find('BusinessAgreements') \ - .find('BusinessAgreement') \ - .find('Connections') \ - .find('Connection') \ - .find('Contracts') \ - .findall('Contract') - - for contract in contracts: - meters.append(contract.findtext('ConnectEAN')) - - return meters + return self._essent.get_EANs() class EssentMeter(Entity): From 06427f5dc7f1b9f622754f7a0843bb2e1c1ffc63 Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Sun, 28 Apr 2019 20:13:22 +0200 Subject: [PATCH 08/20] Update requirements_all --- requirements_all.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_all.txt b/requirements_all.txt index 22a835ea21747f..755d43278ed79a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -42,7 +42,7 @@ Mastodon.py==1.3.1 OPi.GPIO==0.3.6 # homeassistant.components.essent -PyEssent==0.1 +PyEssent==0.9 # homeassistant.components.github PyGithub==1.43.5 From dc0357fd715f3757f307756c1be3f2172ef6f487 Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Sun, 28 Apr 2019 21:06:25 +0200 Subject: [PATCH 09/20] Only check for updates once an hour --- homeassistant/components/essent/sensor.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/essent/sensor.py b/homeassistant/components/essent/sensor.py index cab2dd1cb9df28..16747238dc39ae 100644 --- a/homeassistant/components/essent/sensor.py +++ b/homeassistant/components/essent/sensor.py @@ -1,14 +1,17 @@ """Support for Essent API.""" - +from datetime import timedelta import xml.etree.ElementTree as ET from pyessent import PyEssent from homeassistant.const import ENERGY_KILO_WATT_HOUR, STATE_UNKNOWN from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle DOMAIN = 'essent' +MIN_TIME_BETWEEN_UPDATES = timedelta(hours=1) + def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the Essent platform.""" @@ -66,6 +69,7 @@ def unit_of_measurement(self): return self._meter_unit + @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): """Fetch the energy usage.""" # Retrieve an authenticated session From f6bf934e19183c353190babf81e55e471c2b4fa5 Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Mon, 29 Apr 2019 19:10:35 +0200 Subject: [PATCH 10/20] Use PyEssent 0.10 --- homeassistant/components/essent/manifest.json | 2 +- homeassistant/components/essent/sensor.py | 34 ++++--------------- 2 files changed, 7 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/essent/manifest.json b/homeassistant/components/essent/manifest.json index 8c95c391443461..49189f6bacb15b 100644 --- a/homeassistant/components/essent/manifest.json +++ b/homeassistant/components/essent/manifest.json @@ -2,7 +2,7 @@ "domain": "essent", "name": "Essent", "documentation": "https://www.home-assistant.io/components/essent", - "requirements": ["PyEssent==0.9"], + "requirements": ["PyEssent==0.10"], "dependencies": [], "codeowners": ["@TheLastProject"] } diff --git a/homeassistant/components/essent/sensor.py b/homeassistant/components/essent/sensor.py index 16747238dc39ae..1c3db8f195225b 100644 --- a/homeassistant/components/essent/sensor.py +++ b/homeassistant/components/essent/sensor.py @@ -1,6 +1,5 @@ """Support for Essent API.""" from datetime import timedelta -import xml.etree.ElementTree as ET from pyessent import PyEssent @@ -76,30 +75,9 @@ def update(self): essent = EssentBase(self._username, self._password).get_session() # Read the meter - meter_request = essent.Customer.get_meter_reading_history( - self._meter, only_last_meter_reading=True) - - # Parse out into the root of our data - info_base = ET.fromstring(meter_request.text) \ - .find('response') \ - .find('Installations') \ - .find('Installation') - - # Set meter type now that it's known - self._meter_type = info_base.find('EnergyType').get('text') - - # Retrieve the current status - registers = info_base.find('Meters') \ - .find('Meter') \ - .find('Registers') \ - .findall('Register') - for register in registers: - if (register.findtext('MeteringDirection') != 'LVR' or - register.findtext('TariffType') != self._tariff): - continue - - # Set unit of measurement now that it's known - self._meter_unit = register.findtext('MeasureUnit') - self._state = register.find('MeterReadings') \ - .find('MeterReading') \ - .findtext('ReadingResultValue') + data = essent.read_meter(self._meter, only_last_meter_reading=True) + + self._meter_type = data['type'] + self._meter_unit = data['values']['LVR'][self._tariff]['unit'] + + self._state = data['values']['LVR'][self._tariff]['records'][0] From 589348bbb2a031e52ea20eaee8c42327ad08c5fa Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Mon, 29 Apr 2019 19:22:19 +0200 Subject: [PATCH 11/20] Fixing up Essent component --- homeassistant/components/essent/sensor.py | 28 +++++++++++++++-------- requirements_all.txt | 2 +- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/essent/sensor.py b/homeassistant/components/essent/sensor.py index 1c3db8f195225b..27ee5dff93c40f 100644 --- a/homeassistant/components/essent/sensor.py +++ b/homeassistant/components/essent/sensor.py @@ -2,23 +2,32 @@ from datetime import timedelta from pyessent import PyEssent +import voluptuous as vol -from homeassistant.const import ENERGY_KILO_WATT_HOUR, STATE_UNKNOWN +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ( + CONF_PASSWORD, CONF_USERNAME, ENERGY_KILO_WATT_HOUR) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -from homeassistant.util import Throttle -DOMAIN = 'essent' - -MIN_TIME_BETWEEN_UPDATES = timedelta(hours=1) +SCAN_INTERVAL = timedelta(hours=1) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string +}) def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the Essent platform.""" - username = config['username'] - password = config['password'] + username = config[CONF_USERNAME] + password = config[CONF_PASSWORD] + + devices = [] for meter in EssentBase(username, password).retrieve_meters(): for tariff in ['L', 'N', 'H']: - add_devices([EssentMeter(username, password, meter, tariff)]) + devices.append(EssentMeter(username, password, meter, tariff)) + + add_devices(devices, True) class EssentBase(): @@ -47,7 +56,7 @@ def __init__(self, username, password, meter, tariff): self._password = password self._meter = meter self._tariff = tariff - self._meter_type = STATE_UNKNOWN + self._meter_type = None self._meter_unit = None @property @@ -68,7 +77,6 @@ def unit_of_measurement(self): return self._meter_unit - @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): """Fetch the energy usage.""" # Retrieve an authenticated session diff --git a/requirements_all.txt b/requirements_all.txt index 755d43278ed79a..1b3fe6b4b4e4d8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -42,7 +42,7 @@ Mastodon.py==1.3.1 OPi.GPIO==0.3.6 # homeassistant.components.essent -PyEssent==0.9 +PyEssent==0.10 # homeassistant.components.github PyGithub==1.43.5 From 897bee502519ff29e6091f68ef6cab52c70e2b50 Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Mon, 29 Apr 2019 19:27:56 +0200 Subject: [PATCH 12/20] Fix crash --- homeassistant/components/essent/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/essent/sensor.py b/homeassistant/components/essent/sensor.py index 27ee5dff93c40f..f36a2dd178f5cd 100644 --- a/homeassistant/components/essent/sensor.py +++ b/homeassistant/components/essent/sensor.py @@ -88,4 +88,4 @@ def update(self): self._meter_type = data['type'] self._meter_unit = data['values']['LVR'][self._tariff]['unit'] - self._state = data['values']['LVR'][self._tariff]['records'][0] + self._state = next(iter(data['values']['LVR'][self._tariff]['records'])) From 9f343a74e01f9f890ac7e55eb16a5a8e811751bc Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Mon, 29 Apr 2019 19:32:42 +0200 Subject: [PATCH 13/20] Don't add unused meter/tariff combos --- homeassistant/components/essent/sensor.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/essent/sensor.py b/homeassistant/components/essent/sensor.py index f36a2dd178f5cd..877e308ab11d89 100644 --- a/homeassistant/components/essent/sensor.py +++ b/homeassistant/components/essent/sensor.py @@ -25,7 +25,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): devices = [] for meter in EssentBase(username, password).retrieve_meters(): for tariff in ['L', 'N', 'H']: - devices.append(EssentMeter(username, password, meter, tariff)) + try: + devices.append(EssentMeter(username, password, meter, tariff)) + except KeyError: + pass # Don't add devices for non-existing meter/tariff combinations add_devices(devices, True) From f869cdc29c0e02f328db07c4ec2bc980696cf8cc Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Mon, 29 Apr 2019 19:40:04 +0200 Subject: [PATCH 14/20] Fix lint --- homeassistant/components/essent/sensor.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/essent/sensor.py b/homeassistant/components/essent/sensor.py index 877e308ab11d89..6d60e2c8e0c59d 100644 --- a/homeassistant/components/essent/sensor.py +++ b/homeassistant/components/essent/sensor.py @@ -17,6 +17,7 @@ vol.Required(CONF_PASSWORD): cv.string }) + def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the Essent platform.""" username = config[CONF_USERNAME] @@ -28,7 +29,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): try: devices.append(EssentMeter(username, password, meter, tariff)) except KeyError: - pass # Don't add devices for non-existing meter/tariff combinations + # Don't add devices for non-existing meter/tariff combinations + pass add_devices(devices, True) @@ -91,4 +93,5 @@ def update(self): self._meter_type = data['type'] self._meter_unit = data['values']['LVR'][self._tariff]['unit'] - self._state = next(iter(data['values']['LVR'][self._tariff]['records'])) + self._state = next( + iter(data['values']['LVR'][self._tariff]['records'].values())) From 661dbd39cb0bde7f7b28714da38a40b7849191e9 Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Mon, 29 Apr 2019 20:03:13 +0200 Subject: [PATCH 15/20] Get tariffs per meter --- homeassistant/components/essent/sensor.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/essent/sensor.py b/homeassistant/components/essent/sensor.py index 6d60e2c8e0c59d..90e07c17462be5 100644 --- a/homeassistant/components/essent/sensor.py +++ b/homeassistant/components/essent/sensor.py @@ -24,13 +24,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): password = config[CONF_PASSWORD] devices = [] - for meter in EssentBase(username, password).retrieve_meters(): - for tariff in ['L', 'N', 'H']: - try: - devices.append(EssentMeter(username, password, meter, tariff)) - except KeyError: - # Don't add devices for non-existing meter/tariff combinations - pass + essent = EssentBase(username, password) + for meter in essent.retrieve_meters(): + for tariff in essent.retrieve_meter_tariffs(meter): + devices.append(EssentMeter(username, password, meter, tariff)) add_devices(devices, True) @@ -50,6 +47,12 @@ def retrieve_meters(self): """Retrieve the IDs of the meters used by Essent.""" return self._essent.get_EANs() + def retrieve_meter_tariffs(self, meter): + """Retrieve the tariffs for this meter.""" + data = self._essent.read_meter(meter, only_last_meter_reading=True) + + return data['values']['LVR'].keys() + class EssentMeter(Entity): """Representation of Essent measurements.""" From 5a2e8e3ee042b83d7035f739c1aeaf66d3a0f179 Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Mon, 29 Apr 2019 22:05:05 +0200 Subject: [PATCH 16/20] Don't hammer Essent API --- homeassistant/components/essent/sensor.py | 64 +++++++++++++---------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/essent/sensor.py b/homeassistant/components/essent/sensor.py index 90e07c17462be5..86e839fdf10e11 100644 --- a/homeassistant/components/essent/sensor.py +++ b/homeassistant/components/essent/sensor.py @@ -9,6 +9,7 @@ CONF_PASSWORD, CONF_USERNAME, ENERGY_KILO_WATT_HOUR) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle SCAN_INTERVAL = timedelta(hours=1) @@ -23,13 +24,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): username = config[CONF_USERNAME] password = config[CONF_PASSWORD] - devices = [] essent = EssentBase(username, password) - for meter in essent.retrieve_meters(): - for tariff in essent.retrieve_meter_tariffs(meter): - devices.append(EssentMeter(username, password, meter, tariff)) - - add_devices(devices, True) + add_devices(essent.retrieve_meters(), True) class EssentBase(): @@ -37,7 +33,12 @@ class EssentBase(): def __init__(self, username, password): """Initialize the Essent API.""" - self._essent = PyEssent(username, password) + self._username = username + self._password = password + self._meters = [] + self._meter_data = {} + + self.update() def get_session(self): """Return the active session.""" @@ -45,32 +46,43 @@ def get_session(self): def retrieve_meters(self): """Retrieve the IDs of the meters used by Essent.""" - return self._essent.get_EANs() + meters = [] + for meter in self._meters: + data = self._meter_data[meter] + self._meter_data[meter] = data + for tariff in data['values']['LVR'].keys(): + meters.append(EssentMeter(self, meter, data['type'], tariff, data['values']['LVR'][tariff]['unit'])) - def retrieve_meter_tariffs(self, meter): - """Retrieve the tariffs for this meter.""" - data = self._essent.read_meter(meter, only_last_meter_reading=True) + return meters - return data['values']['LVR'].keys() + def retrieve_meter_data(self, meter): + """Retrieve the data for this meter.""" + return self._meter_data[meter] + + @Throttle(timedelta(minutes=30)) + def update(self): + essent = PyEssent(self._username, self._password) + self._meters = essent.get_EANs() + for meter in self._meters: + self._meter_data[meter] = essent.read_meter(meter, only_last_meter_reading=True) class EssentMeter(Entity): """Representation of Essent measurements.""" - def __init__(self, username, password, meter, tariff): + def __init__(self, essent_base, meter, meter_type, tariff, unit): """Initialize the sensor.""" self._state = None - self._username = username - self._password = password + self._essent_base = essent_base self._meter = meter + self._type = meter_type self._tariff = tariff - self._meter_type = None - self._meter_unit = None + self._unit = unit @property def name(self): """Return the name of the sensor.""" - return "Essent {} ({})".format(self._meter_type, self._tariff) + return "Essent {} ({})".format(self._type, self._tariff) @property def state(self): @@ -80,21 +92,19 @@ def state(self): @property def unit_of_measurement(self): """Return the unit of measurement.""" - if self._meter_unit and self._meter_unit.lower() == 'kwh': + if self._unit.lower() == 'kwh': return ENERGY_KILO_WATT_HOUR - return self._meter_unit + return self._unit def update(self): """Fetch the energy usage.""" - # Retrieve an authenticated session - essent = EssentBase(self._username, self._password).get_session() - - # Read the meter - data = essent.read_meter(self._meter, only_last_meter_reading=True) + # Ensure our data isn't too old + self._essent_base.update() - self._meter_type = data['type'] - self._meter_unit = data['values']['LVR'][self._tariff]['unit'] + # Retrieve our meter + data = self._essent_base.retrieve_meter_data(self._meter) + # Set our value self._state = next( iter(data['values']['LVR'][self._tariff]['records'].values())) From dcf7f79f2ca018b5359489346665ffa1144eada4 Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Mon, 29 Apr 2019 22:10:44 +0200 Subject: [PATCH 17/20] Fix linting errors --- homeassistant/components/essent/sensor.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/essent/sensor.py b/homeassistant/components/essent/sensor.py index 86e839fdf10e11..a52443a6bf71bb 100644 --- a/homeassistant/components/essent/sensor.py +++ b/homeassistant/components/essent/sensor.py @@ -51,7 +51,12 @@ def retrieve_meters(self): data = self._meter_data[meter] self._meter_data[meter] = data for tariff in data['values']['LVR'].keys(): - meters.append(EssentMeter(self, meter, data['type'], tariff, data['values']['LVR'][tariff]['unit'])) + meters.append(EssentMeter( + self, + meter, + data['type'], + tariff, + data['values']['LVR'][tariff]['unit'])) return meters @@ -61,10 +66,12 @@ def retrieve_meter_data(self, meter): @Throttle(timedelta(minutes=30)) def update(self): + """Retrieve the latest meter data from Essent.""" essent = PyEssent(self._username, self._password) self._meters = essent.get_EANs() for meter in self._meters: - self._meter_data[meter] = essent.read_meter(meter, only_last_meter_reading=True) + self._meter_data[meter] = essent.read_meter( + meter, only_last_meter_reading=True) class EssentMeter(Entity): From cdcc9c25adcabbc1bc0b2ff2576bbb1349994605 Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Mon, 29 Apr 2019 22:14:32 +0200 Subject: [PATCH 18/20] Fix old description --- homeassistant/components/essent/sensor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/essent/sensor.py b/homeassistant/components/essent/sensor.py index a52443a6bf71bb..47af32f7479a3b 100644 --- a/homeassistant/components/essent/sensor.py +++ b/homeassistant/components/essent/sensor.py @@ -25,7 +25,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): password = config[CONF_PASSWORD] essent = EssentBase(username, password) - add_devices(essent.retrieve_meters(), True) + add_devices(essent.create_meters(), True) class EssentBase(): @@ -44,8 +44,8 @@ def get_session(self): """Return the active session.""" return self._essent - def retrieve_meters(self): - """Retrieve the IDs of the meters used by Essent.""" + def create_meters(self): + """Initialize meter components for Home Assistant.""" meters = [] for meter in self._meters: data = self._meter_data[meter] From b886b25af99aaed908c89b080ca8d5f85d49c3ca Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Mon, 29 Apr 2019 22:15:40 +0200 Subject: [PATCH 19/20] Fix old call --- homeassistant/components/essent/sensor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/essent/sensor.py b/homeassistant/components/essent/sensor.py index 47af32f7479a3b..c5966b82190421 100644 --- a/homeassistant/components/essent/sensor.py +++ b/homeassistant/components/essent/sensor.py @@ -49,7 +49,6 @@ def create_meters(self): meters = [] for meter in self._meters: data = self._meter_data[meter] - self._meter_data[meter] = data for tariff in data['values']['LVR'].keys(): meters.append(EssentMeter( self, From 626819575efb7e8eadf3386916ff5c2a47ed219f Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Tue, 30 Apr 2019 18:51:18 +0200 Subject: [PATCH 20/20] Cleanup Essent component --- homeassistant/components/essent/sensor.py | 34 ++++++++++------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/essent/sensor.py b/homeassistant/components/essent/sensor.py index c5966b82190421..545ed3d5baf5f5 100644 --- a/homeassistant/components/essent/sensor.py +++ b/homeassistant/components/essent/sensor.py @@ -25,7 +25,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None): password = config[CONF_PASSWORD] essent = EssentBase(username, password) - add_devices(essent.create_meters(), True) + meters = [] + for meter in essent.retrieve_meters(): + data = essent.retrieve_meter_data(meter) + for tariff in data['values']['LVR'].keys(): + meters.append(EssentMeter( + essent, + meter, + data['type'], + tariff, + data['values']['LVR'][tariff]['unit'])) + + add_devices(meters, True) class EssentBase(): @@ -40,24 +51,9 @@ def __init__(self, username, password): self.update() - def get_session(self): - """Return the active session.""" - return self._essent - - def create_meters(self): - """Initialize meter components for Home Assistant.""" - meters = [] - for meter in self._meters: - data = self._meter_data[meter] - for tariff in data['values']['LVR'].keys(): - meters.append(EssentMeter( - self, - meter, - data['type'], - tariff, - data['values']['LVR'][tariff]['unit'])) - - return meters + def retrieve_meters(self): + """Retrieve the list of meters.""" + return self._meters def retrieve_meter_data(self, meter): """Retrieve the data for this meter."""