213 lines
9.8 KiB
Markdown
213 lines
9.8 KiB
Markdown
# Midea AC Integration Debugging
|
|
|
|
## Integration Info
|
|
|
|
- **Repository**: https://github.com/mill1000/midea-ac-py
|
|
- **Integration version**: 2026.4.0
|
|
- **Required library**: `msmart-ng==2026.4.1`
|
|
- **Config flow file**: `/mnt/data/supervisor/homeassistant/custom_components/midea_ac/config_flow.py`
|
|
- **Manifest**: `/mnt/data/supervisor/homeassistant/custom_components/midea_ac/manifest.json`
|
|
|
|
## Cloud Credentials Structure
|
|
|
|
In `config_flow.py`, line ~71:
|
|
```python
|
|
_CLOUD_CREDENTIALS = {
|
|
"DE": ("midea_eu@mailinator.com", "das_ist_passwort1"),
|
|
"KR": ("midea_sea@mailinator.com", "password_for_sea1")
|
|
}
|
|
```
|
|
|
|
These shared credentials are used for the `discover` config flow step. When they expire, the flow aborts with `cloud_connection_failed`.
|
|
|
|
## Config Flow Steps
|
|
|
|
1. **Initiate**: `POST /api/config/config_entries/flow` with `{"handler":"midea_ac"}`
|
|
2. **Menu**: Choose `discover` or `manual`
|
|
3. **Discover**: Submit `{"host":"10.0.40.54","country_code":"DE"}` — uses cloud credentials to get token/k1
|
|
4. **Manual**: Submit `{"id":"DEVICE_ID","host":"IP","port":6444,"device_type":"AC","token":"...","k1":"..."}` — skips cloud, needs pre-obtained token/k1
|
|
|
|
## Library Comparison
|
|
|
|
### midealocal (Python pip package)
|
|
- Import: `from midealocal.cloud import get_midea_cloud`
|
|
- Cloud names: "SmartHome", "NetHome Plus", "Midea Air", "美的美居", "Ariston Clima"
|
|
- Login flow: `get_midea_cloud(cloud_name=..., session=..., account=..., password=...)` → `await cloud.login()`
|
|
- List devices: `await cloud.list_home()` → `await cloud.list_appliances(home_id)`
|
|
- Get keys: `await cloud.get_cloud_keys(int(device_id))` — calls `/v1/iot/secure/getToken`
|
|
- **Issue observed**: getToken returns `{"code":"3004","msg":"value is illegal."}` — empty result
|
|
|
|
### msmart-ng (used by HA integration)
|
|
- Import: `from msmart.cloud import SmartHomeCloud, NetHomePlusCloud`
|
|
- `SmartHomeCloud` has DE shared credentials: `midea_eu@mailinator.com` / `das_ist_passwort1`
|
|
- `SmartHomeCloud` also accepts **personal user credentials** — login succeeds, but `getToken` still returns 3004
|
|
- `NetHomePlusCloud` is used by `Discover.discover_single()` — hardcoded in `_get_cloud()`
|
|
- `Security.udpid(data)` = `sha256(data).digest()`, XOR first 16 bytes with second 16 bytes → `.hex()` (32 chars)
|
|
- `Discover.connect()` tries both `"little"` and `"big"` endians: `device_id.to_bytes(6, endian)`
|
|
- Cloud login internally calls `/v1/user/login/id/get` → `/mj/user/login`
|
|
- CLI: `python3 -m msmart.cli -d -a "EMAIL" -p 'PASS' -i "IP"`
|
|
- **Issue observed**: getToken returns `{"code":"3004","msg":"value is illegal"}` for ALL udpid variants
|
|
|
|
### ⚠️ Package Conflict: midealocal vs msmart-ng
|
|
|
|
Both packages claim the `msmart` Python namespace. Installing `midealocal` pulls in old `msmart==0.2.5` which overwrites `msmart-ng`'s modules. The `security.py` from 0.2.5 tries to import `MSGTYPE_ENCRYPTED_REQUEST` from `msmart.const` which doesn't exist in msmart-ng's const.py.
|
|
|
|
**Fix:**
|
|
```bash
|
|
pip uninstall -y msmart-ng midealocal msmart --break-system-packages
|
|
rm -rf ~/.local/lib/python3.*/site-packages/msmart # Remove stale namespace
|
|
pip install --break-system-packages msmart-ng==2026.4.1
|
|
```
|
|
|
|
**Rule: Use `midealocal` only in throwaway/debug contexts. Never install it alongside `msmart-ng` in any environment that the HA integration depends on.**
|
|
|
|
### msmart (older version, v0.2.5)
|
|
- Different API from msmart-ng
|
|
- `msmart.cloud.cloud` class with `login()`, `gettoken()`, `appliance_list()`
|
|
- Login fails with same "value is illegal" error
|
|
|
|
## Device Details (This Homelab)
|
|
|
|
- **Name**: OGKlimaanlage
|
|
- **Device ID**: 153931628805781
|
|
- **Type**: 172 (AC)
|
|
- **IP**: 10.0.40.54 (VLAN 40)
|
|
- **SN**: 000000P0000000Q1102C8D344AAE0000
|
|
- **SN8**: 00000Q11
|
|
- **Model**: 00000Q11
|
|
- **Model number**: 524
|
|
- **Manufacturer code**: 0000
|
|
- **Protocol**: V3
|
|
- **Port**: 6444 (TCP, **OPEN** but requires auth handshake — unauthenticated connections time out) / 6445 (UDP, responds to discovery broadcast)
|
|
- **Cloud account**: dominik@familie-schoen.com (MSmartHome app)
|
|
- **Cloud**: SmartHome (not NetHome Plus)
|
|
|
|
## Network Requirements
|
|
|
|
- Device is on VLAN 40 (10.0.40.x)
|
|
- HA needs direct L2 access — added third NIC (net2, VLAN 40, tag=40) to VM 106
|
|
- Interface in HAOS: `enp0s20`, configured with `ha network update enp0s20 --ipv4-method auto`
|
|
- HA gets IP 10.0.40.61 via DHCP
|
|
- Device ARP is reachable (MAC 10:2c:8d34:4a:ae) — TCP 6444 is open but requires V3 authentication handshake. Unauthenticated TCP connections time out ("No response from host").
|
|
|
|
## Debugging Commands
|
|
|
|
```bash
|
|
# Check config entry state
|
|
TOKEN=$(cat /tmp/ha_token.txt)
|
|
curl -s -H "Authorization: Bearer $TOKEN" \
|
|
"http://10.0.30.10:8123/api/config/config_entries/entry" | \
|
|
python3 -c "import sys,json; [print(f'{e[\"title\"]}: {e[\"state\"]}') for e in json.load(sys.stdin) if e.get('domain')=='midea_ac']"
|
|
|
|
# Check HA logs for midea errors
|
|
ssh -i ~/.ssh/id_ed25519_proxmox root@10.0.20.10 \
|
|
'qm guest exec 106 -- sh -c "ha core log 2>&1 | grep -i midea | tail -20"'
|
|
|
|
# Local discovery via midealocal
|
|
python3 -m midealocal.cli discover \
|
|
--username "EMAIL" --password 'PASS' \
|
|
--cloud-name "SmartHome" --host "10.0.40.54" --get_sn
|
|
|
|
# Cloud API debug (midealocal)
|
|
python3 << 'PYEOF'
|
|
import asyncio, aiohttp, json
|
|
from midealocal.cloud import get_midea_cloud
|
|
|
|
async def debug():
|
|
async with aiohttp.ClientSession() as session:
|
|
cloud = get_midea_cloud(
|
|
cloud_name="SmartHome", session=session,
|
|
account="EMAIL", password="PASS"
|
|
)
|
|
await cloud.login()
|
|
homes = await cloud.list_home()
|
|
for hid in homes:
|
|
apps = await cloud.list_appliances(hid)
|
|
for aid, info in (apps or {}).items():
|
|
print(f"Device {aid}: {json.dumps(info)}")
|
|
keys = await cloud.get_cloud_keys(int(aid))
|
|
print(f" Keys: {json.dumps(keys)}")
|
|
|
|
asyncio.run(debug())
|
|
PYEOF
|
|
```
|
|
|
|
## Session Findings (2026-06-27 Deep Debug)
|
|
|
|
### Cloud Login Results
|
|
|
|
| Cloud | Endpoint | Login | getToken | Notes |
|
|
|-------|----------|-------|----------|-------|
|
|
| SmartHome (midealocal, shared creds) | mp-prod.appsmb.com | ✅ | ❌ 3004 | `list_appliances` works, returns device with `btToken` + `btMac` |
|
|
| SmartHome (msmart-ng, shared DE creds) | mp-prod.appsmb.com | ✅ | ❌ 3004 | Same API, same failure |
|
|
| SmartHome (msmart-ng, **personal creds**) | mp-prod.appsmb.com | ✅ | ❌ 3004 | Login OK with user account, getToken STILL fails |
|
|
| SmartHome (midealocal, **personal creds**) | mp-prod.appsmb.com | ✅ | ❌ 3004 | Same — confirms device-side issue, not credentials |
|
|
| NetHome Plus (midealocal) | mapp.appsmb.com | ❌ 3101 | N/A | "password error" — account doesn't exist on this cloud |
|
|
| NetHome Plus (msmart-ng) | mapp.appsmb.com | ⏳ rate-limited | N/A | Too many failed attempts from earlier msmart-ng discovery tries |
|
|
| Midea Air (midealocal) | mapp.appsmb.com | ❌ | N/A | Login failed |
|
|
|
|
**Conclusion**: getToken fails regardless of credentials or library. The device has no LAN token in the cloud despite being connected via WLAN and remotely controllable through the MSmartHome app. Initial BLE pairing likely explains the absence of a LAN token — re-pairing via WLAN setup should provision one.
|
|
|
|
### getToken Failure — All udpid Methods Tested
|
|
|
|
**msmart-ng `Security.udpid(data)`**: `sha256(data).digest()`, XOR first 16 bytes with second 16 bytes → `.hex()` (32 hex chars)
|
|
```python
|
|
from msmart.lan import Security
|
|
for endian in ["little", "big"]:
|
|
data = device_id.to_bytes(6, endian)
|
|
udpid = Security.udpid(data).hex() # → "ad4d091264847e0f..." / "b7f074ba7343aa11..."
|
|
```
|
|
|
|
**midealocal `CloudSecurity.get_udp_id()`**: 3 methods (different byte slicing/reversing)
|
|
```python
|
|
# All return {"code":"3004","msg":"value is illegal."}
|
|
methods = {
|
|
0: "REVERSED_BIG", # bytes(reversed(id.to_bytes(8, "big")))
|
|
1: "BIG", # id.to_bytes(6, "big")
|
|
2: "LITTLE", # id.to_bytes(6, "little"),
|
|
}
|
|
```
|
|
|
|
Also tested: 8-byte big/little endian, btMac bytes (`102c8d344aae`), SN string bytes — ALL return 3004.
|
|
|
|
### Device Cloud Info (from list_appliances)
|
|
|
|
```json
|
|
{
|
|
"id": "153931628805781",
|
|
"name": "OGKlimaanlage",
|
|
"type": "0xAC",
|
|
"modelNumber": "524",
|
|
"sn": "000000P0000000Q1102C8D344AAE0000",
|
|
"sn8": "00000Q11",
|
|
"onlineStatus": "1",
|
|
"connectType": "1",
|
|
"btMac": "102c8d344aaf",
|
|
"btToken": "6000001415208481",
|
|
"registerTime": "Tue Jun 17 09:40:37 UTC 2025"
|
|
}
|
|
```
|
|
|
|
**Key insight**: `btToken` and `btMac` present but NO `mac` field (null). `connectType: "1"` indicates initial BLE pairing. However, user confirmed the device IS connected via WLAN and remotely controllable through the MSmartHome app — so the device has WiFi, but the cloud has no LAN token provisioned (likely because initial pairing was via BLE, not WLAN setup).
|
|
|
|
### Default Keys Test
|
|
|
|
`midealocal.DEFAULT_KEYS` (method 99) returns a token/key pair, but authentication via `msmart.lan.LAN.authenticate()` fails with "Error packet received" — these are generic placeholders, not device-specific keys.
|
|
|
|
### Resolution
|
|
|
|
1. **Re-pair the device via WLAN setup in the MSmartHome app** (not Bluetooth) — this should generate a LAN token in the Midea cloud
|
|
2. After re-pairing, re-run `cloud.get_cloud_keys(153931628805781)` — if it returns keys, use the manual config flow
|
|
3. Alternatively: use the MSmartHome app's "Gerät Startposition" (shown as `UnasBiFNEq...`) — this may be the udpid, but it was truncated in the app UI
|
|
4. Once token+k1 obtained: use manual config flow:
|
|
```
|
|
POST /api/config/config_entries/flow {"handler":"midea_ac"}
|
|
→ choose "manual"
|
|
→ {"id":"153931628805781","host":"10.0.40.54","port":6444,"device_type":"AC","token":"TOKEN_HEX","k1":"KEY_HEX"}
|
|
```
|
|
|
|
### Pending
|
|
|
|
- ✅ Midea credentials stored in 1Password (Vault "Hermes", item "Midea MSmartHome", ID jzlkqibo44e6nz6gtvcqf7dg6q)
|
|
- Verify re-pairing via WLAN setup generates a LAN token
|