Skip to content

Commit

Permalink
Merge pull request #71 from barleybobs/v4.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
barleybobs authored Sep 30, 2024
2 parents 6912581 + c35f02f commit 57381e6
Show file tree
Hide file tree
Showing 21 changed files with 246 additions and 158 deletions.
50 changes: 31 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
[![Validate with HACS](https://img.shields.io/github/actions/workflow/status/barleybobs/homeassistant-ecowater-softener/validate-hacs.yml?label=Validate%20with%20HACS&style=for-the-badge)](https://github.com/barleybobs/homeassistant-ecowater-softener/actions)
[![Validate with Hassfest](https://img.shields.io/github/actions/workflow/status/barleybobs/homeassistant-ecowater-softener/validate-with-hassfest.yml?label=Validate%20with%20Hassfest&style=for-the-badge)](https://github.com/barleybobs/homeassistant-ecowater-softener/actions)

# **V3.0.0 BREAKING CHANGES**
**In version 3.0.0 the original sensor is discontinued. There are now individual entities for each piece of data. These new sensor also update every 10 minutes compared to the old sensor which updated every 30 minutes.**

**Before installing version 3.0.0 remove the integration from the "Device and Services" menu. Then install version 3.0.0 and restart before finally setting up the integration again.**
# **v4.0.0 BREAKING CHANGES**
**Entities have been removed and added in v4.0.0. You may have to remove your device from the integrations menu and re-add it.**

# Ecowater water softeners integration for Home Assistant

Expand All @@ -14,40 +12,54 @@
## Installation

#### HACS
1. Go to HACS -> Integrations -> Click +
1. Search for "Ecowater Softener" and add it to HACS
1. Go to `HACS`
1. Search for `Ecowater Softener` and add it to HACS
1. Restart Home Assistant
1. Go to Settings -> Devices & Services -> Integrations -> Click +
1. Search for "Ecowater Softener" and follow the set up instructions
1. Go to `Settings > Devices & Services`
1. Click `+ Add Integration`
1. Search for `Ecowater Softener` and follow the configuration instructions

#### Manually
Copy the `custom_components/ecowater_softener` folder into the config folder.

## Configuration
To add an Ecowater water softener, go to Configuration > Integrations in the UI. Then click the + button and from the list of integrations select Ecowater Softener. You should then see a dialog like the one below.
To add an Ecowater water softener, go to `Configuration > Integrations` in the UI. Then click the `+` button and from the list of integrations select Ecowater Softener. You will then be prompted to enter the username and password for your Ecowater account.

![Ecowater custom component login dialog](images/login.png)

![Ecowater custom component setup dialog](images/setup.png)
After submitting your account login you will either be prompted to pick a device from your Ecowater account as shown below.

You then need to enter the information you use to login on [https://wifi.ecowater.com/Site/Login](https://wifi.ecowater.com/Site/Login). (Serial Number = DSN)
![Ecowater custom component device picker dialog](images/pick_device.png)

Then you will need to select the date format that your Ecowater device uses. You can check this under the `Out of Salt Date` and `Last Recharge` at [https://wifi.ecowater.com/Site/Login](https://wifi.ecowater.com/Site/Login).
Or if you have already added all the devices in your Ecowater account you will recieve this error.

This will then create an device name "Ecowater SERIAL_NUMBER".
![Ecowater custom component all devices added error dialog](images/no_devices.png)

![Device](images/integration.png)
If the device is added successfully it should appear as shown.

![Device](images/devices.png)

This device will then have the entities show below.

![Entities](images/sensors.png)

## Changing Units

To change units select one of the entities and open the more info dialog and click the cog in the top right. This will bring up the settings for the entity. Then select `Unit of Measurement`, and a dropdown will appear where you can select the units you want.

![Entity more info settings dialog](images/change_units.png)


## Credits
- [@ThePrincelle](https://github.com/ThePrincelle) - French Translations
- [@Quotic](https://github.com/Quotic) - German Translations
- [Maxime Princelle](https://github.com/ThePrincelle) - French Translations
- [Bastian](https://github.com/Quotic) - German Translations
- [@figorr](https://github.com/figorr) - Updated deprecated constants & Updated to using `await async_forward_entry_setups` & Updated regex
- [@kylejohnson](https://github.com/kylejohnson) - Discovering and documenting the Ecowater API
- [@mattjgalloway](https://github.com/mattjgalloway) - Sorting manifest.json ordering
- [Kyle Johnson](https://github.com/kylejohnson) - Discovering and documenting the Ecowater API
- [Matt Galloway](https://github.com/mattjgalloway) - Sorting manifest.json ordering
- [@Tazmanian79](https://github.com/Tazmanian79) - Updating state class from measurement to total
- [@heytcass](https://github.com/heytcass) - Grammar fixes
- [Tom Cassady](https://github.com/heytcass) - Grammar fixes
- [@rewardone](https://github.com/rewardone) for creating [ayla-iot-unofficial](https://github.com/rewardone/ayla-iot-unofficial) which is used to fetch the data
- [Jeff Rescignano](https://github.com/JeffResc) for creating [sharkiq](https://github.com/JeffResc/sharkiq) which [ayla-iot-unofficial](https://github.com/rewardone/ayla-iot-unofficial) is based on.

## License
[MIT](https://choosealicense.com/licenses/mit/)
Binary file removed attributes.png
Binary file not shown.
48 changes: 42 additions & 6 deletions custom_components/ecowater_softener/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,70 @@
from typing import Any, Dict, Optional

from homeassistant import config_entries
import homeassistant.helpers.config_validation as cv
import voluptuous as vol

from .const import DOMAIN

import ecowater_softener

_LOGGER = logging.getLogger(__name__)

DATA_SCHEMA_USER = vol.Schema(
{
vol.Required("username"): str,
vol.Required("password"): str,
vol.Required("serialnumber"): str,
vol.Required("dateformat"): vol.In(['dd/mm/yyyy', 'mm/dd/yyyy'])
vol.Required("password"): str
}
)

class EcowaterConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Ecowater config flow."""

data: Optional[Dict[str, Any]]
errors: Dict[str, str] = {}

async def async_step_user(self, user_input: Optional[Dict[str, Any]] = None):
"""Invoked when a user initiates a flow via the user interface."""
errors: Dict[str, str] = {}
if user_input is not None:
self.data = user_input
return self.async_create_entry(title="Ecowater " + self.data["serialnumber"], data=self.data)

try:
ecowater_account = await self.hass.async_add_executor_job(lambda: ecowater_softener.EcowaterAccount(self.data["username"], self.data["password"]))
except:
errors["base"] = "login_fail"
return self.async_show_form(
step_id="user",
data_schema=DATA_SCHEMA_USER,
errors=errors
)

ecowater_devices = await self.hass.async_add_executor_job(lambda: ecowater_account.get_devices())

existing_entries = self._async_current_entries()
configured_serial_numbers = {entry.data["device_serial_number"] for entry in existing_entries}

self.device_list = [device.serial_number for device in ecowater_devices if device.serial_number not in configured_serial_numbers]

if len(self.device_list) == 0:
return self.async_abort(reason="no_available_devices")

return await self.async_step_device()


return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA_USER
step_id="user",
data_schema=DATA_SCHEMA_USER
)

async def async_step_device(self, user_input: Optional[Dict[str, Any]] = None):
"""Invoked after a user has provided their Ecowater account credentials."""
if user_input is not None:
self.data["device_serial_number"] = user_input["device_serial_number"]

return self.async_create_entry(title="Ecowater " + self.data["device_serial_number"], data=self.data)

return self.async_show_form(
step_id="device", data_schema= vol.Schema({
vol.Required("device_serial_number"): vol.In(self.device_list)
})
)
20 changes: 13 additions & 7 deletions custom_components/ecowater_softener/const.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
DOMAIN = "ecowater_softener"

STATUS = "status"
DAYS_UNTIL_OUT_OF_SALT = "days_until_out_of_salt"
OUT_OF_SALT_ON = "out_of_salt_on"
MODEL = "model"
SOFTWARE_VERSION = "software_version"
DAYS_UNTIL_OUT_OF_SALT = "out_of_salt_days"
OUT_OF_SALT_ON = "out_of_salt_date"
SALT_LEVEL_PERCENTAGE = "salt_level_percentage"
WATER_USAGE_TODAY = "water_used_today"
WATER_USAGE_DAILY_AVERAGE = "water_used_per_day_average"
SALT_TYPE = "salt_type"
WATER_USAGE_TODAY = "water_use_today"
WATER_USAGE_DAILY_AVERAGE = "water_use_avg_daily"
WATER_AVAILABLE = "water_available"
WATER_UNITS = "water_units"
CURRENT_WATER_FLOW = "current_water_flow"
DAYS_SINCE_RECHARGE = "last_recharge_days"
LAST_RECHARGE = "last_recharge_date"
RECHARGE_ENABLED = "recharge_enabled"
RECHARGE_SCHEDULED = "recharge_scheduled"
RECHARGE_STATUS = "recharge_status"
ROCK_REMOVED = "rock_removed"
ROCK_REMOVED_DAILY_AVERAGE = "rock_removed_avg_daily"
66 changes: 10 additions & 56 deletions custom_components/ecowater_softener/coordinator.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,16 @@
from datetime import datetime, timedelta
import re
from datetime import timedelta
import logging

from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from ecowater_softener import Ecowater

from .const import (
STATUS,
DAYS_UNTIL_OUT_OF_SALT,
OUT_OF_SALT_ON,
SALT_LEVEL_PERCENTAGE,
WATER_USAGE_TODAY,
WATER_USAGE_DAILY_AVERAGE,
WATER_AVAILABLE,
WATER_UNITS,
RECHARGE_ENABLED,
RECHARGE_SCHEDULED,
)
import ecowater_softener

_LOGGER = logging.getLogger(__name__)

class EcowaterDataCoordinator(DataUpdateCoordinator):
"""Class to manage fetching Ecowater data."""

def __init__(self, hass, username, password, serialnumber, dateformat):
def __init__(self, hass, username, password, serialnumber):
"""Initialize Ecowater coordinator."""
super().__init__(
hass,
Expand All @@ -35,51 +21,19 @@ def __init__(self, hass, username, password, serialnumber, dateformat):
self._username = username
self._password = password
self._serialnumber = serialnumber
self._dateformat = dateformat

async def _async_update_data(self):
"""Fetch data from API endpoint.
This is the place to pre-process the data to lookup tables
so entities can quickly look up their data.
"""
try:
data = {}

ecowaterDevice = Ecowater(self._username, self._password, self._serialnumber)
data_json = await self.hass.async_add_executor_job(lambda: ecowaterDevice._get())

nextRecharge_re = r"device-info-nextRecharge'\)\.html\('(?P<nextRecharge>.*)'"

data[STATUS] = 'Online' if data_json['online'] == True else 'Offline'
data[DAYS_UNTIL_OUT_OF_SALT] = data_json['out_of_salt_days']

# Checks if date is 'today' or 'tomorrow'
if str(data_json['out_of_salt']).lower() == 'today':
data[OUT_OF_SALT_ON] = datetime.today().strftime('%Y-%m-%d')
elif str(data_json['out_of_salt']).lower() == 'tomorrow':
data[OUT_OF_SALT_ON] = (datetime.today() + timedelta(days=1)).strftime('%Y-%m-%d')
elif str(data_json['out_of_salt']).lower() == 'yesterday':
data[OUT_OF_SALT_ON] = (datetime.today() - timedelta(days=1)).strftime('%Y-%m-%d')
# Runs correct datetime.strptime() depending on date format entered during setup.
elif self._dateformat == "dd/mm/yyyy":
data[OUT_OF_SALT_ON] = datetime.strptime(data_json['out_of_salt'], '%d/%m/%Y').strftime('%Y-%m-%d')
elif self._dateformat == "mm/dd/yyyy":
data[OUT_OF_SALT_ON] = datetime.strptime(data_json['out_of_salt'], '%m/%d/%Y').strftime('%Y-%m-%d')
else:
data[OUT_OF_SALT_ON] = ''
_LOGGER.exception(
f"Error: Date format not set"
)
try:
ecowater_account = await self.hass.async_add_executor_job(lambda: ecowater_softener.EcowaterAccount(self._username, self._password))
ecowater_devices = await self.hass.async_add_executor_job(lambda: ecowater_account.get_devices())
device = list(filter(lambda device: device.serial_number == self._serialnumber ,ecowater_devices))[0]
await self.hass.async_add_executor_job(lambda: device.update())

data[SALT_LEVEL_PERCENTAGE] = data_json['salt_level_percent']
data[WATER_USAGE_TODAY] = data_json['water_today']
data[WATER_USAGE_DAILY_AVERAGE] = data_json['water_avg']
data[WATER_AVAILABLE] = data_json['water_avail']
data[WATER_UNITS] = str(data_json['water_units'])
data[RECHARGE_ENABLED] = data_json['rechargeEnabled']
data[RECHARGE_SCHEDULED] = False if ( re.search(nextRecharge_re, data_json['recharge']) ).group('nextRecharge') == 'Not Scheduled' else True

return data
return device
except Exception as e:
raise UpdateFailed(f"Error communicating with API: {e}")
raise UpdateFailed(f"Error communicating with Ayla API: {e}")
4 changes: 2 additions & 2 deletions custom_components/ecowater_softener/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
"documentation": "https://github.com/barleybobs/homeassistant-ecowater-softener/",
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/barleybobs/homeassistant-ecowater-softener/issues",
"requirements": ["ecowater-softener==1.0.0"],
"version": "3.0.0"
"requirements": ["ecowater-softener==2.1.3"],
"version": "4.0.0"
}
Loading

0 comments on commit 57381e6

Please sign in to comment.