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.
Files changed (30) hide show
  1. pyklyqa_pet-0.1.0/.gitignore +13 -0
  2. pyklyqa_pet-0.1.0/PKG-INFO +290 -0
  3. pyklyqa_pet-0.1.0/README.md +270 -0
  4. pyklyqa_pet-0.1.0/pyklyqa_pet/__init__.py +64 -0
  5. pyklyqa_pet-0.1.0/pyklyqa_pet/airpurifier.py +146 -0
  6. pyklyqa_pet-0.1.0/pyklyqa_pet/cloud.py +113 -0
  7. pyklyqa_pet-0.1.0/pyklyqa_pet/const.py +32 -0
  8. pyklyqa_pet-0.1.0/pyklyqa_pet/device.py +176 -0
  9. pyklyqa_pet-0.1.0/pyklyqa_pet/discovery.py +72 -0
  10. pyklyqa_pet-0.1.0/pyklyqa_pet/exceptions.py +22 -0
  11. pyklyqa_pet-0.1.0/pyklyqa_pet/foody.py +146 -0
  12. pyklyqa_pet-0.1.0/pyklyqa_pet/py.typed +0 -0
  13. pyklyqa_pet-0.1.0/pyklyqa_pet/welly.py +182 -0
  14. pyklyqa_pet-0.1.0/pyproject.toml +71 -0
  15. pyklyqa_pet-0.1.0/tests_lib/__init__.py +0 -0
  16. pyklyqa_pet-0.1.0/tests_lib/conftest.py +110 -0
  17. pyklyqa_pet-0.1.0/tests_lib/fixtures/airpurifier_state.json +37 -0
  18. pyklyqa_pet-0.1.0/tests_lib/fixtures/foody_settings.json +12 -0
  19. pyklyqa_pet-0.1.0/tests_lib/fixtures/foody_state.json +23 -0
  20. pyklyqa_pet-0.1.0/tests_lib/fixtures/system_info.json +19 -0
  21. pyklyqa_pet-0.1.0/tests_lib/fixtures/welly_settings.json +17 -0
  22. pyklyqa_pet-0.1.0/tests_lib/fixtures/welly_state.json +21 -0
  23. pyklyqa_pet-0.1.0/tests_lib/test_airpurifier.py +107 -0
  24. pyklyqa_pet-0.1.0/tests_lib/test_cloud.py +127 -0
  25. pyklyqa_pet-0.1.0/tests_lib/test_device.py +113 -0
  26. pyklyqa_pet-0.1.0/tests_lib/test_discovery.py +69 -0
  27. pyklyqa_pet-0.1.0/tests_lib/test_foody.py +97 -0
  28. pyklyqa_pet-0.1.0/tests_lib/test_public_api.py +35 -0
  29. pyklyqa_pet-0.1.0/tests_lib/test_version.py +5 -0
  30. pyklyqa_pet-0.1.0/tests_lib/test_welly.py +97 -0
@@ -0,0 +1,13 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .mypy_cache/
5
+ .ruff_cache/
6
+ .pytest_cache/
7
+ .coverage
8
+ htmlcov/
9
+ dist/
10
+ docker/config/*
11
+ !docker/config/configuration.yaml
12
+ docs/superpowers/
13
+ .ha-native/
@@ -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
+ ]