Skip to content

Commit

Permalink
Spa Client v3.0
Browse files Browse the repository at this point in the history
- Added fault log sensor (shows last fault message in list)
- Updated lighting entity to handle color mode requirements
- Code cleanup
  • Loading branch information
plmilord authored May 26, 2024
1 parent 3c2c2a4 commit 5187fbb
Show file tree
Hide file tree
Showing 9 changed files with 851 additions and 645 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

Since the event where my spa emptied when it was -30°C outside and it took me a while to find out (luckily, more fear than harm!)... I tried to find a solution to better supervise my spa! Initially, I wanted to replicate my iPhone's **Coast Spas** App in Home Assistant so that I could create notifications, track, control and automate/script some stuff. I was also looking to replicate my home automation in the **Home** App to simplify my family's access to all my different platforms. Home Assistant was the perfect fit for that!

**Spa Client** is inspired by several similar projects and the work of many people. With version 2.0, several elements have been improved in order to represent the App **Coast Spas** as faithfully as possible. During installation, all the components are created according to the configuration of your spa!
**Spa Client** is inspired by several similar projects and the work of many people. With version 2.0 and later, several elements have been improved in order to represent the App **Coast Spas** as faithfully as possible. During installation, all the components are created according to the configuration of your spa!

## What you need

Expand Down Expand Up @@ -64,6 +64,7 @@ Filter Cycle 2 Begins | Time | ✓ | N/A
Filter Cycle 2 Runs | Time | ✓ | N/A
Filter Cycle 2 Status | Binary sensor | ✓ | Begins, Runs, Ends
Heat Mode | Switch | ✓ | Ready, Rest, Ready in Rest
Last Known Fault | Sensor | ✓ | N/A
Light 1 | Light | ✓ | False, True
Light 2 | Light | ? | False, True
Mister | Switch | ? | Off, On
Expand All @@ -90,7 +91,6 @@ Time sync with Home Assistant | ✓

- [ ] Bring back the ability to configure this custom component via the entries in configuration.yaml
- [ ] Change the way I update entities (from polling mode to subscribing to updates)
- [ ] Implement the other spa messages (fault log, gfi test, etc.)

### Completed

Expand All @@ -99,6 +99,7 @@ Time sync with Home Assistant | ✓
- [x] Allow the installation of this custom component through the Home Assistant integrations menu (use of config_flow.py)
- [x] Create an icon and logo for this custom component
- [x] Customize entity IDs with **Spa Client** custom name to allow multiple integrations in the same Home Assistant instance
- [x] Implement the other spa messages (fault log, gfi test, etc.)
- [x] Investigate why it takes so long to load the component on an RPi (~2s on docker; ~85s on RPi3)
- [x] Manage the availability of entities while not connected

Expand Down
11 changes: 7 additions & 4 deletions custom_components/spaclient/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,19 @@ async def async_setup_entry(hass, config_entry):
if not connected:
raise ConfigEntryNotReady

await spa.send_module_identification_request()
await spa.send_configuration_request()
await spa.send_information_request()
await spa.send_additional_information_request()
await spa.send_configuration_request()
await spa.send_fault_log_request()
await spa.send_filter_cycles_request()
await spa.send_gfci_test_request()
await spa.send_information_request()
await spa.send_module_identification_request()
await spa.send_preferences_request()

await update_listener(hass, config_entry)

hass.loop.create_task(spa.read_all_msg())
hass.loop.create_task(spa.keep_alive_call())
hass.loop.create_task(spa.read_all_msg())

for component in SPACLIENT_COMPONENTS:
hass.async_create_task(hass.config_entries.async_forward_entry_setup(config_entry, component))
Expand Down
24 changes: 24 additions & 0 deletions custom_components/spaclient/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,39 @@
"binary_sensor",
"climate",
"light",
"sensor",
"switch",
"time",
]

FAULT_MSG = {
15: "Sensors are out of sync",
16: "The water flow is low",
17: "The water flow has failed",
18: "The settings have been reset",
19: "Priming Mode",
20: "The clock has failed",
21: "The settings have been reset",
22: "Program memory failure",
26: "Sensors are out of sync -- Call for service",
27: "The heater is dry",
28: "The heater may be dry",
29: "The water is too hot",
30: "The heater is too hot",
31: "Sensor A Fault",
32: "Sensor B Fault",
34: "A pump may be stuck on",
35: "Hot fault",
36: "The GFCI test failed",
37: "Standby Mode (Hold Mode)"
}

