Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@kylejohnson
Last active October 26, 2023 19:03
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save kylejohnson/89784569ad613387346c6844432274f5 to your computer and use it in GitHub Desktop.
Save kylejohnson/89784569ad613387346c6844432274f5 to your computer and use it in GitHub Desktop.
#!/usr/bin/python3
import requests
import json
import re
import paho.mqtt.client as mqtt
import paho.mqtt.publish as publish
# Regex to match the hidden input on the initial log in page
request_validation_re = re.compile(r'<input name="__RequestVerificationToken" type="hidden" value="([^"]*)" />')
# The serial number of your ecowater device
dsn = { "dsn": 'serialnumber' }
# The initial form data
payload = {
"Email" : "username",
"Password" : "password",
"Remember" : 'false'
}
# The headers needed for the JSON request
headers = {
'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language' : 'en-US,en;q=0.5',
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:73.0) Gecko/20100101 Firefox/73.0'
}
# MQTT server details
mqtt_host = '127.0.0.1'
mqtt_port = 1883
mqtt_user = 'username'
mqtt_pass = 'password'
def querySite():
with requests.Session() as s:
# Initial GET request
try:
g = s.get('https://www.wifi.ecowater.com/Site/Login')
except requests.exceptions.RequestException as e:
print(f"Problem connecting to ecowater.com: {e}")
raise
# Grab the token from the hidden input
tokens = request_validation_re.findall(g.text)
payload['__RequestVerificationToken'] = tokens[0]
# Log in to the site
try:
login = s.post('https://www.wifi.ecowater.com/Site/Login', data=payload)
except requests.exceptions.RequestException as e:
print(f"Problem logging in to ecowater.com: {e}")
raise
# Add the correct Referer header
headers['Referer'] = login.url + '/' + dsn['dsn']
# Query the JSON endpoint for the data that we actually want
try:
data = s.post('https://www.wifi.ecowater.com/Dashboard/UpdateFrequentData', data=dsn, headers=headers)
except requests.exceptions.RequestException as e:
print(f"Problem getting JSON from ecowater.com: {e}")
raise
if data.status_code != 200:
print("Status code from ecowater.com was not 200")
raise ValueError("Status code from ecowater.com was not 200")
parseData(data.text)
def parseData(text):
# Load the data in to json
j = json.loads(text)
# Ensure at least one of the returned values makes sense (sanity check)
# All values were "0" once or twice.
assert j['water_flow'] >= 0, "Water flow does not appear to be a real number."
# Placeholder for each message
messages = []
# Extract the next recharge date, into something usable.
# False if we match 'Not Scheduled', else True
# Only sets this topic if we have the initial regex match.
nextRecharge_re = "device-info-nextRecharge'\)\.html\('(?P<nextRecharge>.*)'"
nextRecharge_result = re.search(nextRecharge_re, j['recharge'])
if nextRecharge_result:
msg = {
'topic': 'ecowater/rechargeTomorrow',
'payload': False if nextRecharge_result.group('nextRecharge') == 'Not Scheduled' else True
}
messages.append(msg)
# Delete the elements that we don't want
del j['out_of_salt']
del j['out_of_salt_days']
del j['water_units']
del j['time']
del j['recharge']
# Format each piece of json data in to a mqtt 'message'
for d in j:
msg = {
'topic': 'ecowater/' + d,
'payload': j[d]
}
messages.append(msg)
publishMessages(messages)
def publishMessages(messages):
# Publish the message, consisting of all of the json data
if len(messages) > 0:
publish.multiple(messages, hostname=mqtt_host, port=mqtt_port, auth={'username': mqtt_user, 'password': mqtt_pass})
else:
print('Messages is empty - nothing to publish.')
if __name__ == '__main__':
querySite()
@iamelk
Copy link

iamelk commented Sep 7, 2021

Thats amazing - thanks @barleybobs ! Ready and willing to test when its out!

@PanMat
Copy link

PanMat commented Jan 27, 2022

The code seems to be working like a charm for me in HA...thanks for publishing it!

The "ecowater/recharge" topic seems to be publishing a list which seems to be confusing the HA sensor, attached are value of payload on this topic when ecowater is recharging and when it is not.
Ecowater

Is there a way to clean up the payload (for ecowater/recharge) in the script or create another binary sensor that shows the current status of water softener recharging at any given moment?

@Benny-Git
Copy link

Thanks for that script. I updated it to set the last messages to be retained, so a new started subscriber always gets the latest values immediately: https://gist.github.com/Benny-Git/0a73a280116fc61f8e0a21e9d6e62d8d

@BillyFKidney
Copy link

BillyFKidney commented Nov 15, 2022

I agree that a custom integration is the right way forward. I have in the mean time created a solution using Appdaemon. The solution is based on a modified version of the script ecowater.py that @kylejohnson has written.

Below what I have done to get it running:

  1. Install AppDaemon Add-on via the Supervisor and start it
  2. Upload the modified version of ecowater.py and place in this folder: /config/appdaemon/apps (the file can just be uploaded via the File Editor integration in HA)
  3. Add the content of apps.yaml to /config/appdaemon/apps/apps.yaml
  4. Add Ecowater credentials and DSN to the secrets file /config/secrets.yaml
    ecowater_user:
    ecowater_password:
    ecowater_dsn:
  5. Define the schedule via the parameters below in apps.yaml. The schedule below is hourly 15:00 min past and daily at 23:59:00. The schedules can be disabled by removing the parameters from the file.
    dailyruntime: [23, 0, 59]
    hourlyruntime: [0, 15, 0]
  6. Restart AppDaemon in order to read the information from the secrets file

Currently there are two log statements (self.log...) in ecowater.py - so it is possible to see if the app is running and able to fetch data. The log output can be seen from the AppDaemon plugin (Log tab) via the Supervisor. AppDaemon will reload the code whenever one of the files are saved, so it is easy to check if it is working by just setting the hourlyruntime a minute later than current time and save the file.

I am new to Python, AppDaemon and github so the code is not perfect and might have to be done differently - but it works in my HA setup.

FYI: apps.yaml has the ecowater_website is incorrectly listed, the correct site would be https://wifi.ecowater.com/Site/Login (no www)

Looks like ecowater.py had www listed too:
# Query the JSON endpoint for the data that we actually want
data = s.post('https://wifi.ecowater.com/Dashboard/UpdateFrequentData', data=dsn, headers=headers)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment