kettle-x2p-cli 1.0.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.
- kettle_x2p_cli-1.0.0/CHANGELOG.md +27 -0
- kettle_x2p_cli-1.0.0/LICENSE +22 -0
- kettle_x2p_cli-1.0.0/MANIFEST.in +3 -0
- kettle_x2p_cli-1.0.0/PKG-INFO +198 -0
- kettle_x2p_cli-1.0.0/README.md +178 -0
- kettle_x2p_cli-1.0.0/SPEC.md +68 -0
- kettle_x2p_cli-1.0.0/pyproject.toml +40 -0
- kettle_x2p_cli-1.0.0/setup.cfg +4 -0
- kettle_x2p_cli-1.0.0/src/kettle/__init__.py +3 -0
- kettle_x2p_cli-1.0.0/src/kettle/__main__.py +4 -0
- kettle_x2p_cli-1.0.0/src/kettle/aes.py +151 -0
- kettle_x2p_cli-1.0.0/src/kettle/cli.py +437 -0
- kettle_x2p_cli-1.0.0/src/kettle/config.py +21 -0
- kettle_x2p_cli-1.0.0/src/kettle/miio.py +220 -0
- kettle_x2p_cli-1.0.0/src/kettle/wizard.py +167 -0
- kettle_x2p_cli-1.0.0/src/kettle_x2p_cli.egg-info/PKG-INFO +198 -0
- kettle_x2p_cli-1.0.0/src/kettle_x2p_cli.egg-info/SOURCES.txt +23 -0
- kettle_x2p_cli-1.0.0/src/kettle_x2p_cli.egg-info/dependency_links.txt +1 -0
- kettle_x2p_cli-1.0.0/src/kettle_x2p_cli.egg-info/entry_points.txt +2 -0
- kettle_x2p_cli-1.0.0/src/kettle_x2p_cli.egg-info/top_level.txt +1 -0
- kettle_x2p_cli-1.0.0/tests/helpers.py +111 -0
- kettle_x2p_cli-1.0.0/tests/test_aes.py +57 -0
- kettle_x2p_cli-1.0.0/tests/test_cli.py +122 -0
- kettle_x2p_cli-1.0.0/tests/test_miio.py +84 -0
- kettle_x2p_cli-1.0.0/tests/test_wizard.py +123 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [1.0.0] - 2026-10-16
|
|
11
|
+
### Added
|
|
12
|
+
- Initial release, available from [PyPI](https://pypi.org/project/kettle-x2p-cli/) (`pip install kettle-x2p-cli`),
|
|
13
|
+
the [mane tap](https://github.com/ManeFunction/homebrew-tap) (`brew install kettle`) and GitHub releases.
|
|
14
|
+
Local control of the Xiaomi Smart Kettle 2 Pro (EU, `xiaomi.kettle.v20`) over the LAN:
|
|
15
|
+
`boil`, `warm-to <temp>`, `warm-keep <temp> <minutes>`, `cancel` and `status`, plus an interactive `shell`.
|
|
16
|
+
- `kettle setup`: guided first-time setup with on-screen instructions. It explains how to pair the kettle in the
|
|
17
|
+
Xiaomi Home app, where to get the token extractor, asks for the token (hidden input), finds the kettle on the
|
|
18
|
+
network by trying the token on each Xiaomi device, then verifies and saves it.
|
|
19
|
+
- `info`, `find` (re-locates the kettle after an IP change) and `hello` (raw handshake, for diagnostics).
|
|
20
|
+
- `provision` for an advanced, experimental Wi-Fi setup of a kettle in setup mode.
|
|
21
|
+
- Safety checks before heating: the kettle must be `xiaomi.kettle.v20`, idle, without a fault and on its base;
|
|
22
|
+
the exact writes are printed and confirmed (`--yes` is required without a terminal). `--dry-run` prints the
|
|
23
|
+
requests without sending anything.
|
|
24
|
+
- Config in `~/.config/kettle/config.json` (mode 600). The client re-handshakes and re-syncs the device
|
|
25
|
+
timestamp on its own.
|
|
26
|
+
- No dependencies: AES-128-CBC is implemented in pure Python (checked against the FIPS-197 and NIST SP 800-38A
|
|
27
|
+
test vectors).
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
The MIT License (MIT)
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2026 Ilia Petrov-Komotskii (Mane Function)
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kettle-x2p-cli
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Local control of the Xiaomi Smart Kettle 2 Pro over your Wi-Fi, no cloud at runtime.
|
|
5
|
+
Author-email: Ilia Petrov-Komotskii <ilia@inkedkettle.art>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ManeFunction/kettle-x2p-cli
|
|
8
|
+
Project-URL: Repository, https://github.com/ManeFunction/kettle-x2p-cli
|
|
9
|
+
Project-URL: Issues, https://github.com/ManeFunction/kettle-x2p-cli/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/ManeFunction/kettle-x2p-cli/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: xiaomi,mijia,miot,miio,kettle,smart-home,home-assistant,cli
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Topic :: Home Automation
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# kettle
|
|
22
|
+
|
|
23
|
+
Control your [Xiaomi Smart Kettle 2 Pro](https://www.mi.com/global/product/xiaomi-smart-kettle-2-pro/) from the
|
|
24
|
+
Terminal: boil, heat to a temperature, keep warm for up to 24 hours, check the state or stop it. Everything runs over
|
|
25
|
+
your own Wi-Fi, directly to the kettle - no Xiaomi Home app and no cloud connection when you use it.
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
$ kettle warm-keep 80 120
|
|
29
|
+
warm to 80 C and keep warm 120 min: water at 41 C, idle, no fault.
|
|
30
|
+
About to send set_properties:
|
|
31
|
+
(2,4) = 80
|
|
32
|
+
...
|
|
33
|
+
Start heating? [y/N] y
|
|
34
|
+
status: heating, 41 C, fault 0
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Written for the EU model (BHR9107EU, `xiaomi.kettle.v20`, 1.7 L, 1800 W), tested on firmware `2.2.7_0016`.
|
|
38
|
+
It has no dependencies except Python itself. This is the standalone tool and a documented protocol reference ([SPEC.md](https://github.com/ManeFunction/kettle-x2p-cli/blob/main/SPEC.md))
|
|
39
|
+
made just for fun... or not. Setting a kettle right from the Terminal while working is pretty neat, actually :)
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Features
|
|
44
|
+
|
|
45
|
+
| Command | What it does |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `kettle setup` | Guided first-time setup: asks for the token, finds the kettle, checks and saves it. |
|
|
48
|
+
| `kettle boil` | Heats to boiling (99 °C, the kettle's boil mode). |
|
|
49
|
+
| `kettle warm-to <40-99>` | Heats to that temperature in °C, then stops. |
|
|
50
|
+
| `kettle warm-keep <40-99> <60-1440>` | Heats to that temperature and keeps it warm for that many minutes (1-24 h). |
|
|
51
|
+
| `kettle cancel` | Stops heating / keep-warm. |
|
|
52
|
+
| `kettle status` | State, current temperature, target, fault, keep-warm settings. |
|
|
53
|
+
| `kettle shell` | Interactive session with the same commands. |
|
|
54
|
+
| `kettle info` | Model, firmware and MAC from the kettle. |
|
|
55
|
+
| `kettle find` | Finds the kettle on the network again if its IP changed. |
|
|
56
|
+
|
|
57
|
+
Useful flags go before the command: `--dry-run` (print the requests, send nothing), `--yes` (skip the heating
|
|
58
|
+
confirmation), `--debug` (raw packets), `--ip`, `--token`.
|
|
59
|
+
|
|
60
|
+
**Safety.** Heating commands refuse to start unless the kettle is a `xiaomi.kettle.v20`, idle, on its base and
|
|
61
|
+
reporting no fault, and a status read that fails or is unclear never starts anything. They print exactly what they
|
|
62
|
+
will send and ask `y/N` (without a terminal, `--yes` is required). This model has no water-level reading, so a
|
|
63
|
+
low-water condition only shows up as a fault code. Heat water remotely only when you know the kettle has water in it.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Installation
|
|
68
|
+
|
|
69
|
+
**kettle** is available from a variety of sources.
|
|
70
|
+
`pip` or `brew` is recommended, because they have a convenient way to manage updates automatically.
|
|
71
|
+
|
|
72
|
+
1) **pip (Recommended for everyone with a Python environment)**
|
|
73
|
+
- You can check if you have Python installed by running `python --version` in the Terminal or cmd.
|
|
74
|
+
- For Mac and Linux users, there is a high chance that you already have Python installed on your system.
|
|
75
|
+
- For Windows users, you can download Python from the [official website](https://www.python.org/downloads/).
|
|
76
|
+
The tool is built and tested for macOS and Linux; it should work on Windows too, but that is untested.
|
|
77
|
+
- After confirmation, install **kettle** through the [PyPI](https://pypi.org/project/kettle-x2p-cli) package
|
|
78
|
+
manager, typing `pip install kettle-x2p-cli` in the Terminal. For Mac users, you may need to use `pip3`
|
|
79
|
+
instead of `pip`. The package is called `kettle-x2p-cli`; the command it installs is `kettle`.
|
|
80
|
+
- Verify the installation with `kettle --version`.
|
|
81
|
+
- You are perfect, you can use the app with `kettle` from any folder in your system.
|
|
82
|
+
2) **brew (Recommended for Mac and Linux users)**
|
|
83
|
+
- Type `brew tap manefunction/tap` in your Terminal to add my custom tap (app source) to your brew sources,
|
|
84
|
+
if you haven't already.
|
|
85
|
+
- Type `brew install kettle` to install the application itself.
|
|
86
|
+
- Verify the installation with `kettle --version`.
|
|
87
|
+
- You are perfect, you can use the app with `kettle` from any folder in your system.
|
|
88
|
+
3) **Python package (manual installation, for advanced users)**
|
|
89
|
+
- Clone the repository or download the source code from GitHub.
|
|
90
|
+
- Go to the folder with the code in your Terminal.
|
|
91
|
+
- Run `pip install .` to install `kettle` to your system.
|
|
92
|
+
- Run `kettle --version` to verify the install.
|
|
93
|
+
|
|
94
|
+
Updates arrive with `pip install --upgrade kettle-x2p-cli` or `brew upgrade`. Your saved kettle settings live outside the install
|
|
95
|
+
(`~/.config/kettle/`), so upgrading or reinstalling never loses them. (`pip` may ask you to use a virtual environment or `pipx` on newer
|
|
96
|
+
systems; `pipx install kettle-x2p-cli` is a good fit for a command line tool.)
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## First-time setup
|
|
101
|
+
|
|
102
|
+
Run `kettle setup` and follow the instructions on the screen. It takes about five minutes the first time and is
|
|
103
|
+
never needed again unless you re-pair the kettle.
|
|
104
|
+
|
|
105
|
+
1. **Add the kettle to the Xiaomi Home app** (iOS / Android) so it joins your 2.4 GHz Wi-Fi.
|
|
106
|
+
2. **Get the kettle's token** with the free, open-source
|
|
107
|
+
[Xiaomi Cloud Tokens Extractor](https://github.com/PiotrMachowski/Xiaomi-Cloud-Tokens-Extractor). The setup
|
|
108
|
+
screen shows the exact commands for Windows, macOS and Linux. It asks for your Xiaomi login and lists your devices
|
|
109
|
+
with their tokens; copy the 32-character token of *Xiaomi Smart Kettle 2 Pro* (`xiaomi.kettle.v20`).
|
|
110
|
+
3. **Paste the token** into `kettle setup` (the input is hidden). It scans your network, tries the token on every
|
|
111
|
+
Xiaomi device it finds, and the one that answers is your kettle.
|
|
112
|
+
4. It checks the model and saves everything to `~/.config/kettle/config.json` (readable by you only).
|
|
113
|
+
|
|
114
|
+
Then try `kettle status`. Reserve the kettle's address in your router (DHCP reservation) so it never moves; if it
|
|
115
|
+
does, `kettle find` locates it again.
|
|
116
|
+
|
|
117
|
+
For scripts, skip the prompts: `kettle --token <token> --ip <ip> setup`. Note that a token typed on the command
|
|
118
|
+
line is visible in your shell history, so the interactive prompt is the safer way.
|
|
119
|
+
|
|
120
|
+
### Why the Xiaomi app and cloud are needed once
|
|
121
|
+
|
|
122
|
+
The kettle talks to the local network with a secret 16-byte token, and only Xiaomi's cloud keeps it for you.
|
|
123
|
+
On firmware `2.2.7` the kettle changes that token when it joins your Wi-Fi, so the token it shows while in
|
|
124
|
+
setup mode stops working afterwards. I tried the alternatives (including the app's own extra provisioning parameters,
|
|
125
|
+
`kettle provision --app-params`); none gave a usable token without the cloud. After the one-time extraction,
|
|
126
|
+
all control is local. If you re-pair or reset the kettle, the token changes and you run `kettle setup` again.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Usage
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
kettle status # what is it doing?
|
|
134
|
+
kettle boil # asks for confirmation, then heats to boiling
|
|
135
|
+
kettle warm-to 70 # 70 °C, then stop (green tea)
|
|
136
|
+
kettle warm-keep 85 90 # 85 °C and keep it there for 90 minutes
|
|
137
|
+
kettle cancel # stop everything
|
|
138
|
+
kettle --dry-run warm-to 70 # show what would be sent
|
|
139
|
+
kettle shell # keep a session open
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
In `kettle shell` you type the same commands without the `kettle` prefix (`boil`, `warm-to 70`, `status`, `quit`).
|
|
143
|
+
Keep-warm time is limited by the kettle to 60-1440 minutes, and the target temperature to 40-99 °C; anything
|
|
144
|
+
else is rejected before it is sent.
|
|
145
|
+
|
|
146
|
+
### Troubleshooting
|
|
147
|
+
|
|
148
|
+
- **"no reply from ..."**: the kettle is offline, the IP changed (`kettle find`), or the token is out of date
|
|
149
|
+
(the kettle silently ignores requests it cannot decrypt). Run `kettle setup` again if you re-paired it.
|
|
150
|
+
- **Setup does not find the kettle**: your computer and the kettle must be on the same network (no guest network
|
|
151
|
+
or VLAN between them). You can enter the IP by hand; it is in your router's client list.
|
|
152
|
+
- **"kettle reports fault ..."**: check the water level and that the kettle sits on its base. Fault codes are not
|
|
153
|
+
documented by Xiaomi; `0` means none.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## How it works
|
|
158
|
+
|
|
159
|
+
The kettle speaks Xiaomi's *miio* protocol over UDP port 54321: a hello handshake that returns the device id and
|
|
160
|
+
a timestamp, then JSON-RPC calls encrypted with AES-128-CBC keyed from the token. Its functions are described by
|
|
161
|
+
a MIoT spec (service/property numbers); the ones used here are listed in [SPEC.md](https://github.com/ManeFunction/kettle-x2p-cli/blob/main/SPEC.md). The client is a few hundred
|
|
162
|
+
lines of standard-library Python, including its own AES, and handles re-handshakes and timestamp drift by itself.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Development
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
python3 -m venv .venv && .venv/bin/pip install -e .
|
|
170
|
+
.venv/bin/python -m unittest discover -s tests -v
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The tests run against a fake kettle on the loopback interface that speaks the real wire format; nothing touches a
|
|
174
|
+
real device. The release workflow checks that the tag matches `src/kettle/__init__.py`, builds the package and
|
|
175
|
+
publishes it to PyPI and creates the GitHub release; the Homebrew formula is bumped automatically.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Credits
|
|
180
|
+
|
|
181
|
+
- [python-miio](https://github.com/rytilahti/python-miio) for documenting the miio protocol.
|
|
182
|
+
- [Xiaomi Cloud Tokens Extractor](https://github.com/PiotrMachowski/Xiaomi-Cloud-Tokens-Extractor) by Piotr Machowski.
|
|
183
|
+
- [miot-spec.org](https://home.miot-spec.com/) for the device specs, and username-ardrey for
|
|
184
|
+
[Xiaomi Smart Kettle integration for Home Assistant](https://github.com/username-andrey/xiaomi-smart-kettle-ha),
|
|
185
|
+
which shows how this model is started.
|
|
186
|
+
|
|
187
|
+
This is an unofficial project and is not affiliated with Xiaomi.
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
## Repository info
|
|
191
|
+
|
|
192
|
+
This repo follows the [Conventional Commits](https://www.conventionalcommits.org/) specification.
|
|
193
|
+
|
|
194
|
+
[](https://github.com/ManeFunction/kettle-x2p-cli/releases/latest)
|
|
195
|
+
[](https://github.com/ManeFunction/kettle-x2p-cli/releases)
|
|
196
|
+
[](https://pypi.org/project/kettle-x2p-cli/)
|
|
197
|
+
[](https://pypi.org/project/kettle-x2p-cli/)
|
|
198
|
+
[](https://github.com/sponsors/ManeFunction)
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# kettle
|
|
2
|
+
|
|
3
|
+
Control your [Xiaomi Smart Kettle 2 Pro](https://www.mi.com/global/product/xiaomi-smart-kettle-2-pro/) from the
|
|
4
|
+
Terminal: boil, heat to a temperature, keep warm for up to 24 hours, check the state or stop it. Everything runs over
|
|
5
|
+
your own Wi-Fi, directly to the kettle - no Xiaomi Home app and no cloud connection when you use it.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
$ kettle warm-keep 80 120
|
|
9
|
+
warm to 80 C and keep warm 120 min: water at 41 C, idle, no fault.
|
|
10
|
+
About to send set_properties:
|
|
11
|
+
(2,4) = 80
|
|
12
|
+
...
|
|
13
|
+
Start heating? [y/N] y
|
|
14
|
+
status: heating, 41 C, fault 0
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Written for the EU model (BHR9107EU, `xiaomi.kettle.v20`, 1.7 L, 1800 W), tested on firmware `2.2.7_0016`.
|
|
18
|
+
It has no dependencies except Python itself. This is the standalone tool and a documented protocol reference ([SPEC.md](https://github.com/ManeFunction/kettle-x2p-cli/blob/main/SPEC.md))
|
|
19
|
+
made just for fun... or not. Setting a kettle right from the Terminal while working is pretty neat, actually :)
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
| Command | What it does |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `kettle setup` | Guided first-time setup: asks for the token, finds the kettle, checks and saves it. |
|
|
28
|
+
| `kettle boil` | Heats to boiling (99 °C, the kettle's boil mode). |
|
|
29
|
+
| `kettle warm-to <40-99>` | Heats to that temperature in °C, then stops. |
|
|
30
|
+
| `kettle warm-keep <40-99> <60-1440>` | Heats to that temperature and keeps it warm for that many minutes (1-24 h). |
|
|
31
|
+
| `kettle cancel` | Stops heating / keep-warm. |
|
|
32
|
+
| `kettle status` | State, current temperature, target, fault, keep-warm settings. |
|
|
33
|
+
| `kettle shell` | Interactive session with the same commands. |
|
|
34
|
+
| `kettle info` | Model, firmware and MAC from the kettle. |
|
|
35
|
+
| `kettle find` | Finds the kettle on the network again if its IP changed. |
|
|
36
|
+
|
|
37
|
+
Useful flags go before the command: `--dry-run` (print the requests, send nothing), `--yes` (skip the heating
|
|
38
|
+
confirmation), `--debug` (raw packets), `--ip`, `--token`.
|
|
39
|
+
|
|
40
|
+
**Safety.** Heating commands refuse to start unless the kettle is a `xiaomi.kettle.v20`, idle, on its base and
|
|
41
|
+
reporting no fault, and a status read that fails or is unclear never starts anything. They print exactly what they
|
|
42
|
+
will send and ask `y/N` (without a terminal, `--yes` is required). This model has no water-level reading, so a
|
|
43
|
+
low-water condition only shows up as a fault code. Heat water remotely only when you know the kettle has water in it.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
**kettle** is available from a variety of sources.
|
|
50
|
+
`pip` or `brew` is recommended, because they have a convenient way to manage updates automatically.
|
|
51
|
+
|
|
52
|
+
1) **pip (Recommended for everyone with a Python environment)**
|
|
53
|
+
- You can check if you have Python installed by running `python --version` in the Terminal or cmd.
|
|
54
|
+
- For Mac and Linux users, there is a high chance that you already have Python installed on your system.
|
|
55
|
+
- For Windows users, you can download Python from the [official website](https://www.python.org/downloads/).
|
|
56
|
+
The tool is built and tested for macOS and Linux; it should work on Windows too, but that is untested.
|
|
57
|
+
- After confirmation, install **kettle** through the [PyPI](https://pypi.org/project/kettle-x2p-cli) package
|
|
58
|
+
manager, typing `pip install kettle-x2p-cli` in the Terminal. For Mac users, you may need to use `pip3`
|
|
59
|
+
instead of `pip`. The package is called `kettle-x2p-cli`; the command it installs is `kettle`.
|
|
60
|
+
- Verify the installation with `kettle --version`.
|
|
61
|
+
- You are perfect, you can use the app with `kettle` from any folder in your system.
|
|
62
|
+
2) **brew (Recommended for Mac and Linux users)**
|
|
63
|
+
- Type `brew tap manefunction/tap` in your Terminal to add my custom tap (app source) to your brew sources,
|
|
64
|
+
if you haven't already.
|
|
65
|
+
- Type `brew install kettle` to install the application itself.
|
|
66
|
+
- Verify the installation with `kettle --version`.
|
|
67
|
+
- You are perfect, you can use the app with `kettle` from any folder in your system.
|
|
68
|
+
3) **Python package (manual installation, for advanced users)**
|
|
69
|
+
- Clone the repository or download the source code from GitHub.
|
|
70
|
+
- Go to the folder with the code in your Terminal.
|
|
71
|
+
- Run `pip install .` to install `kettle` to your system.
|
|
72
|
+
- Run `kettle --version` to verify the install.
|
|
73
|
+
|
|
74
|
+
Updates arrive with `pip install --upgrade kettle-x2p-cli` or `brew upgrade`. Your saved kettle settings live outside the install
|
|
75
|
+
(`~/.config/kettle/`), so upgrading or reinstalling never loses them. (`pip` may ask you to use a virtual environment or `pipx` on newer
|
|
76
|
+
systems; `pipx install kettle-x2p-cli` is a good fit for a command line tool.)
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## First-time setup
|
|
81
|
+
|
|
82
|
+
Run `kettle setup` and follow the instructions on the screen. It takes about five minutes the first time and is
|
|
83
|
+
never needed again unless you re-pair the kettle.
|
|
84
|
+
|
|
85
|
+
1. **Add the kettle to the Xiaomi Home app** (iOS / Android) so it joins your 2.4 GHz Wi-Fi.
|
|
86
|
+
2. **Get the kettle's token** with the free, open-source
|
|
87
|
+
[Xiaomi Cloud Tokens Extractor](https://github.com/PiotrMachowski/Xiaomi-Cloud-Tokens-Extractor). The setup
|
|
88
|
+
screen shows the exact commands for Windows, macOS and Linux. It asks for your Xiaomi login and lists your devices
|
|
89
|
+
with their tokens; copy the 32-character token of *Xiaomi Smart Kettle 2 Pro* (`xiaomi.kettle.v20`).
|
|
90
|
+
3. **Paste the token** into `kettle setup` (the input is hidden). It scans your network, tries the token on every
|
|
91
|
+
Xiaomi device it finds, and the one that answers is your kettle.
|
|
92
|
+
4. It checks the model and saves everything to `~/.config/kettle/config.json` (readable by you only).
|
|
93
|
+
|
|
94
|
+
Then try `kettle status`. Reserve the kettle's address in your router (DHCP reservation) so it never moves; if it
|
|
95
|
+
does, `kettle find` locates it again.
|
|
96
|
+
|
|
97
|
+
For scripts, skip the prompts: `kettle --token <token> --ip <ip> setup`. Note that a token typed on the command
|
|
98
|
+
line is visible in your shell history, so the interactive prompt is the safer way.
|
|
99
|
+
|
|
100
|
+
### Why the Xiaomi app and cloud are needed once
|
|
101
|
+
|
|
102
|
+
The kettle talks to the local network with a secret 16-byte token, and only Xiaomi's cloud keeps it for you.
|
|
103
|
+
On firmware `2.2.7` the kettle changes that token when it joins your Wi-Fi, so the token it shows while in
|
|
104
|
+
setup mode stops working afterwards. I tried the alternatives (including the app's own extra provisioning parameters,
|
|
105
|
+
`kettle provision --app-params`); none gave a usable token without the cloud. After the one-time extraction,
|
|
106
|
+
all control is local. If you re-pair or reset the kettle, the token changes and you run `kettle setup` again.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Usage
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
kettle status # what is it doing?
|
|
114
|
+
kettle boil # asks for confirmation, then heats to boiling
|
|
115
|
+
kettle warm-to 70 # 70 °C, then stop (green tea)
|
|
116
|
+
kettle warm-keep 85 90 # 85 °C and keep it there for 90 minutes
|
|
117
|
+
kettle cancel # stop everything
|
|
118
|
+
kettle --dry-run warm-to 70 # show what would be sent
|
|
119
|
+
kettle shell # keep a session open
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
In `kettle shell` you type the same commands without the `kettle` prefix (`boil`, `warm-to 70`, `status`, `quit`).
|
|
123
|
+
Keep-warm time is limited by the kettle to 60-1440 minutes, and the target temperature to 40-99 °C; anything
|
|
124
|
+
else is rejected before it is sent.
|
|
125
|
+
|
|
126
|
+
### Troubleshooting
|
|
127
|
+
|
|
128
|
+
- **"no reply from ..."**: the kettle is offline, the IP changed (`kettle find`), or the token is out of date
|
|
129
|
+
(the kettle silently ignores requests it cannot decrypt). Run `kettle setup` again if you re-paired it.
|
|
130
|
+
- **Setup does not find the kettle**: your computer and the kettle must be on the same network (no guest network
|
|
131
|
+
or VLAN between them). You can enter the IP by hand; it is in your router's client list.
|
|
132
|
+
- **"kettle reports fault ..."**: check the water level and that the kettle sits on its base. Fault codes are not
|
|
133
|
+
documented by Xiaomi; `0` means none.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## How it works
|
|
138
|
+
|
|
139
|
+
The kettle speaks Xiaomi's *miio* protocol over UDP port 54321: a hello handshake that returns the device id and
|
|
140
|
+
a timestamp, then JSON-RPC calls encrypted with AES-128-CBC keyed from the token. Its functions are described by
|
|
141
|
+
a MIoT spec (service/property numbers); the ones used here are listed in [SPEC.md](https://github.com/ManeFunction/kettle-x2p-cli/blob/main/SPEC.md). The client is a few hundred
|
|
142
|
+
lines of standard-library Python, including its own AES, and handles re-handshakes and timestamp drift by itself.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Development
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
python3 -m venv .venv && .venv/bin/pip install -e .
|
|
150
|
+
.venv/bin/python -m unittest discover -s tests -v
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The tests run against a fake kettle on the loopback interface that speaks the real wire format; nothing touches a
|
|
154
|
+
real device. The release workflow checks that the tag matches `src/kettle/__init__.py`, builds the package and
|
|
155
|
+
publishes it to PyPI and creates the GitHub release; the Homebrew formula is bumped automatically.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Credits
|
|
160
|
+
|
|
161
|
+
- [python-miio](https://github.com/rytilahti/python-miio) for documenting the miio protocol.
|
|
162
|
+
- [Xiaomi Cloud Tokens Extractor](https://github.com/PiotrMachowski/Xiaomi-Cloud-Tokens-Extractor) by Piotr Machowski.
|
|
163
|
+
- [miot-spec.org](https://home.miot-spec.com/) for the device specs, and username-ardrey for
|
|
164
|
+
[Xiaomi Smart Kettle integration for Home Assistant](https://github.com/username-andrey/xiaomi-smart-kettle-ha),
|
|
165
|
+
which shows how this model is started.
|
|
166
|
+
|
|
167
|
+
This is an unofficial project and is not affiliated with Xiaomi.
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
## Repository info
|
|
171
|
+
|
|
172
|
+
This repo follows the [Conventional Commits](https://www.conventionalcommits.org/) specification.
|
|
173
|
+
|
|
174
|
+
[](https://github.com/ManeFunction/kettle-x2p-cli/releases/latest)
|
|
175
|
+
[](https://github.com/ManeFunction/kettle-x2p-cli/releases)
|
|
176
|
+
[](https://pypi.org/project/kettle-x2p-cli/)
|
|
177
|
+
[](https://pypi.org/project/kettle-x2p-cli/)
|
|
178
|
+
[](https://github.com/sponsors/ManeFunction)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# MIoT spec: Xiaomi Smart Kettle 2 Pro (EU, BHR9107EU)
|
|
2
|
+
|
|
3
|
+
**Model string: `xiaomi.kettle.v20`** ("Xiaomi Smart Kettle 2 Pro (欧规版)").
|
|
4
|
+
URN: `urn:miot-spec-v2:device:kettle:0000A009:xiaomi-v20:1`
|
|
5
|
+
|
|
6
|
+
> Status: **model confirmed on the device** (`miIO.info` returned `model: xiaomi.kettle.v20`,
|
|
7
|
+
> firmware `2.2.7_0016`, hw `esp32`). `boil`, `warm-to`, `warm-keep`, `cancel` and `status` are all verified on the device.
|
|
8
|
+
|
|
9
|
+
Sources (two agree on all numbers): home.miot-spec.com/spec/xiaomi.kettle.v20 and
|
|
10
|
+
miot-spec.org/miot-spec-v2/instance?type=<URN above>.
|
|
11
|
+
|
|
12
|
+
## siid 2: kettle
|
|
13
|
+
|
|
14
|
+
| piid | name | format | access | range / values | unit |
|
|
15
|
+
|---|---|---|---|---|---|
|
|
16
|
+
| 1 | status | uint8 | r/n | 0 Idle, 1 Heating, 2 Boiling, 3 Cooling down, 4 Keep warm | |
|
|
17
|
+
| 2 | fault | uint8 | r/n | 0–255 (0 = none) | |
|
|
18
|
+
| 3 | temperature | int8 | r/n | -30–100 | °C |
|
|
19
|
+
| 4 | target-temperature | uint8 | r/w/n | 40–99 | °C |
|
|
20
|
+
| 5 | auto-keep-warm | bool | r/w/n | | |
|
|
21
|
+
| 6 | keep-warm-temperature | uint8 | r/w/n | 0–100 | °C |
|
|
22
|
+
| 7 | on | bool | r/w/n | | |
|
|
23
|
+
|
|
24
|
+
## siid 3: function (xiaomi-spec)
|
|
25
|
+
|
|
26
|
+
| piid | name | format | access | range / values | unit |
|
|
27
|
+
|---|---|---|---|---|---|
|
|
28
|
+
| 1 | keep-warm-time | uint16 | r/w/n | **60–1440** | min |
|
|
29
|
+
| 2 | custom-knob-temp | bool | r/w/n | | |
|
|
30
|
+
| 4 | lift-remember-temp | bool | r/w/n | | |
|
|
31
|
+
| 5 | boiling-reminder | bool | r/w/n | | |
|
|
32
|
+
| 6 | keep-warm-reminder | bool | r/w/n | | |
|
|
33
|
+
| 7 | kettle-lifting | bool | r/n | true = kettle lifted off base | |
|
|
34
|
+
| 8 | extended-mode | string | r/w/n | `name,temp,keep,keep_temp,minutes_...` presets | |
|
|
35
|
+
| 10 | warming-time | uint16 | r/n | 0–720 | min |
|
|
36
|
+
| 11 | target-mode | uint8 | r/w/n | 0 Heating, 1 Boiling, 10–15 Extended 1–6 | |
|
|
37
|
+
| 12 | heat-mode | string | r/w/n | `"<target>,<keep 1/0>,<keep_temp>,<keep_min>"`, e.g. `70,1,45,1440` | |
|
|
38
|
+
| 13 | boil-mode | string | r/w/n | same layout, e.g. `99,1,45,1440` | |
|
|
39
|
+
|
|
40
|
+
Action: **siid 3, aiid 1 `stop-work`** (in: `[]`, out: `[]`).
|
|
41
|
+
|
|
42
|
+
Other services (not used): siid 6 no-disturb (piid 1, bool); siid 4 knob-setting (piid 1–7, strings);
|
|
43
|
+
siid 5 local-timing (piid 1–6 strings, action aiid 1 push-water-reminder). Events omitted.
|
|
44
|
+
|
|
45
|
+
There is **no water-level property** in this spec. The closest safety signals are `fault` (2,2),
|
|
46
|
+
`status` (2,1) and `kettle-lifting` (3,7). The unofficial HA integration for this model reports that
|
|
47
|
+
fault `E2` = low water, but I have not confirmed the numeric fault code for it.
|
|
48
|
+
|
|
49
|
+
## Proposed command mapping
|
|
50
|
+
|
|
51
|
+
How the device is *started* is not defined by the spec itself; the batch below comes from the community integration
|
|
52
|
+
[username-andrey/xiaomi-smart-kettle-ha](https://github.com/username-andrey/xiaomi-smart-kettle-ha)
|
|
53
|
+
(tested on firmware v2.2.7_0016), which sends one `set_properties` with these items in this order.
|
|
54
|
+
`boil` was confirmed on our hardware (starts heating without writing `on`).
|
|
55
|
+
|
|
56
|
+
| command | set_properties (siid,piid = value) |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `boil` | (2,4)=99, (2,5)=false, (2,6)=keep_temp, (3,1)=keep_min, (3,11)=1, (3,13)=`"99,0,<keep_temp>,<keep_min>"` |
|
|
59
|
+
| `warm-to T` | (2,4)=T, (2,5)=false, (2,6)=keep_temp, (3,1)=keep_min, (3,11)=0, (3,12)=`"T,0,<keep_temp>,<keep_min>"` |
|
|
60
|
+
| `warm-keep T M` | (2,4)=T, (2,5)=true, (2,6)=T, (3,1)=M, (3,11)=0, (3,12)=`"T,1,T,M"` |
|
|
61
|
+
| `cancel` | action (3,1) stop-work |
|
|
62
|
+
| `status` | get (2,1) (2,2) (2,3) (2,4) (2,5) (2,6) (3,7) (3,10) (3,11) |
|
|
63
|
+
|
|
64
|
+
Validation limits taken from the spec: target 40–99 °C; keep-warm minutes 60–1440 (spec minimum is
|
|
65
|
+
60, so `warm-keep` rejects less than 1 h).
|
|
66
|
+
|
|
67
|
+
Open points:
|
|
68
|
+
Which `fault` value means low water is still unknown.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kettle-x2p-cli"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Local control of the Xiaomi Smart Kettle 2 Pro over your Wi-Fi, no cloud at runtime."
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "Ilia Petrov-Komotskii", email = "ilia@inkedkettle.art" }
|
|
11
|
+
]
|
|
12
|
+
license = "MIT"
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
requires-python = ">=3.9"
|
|
15
|
+
keywords = ["xiaomi", "mijia", "miot", "miio", "kettle", "smart-home", "home-assistant", "cli"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Environment :: Console",
|
|
20
|
+
"Topic :: Home Automation",
|
|
21
|
+
]
|
|
22
|
+
dependencies = []
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://github.com/ManeFunction/kettle-x2p-cli"
|
|
26
|
+
Repository = "https://github.com/ManeFunction/kettle-x2p-cli"
|
|
27
|
+
Issues = "https://github.com/ManeFunction/kettle-x2p-cli/issues"
|
|
28
|
+
Changelog = "https://github.com/ManeFunction/kettle-x2p-cli/blob/main/CHANGELOG.md"
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
kettle = "kettle.cli:main"
|
|
32
|
+
|
|
33
|
+
[tool.setuptools.dynamic]
|
|
34
|
+
version = { attr = "kettle.version" }
|
|
35
|
+
|
|
36
|
+
[tool.setuptools.packages.find]
|
|
37
|
+
where = ["src"]
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.package-dir]
|
|
40
|
+
"" = "src"
|