pywfrac 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.
- pywfrac-0.1.0/.github/workflows/ci.yml +61 -0
- pywfrac-0.1.0/.github/workflows/release.yml +62 -0
- pywfrac-0.1.0/.gitignore +11 -0
- pywfrac-0.1.0/LICENSE +22 -0
- pywfrac-0.1.0/PKG-INFO +101 -0
- pywfrac-0.1.0/README.md +71 -0
- pywfrac-0.1.0/pyproject.toml +70 -0
- pywfrac-0.1.0/src/pywfrac/__init__.py +45 -0
- pywfrac-0.1.0/src/pywfrac/capabilities.py +97 -0
- pywfrac-0.1.0/src/pywfrac/error_codes.py +84 -0
- pywfrac-0.1.0/src/pywfrac/models/__init__.py +0 -0
- pywfrac-0.1.0/src/pywfrac/models/aircon.py +166 -0
- pywfrac-0.1.0/src/pywfrac/parser.py +748 -0
- pywfrac-0.1.0/src/pywfrac/py.typed +0 -0
- pywfrac-0.1.0/src/pywfrac/repository.py +484 -0
- pywfrac-0.1.0/src/pywfrac/utils.py +531 -0
- pywfrac-0.1.0/tests/__init__.py +0 -0
- pywfrac-0.1.0/tests/live_captures.py +94 -0
- pywfrac-0.1.0/tests/test_capabilities.py +50 -0
- pywfrac-0.1.0/tests/test_error_codes.py +39 -0
- pywfrac-0.1.0/tests/test_rac_parser.py +723 -0
- pywfrac-0.1.0/tests/test_repository.py +399 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
pytest:
|
|
9
|
+
name: Pytest (${{ matrix.python-version }})
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python-version }}
|
|
19
|
+
- name: Install
|
|
20
|
+
run: pip install -e ".[dev]"
|
|
21
|
+
- name: Run tests
|
|
22
|
+
run: pytest --cov=src/pywfrac --cov-report=term-missing
|
|
23
|
+
|
|
24
|
+
mypy:
|
|
25
|
+
name: Mypy
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v4
|
|
29
|
+
- uses: actions/setup-python@v5
|
|
30
|
+
with:
|
|
31
|
+
python-version: "3.13"
|
|
32
|
+
- name: Install
|
|
33
|
+
run: pip install -e ".[dev]"
|
|
34
|
+
- name: Run mypy
|
|
35
|
+
run: mypy
|
|
36
|
+
|
|
37
|
+
ruff:
|
|
38
|
+
name: Ruff
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v4
|
|
42
|
+
- uses: actions/setup-python@v5
|
|
43
|
+
with:
|
|
44
|
+
python-version: "3.13"
|
|
45
|
+
- name: Install
|
|
46
|
+
run: pip install -e ".[dev]"
|
|
47
|
+
- name: Run ruff
|
|
48
|
+
run: ruff check .
|
|
49
|
+
|
|
50
|
+
build:
|
|
51
|
+
name: Build package
|
|
52
|
+
runs-on: ubuntu-latest
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/checkout@v4
|
|
55
|
+
- uses: actions/setup-python@v5
|
|
56
|
+
with:
|
|
57
|
+
python-version: "3.13"
|
|
58
|
+
- name: Install build
|
|
59
|
+
run: pip install build
|
|
60
|
+
- name: Build sdist and wheel
|
|
61
|
+
run: python -m build
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Triggered by pushing a version tag (vX.Y.Z). Builds sdist+wheel and
|
|
4
|
+
# publishes to PyPI via Trusted Publishing (OIDC) - no token stored in this
|
|
5
|
+
# repo. The PyPI project must have this workflow registered as a trusted
|
|
6
|
+
# publisher first (pypi.org project settings -> Publishing), which is a
|
|
7
|
+
# one-time manual step done from the PyPI web UI.
|
|
8
|
+
|
|
9
|
+
on:
|
|
10
|
+
push:
|
|
11
|
+
tags:
|
|
12
|
+
- "v*"
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
check-version:
|
|
19
|
+
name: Check tag against pyproject.toml
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
- name: Compare tag to the package version
|
|
24
|
+
run: |
|
|
25
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
26
|
+
version=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
|
|
27
|
+
if [ "$tag" != "$version" ]; then
|
|
28
|
+
echo "::error::tag $GITHUB_REF_NAME does not match pyproject.toml version $version"
|
|
29
|
+
exit 1
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
build:
|
|
33
|
+
name: Build distribution
|
|
34
|
+
needs: check-version
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@v4
|
|
38
|
+
- uses: actions/setup-python@v5
|
|
39
|
+
with:
|
|
40
|
+
python-version: "3.13"
|
|
41
|
+
- name: Install build
|
|
42
|
+
run: pip install build
|
|
43
|
+
- name: Build sdist and wheel
|
|
44
|
+
run: python -m build
|
|
45
|
+
- uses: actions/upload-artifact@v4
|
|
46
|
+
with:
|
|
47
|
+
name: dist
|
|
48
|
+
path: dist/
|
|
49
|
+
|
|
50
|
+
publish:
|
|
51
|
+
name: Publish to PyPI
|
|
52
|
+
needs: build
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
environment: pypi
|
|
55
|
+
permissions:
|
|
56
|
+
id-token: write # OIDC for Trusted Publishing
|
|
57
|
+
steps:
|
|
58
|
+
- uses: actions/download-artifact@v4
|
|
59
|
+
with:
|
|
60
|
+
name: dist
|
|
61
|
+
path: dist/
|
|
62
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
pywfrac-0.1.0/.gitignore
ADDED
pywfrac-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 jeatheak
|
|
4
|
+
Copyright (c) 2026 blues-sechseck
|
|
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.
|
pywfrac-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pywfrac
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Async client library for Mitsubishi Heavy Industries WF-RAC WiFi module air conditioners
|
|
5
|
+
Project-URL: Homepage, https://github.com/blues-sechseck/pywfrac
|
|
6
|
+
Project-URL: Protocol reference, https://github.com/blues-sechseck/Mitsubishi-WF-RAC-Integration/blob/main/ha-integration/docs/wf-rac-module-reference.md
|
|
7
|
+
Project-URL: Changelog, https://github.com/blues-sechseck/pywfrac/releases
|
|
8
|
+
Author-email: Daniel <daosthofen@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: air-conditioner,hvac,mitsubishi,wf-rac
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Home Automation
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: aiohttp>=3.9
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
28
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# pywfrac
|
|
32
|
+
|
|
33
|
+
[](https://pypi.org/project/pywfrac/)
|
|
34
|
+
[](https://github.com/blues-sechseck/pywfrac/actions/workflows/ci.yml)
|
|
35
|
+
|
|
36
|
+
Async Python client library for **Mitsubishi Heavy Industries** air
|
|
37
|
+
conditioners that use the **WF-RAC** WiFi module and the **Smart M-Air** app.
|
|
38
|
+
Talks the module's local HTTP(S) API and decodes/encodes the `airconStat`
|
|
39
|
+
protocol carried over it.
|
|
40
|
+
|
|
41
|
+
> **Not for Mitsubishi Electric systems** (e.g. a MAC-577IF2-E interface) or
|
|
42
|
+
> MELCloud — different manufacturer, different protocol.
|
|
43
|
+
|
|
44
|
+
This library is the protocol layer extracted from the
|
|
45
|
+
[`mitsubishi_wf_rac`](https://github.com/blues-sechseck/Mitsubishi-WF-RAC-Integration)
|
|
46
|
+
Home Assistant integration, which stays the reference for field notes and
|
|
47
|
+
supported devices. `pywfrac` has no dependency on Home Assistant — it only
|
|
48
|
+
needs an `aiohttp.ClientSession`.
|
|
49
|
+
|
|
50
|
+
## Install
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
pip install pywfrac
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import aiohttp
|
|
60
|
+
from pywfrac import Repository
|
|
61
|
+
|
|
62
|
+
async def main() -> None:
|
|
63
|
+
async with aiohttp.ClientSession() as session:
|
|
64
|
+
repo = Repository(
|
|
65
|
+
session,
|
|
66
|
+
hostname="192.168.1.50",
|
|
67
|
+
port=51443,
|
|
68
|
+
operator_id="my-operator-id",
|
|
69
|
+
device_id="my-device-id",
|
|
70
|
+
)
|
|
71
|
+
airco_id = await repo.get_airco_id()
|
|
72
|
+
await repo.update_account_info(airco_id, time_zone="Europe/Berlin")
|
|
73
|
+
stats = await repo.get_aircon_stats(airco_id)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`Repository` discovers whether the module speaks plain HTTP or HTTPS on the
|
|
77
|
+
first request and remembers the result; construct it with `method="http"` or
|
|
78
|
+
`method="https"` to skip discovery if you already know. Pass `cert_path` to
|
|
79
|
+
use a captured module certificate instead of the permissive fallback context
|
|
80
|
+
— see `Repository._get_ssl_context` for how to capture one.
|
|
81
|
+
|
|
82
|
+
`RacParser` turns a raw `getAirconStat` response into an `Aircon`/`AirconStat`
|
|
83
|
+
object and back; see the protocol reference linked below for the field
|
|
84
|
+
layout.
|
|
85
|
+
|
|
86
|
+
## Protocol reference
|
|
87
|
+
|
|
88
|
+
The `airconStat` wire format, error codes, and per-model capability tables
|
|
89
|
+
are documented in the integration's
|
|
90
|
+
[module reference](https://github.com/blues-sechseck/Mitsubishi-WF-RAC-Integration/blob/main/ha-integration/docs/wf-rac-module-reference.md).
|
|
91
|
+
|
|
92
|
+
## Versioning
|
|
93
|
+
|
|
94
|
+
This library follows [SemVer](https://semver.org/), independent of the
|
|
95
|
+
integration's own CalVer release scheme — pin an exact version.
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT, see [LICENSE](LICENSE). Extracted from code originally by
|
|
100
|
+
[@jeatheak](https://github.com/jeatheak), maintained since by
|
|
101
|
+
[@blues-sechseck](https://github.com/blues-sechseck).
|
pywfrac-0.1.0/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# pywfrac
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/pywfrac/)
|
|
4
|
+
[](https://github.com/blues-sechseck/pywfrac/actions/workflows/ci.yml)
|
|
5
|
+
|
|
6
|
+
Async Python client library for **Mitsubishi Heavy Industries** air
|
|
7
|
+
conditioners that use the **WF-RAC** WiFi module and the **Smart M-Air** app.
|
|
8
|
+
Talks the module's local HTTP(S) API and decodes/encodes the `airconStat`
|
|
9
|
+
protocol carried over it.
|
|
10
|
+
|
|
11
|
+
> **Not for Mitsubishi Electric systems** (e.g. a MAC-577IF2-E interface) or
|
|
12
|
+
> MELCloud — different manufacturer, different protocol.
|
|
13
|
+
|
|
14
|
+
This library is the protocol layer extracted from the
|
|
15
|
+
[`mitsubishi_wf_rac`](https://github.com/blues-sechseck/Mitsubishi-WF-RAC-Integration)
|
|
16
|
+
Home Assistant integration, which stays the reference for field notes and
|
|
17
|
+
supported devices. `pywfrac` has no dependency on Home Assistant — it only
|
|
18
|
+
needs an `aiohttp.ClientSession`.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
pip install pywfrac
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import aiohttp
|
|
30
|
+
from pywfrac import Repository
|
|
31
|
+
|
|
32
|
+
async def main() -> None:
|
|
33
|
+
async with aiohttp.ClientSession() as session:
|
|
34
|
+
repo = Repository(
|
|
35
|
+
session,
|
|
36
|
+
hostname="192.168.1.50",
|
|
37
|
+
port=51443,
|
|
38
|
+
operator_id="my-operator-id",
|
|
39
|
+
device_id="my-device-id",
|
|
40
|
+
)
|
|
41
|
+
airco_id = await repo.get_airco_id()
|
|
42
|
+
await repo.update_account_info(airco_id, time_zone="Europe/Berlin")
|
|
43
|
+
stats = await repo.get_aircon_stats(airco_id)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`Repository` discovers whether the module speaks plain HTTP or HTTPS on the
|
|
47
|
+
first request and remembers the result; construct it with `method="http"` or
|
|
48
|
+
`method="https"` to skip discovery if you already know. Pass `cert_path` to
|
|
49
|
+
use a captured module certificate instead of the permissive fallback context
|
|
50
|
+
— see `Repository._get_ssl_context` for how to capture one.
|
|
51
|
+
|
|
52
|
+
`RacParser` turns a raw `getAirconStat` response into an `Aircon`/`AirconStat`
|
|
53
|
+
object and back; see the protocol reference linked below for the field
|
|
54
|
+
layout.
|
|
55
|
+
|
|
56
|
+
## Protocol reference
|
|
57
|
+
|
|
58
|
+
The `airconStat` wire format, error codes, and per-model capability tables
|
|
59
|
+
are documented in the integration's
|
|
60
|
+
[module reference](https://github.com/blues-sechseck/Mitsubishi-WF-RAC-Integration/blob/main/ha-integration/docs/wf-rac-module-reference.md).
|
|
61
|
+
|
|
62
|
+
## Versioning
|
|
63
|
+
|
|
64
|
+
This library follows [SemVer](https://semver.org/), independent of the
|
|
65
|
+
integration's own CalVer release scheme — pin an exact version.
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
MIT, see [LICENSE](LICENSE). Extracted from code originally by
|
|
70
|
+
[@jeatheak](https://github.com/jeatheak), maintained since by
|
|
71
|
+
[@blues-sechseck](https://github.com/blues-sechseck).
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pywfrac"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Async client library for Mitsubishi Heavy Industries WF-RAC WiFi module air conditioners"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Daniel", email = "daosthofen@gmail.com" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["mitsubishi", "wf-rac", "air-conditioner", "hvac"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Topic :: Home Automation",
|
|
25
|
+
"Typing :: Typed",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"aiohttp>=3.9",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
dev = [
|
|
33
|
+
"pytest",
|
|
34
|
+
"pytest-asyncio",
|
|
35
|
+
"pytest-cov",
|
|
36
|
+
"mypy",
|
|
37
|
+
"ruff",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://github.com/blues-sechseck/pywfrac"
|
|
42
|
+
"Protocol reference" = "https://github.com/blues-sechseck/Mitsubishi-WF-RAC-Integration/blob/main/ha-integration/docs/wf-rac-module-reference.md"
|
|
43
|
+
Changelog = "https://github.com/blues-sechseck/pywfrac/releases"
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build.targets.wheel]
|
|
46
|
+
packages = ["src/pywfrac"]
|
|
47
|
+
|
|
48
|
+
[tool.pytest.ini_options]
|
|
49
|
+
asyncio_mode = "auto"
|
|
50
|
+
testpaths = ["tests"]
|
|
51
|
+
|
|
52
|
+
[tool.mypy]
|
|
53
|
+
files = ["src/pywfrac"]
|
|
54
|
+
strict = true
|
|
55
|
+
|
|
56
|
+
[tool.ruff]
|
|
57
|
+
target-version = "py311"
|
|
58
|
+
# 100, not the default 88: the code this library was extracted from (and its
|
|
59
|
+
# test suite, copied over unchanged) already runs comments/messages past 88
|
|
60
|
+
# in places, and reflowing logic untouched from the integration isn't the
|
|
61
|
+
# point of this migration.
|
|
62
|
+
line-length = 100
|
|
63
|
+
|
|
64
|
+
[tool.ruff.lint]
|
|
65
|
+
select = ["E", "F", "I", "UP"]
|
|
66
|
+
|
|
67
|
+
[tool.ruff.lint.per-file-ignores]
|
|
68
|
+
# Parametrize tables copied verbatim from the integration's test suite - not
|
|
69
|
+
# reflowing data literals that aren't ours to redesign.
|
|
70
|
+
"tests/test_rac_parser.py" = ["E501", "E731"]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""pywfrac - async client library for Mitsubishi Heavy Industries WF-RAC modules.
|
|
2
|
+
|
|
3
|
+
Talks the local HTTP(S) API of the WF-RAC/WCBN4612L WiFi adapter that ships
|
|
4
|
+
with several MHI split-system air conditioners, and decodes/encodes the
|
|
5
|
+
airconStat protocol carried over it. Extracted from the
|
|
6
|
+
`mitsubishi_wf_rac` Home Assistant integration
|
|
7
|
+
(https://github.com/blues-sechseck/Mitsubishi-WF-RAC-Integration), which
|
|
8
|
+
remains the reference for the protocol's field notes.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .capabilities import ModelCapabilities, get_capabilities
|
|
12
|
+
from .error_codes import describe_error_code
|
|
13
|
+
from .models.aircon import Aircon, AirconCommands, AirconStat, HomeLeaveModeSetting
|
|
14
|
+
from .parser import RacParser
|
|
15
|
+
from .repository import (
|
|
16
|
+
READ_RESULT_CODES,
|
|
17
|
+
RESULT_CODES,
|
|
18
|
+
Repository,
|
|
19
|
+
WfRacCommandError,
|
|
20
|
+
WfRacConnectionError,
|
|
21
|
+
WfRacError,
|
|
22
|
+
WfRacRegistrationError,
|
|
23
|
+
WfRacWriteRefusedError,
|
|
24
|
+
describe_result,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"Repository",
|
|
29
|
+
"WfRacError",
|
|
30
|
+
"WfRacCommandError",
|
|
31
|
+
"WfRacRegistrationError",
|
|
32
|
+
"WfRacWriteRefusedError",
|
|
33
|
+
"WfRacConnectionError",
|
|
34
|
+
"RESULT_CODES",
|
|
35
|
+
"READ_RESULT_CODES",
|
|
36
|
+
"describe_result",
|
|
37
|
+
"RacParser",
|
|
38
|
+
"Aircon",
|
|
39
|
+
"AirconStat",
|
|
40
|
+
"AirconCommands",
|
|
41
|
+
"HomeLeaveModeSetting",
|
|
42
|
+
"ModelCapabilities",
|
|
43
|
+
"get_capabilities",
|
|
44
|
+
"describe_error_code",
|
|
45
|
+
]
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Per-model feature capability table, ported from the official app.
|
|
2
|
+
|
|
3
|
+
Ground truth: the app's own `res/values/arrays.xml`
|
|
4
|
+
(`model_no_type_function_*`, 5 tables x 17 flags) and `model/ModelNoType.java`
|
|
5
|
+
(flag order, table selection).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Final
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class ModelCapabilities:
|
|
14
|
+
"""Feature flags for one `model_no_type` table entry (17 booleans, in the
|
|
15
|
+
app's own field order - see ModelNoType.java's getters)."""
|
|
16
|
+
|
|
17
|
+
power_consumption: bool
|
|
18
|
+
vacant_property: bool
|
|
19
|
+
self_clean_operation: bool
|
|
20
|
+
wind_direction_lr: bool
|
|
21
|
+
entrust: bool
|
|
22
|
+
led_light: bool
|
|
23
|
+
auto_heating: bool
|
|
24
|
+
home_leave_mode: bool
|
|
25
|
+
temp_information: bool
|
|
26
|
+
indoor_and_outdoor_temp: bool
|
|
27
|
+
preset_temp_auto: bool
|
|
28
|
+
call_center_phone_number: bool
|
|
29
|
+
outdoor_temp_always_show: bool
|
|
30
|
+
outdoor_temp_outside_tokyo: bool
|
|
31
|
+
cool_hot_judge: bool
|
|
32
|
+
preset_temp_range_2: bool
|
|
33
|
+
operation_data: bool
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# Transcribed 1:1 from arrays.xml (item order == flag order above).
|
|
37
|
+
_SEPARATE_2021: Final = ModelCapabilities(
|
|
38
|
+
power_consumption=True, vacant_property=False, self_clean_operation=False,
|
|
39
|
+
wind_direction_lr=True, entrust=True, led_light=True, auto_heating=True,
|
|
40
|
+
home_leave_mode=False, temp_information=True, indoor_and_outdoor_temp=True,
|
|
41
|
+
preset_temp_auto=False, call_center_phone_number=False,
|
|
42
|
+
outdoor_temp_always_show=False, outdoor_temp_outside_tokyo=True,
|
|
43
|
+
cool_hot_judge=True, preset_temp_range_2=False, operation_data=False,
|
|
44
|
+
)
|
|
45
|
+
_GLOBAL_2022: Final = ModelCapabilities(
|
|
46
|
+
power_consumption=True, vacant_property=True, self_clean_operation=True,
|
|
47
|
+
wind_direction_lr=True, entrust=True, led_light=False, auto_heating=True,
|
|
48
|
+
home_leave_mode=True, temp_information=True, indoor_and_outdoor_temp=True,
|
|
49
|
+
preset_temp_auto=False, call_center_phone_number=False,
|
|
50
|
+
outdoor_temp_always_show=True, outdoor_temp_outside_tokyo=False,
|
|
51
|
+
cool_hot_judge=True, preset_temp_range_2=False, operation_data=False,
|
|
52
|
+
)
|
|
53
|
+
_HIGH_END_FOR_JAPANESE_2023: Final = ModelCapabilities(
|
|
54
|
+
power_consumption=True, vacant_property=False, self_clean_operation=True,
|
|
55
|
+
wind_direction_lr=True, entrust=True, led_light=False, auto_heating=False,
|
|
56
|
+
home_leave_mode=False, temp_information=True, indoor_and_outdoor_temp=True,
|
|
57
|
+
preset_temp_auto=False, call_center_phone_number=True,
|
|
58
|
+
outdoor_temp_always_show=False, outdoor_temp_outside_tokyo=False,
|
|
59
|
+
cool_hot_judge=True, preset_temp_range_2=False, operation_data=False,
|
|
60
|
+
)
|
|
61
|
+
_ZT_2025: Final = ModelCapabilities(
|
|
62
|
+
power_consumption=False, vacant_property=True, self_clean_operation=True,
|
|
63
|
+
wind_direction_lr=True, entrust=True, led_light=False, auto_heating=True,
|
|
64
|
+
home_leave_mode=True, temp_information=True, indoor_and_outdoor_temp=True,
|
|
65
|
+
preset_temp_auto=False, call_center_phone_number=False,
|
|
66
|
+
outdoor_temp_always_show=True, outdoor_temp_outside_tokyo=False,
|
|
67
|
+
cool_hot_judge=True, preset_temp_range_2=True, operation_data=True,
|
|
68
|
+
)
|
|
69
|
+
_FDT_2023: Final = ModelCapabilities(
|
|
70
|
+
power_consumption=False, vacant_property=False, self_clean_operation=False,
|
|
71
|
+
wind_direction_lr=False, entrust=False, led_light=True, auto_heating=False,
|
|
72
|
+
home_leave_mode=False, temp_information=False, indoor_and_outdoor_temp=True,
|
|
73
|
+
preset_temp_auto=True, call_center_phone_number=False,
|
|
74
|
+
outdoor_temp_always_show=True, outdoor_temp_outside_tokyo=False,
|
|
75
|
+
cool_hot_judge=False, preset_temp_range_2=False, operation_data=False,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Raw ModelNr byte -> table. Not a sequential scheme - hardcoded in
|
|
79
|
+
# ModelNoType.java:getArrayPosition(): 1/2/3/64 map to their own table,
|
|
80
|
+
# everything else (including 0) falls back to separate_2021.
|
|
81
|
+
_TABLE_BY_RAW: Final = {
|
|
82
|
+
1: _GLOBAL_2022,
|
|
83
|
+
2: _HIGH_END_FOR_JAPANESE_2023,
|
|
84
|
+
3: _ZT_2025,
|
|
85
|
+
64: _FDT_2023,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# `wind_direction_lr` says what the model line supports, not what the unit
|
|
90
|
+
# reports back: on the legacy bus protocol the module pins the left/right vane
|
|
91
|
+
# and 3D-auto bits regardless of model (rac_parser.py, and section 6.7 of
|
|
92
|
+
# docs/wf-rac-module-reference.md). Gate a horizontal-swing entity on the
|
|
93
|
+
# observed state byte, not on this flag alone.
|
|
94
|
+
def get_capabilities(model_nr_raw: int) -> ModelCapabilities:
|
|
95
|
+
"""Return the capability table for a device's raw ModelNr byte
|
|
96
|
+
(`content[0] & 127`, see rac_parser.py's `ModelNrRaw`)."""
|
|
97
|
+
return _TABLE_BY_RAW.get(model_nr_raw, _SEPARATE_2021)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Plain-language descriptions for the self-diagnosis ErrorCode (rac_parser.py).
|
|
2
|
+
|
|
3
|
+
Transcribed from the official MHI service/user manuals and the SRK
|
|
4
|
+
databooks. Not hardware-verified against a real occurrence - only
|
|
5
|
+
cross-checked between documents.
|
|
6
|
+
|
|
7
|
+
The unit numbers errors and protective stops out of one shared space (the
|
|
8
|
+
databooks' "Error code, stop code table"), which is why the same number is
|
|
9
|
+
described once here and reused for both spellings: E<n> is the escalated,
|
|
10
|
+
displayed fault, M<n> the protective stop that has not (yet) escalated - see
|
|
11
|
+
describe_error_code().
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from typing import Final
|
|
15
|
+
|
|
16
|
+
# High confidence: present with matching wording in the SRR service manual, the
|
|
17
|
+
# SRK user's manual and the ZS-WF/ZSX-WF/ZR-WF databooks. The databook tables
|
|
18
|
+
# are word-for-word identical across those three series, so these are not
|
|
19
|
+
# specific to one model family.
|
|
20
|
+
_HIGH_CONFIDENCE: Final[dict[str, str]] = {
|
|
21
|
+
"1": "Fehler Kabel-Fernbedienung",
|
|
22
|
+
"5": "Signalübertragungsfehler Indoor/Outdoor",
|
|
23
|
+
"35": "Kühl-Hochdruckschutz",
|
|
24
|
+
"36": "Verdichter-Übertemperatur (110 °C)",
|
|
25
|
+
"37": "Außenwärmetauscher-Sensor-Fehler",
|
|
26
|
+
"38": "Außenlufttemperatur-Sensor-Fehler",
|
|
27
|
+
"39": "Druckgasleitungs-Sensor-Fehler",
|
|
28
|
+
"40": "Absperrventil (Gasseite) geschlossen",
|
|
29
|
+
"42": "Current Cut (Startstrom-Abschaltung)",
|
|
30
|
+
"47": "Active-Filter-Spannungsfehler",
|
|
31
|
+
"48": "Außenlüfter-Fehler",
|
|
32
|
+
"51": "Leistungstransistor-Fehler",
|
|
33
|
+
"57": "Kältekreis-Schutzabschaltung",
|
|
34
|
+
"58": "Current-Safe-Abschaltung",
|
|
35
|
+
# Was "Störung Außeneinheit" until the databooks named the actual causes.
|
|
36
|
+
"59": "Verdichter-Verkabelung unterbrochen / Spannungseinbruch",
|
|
37
|
+
"61": "Verbindungsleitungen Innen-/Außeneinheit fehlerhaft",
|
|
38
|
+
"62": "Serielle Übertragungsstörung",
|
|
39
|
+
"80": "Innenlüfter-Motor gestört",
|
|
40
|
+
"82": "Innenwärmetauscher-Sensor-Fehler",
|
|
41
|
+
"84": "Kondensationsschutz aktiv",
|
|
42
|
+
"85": "Frostschutz aktiv",
|
|
43
|
+
"86": "Heiz-Hochdruckschutz aktiv",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Medium confidence: the number is confirmed for a different indoor unit form
|
|
47
|
+
# factor only (9: SRR series, explicitly marked "SRR series only" in the
|
|
48
|
+
# service manual), or it appears in no databook table at all (16 is the SRR
|
|
49
|
+
# indoor fan motor number - the SRK tables use 80 for that; 60 has no entry in
|
|
50
|
+
# any of the three).
|
|
51
|
+
_MEDIUM_CONFIDENCE: Final[dict[str, str]] = {
|
|
52
|
+
"9": "Drain-Störung (Service-Manual: nur SRR-Baureihe, für SRK unbestätigt)",
|
|
53
|
+
"16": "Innenlüfter-Fehler (SRR-Nummer; SRK meldet dafür 80)",
|
|
54
|
+
"60": "Rotor-Blockade (in keiner Databook-Tabelle belegt)",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def describe_error_code(code: str) -> str | None:
|
|
59
|
+
"""Klartext description for an E<n> or M<n> code, or None if undocumented.
|
|
60
|
+
|
|
61
|
+
Both spellings resolve through the same table: the unit numbers protective
|
|
62
|
+
stops and displayed faults out of one space, so M35 and E35 are the same
|
|
63
|
+
condition at different escalation stages (the databooks' auto-recovery
|
|
64
|
+
column gives the count - e.g. cooling high pressure control after 5
|
|
65
|
+
occurrences, compressor overheat after 2). M codes are prefixed so the
|
|
66
|
+
distinction survives into the UI, since a protective stop that recovered on
|
|
67
|
+
its own is not the same news as a fault the unit is displaying.
|
|
68
|
+
|
|
69
|
+
Anything outside the tables gets no text rather than a guess.
|
|
70
|
+
"""
|
|
71
|
+
if not code or code == "00":
|
|
72
|
+
return None
|
|
73
|
+
prefix, number = code[0], code[1:]
|
|
74
|
+
if prefix not in ("E", "M") or not number.isdigit():
|
|
75
|
+
return None
|
|
76
|
+
# rac_parser spells M codes zero-padded ("M01") and E codes bare ("E1"),
|
|
77
|
+
# so normalise before the lookup rather than keying both spellings.
|
|
78
|
+
number = str(int(number))
|
|
79
|
+
description = _HIGH_CONFIDENCE.get(number) or _MEDIUM_CONFIDENCE.get(number)
|
|
80
|
+
if description is None:
|
|
81
|
+
return None
|
|
82
|
+
if prefix == "M":
|
|
83
|
+
return f"Schutzabschaltung: {description}"
|
|
84
|
+
return description
|
|
File without changes
|