ICONS = {
"Auxiliary 1": "mdi:numeric-1-circle-outline",
"Auxiliary 2": "mdi:numeric-2-circle-outline",
"Blower": "mdi:weather-windy",
"Circulation Pump": "mdi:fan",
"Fault Log": "mdi:archive-alert",
"Filter Cycle": "mdi:sync",
"Heat Mode": "mdi:alpha-r",
"Mister": "mdi:auto-fix",
Expand Down
12 changes: 11 additions & 1 deletion custom_components/spaclient/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from . import SpaClientDevice
from .const import _LOGGER, DOMAIN, SPA
from datetime import timedelta
from homeassistant.components.light import LightEntity
from homeassistant.components.light import ColorMode, LightEntity

SCAN_INTERVAL = timedelta(seconds=1)

Expand Down Expand Up @@ -42,6 +42,16 @@ def name(self):
"""Return the name of the device."""
return 'Light ' + str(self._light_num)

@property
def color_mode(self):
"""Return the color mode of the light."""
return ColorMode.ONOFF

@property
def supported_color_modes(self):
"""Return supported color modes."""
return [ColorMode.ONOFF]

@property
def is_on(self):
"""Return true if light is on."""
Expand Down
2 changes: 1 addition & 1 deletion custom_components/spaclient/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
"documentation": "https://github.com/plmilord/Hass.io-custom-component-spaclient",
"iot_class": "local_push",
"issue_tracker": "https://github.com/plmilord/Hass.io-custom-component-spaclient/issues",
"version": "2.83"
"version": "3.0"
}
60 changes: 60 additions & 0 deletions custom_components/spaclient/sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Support for Spa Client sensors."""
# Import the device class from the component that you want to support
from . import SpaClientDevice
from .const import _LOGGER, DOMAIN, ICONS, FAULT_MSG, SPA
from datetime import timedelta
from homeassistant.components.sensor import SensorEntity

SCAN_INTERVAL = timedelta(seconds=1)


async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Spa Client sensors."""

spaclient = hass.data[DOMAIN][config_entry.entry_id][SPA]
entities = []

entities.append(SpaFaultLog(spaclient, config_entry))

async_add_entities(entities, True)


class SpaFaultLog(SpaClientDevice, SensorEntity):
"""Representation of a sensor."""

def __init__(self, spaclient, config_entry):
"""Initialize the device."""
super().__init__(spaclient, config_entry)
self._spaclient = spaclient
self._sensor_type = None
self._icon = ICONS.get('Fault Log')

@property
def unique_id(self) -> str:
"""Return a unique ID."""
return f"{self._spaclient.get_macaddr().replace(':', '')}#fault_log"

@property
def device_class(self):
"""Return the class of this sensor."""
return self._sensor_type

@property
def name(self):
"""Return the name of the sensor."""
return 'Last Known Fault'

@property
def icon(self):
"""Return the icon of the device."""
return self._icon

@property
def native_value(self):
"""Return the state of this sensor."""
return FAULT_MSG.get(self._spaclient.get_fault_log_msg_code())

@property
def available(self) -> bool:
"""Return True if entity is available."""
return self._spaclient.get_gateway_status()
Loading

2 comments on commit 5187fbb

@anon-noname
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for keeping this maintained. I appreciate the addition of time sync. I haven't tested it yet but that isn't really necessary.

I would like to see ?sensors? support consumption values so that I can track energy consumption etc. Have I missed something really basic? It wouldn't surprise me that I have! Does this integration provide sufficient temporal resolution? I don't have a lot of time for unproductive/deep learning at the moment. There are more basic things I need to attend to in my system.

What I have in mind is the ability to enter a value such as Watts for each significant load segment - circulator, each pump, and heater. From there Energy should be able to track so long as on and off times are accurate to within a few seconds I think.

I will incrementally learn all 'the things' but as said, I can't build wheels at the moment.

If you want, I can open an issue but this is more of a feature request. Not sure where to do that.

Anyway. thanks again for doing this! It is really appreciated.

Anon

@plmilord
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, I was thinking of the same kind of new features for this component. I don't know if it's feasable, but like you, I don't have much time for the project. I take note and start looking at this in my spare times. If you have any suggestions of components that do the same processing (manual power inputs for loads), let me know.

Please sign in to comment.