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.
@@ -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
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ .mypy_cache/
5
+ .pytest_cache/
6
+ .ruff_cache/
7
+ .coverage
8
+ htmlcov/
9
+ dist/
10
+ build/
11
+ *.egg-info/
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
+ [![PyPI](https://img.shields.io/pypi/v/pywfrac)](https://pypi.org/project/pywfrac/)
34
+ [![CI](https://github.com/blues-sechseck/pywfrac/actions/workflows/ci.yml/badge.svg)](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).
@@ -0,0 +1,71 @@
1
+ # pywfrac
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/pywfrac)](https://pypi.org/project/pywfrac/)
4
+ [![CI](https://github.com/blues-sechseck/pywfrac/actions/workflows/ci.yml/badge.svg)](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