Open figorr opened 1 week ago
I think maybe I found how to include the "status" sensor and the "last_api_call_successful" sensor to show which was the last time and date from a successful connection to API and therefore if the API is "online" or "offline".
The solution requires to edit ecowater.py in order to create a couple more properties. And the we can add theses properties as sensors in the sensor.py and const.py files.
ecowater.py code:
import ayla_iot_unofficial, datetime, time
import logging
from .const import (
APP_ID,
APP_SECRET,
UPDATE_PROPERTY,
SALT_TENTHS_MAX
)
# Setup the logger
_LOGGER = logging.getLogger(__name__)
class EcowaterDevice(ayla_iot_unofficial.device.Device):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._last_api_call_successful = None # Store date and time from the last successful connection to API
def update(self, property_list=None):
try:
data = super(EcowaterDevice, self).update(property_list)
# If API call was successful, we update time and date from the last successful connection
self._last_api_call_successful = datetime.datetime.now(datetime.timezone.utc)
_LOGGER.info("Successful API connection. Last successful call was registered.")
except Exception as e:
# If API call was failed, we kept the last successful connection date and time and we register the error
_LOGGER.error(f"Error calling to API: {e}")
return None
# If data hasn't been updated in the last 5 mins then tell the device to update the data and wait 30 secs for the device to update the data
last_updated = datetime.datetime.strptime(
self.properties_full["gallons_used_today"]["data_updated_at"], "%Y-%m-%dT%H:%M:%SZ"
)
last_updated = last_updated.replace(tzinfo=datetime.timezone.utc)
current_time = datetime.datetime.now(datetime.timezone.utc)
five_minutes_ago = current_time - datetime.timedelta(minutes=5)
if last_updated < five_minutes_ago:
self.ayla_api.self_request(
'post', self.set_property_endpoint(UPDATE_PROPERTY), json={'datapoint': {'value': 1}}
)
_LOGGER.info("Asking for data device update.")
time.sleep(30)
data = super(EcowaterDevice, self).update(property_list)
return data
# API Info
@property
def last_api_call_successful(self) -> datetime.datetime:
"""It returns the date and time from the last successful connection to API or None if never there was a successful connection."""
return self._last_api_call_successful
@property
def status(self) -> str:
"""It returns 'online' if last call to API was successful, 'offline' if not."""
if self._last_api_call_successful:
return "online"
else:
return "offline"
# Device Info
@property
def model(self) -> str:
return self.get_property_value('model_description')
@property
def software_version(self) -> str:
return self.get_property_value("base_software_version")
@property
def ip_address(self) -> str:
return self._device_ip_address
@property
def rssi(self) -> int:
return self.get_property_value("rf_signal_strength_dbm")
# Water
@property
def water_use_avg_daily(self) -> int:
return self.get_property_value("avg_daily_use_gals")
@property
def water_use_today(self) -> int:
return self.get_property_value("gallons_used_today")
@property
def water_available(self) -> int:
return self.get_property_value("treated_water_avail_gals")
# Water flow
@property
def current_water_flow(self) -> float:
return self.get_property_value("current_water_flow_gpm") / 10
# Salt
@property
def salt_level_percentage(self) -> float:
return (self.get_property_value("salt_level_tenths") * 100) / SALT_TENTHS_MAX[str(self.get_property_value("model_id"))]
@property
def out_of_salt_days(self) -> int:
return self.get_property_value("out_of_salt_estimate_days")
@property
def out_of_salt_date(self) -> datetime.date:
return datetime.datetime.now().date() + datetime.timedelta(days = self.get_property_value("out_of_salt_estimate_days"))
@property
def salt_type(self) -> str:
if self.get_property_value("salt_type_enum") == 0:
return "NaCl"
else:
return "KCl"
# Rock
@property
def rock_removed_avg_daily(self) -> float:
return self.get_property_value("daily_avg_rock_removed_lbs") /10000
@property
def rock_removed(self) -> float:
return self.get_property_value("total_rock_removed_lbs") / 10
# Recharge
@property
def recharge_status(self) -> str:
if self.get_property_value("regen_status_enum") == 0:
return "None"
elif self.get_property_value("regen_status_enum") == 1:
return "Scheduled"
else:
return "Recharging"
@property
def recharge_enabled(self) -> bool:
return self.get_property_value("regen_enable_enum") == 1
@property
def recharge_scheduled(self) -> bool:
return self.get_property_value("regen_status_enum") == 1
@property
def recharge_recharging(self) -> bool:
return self.get_property_value("regen_status_enum") == 2
@property
def last_recharge_days(self) -> int:
return self.get_property_value("days_since_last_regen")
@property
def last_recharge_date(self) -> datetime.date:
return datetime.datetime.now().date() - datetime.timedelta(days = self.get_property_value("days_since_last_regen"))
class EcowaterAccount:
def __init__(self, username: str, password: str) -> None:
self.ayla_api = ayla_iot_unofficial.new_ayla_api(username, password, APP_ID, APP_SECRET)
self.ayla_api.sign_in()
def get_devices(self) -> list:
devices = self.ayla_api.get_devices()
# Filter for Ecowater devices
devices = list(filter(lambda device: device._oem_model_number.startswith("EWS"), devices))
# Convert devices to EcowaterDevice Class
for device in devices:
setattr(device, "__class__", EcowaterDevice)
return devices
Then we should add at sensor.py the new sensors:
...
from .const import (
DOMAIN,
MODEL,
SOFTWARE_VERSION,
WATER_AVAILABLE,
WATER_USAGE_TODAY,
WATER_USAGE_DAILY_AVERAGE,
CURRENT_WATER_FLOW,
SALT_LEVEL_PERCENTAGE,
OUT_OF_SALT_ON,
DAYS_UNTIL_OUT_OF_SALT,
SALT_TYPE,
LAST_RECHARGE,
DAYS_SINCE_RECHARGE,
RECHARGE_ENABLED,
RECHARGE_STATUS,
ROCK_REMOVED,
ROCK_REMOVED_DAILY_AVERAGE,
LAST_API_CALL_SUCCESSFUL, # New line
STATUS # New line
)
...
),
EcowaterSensorEntityDescription(
key=LAST_API_CALL_SUCCESSFUL,
name="Last Update",
icon="mdi:calendar-clock",
device_class=SensorDeviceClass.TIMESTAMP
),
EcowaterSensorEntityDescription(
key=STATUS,
name="Status",
icon="mdi:lan-connect"
)
...
And we should edit const.py to add a couple of lines:
LAST_API_CALL_SUCCESSFUL = "last_api_call_successful"
STATUS = "status"
@barleybobs, do you think this could work and wouldn't break the code?
Tested and it works OK. No warnings at logs.
In the previous code there was a "status" sensor that was showing if the device was "online" or "offline"
At ecowater.py there was this part of code:
Was the API sending the data for the status sensor? I don't know if it could be easily implemented in the new code? Or maybe it could be added in another way.
Then the integration, at sensor.py was adding the status sensor:
It is a sensor I was using to manage an automation that sends a notification when the device became "offline". It is not a huge problem ... I can modify the automation just to check if any other sensor (like "water used today") became "unavailable" or "unknown".
Thank you for all the work. The integration looks great.