├── README.md ├── esb-smart-meter-reader.py └── esb-smart-meter.png /README.md: -------------------------------------------------------------------------------- 1 | # esb-smart-meter-reading-automation 2 | 3 | ![](https://github.com/badger707/esb-smart-meter-reading-automation/blob/main/esb-smart-meter.png) 4 |

5 | ## How to read your Smart Meter data automatically? 6 | Simple Python code to download your smart electricy meter readings/data from ESB Networks user portal. 7 |
8 | ## Requirements
9 | * You need to create account with ESBN here https://myaccount.esbnetworks.ie
10 | * In your account, link your electricity meter MPRN 11 |

12 | ## Script Setup
13 | * update MPRN, user and password in the script:
14 | ``` 15 | meter_mprn = "mprn_number" 16 | esb_user_name = "email@email.com" 17 | esb_password = "password" 18 | ``` 19 | 20 | ## Output Format
21 | * Either CSV of JSON. Select required format by uncommenting one of last 2 rows where it says:
22 | ``` 23 | ###/ Select file format of your choice /### 24 | #print(csv_file) 25 | print(json_file) 26 | ``` 27 | 28 | ## Debug Mode for troubleshooting 29 | * Set debug_mode to True if you want to see extended info of what sript is doing and sending/receiving. 30 | ```` 31 | ## Debug Mode print messages, set to True or False ## 32 | debug_mode=False 33 | ```` 34 | 35 | ## Error Messages 36 | * When things goes wrong and User Portal starts serving human verification pages, script will stop with one or another error message based on received response content analysis: 37 | ```` 38 | [Script Message] Unable to reach login page -- too many retries (max=2 in 24h) or prior sessions was not closed properly. Please try again after midnight. 39 | ```` 40 | ```` 41 | [FAILED] Unable to get full set of required cookies -- too many retries (captcha?) or prior sessions was not closed properly. Please wait 6 hours for server to timeout and try again. 42 | ```` 43 | 44 | ## Known Limitations
45 | * ESBN User Portal have enabled human verification process for logins since around Nov'24, this creates inconvenience/chalenges regardless of what you use -- standard web browser or script like this. 46 | * Server side limit: it does allow you to make only 2 clean logins per one IP per 24 hours without triggering human verification or captcha traps. 47 | * Trying to make 3 or more logins during the day - server will start serving human verification pages or captcha or complain about disabled javascript or disabled cookies on your side. This script will detect this and will provide comments in console/terminal and will terminate/stop. 48 | * Server side timers/blockers/limits resets once a day at midnight - plan your workflow accordingly. 49 | 50 | 51 | ## UPDATES:
52 | * 04-Jan-2025 -- Reworked login and file download proccess due to Nov'24 portal changes. 53 | * 24-Jul-2024 -- Python script changes to accomodate ESB Networks user portal changes to download historic usage file. 54 | * 09-May-2024 -- there was some changes on ESB side and this broke CSV parsing in script, fixed & tested, JSON output works as expected. 55 | 56 | 57 | -------------------------------------------------------------------------------- /esb-smart-meter-reader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import requests 4 | from random import randint 5 | from time import sleep 6 | from bs4 import BeautifulSoup # pip install beautifulsoup4 7 | import re as re 8 | import json 9 | import csv 10 | 11 | ## Debug Mode print messages, set to True or False ## 12 | debug_mode=False 13 | 14 | ###### START OF SCRIPT ###### 15 | 16 | if debug_mode:print("##### REQUEST 1 -- GET [https://myaccount.esbnetworks.ie/] ######") 17 | meter_mprn = "mprn_number" 18 | esb_user_name = "email@email.com" 19 | esb_password = "password" 20 | session = requests.Session() 21 | session.headers.update({ 22 | 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0', 23 | }) 24 | 25 | try: 26 | request_1_response = session.get('https://myaccount.esbnetworks.ie/', allow_redirects=True, timeout= (10,5)) # timeout -- 10sec for connect and 5sec for response 27 | except requests.exceptions.Timeout: 28 | print("[FAILED] The request timed out, server is not responding. Try again later.") 29 | session.close() 30 | raise SystemExit(0) 31 | except requests.exceptions.RequestException as e: 32 | print("An error occurred:", e) 33 | session.close() 34 | raise SystemExit(0) 35 | 36 | result = re.findall(r"(?<=var SETTINGS = )\S*;", str(request_1_response.content)) 37 | settings = json.loads(result[0][:-1]) 38 | tester_soup = BeautifulSoup(request_1_response.content, 'html.parser') 39 | page_title = tester_soup.find("title") 40 | request_1_response_cookies = session.cookies.get_dict() 41 | x_csrf_token = settings['csrf'] 42 | transId = settings['transId'] 43 | 44 | if debug_mode: 45 | print("[!] Request #1 Page Title :: ", page_title.text) 46 | print("[!] Request #1 Status Code ::", request_1_response.status_code) 47 | print("[!] Request #1 Response Headers ::", request_1_response.headers) 48 | print("[!] Request #1 Cookies Captured ::", request_1_response_cookies) 49 | print("x_csrf_token ::", x_csrf_token) 50 | print("transId ::", transId) 51 | 52 | x_ms_cpim_sso = request_1_response_cookies.get('x-ms-cpim-sso:esbntwkscustportalprdb2c01.onmicrosoft.com_0') 53 | x_ms_cpim_csrf = request_1_response_cookies.get('x-ms-cpim-csrf') 54 | x_ms_cpim_trans = request_1_response_cookies.get('x-ms-cpim-trans') 55 | 56 | if debug_mode: 57 | print("##### creating x_ms_cpim cookies ######") 58 | print("x_ms_cpim_sso ::", request_1_response_cookies.get('x-ms-cpim-sso:esbntwkscustportalprdb2c01.onmicrosoft.com_0')) 59 | print("x_ms_cpim_csrf ::", request_1_response_cookies.get('x-ms-cpim-csrf')) 60 | print("x_ms_cpim_trans ::", request_1_response_cookies.get('x-ms-cpim-trans')) 61 | print("##### REQUEST 2 -- POST [SelfAsserted] ######") 62 | 63 | sleeping_delay= randint(10,20) 64 | if debug_mode:print('random sleep for',sleeping_delay,'seconds...') 65 | sleep(sleeping_delay) 66 | 67 | request_2_response = session.post( 68 | 'https://login.esbnetworks.ie/esbntwkscustportalprdb2c01.onmicrosoft.com/B2C_1A_signup_signin/SelfAsserted?tx=' + transId + '&p=B2C_1A_signup_signin', 69 | data={ 70 | 'signInName': esb_user_name, 71 | 'password': esb_password, 72 | 'request_type': 'RESPONSE' 73 | }, 74 | headers={ 75 | 'x-csrf-token': x_csrf_token, 76 | 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0', 77 | 'Accept': 'application/json, text/javascript, */*; q=0.01', 78 | 'Accept-Language': 'en-US,en;q=0.5', 79 | 'Accept-Encoding': 'gzip, deflate, br', 80 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 81 | 'X-Requested-With': 'XMLHttpRequest', 82 | 'Origin': 'https://login.esbnetworks.ie', 83 | 'Dnt': '1', 84 | 'Sec-Gpc': '1', 85 | 'Sec-Fetch-Dest': 'empty', 86 | 'Sec-Fetch-Mode': 'cors', 87 | 'Sec-Fetch-Site': 'same-origin', 88 | 'Priority': 'u=0', 89 | 'Te': 'trailers', 90 | }, 91 | cookies={ 92 | 'x-ms-cpim-csrf':request_1_response_cookies.get('x-ms-cpim-csrf'), 93 | # 'x-ms-cpim-sso:esbntwkscustportalprdb2c01.onmicrosoft.com_0':request_1_response_cookies.get('x-ms-cpim-sso:esbntwkscustportalprdb2c01.onmicrosoft.com_0'), 94 | 'x-ms-cpim-trans':request_1_response_cookies.get('x-ms-cpim-trans'), 95 | }, 96 | allow_redirects=False) 97 | 98 | request_2_response_cookies = session.cookies.get_dict() 99 | if debug_mode: 100 | print("[!] Request #2 Status Code ::", request_2_response.status_code) 101 | print("[!] Request #2 Response Headers ::", request_2_response.headers) 102 | print("[!] Request #2 Cookies Captured :: ", request_2_response_cookies) 103 | print("[!] Request #2 text :: ", request_2_response.text) 104 | print("##### REQUEST 3 -- GET [API CombinedSigninAndSignup] ######") 105 | 106 | request_3_response = session.get( 107 | 'https://login.esbnetworks.ie/esbntwkscustportalprdb2c01.onmicrosoft.com/B2C_1A_signup_signin/api/CombinedSigninAndSignup/confirmed', 108 | params={ 109 | 'rememberMe': False, 110 | 'csrf_token': x_csrf_token, 111 | 'tx': transId, 112 | 'p': 'B2C_1A_signup_signin', 113 | }, 114 | headers={ 115 | "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0", 116 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 117 | "Accept-Language": "en-US,en;q=0.5", 118 | "Accept-Encoding": "gzip, deflate, br", 119 | "Dnt": "1", 120 | "Sec-Gpc": "1", 121 | "Sec-Fetch-Dest": "document", 122 | "Sec-Fetch-Mode": "navigate", 123 | "Sec-Fetch-Site": "same-origin", 124 | "Priority": "u=0, i", 125 | "Pragma": "no-cache", 126 | "Cache-Control": "no-cache", 127 | "Te": "trailers", 128 | }, 129 | cookies={ 130 | "x-ms-cpim-csrf":request_2_response_cookies.get("x-ms-cpim-csrf"), 131 | # "x-ms-cpim-sso:esbntwkscustportalprdb2c01.onmicrosoft.com_0": request_2_response_cookies.get("x-ms-cpim-sso:esbntwkscustportalprdb2c01.onmicrosoft.com_0"), 132 | 'x-ms-cpim-trans':request_2_response_cookies.get("x-ms-cpim-trans"), 133 | }, 134 | ) 135 | 136 | tester_soup = BeautifulSoup(request_3_response.content, 'html.parser') 137 | page_title = tester_soup.find("title") 138 | request_3_response_cookies = session.cookies.get_dict() 139 | 140 | if debug_mode: 141 | print("[!] Page Title :: ", page_title.text) # will print "Loading..." if failed 142 | print("[!] Request #3 Status Code ::", request_3_response.status_code) 143 | print("[!] Request #3 Response Headers ::", request_3_response.headers) 144 | print("[!] Request #3 Cookies Captured :: ", request_3_response_cookies) 145 | print("[!] Request #3 Content :: ", request_3_response.content) 146 | print("##### TEST IF SUCCESS ######") 147 | 148 | request_3_response_head_test = request_3_response.text[0:21] 149 | if (request_3_response_head_test == "