pyklyqa-pet 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyklyqa_pet-0.1.0/.gitignore +13 -0
- pyklyqa_pet-0.1.0/PKG-INFO +290 -0
- pyklyqa_pet-0.1.0/README.md +270 -0
- pyklyqa_pet-0.1.0/pyklyqa_pet/__init__.py +64 -0
- pyklyqa_pet-0.1.0/pyklyqa_pet/airpurifier.py +146 -0
- pyklyqa_pet-0.1.0/pyklyqa_pet/cloud.py +113 -0
- pyklyqa_pet-0.1.0/pyklyqa_pet/const.py +32 -0
- pyklyqa_pet-0.1.0/pyklyqa_pet/device.py +176 -0
- pyklyqa_pet-0.1.0/pyklyqa_pet/discovery.py +72 -0
- pyklyqa_pet-0.1.0/pyklyqa_pet/exceptions.py +22 -0
- pyklyqa_pet-0.1.0/pyklyqa_pet/foody.py +146 -0
- pyklyqa_pet-0.1.0/pyklyqa_pet/py.typed +0 -0
- pyklyqa_pet-0.1.0/pyklyqa_pet/welly.py +182 -0
- pyklyqa_pet-0.1.0/pyproject.toml +71 -0
- pyklyqa_pet-0.1.0/tests_lib/__init__.py +0 -0
- pyklyqa_pet-0.1.0/tests_lib/conftest.py +110 -0
- pyklyqa_pet-0.1.0/tests_lib/fixtures/airpurifier_state.json +37 -0
- pyklyqa_pet-0.1.0/tests_lib/fixtures/foody_settings.json +12 -0
- pyklyqa_pet-0.1.0/tests_lib/fixtures/foody_state.json +23 -0
- pyklyqa_pet-0.1.0/tests_lib/fixtures/system_info.json +19 -0
- pyklyqa_pet-0.1.0/tests_lib/fixtures/welly_settings.json +17 -0
- pyklyqa_pet-0.1.0/tests_lib/fixtures/welly_state.json +21 -0
- pyklyqa_pet-0.1.0/tests_lib/test_airpurifier.py +107 -0
- pyklyqa_pet-0.1.0/tests_lib/test_cloud.py +127 -0
- pyklyqa_pet-0.1.0/tests_lib/test_device.py +113 -0
- pyklyqa_pet-0.1.0/tests_lib/test_discovery.py +69 -0
- pyklyqa_pet-0.1.0/tests_lib/test_foody.py +97 -0
- pyklyqa_pet-0.1.0/tests_lib/test_public_api.py +35 -0
- pyklyqa_pet-0.1.0/tests_lib/test_version.py +5 -0
- pyklyqa_pet-0.1.0/tests_lib/test_welly.py +97 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pyklyqa-pet
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Async client for Klyqa pet devices (Welly, Foody, Airpurifier) using the local QConnex REST API
|
|
5
|
+
Author: Michael Sauer
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Classifier: Framework :: AsyncIO
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Topic :: Home Automation
|
|
10
|
+
Requires-Python: >=3.13
|
|
11
|
+
Requires-Dist: aiohttp>=3.10
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: homeassistant==2026.9.0; extra == 'dev'
|
|
14
|
+
Requires-Dist: mypy>=1.14; extra == 'dev'
|
|
15
|
+
Requires-Dist: pre-commit; extra == 'dev'
|
|
16
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
17
|
+
Requires-Dist: pytest-homeassistant-custom-component==0.13.363; extra == 'dev'
|
|
18
|
+
Requires-Dist: ruff>=0.9; extra == 'dev'
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# Klyqa Pet for Home Assistant
|
|
22
|
+
|
|
23
|
+
Klyqa Pet is a Home Assistant custom integration for Klyqa's pet devices: the
|
|
24
|
+
**Welly** water fountain, the **Foody** feeder and the **Airpurifier**. All day-to-day
|
|
25
|
+
control — reading sensors, changing settings, dispensing food, switching the fan on —
|
|
26
|
+
talks directly to the device's local QConnex REST API over your LAN. The Klyqa cloud
|
|
27
|
+
is only used once, during setup, to look up the access token each device needs; after
|
|
28
|
+
that the integration never depends on the cloud being reachable.
|
|
29
|
+
|
|
30
|
+
## Supported devices
|
|
31
|
+
|
|
32
|
+
| Product ID | Device | Status |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| `@klyqa.welly` | Welly water fountain | Supported |
|
|
35
|
+
| `@klyqa.welly-dev` | Welly water fountain (development firmware) | Supported |
|
|
36
|
+
| `@klyqa.foody` | Foody feeder | Supported |
|
|
37
|
+
| `@klyqa.foody-dev` | Foody feeder (development firmware) | Supported |
|
|
38
|
+
| `@klyqa.airpurifier2` | Airpurifier (2nd generation) | Supported |
|
|
39
|
+
| `@klyqa.airpurifier2-dev` | Airpurifier (2nd generation, development firmware) | Supported |
|
|
40
|
+
| `@pfriendly.water-fountain` / `-dev` | Welly, rebranded product ID | Recognised, untested |
|
|
41
|
+
| `@pfriendly.foody` / `-dev` | Foody, rebranded product ID | Recognised, untested |
|
|
42
|
+
| `@pfriendly.airpurifier` / `-dev` | Airpurifier, rebranded product ID | Recognised, untested |
|
|
43
|
+
|
|
44
|
+
The `@pfriendly.*` product IDs map to the same device classes as their `@klyqa.*`
|
|
45
|
+
counterparts but have not been verified against real hardware.
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
### HACS (recommended)
|
|
50
|
+
|
|
51
|
+
1. In HACS, open the menu in the top right and choose **Custom repositories**.
|
|
52
|
+
2. Add `https://github.com/ninharp/ha-klyqa-pet` with category **Integration**.
|
|
53
|
+
3. Search for "Klyqa Pet" in HACS and install it.
|
|
54
|
+
4. Restart Home Assistant.
|
|
55
|
+
|
|
56
|
+
### Manual
|
|
57
|
+
|
|
58
|
+
1. Copy the `custom_components/klyqa_pet` folder from this repository into your Home
|
|
59
|
+
Assistant `config/custom_components` directory.
|
|
60
|
+
2. Restart Home Assistant.
|
|
61
|
+
|
|
62
|
+
### A note on the `pyklyqa-pet` dependency
|
|
63
|
+
|
|
64
|
+
The integration's `manifest.json` requires the `pyklyqa-pet` library from PyPI so
|
|
65
|
+
that HACS and manual installs can pull it in automatically. Until that library is
|
|
66
|
+
published, a normal HACS or manual install will fail during setup with "Requirements
|
|
67
|
+
for klyqa_pet not found". Until then, run Home Assistant for development with
|
|
68
|
+
`--skip-pip-packages pyklyqa-pet` and the repository root on `PYTHONPATH`, so the
|
|
69
|
+
in-repo copy of the library is used instead — see
|
|
70
|
+
[`docker/README.md`](docker/README.md) for a working example.
|
|
71
|
+
|
|
72
|
+
## Configuration
|
|
73
|
+
|
|
74
|
+
Configuration is done entirely from the UI — there is nothing to add to
|
|
75
|
+
`configuration.yaml`.
|
|
76
|
+
|
|
77
|
+
1. Go to **Settings → Devices & services → Add integration** and search for
|
|
78
|
+
**Klyqa Pet**.
|
|
79
|
+
2. Enter:
|
|
80
|
+
- **Environment** — Production or Test. Use Production unless you have a Klyqa
|
|
81
|
+
test account.
|
|
82
|
+
- **Email** — your Klyqa account email.
|
|
83
|
+
- **Password** — your Klyqa account password.
|
|
84
|
+
3. The integration signs in once, fetches the access token of every device on the
|
|
85
|
+
account, and sets up a coordinator per device. If a Klyqa device also announces
|
|
86
|
+
itself on the network via mDNS, Home Assistant offers the same sign-in dialog as a
|
|
87
|
+
discovered flow — accept it and sign in with the account the device is paired with.
|
|
88
|
+
|
|
89
|
+
### Adding a device manually
|
|
90
|
+
|
|
91
|
+
Some devices — for example a development unit — are not paired with a cloud account
|
|
92
|
+
and are never returned by the cloud login. For these, open the integration's
|
|
93
|
+
**Configure** dialog and choose **Add a device manually**:
|
|
94
|
+
|
|
95
|
+
| Field | Description |
|
|
96
|
+
|---|---|
|
|
97
|
+
| Host | IP address or hostname of the device |
|
|
98
|
+
| Port | REST API port, defaults to `3333` |
|
|
99
|
+
| Device access token | The device's access token |
|
|
100
|
+
|
|
101
|
+
Klyqa development firmware accepts the fixed token `aabbccddeeff0011223344` for local
|
|
102
|
+
testing; it is rejected by production firmware.
|
|
103
|
+
|
|
104
|
+
## Supported functionality
|
|
105
|
+
|
|
106
|
+
Every device also exposes these common diagnostic entities (disabled by default,
|
|
107
|
+
except where noted):
|
|
108
|
+
|
|
109
|
+
| Platform | Entity | Notes |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| sensor | Wi-Fi signal, Firmware version, SDK version, Last boot | Diagnostic, disabled by default |
|
|
112
|
+
| button | Restart | Diagnostic, disabled by default; generic SDK reboot command |
|
|
113
|
+
|
|
114
|
+
### Welly (water fountain)
|
|
115
|
+
|
|
116
|
+
| Platform | Entity | Notes |
|
|
117
|
+
|---|---|---|
|
|
118
|
+
| select | Mode | Sensing, 24h fresh water, water change, self-wash, drain |
|
|
119
|
+
| switch | Heating | |
|
|
120
|
+
| switch | Light, Ambient light, Sensor mode light, Clean tank low alert, Dirty tank full alert, Super power saving, Telemetry | Config category |
|
|
121
|
+
| number | Heating temperature, Daily drinking goal, Radar sensitivity, Circulation pump speed, Clean tank low threshold, Dirty tank full threshold | Config category |
|
|
122
|
+
| sensor | Water temperature, Clean tank volume, Sewage tank volume, Drinking volume, Total consumption, Last drinking, Filter life | |
|
|
123
|
+
| sensor | Pump status, Power status, Power supply, Descaling status, Light effect, Battery | Power status/supply, descaling status, light effect and battery are diagnostic |
|
|
124
|
+
| binary_sensor | Water tray low, Pump problem, Do not disturb, Charging | |
|
|
125
|
+
| button | Start descaling, Stop descaling | |
|
|
126
|
+
|
|
127
|
+
### Foody (feeder)
|
|
128
|
+
|
|
129
|
+
| Platform | Entity | Notes |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| number | Portions | Local helper (1–40) that sets how much the Dispense food button dispenses |
|
|
132
|
+
| button | Dispense food, Play voice recording, Query bowl weight | |
|
|
133
|
+
| switch | Indicator LED, Pet lock, Beep, Feeding audio, Telemetry | Config category |
|
|
134
|
+
| number | Feed audio volume | Config category |
|
|
135
|
+
| select | Custom button function, Battery mode, Charging protection | Config category |
|
|
136
|
+
| sensor | Bowl remaining, Real-time weight, Feeding state, Bowl state, Food bin, Error state, Last manual feeding, Last manual portions, Last scheduled feeding, Last scheduled portions, Next scheduled feeding | |
|
|
137
|
+
| sensor | Battery, MCU firmware version | Diagnostic category |
|
|
138
|
+
| binary_sensor | Power, Power adapter, Problem, Food low, Bowl removed | |
|
|
139
|
+
|
|
140
|
+
The Foody's built-in battery sensor reports "unknown" on units that run only on
|
|
141
|
+
mains power, since the device never reports a battery level in that configuration.
|
|
142
|
+
|
|
143
|
+
### Airpurifier
|
|
144
|
+
|
|
145
|
+
| Platform | Entity | Notes |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| fan | Air purifier | On/off, speed (3 levels), preset modes standalone/auto/night/pet |
|
|
148
|
+
| light | LED | On/off, RGB colour and brightness of the ring |
|
|
149
|
+
| switch | Ionizer, Child lock, Key tone | Ionizer is a primary control; Child lock and Key tone are config category |
|
|
150
|
+
| sensor | PM2.5, Air quality, Filter remaining, Filter life (%), Total run time, Air volume, Pet mode time | |
|
|
151
|
+
| binary_sensor | Tilted, Filter removed, Ionizer active | |
|
|
152
|
+
|
|
153
|
+
## Data updates
|
|
154
|
+
|
|
155
|
+
Each device is polled independently through its own `DataUpdateCoordinator`:
|
|
156
|
+
|
|
157
|
+
- Device state and settings are refreshed every **15 seconds**.
|
|
158
|
+
- System information (firmware/SDK version, last boot, etc.) is refreshed every
|
|
159
|
+
**5 minutes**, since it changes far less often.
|
|
160
|
+
- Devices are also discovered passively via mDNS (`_qcxrest._tcp`). If a known
|
|
161
|
+
device's IP address changes, the mDNS listener picks up the new address
|
|
162
|
+
automatically — no reconfiguration needed.
|
|
163
|
+
|
|
164
|
+
## Example automations
|
|
165
|
+
|
|
166
|
+
Notify when the Welly's water tray runs low:
|
|
167
|
+
|
|
168
|
+
```yaml
|
|
169
|
+
automation:
|
|
170
|
+
- alias: "Notify when the water tray is low"
|
|
171
|
+
triggers:
|
|
172
|
+
- trigger: state
|
|
173
|
+
entity_id: binary_sensor.klyqa_welly_a1b2c3_water_tray_low
|
|
174
|
+
to: "on"
|
|
175
|
+
actions:
|
|
176
|
+
- action: notify.mobile_app_your_phone
|
|
177
|
+
data:
|
|
178
|
+
message: "The Welly water tray is running low."
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Dispense 2 portions from the Foody every morning at 07:00:
|
|
182
|
+
|
|
183
|
+
```yaml
|
|
184
|
+
automation:
|
|
185
|
+
- alias: "Morning feeding"
|
|
186
|
+
triggers:
|
|
187
|
+
- trigger: time
|
|
188
|
+
at: "07:00:00"
|
|
189
|
+
actions:
|
|
190
|
+
- action: number.set_value
|
|
191
|
+
target:
|
|
192
|
+
entity_id: number.klyqa_foody_d4e5f6_portions
|
|
193
|
+
data:
|
|
194
|
+
value: 2
|
|
195
|
+
- action: button.press
|
|
196
|
+
target:
|
|
197
|
+
entity_id: button.klyqa_foody_d4e5f6_dispense_food
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Replace the entity IDs above with the ones Home Assistant assigned to your own
|
|
201
|
+
devices (Settings → Devices & services → Klyqa Pet → your device).
|
|
202
|
+
|
|
203
|
+
## Known limitations
|
|
204
|
+
|
|
205
|
+
- Feeding schedules/timers, pet tags, scale calibration, firmware updates and
|
|
206
|
+
uploading custom voice recordings are not exposed by this integration. The
|
|
207
|
+
device's local REST API does not expose all of these, and some (like firmware
|
|
208
|
+
updates) are intentionally left to the manufacturer's app.
|
|
209
|
+
- The first-generation Klyqa air purifier (`@klyqa.cleaning.airpurifier1`) uses a
|
|
210
|
+
different local API and is not supported.
|
|
211
|
+
- On Foody units that run on mains power only, the battery sensor stays "unknown"
|
|
212
|
+
because the device never reports a battery level.
|
|
213
|
+
- Automatic discovery relies on mDNS (`_qcxrest._tcp`), which requires Home
|
|
214
|
+
Assistant to have access to LAN multicast traffic. This works when Home Assistant
|
|
215
|
+
runs natively (Home Assistant OS, Supervised, or a plain Python/venv install) or in
|
|
216
|
+
a container with host networking. It does **not** work in Docker Desktop on macOS,
|
|
217
|
+
because Docker Desktop's networking does not forward multicast traffic to
|
|
218
|
+
containers even with `network_mode: host`. On such setups, use the **Add a device
|
|
219
|
+
manually** option described above instead.
|
|
220
|
+
|
|
221
|
+
## Troubleshooting
|
|
222
|
+
|
|
223
|
+
**A device shows as unavailable.** Confirm the device is powered on and on the same
|
|
224
|
+
network as Home Assistant, and that Home Assistant can reach it on port `3333`
|
|
225
|
+
(`curl -H "Authorization: <token>" http://<device-ip>:3333/api/v1/system/info`). If
|
|
226
|
+
the device was previously discovered via mDNS and has since changed its IP address,
|
|
227
|
+
wait for the next mDNS announcement or restart Home Assistant to force a re-resolve.
|
|
228
|
+
|
|
229
|
+
**A device rejects its access token.** This affects only that one device: it becomes
|
|
230
|
+
unavailable and the log shows a warning naming it ("… rejects the access token from
|
|
231
|
+
the Klyqa account; re-pair the device in the Klyqa app"). This does **not** trigger a
|
|
232
|
+
re-authentication prompt — the integration first tries to recover by fetching a fresh
|
|
233
|
+
token from the cloud, and only asks you to sign in again if that cloud login itself
|
|
234
|
+
fails (see below). To fix a device stuck like this, either re-pair it in the Klyqa app
|
|
235
|
+
so the cloud hands out a new token, or remove and re-add it with **Add a device
|
|
236
|
+
manually** using its current token.
|
|
237
|
+
|
|
238
|
+
**The integration asks you to re-authenticate.** This happens only when the Klyqa
|
|
239
|
+
cloud itself rejects your account password (not when an individual device rejects its
|
|
240
|
+
token). Go to the integration's entry and follow the re-authentication prompt with
|
|
241
|
+
your current password.
|
|
242
|
+
|
|
243
|
+
**Enable debug logging** to see the raw REST requests/responses:
|
|
244
|
+
|
|
245
|
+
```yaml
|
|
246
|
+
logger:
|
|
247
|
+
default: info
|
|
248
|
+
logs:
|
|
249
|
+
custom_components.klyqa_pet: debug
|
|
250
|
+
pyklyqa_pet: debug
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
If you need to file an issue, first download the integration's diagnostics
|
|
254
|
+
(Settings → Devices & services → Klyqa Pet → your device → Download diagnostics) —
|
|
255
|
+
this redacts your email, password and access tokens and includes the raw device
|
|
256
|
+
state, which is invaluable for debugging.
|
|
257
|
+
|
|
258
|
+
## Removal
|
|
259
|
+
|
|
260
|
+
Removing the integration is a single step: go to **Settings → Devices & services**,
|
|
261
|
+
open the Klyqa Pet entry, and choose **Delete**. All devices and entities created by
|
|
262
|
+
the integration are removed from Home Assistant. Nothing is changed on the physical
|
|
263
|
+
devices themselves — no factory reset, no token revocation — so they keep working
|
|
264
|
+
with the Klyqa app.
|
|
265
|
+
|
|
266
|
+
## Development
|
|
267
|
+
|
|
268
|
+
This repository contains both the `pyklyqa_pet` client library and the
|
|
269
|
+
`custom_components/klyqa_pet` integration, developed together.
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
uv venv
|
|
273
|
+
uv pip install -e ".[dev]"
|
|
274
|
+
pytest
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
- A Dockerised Home Assistant instance for manual testing is described in
|
|
278
|
+
[`docker/README.md`](docker/README.md).
|
|
279
|
+
- `scripts/probe_devices.py` discovers Klyqa devices on the LAN via mDNS and dumps
|
|
280
|
+
their system info and state — useful for verifying connectivity and firmware
|
|
281
|
+
behaviour outside of Home Assistant.
|
|
282
|
+
- `brands/klyqa_pet/` contains the icon and logo assets prepared for submission
|
|
283
|
+
to [home-assistant/brands](https://github.com/home-assistant/brands).
|
|
284
|
+
- The integration implements the Home Assistant Bronze through Platinum quality
|
|
285
|
+
scale rules (see `custom_components/klyqa_pet/quality_scale.yaml`), except for the
|
|
286
|
+
`brands` rule: the assets above still need to be submitted and merged upstream
|
|
287
|
+
before that rule — and the manifest's `quality_scale` claim — can be marked done.
|
|
288
|
+
|
|
289
|
+
Pull requests are welcome at
|
|
290
|
+
[github.com/ninharp/ha-klyqa-pet](https://github.com/ninharp/ha-klyqa-pet).
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# Klyqa Pet for Home Assistant
|
|
2
|
+
|
|
3
|
+
Klyqa Pet is a Home Assistant custom integration for Klyqa's pet devices: the
|
|
4
|
+
**Welly** water fountain, the **Foody** feeder and the **Airpurifier**. All day-to-day
|
|
5
|
+
control — reading sensors, changing settings, dispensing food, switching the fan on —
|
|
6
|
+
talks directly to the device's local QConnex REST API over your LAN. The Klyqa cloud
|
|
7
|
+
is only used once, during setup, to look up the access token each device needs; after
|
|
8
|
+
that the integration never depends on the cloud being reachable.
|
|
9
|
+
|
|
10
|
+
## Supported devices
|
|
11
|
+
|
|
12
|
+
| Product ID | Device | Status |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| `@klyqa.welly` | Welly water fountain | Supported |
|
|
15
|
+
| `@klyqa.welly-dev` | Welly water fountain (development firmware) | Supported |
|
|
16
|
+
| `@klyqa.foody` | Foody feeder | Supported |
|
|
17
|
+
| `@klyqa.foody-dev` | Foody feeder (development firmware) | Supported |
|
|
18
|
+
| `@klyqa.airpurifier2` | Airpurifier (2nd generation) | Supported |
|
|
19
|
+
| `@klyqa.airpurifier2-dev` | Airpurifier (2nd generation, development firmware) | Supported |
|
|
20
|
+
| `@pfriendly.water-fountain` / `-dev` | Welly, rebranded product ID | Recognised, untested |
|
|
21
|
+
| `@pfriendly.foody` / `-dev` | Foody, rebranded product ID | Recognised, untested |
|
|
22
|
+
| `@pfriendly.airpurifier` / `-dev` | Airpurifier, rebranded product ID | Recognised, untested |
|
|
23
|
+
|
|
24
|
+
The `@pfriendly.*` product IDs map to the same device classes as their `@klyqa.*`
|
|
25
|
+
counterparts but have not been verified against real hardware.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
### HACS (recommended)
|
|
30
|
+
|
|
31
|
+
1. In HACS, open the menu in the top right and choose **Custom repositories**.
|
|
32
|
+
2. Add `https://github.com/ninharp/ha-klyqa-pet` with category **Integration**.
|
|
33
|
+
3. Search for "Klyqa Pet" in HACS and install it.
|
|
34
|
+
4. Restart Home Assistant.
|
|
35
|
+
|
|
36
|
+
### Manual
|
|
37
|
+
|
|
38
|
+
1. Copy the `custom_components/klyqa_pet` folder from this repository into your Home
|
|
39
|
+
Assistant `config/custom_components` directory.
|
|
40
|
+
2. Restart Home Assistant.
|
|
41
|
+
|
|
42
|
+
### A note on the `pyklyqa-pet` dependency
|
|
43
|
+
|
|
44
|
+
The integration's `manifest.json` requires the `pyklyqa-pet` library from PyPI so
|
|
45
|
+
that HACS and manual installs can pull it in automatically. Until that library is
|
|
46
|
+
published, a normal HACS or manual install will fail during setup with "Requirements
|
|
47
|
+
for klyqa_pet not found". Until then, run Home Assistant for development with
|
|
48
|
+
`--skip-pip-packages pyklyqa-pet` and the repository root on `PYTHONPATH`, so the
|
|
49
|
+
in-repo copy of the library is used instead — see
|
|
50
|
+
[`docker/README.md`](docker/README.md) for a working example.
|
|
51
|
+
|
|
52
|
+
## Configuration
|
|
53
|
+
|
|
54
|
+
Configuration is done entirely from the UI — there is nothing to add to
|
|
55
|
+
`configuration.yaml`.
|
|
56
|
+
|
|
57
|
+
1. Go to **Settings → Devices & services → Add integration** and search for
|
|
58
|
+
**Klyqa Pet**.
|
|
59
|
+
2. Enter:
|
|
60
|
+
- **Environment** — Production or Test. Use Production unless you have a Klyqa
|
|
61
|
+
test account.
|
|
62
|
+
- **Email** — your Klyqa account email.
|
|
63
|
+
- **Password** — your Klyqa account password.
|
|
64
|
+
3. The integration signs in once, fetches the access token of every device on the
|
|
65
|
+
account, and sets up a coordinator per device. If a Klyqa device also announces
|
|
66
|
+
itself on the network via mDNS, Home Assistant offers the same sign-in dialog as a
|
|
67
|
+
discovered flow — accept it and sign in with the account the device is paired with.
|
|
68
|
+
|
|
69
|
+
### Adding a device manually
|
|
70
|
+
|
|
71
|
+
Some devices — for example a development unit — are not paired with a cloud account
|
|
72
|
+
and are never returned by the cloud login. For these, open the integration's
|
|
73
|
+
**Configure** dialog and choose **Add a device manually**:
|
|
74
|
+
|
|
75
|
+
| Field | Description |
|
|
76
|
+
|---|---|
|
|
77
|
+
| Host | IP address or hostname of the device |
|
|
78
|
+
| Port | REST API port, defaults to `3333` |
|
|
79
|
+
| Device access token | The device's access token |
|
|
80
|
+
|
|
81
|
+
Klyqa development firmware accepts the fixed token `aabbccddeeff0011223344` for local
|
|
82
|
+
testing; it is rejected by production firmware.
|
|
83
|
+
|
|
84
|
+
## Supported functionality
|
|
85
|
+
|
|
86
|
+
Every device also exposes these common diagnostic entities (disabled by default,
|
|
87
|
+
except where noted):
|
|
88
|
+
|
|
89
|
+
| Platform | Entity | Notes |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| sensor | Wi-Fi signal, Firmware version, SDK version, Last boot | Diagnostic, disabled by default |
|
|
92
|
+
| button | Restart | Diagnostic, disabled by default; generic SDK reboot command |
|
|
93
|
+
|
|
94
|
+
### Welly (water fountain)
|
|
95
|
+
|
|
96
|
+
| Platform | Entity | Notes |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| select | Mode | Sensing, 24h fresh water, water change, self-wash, drain |
|
|
99
|
+
| switch | Heating | |
|
|
100
|
+
| switch | Light, Ambient light, Sensor mode light, Clean tank low alert, Dirty tank full alert, Super power saving, Telemetry | Config category |
|
|
101
|
+
| number | Heating temperature, Daily drinking goal, Radar sensitivity, Circulation pump speed, Clean tank low threshold, Dirty tank full threshold | Config category |
|
|
102
|
+
| sensor | Water temperature, Clean tank volume, Sewage tank volume, Drinking volume, Total consumption, Last drinking, Filter life | |
|
|
103
|
+
| sensor | Pump status, Power status, Power supply, Descaling status, Light effect, Battery | Power status/supply, descaling status, light effect and battery are diagnostic |
|
|
104
|
+
| binary_sensor | Water tray low, Pump problem, Do not disturb, Charging | |
|
|
105
|
+
| button | Start descaling, Stop descaling | |
|
|
106
|
+
|
|
107
|
+
### Foody (feeder)
|
|
108
|
+
|
|
109
|
+
| Platform | Entity | Notes |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| number | Portions | Local helper (1–40) that sets how much the Dispense food button dispenses |
|
|
112
|
+
| button | Dispense food, Play voice recording, Query bowl weight | |
|
|
113
|
+
| switch | Indicator LED, Pet lock, Beep, Feeding audio, Telemetry | Config category |
|
|
114
|
+
| number | Feed audio volume | Config category |
|
|
115
|
+
| select | Custom button function, Battery mode, Charging protection | Config category |
|
|
116
|
+
| sensor | Bowl remaining, Real-time weight, Feeding state, Bowl state, Food bin, Error state, Last manual feeding, Last manual portions, Last scheduled feeding, Last scheduled portions, Next scheduled feeding | |
|
|
117
|
+
| sensor | Battery, MCU firmware version | Diagnostic category |
|
|
118
|
+
| binary_sensor | Power, Power adapter, Problem, Food low, Bowl removed | |
|
|
119
|
+
|
|
120
|
+
The Foody's built-in battery sensor reports "unknown" on units that run only on
|
|
121
|
+
mains power, since the device never reports a battery level in that configuration.
|
|
122
|
+
|
|
123
|
+
### Airpurifier
|
|
124
|
+
|
|
125
|
+
| Platform | Entity | Notes |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| fan | Air purifier | On/off, speed (3 levels), preset modes standalone/auto/night/pet |
|
|
128
|
+
| light | LED | On/off, RGB colour and brightness of the ring |
|
|
129
|
+
| switch | Ionizer, Child lock, Key tone | Ionizer is a primary control; Child lock and Key tone are config category |
|
|
130
|
+
| sensor | PM2.5, Air quality, Filter remaining, Filter life (%), Total run time, Air volume, Pet mode time | |
|
|
131
|
+
| binary_sensor | Tilted, Filter removed, Ionizer active | |
|
|
132
|
+
|
|
133
|
+
## Data updates
|
|
134
|
+
|
|
135
|
+
Each device is polled independently through its own `DataUpdateCoordinator`:
|
|
136
|
+
|
|
137
|
+
- Device state and settings are refreshed every **15 seconds**.
|
|
138
|
+
- System information (firmware/SDK version, last boot, etc.) is refreshed every
|
|
139
|
+
**5 minutes**, since it changes far less often.
|
|
140
|
+
- Devices are also discovered passively via mDNS (`_qcxrest._tcp`). If a known
|
|
141
|
+
device's IP address changes, the mDNS listener picks up the new address
|
|
142
|
+
automatically — no reconfiguration needed.
|
|
143
|
+
|
|
144
|
+
## Example automations
|
|
145
|
+
|
|
146
|
+
Notify when the Welly's water tray runs low:
|
|
147
|
+
|
|
148
|
+
```yaml
|
|
149
|
+
automation:
|
|
150
|
+
- alias: "Notify when the water tray is low"
|
|
151
|
+
triggers:
|
|
152
|
+
- trigger: state
|
|
153
|
+
entity_id: binary_sensor.klyqa_welly_a1b2c3_water_tray_low
|
|
154
|
+
to: "on"
|
|
155
|
+
actions:
|
|
156
|
+
- action: notify.mobile_app_your_phone
|
|
157
|
+
data:
|
|
158
|
+
message: "The Welly water tray is running low."
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Dispense 2 portions from the Foody every morning at 07:00:
|
|
162
|
+
|
|
163
|
+
```yaml
|
|
164
|
+
automation:
|
|
165
|
+
- alias: "Morning feeding"
|
|
166
|
+
triggers:
|
|
167
|
+
- trigger: time
|
|
168
|
+
at: "07:00:00"
|
|
169
|
+
actions:
|
|
170
|
+
- action: number.set_value
|
|
171
|
+
target:
|
|
172
|
+
entity_id: number.klyqa_foody_d4e5f6_portions
|
|
173
|
+
data:
|
|
174
|
+
value: 2
|
|
175
|
+
- action: button.press
|
|
176
|
+
target:
|
|
177
|
+
entity_id: button.klyqa_foody_d4e5f6_dispense_food
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Replace the entity IDs above with the ones Home Assistant assigned to your own
|
|
181
|
+
devices (Settings → Devices & services → Klyqa Pet → your device).
|
|
182
|
+
|
|
183
|
+
## Known limitations
|
|
184
|
+
|
|
185
|
+
- Feeding schedules/timers, pet tags, scale calibration, firmware updates and
|
|
186
|
+
uploading custom voice recordings are not exposed by this integration. The
|
|
187
|
+
device's local REST API does not expose all of these, and some (like firmware
|
|
188
|
+
updates) are intentionally left to the manufacturer's app.
|
|
189
|
+
- The first-generation Klyqa air purifier (`@klyqa.cleaning.airpurifier1`) uses a
|
|
190
|
+
different local API and is not supported.
|
|
191
|
+
- On Foody units that run on mains power only, the battery sensor stays "unknown"
|
|
192
|
+
because the device never reports a battery level.
|
|
193
|
+
- Automatic discovery relies on mDNS (`_qcxrest._tcp`), which requires Home
|
|
194
|
+
Assistant to have access to LAN multicast traffic. This works when Home Assistant
|
|
195
|
+
runs natively (Home Assistant OS, Supervised, or a plain Python/venv install) or in
|
|
196
|
+
a container with host networking. It does **not** work in Docker Desktop on macOS,
|
|
197
|
+
because Docker Desktop's networking does not forward multicast traffic to
|
|
198
|
+
containers even with `network_mode: host`. On such setups, use the **Add a device
|
|
199
|
+
manually** option described above instead.
|
|
200
|
+
|
|
201
|
+
## Troubleshooting
|
|
202
|
+
|
|
203
|
+
**A device shows as unavailable.** Confirm the device is powered on and on the same
|
|
204
|
+
network as Home Assistant, and that Home Assistant can reach it on port `3333`
|
|
205
|
+
(`curl -H "Authorization: <token>" http://<device-ip>:3333/api/v1/system/info`). If
|
|
206
|
+
the device was previously discovered via mDNS and has since changed its IP address,
|
|
207
|
+
wait for the next mDNS announcement or restart Home Assistant to force a re-resolve.
|
|
208
|
+
|
|
209
|
+
**A device rejects its access token.** This affects only that one device: it becomes
|
|
210
|
+
unavailable and the log shows a warning naming it ("… rejects the access token from
|
|
211
|
+
the Klyqa account; re-pair the device in the Klyqa app"). This does **not** trigger a
|
|
212
|
+
re-authentication prompt — the integration first tries to recover by fetching a fresh
|
|
213
|
+
token from the cloud, and only asks you to sign in again if that cloud login itself
|
|
214
|
+
fails (see below). To fix a device stuck like this, either re-pair it in the Klyqa app
|
|
215
|
+
so the cloud hands out a new token, or remove and re-add it with **Add a device
|
|
216
|
+
manually** using its current token.
|
|
217
|
+
|
|
218
|
+
**The integration asks you to re-authenticate.** This happens only when the Klyqa
|
|
219
|
+
cloud itself rejects your account password (not when an individual device rejects its
|
|
220
|
+
token). Go to the integration's entry and follow the re-authentication prompt with
|
|
221
|
+
your current password.
|
|
222
|
+
|
|
223
|
+
**Enable debug logging** to see the raw REST requests/responses:
|
|
224
|
+
|
|
225
|
+
```yaml
|
|
226
|
+
logger:
|
|
227
|
+
default: info
|
|
228
|
+
logs:
|
|
229
|
+
custom_components.klyqa_pet: debug
|
|
230
|
+
pyklyqa_pet: debug
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
If you need to file an issue, first download the integration's diagnostics
|
|
234
|
+
(Settings → Devices & services → Klyqa Pet → your device → Download diagnostics) —
|
|
235
|
+
this redacts your email, password and access tokens and includes the raw device
|
|
236
|
+
state, which is invaluable for debugging.
|
|
237
|
+
|
|
238
|
+
## Removal
|
|
239
|
+
|
|
240
|
+
Removing the integration is a single step: go to **Settings → Devices & services**,
|
|
241
|
+
open the Klyqa Pet entry, and choose **Delete**. All devices and entities created by
|
|
242
|
+
the integration are removed from Home Assistant. Nothing is changed on the physical
|
|
243
|
+
devices themselves — no factory reset, no token revocation — so they keep working
|
|
244
|
+
with the Klyqa app.
|
|
245
|
+
|
|
246
|
+
## Development
|
|
247
|
+
|
|
248
|
+
This repository contains both the `pyklyqa_pet` client library and the
|
|
249
|
+
`custom_components/klyqa_pet` integration, developed together.
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
uv venv
|
|
253
|
+
uv pip install -e ".[dev]"
|
|
254
|
+
pytest
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
- A Dockerised Home Assistant instance for manual testing is described in
|
|
258
|
+
[`docker/README.md`](docker/README.md).
|
|
259
|
+
- `scripts/probe_devices.py` discovers Klyqa devices on the LAN via mDNS and dumps
|
|
260
|
+
their system info and state — useful for verifying connectivity and firmware
|
|
261
|
+
behaviour outside of Home Assistant.
|
|
262
|
+
- `brands/klyqa_pet/` contains the icon and logo assets prepared for submission
|
|
263
|
+
to [home-assistant/brands](https://github.com/home-assistant/brands).
|
|
264
|
+
- The integration implements the Home Assistant Bronze through Platinum quality
|
|
265
|
+
scale rules (see `custom_components/klyqa_pet/quality_scale.yaml`), except for the
|
|
266
|
+
`brands` rule: the assets above still need to be submitted and merged upstream
|
|
267
|
+
before that rule — and the manifest's `quality_scale` claim — can be marked done.
|
|
268
|
+
|
|
269
|
+
Pull requests are welcome at
|
|
270
|
+
[github.com/ninharp/ha-klyqa-pet](https://github.com/ninharp/ha-klyqa-pet).
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Async client library for Klyqa pet devices."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import aiohttp
|
|
6
|
+
|
|
7
|
+
from .airpurifier import AirPurifierDevice, AirPurifierRunMode, AirPurifierState
|
|
8
|
+
from .cloud import CloudDevice, KlyqaCloudClient
|
|
9
|
+
from .const import DEFAULT_PORT, DEV_ACCESS_TOKEN, ZEROCONF_TYPE, DeviceType, Environment
|
|
10
|
+
from .device import KlyqaDevice, SystemInfo
|
|
11
|
+
from .discovery import DiscoveredDevice, device_type_from_product_id, parse_zeroconf_properties
|
|
12
|
+
from .exceptions import KlyqaAuthError, KlyqaConnectionError, KlyqaDeviceError, KlyqaError
|
|
13
|
+
from .foody import FoodyDevice, FoodySettings, FoodyState
|
|
14
|
+
from .welly import WellyDevice, WellyMode, WellySettings, WellyState
|
|
15
|
+
|
|
16
|
+
__version__ = "0.1.0"
|
|
17
|
+
|
|
18
|
+
_DEVICE_CLASSES: dict[DeviceType, type[KlyqaDevice]] = {
|
|
19
|
+
DeviceType.WELLY: WellyDevice,
|
|
20
|
+
DeviceType.FOODY: FoodyDevice,
|
|
21
|
+
DeviceType.AIRPURIFIER: AirPurifierDevice,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def create_device(
|
|
26
|
+
device_type: DeviceType,
|
|
27
|
+
session: aiohttp.ClientSession,
|
|
28
|
+
host: str,
|
|
29
|
+
access_token: str,
|
|
30
|
+
port: int = DEFAULT_PORT,
|
|
31
|
+
) -> KlyqaDevice:
|
|
32
|
+
"""Instantiate the device client class matching the device type."""
|
|
33
|
+
return _DEVICE_CLASSES[device_type](session, host, access_token, port)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"DEFAULT_PORT",
|
|
38
|
+
"DEV_ACCESS_TOKEN",
|
|
39
|
+
"ZEROCONF_TYPE",
|
|
40
|
+
"AirPurifierDevice",
|
|
41
|
+
"AirPurifierRunMode",
|
|
42
|
+
"AirPurifierState",
|
|
43
|
+
"CloudDevice",
|
|
44
|
+
"DeviceType",
|
|
45
|
+
"DiscoveredDevice",
|
|
46
|
+
"Environment",
|
|
47
|
+
"FoodyDevice",
|
|
48
|
+
"FoodySettings",
|
|
49
|
+
"FoodyState",
|
|
50
|
+
"KlyqaAuthError",
|
|
51
|
+
"KlyqaCloudClient",
|
|
52
|
+
"KlyqaConnectionError",
|
|
53
|
+
"KlyqaDevice",
|
|
54
|
+
"KlyqaDeviceError",
|
|
55
|
+
"KlyqaError",
|
|
56
|
+
"SystemInfo",
|
|
57
|
+
"WellyDevice",
|
|
58
|
+
"WellyMode",
|
|
59
|
+
"WellySettings",
|
|
60
|
+
"WellyState",
|
|
61
|
+
"create_device",
|
|
62
|
+
"device_type_from_product_id",
|
|
63
|
+
"parse_zeroconf_properties",
|
|
64
|
+
]
|