Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## [Unreleased]

### Fixes
- Midea: MSmartHome-Cloud-Login — Anfrage-Felder `format`/`clientType`/`platform` jetzt als Zahlen (statt Strings) gemäß Referenz; behebt die Cloud-Ablehnung „value is illegal". Fehlermeldungen führen nun den Midea-Fehlercode mit.

---

## [1.103.0] — 2026-06-26

### Features
Expand Down
15 changes: 10 additions & 5 deletions src/services/midea/mideaCloud.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,11 @@ class MideaCloud {
const body = opts.raw ? data : {
appId: c.appId,
src: c.appId,
format: '2',
clientType: '1',
// NUMERIC types per cloud.py BaseCloud.FORMAT=2 / CLIENT_TYPE=1 (NOT strings):
// the Midea cloud validates these strictly and answers "value is illegal"
// when they arrive JSON-encoded as "2"/"1".
format: 2,
clientType: 1,
language: 'en_US',
deviceId: this.deviceId,
stamp: timestamp(),
Expand Down Expand Up @@ -152,7 +155,9 @@ class MideaCloud {
if (m.includes('2fa') || m.includes('verification') || m.includes('captcha')) {
return new MideaCloudError(msg || '2FA required', 'MIDEA_CLOUD_2FA_REQUIRED');
}
return new MideaCloudError(msg || `cloud error ${code}`, 'MIDEA_CLOUD_ERROR');
// Surface the Midea error code so an unverified cloud schema can be diagnosed
// from the user-visible message (e.g. "value is illegal (Midea-Code 1010)").
return new MideaCloudError(`${msg || 'cloud error'} (Midea-Code ${code})`, 'MIDEA_CLOUD_ERROR');
}

async _getLoginId(account) {
Expand Down Expand Up @@ -204,13 +209,13 @@ class MideaCloud {
// request cloud.py does NOT pass through _build_request_body).
const r = await this._request('/mj/user/login', {
data: {
platform: '2',
platform: 2, // BaseCloud.FORMAT (number), cloud.py:283
deviceId: this.deviceId,
},
iotData: {
appId: this.cfg.appId,
src: this.cfg.appId,
clientType: '1',
clientType: 1, // BaseCloud.CLIENT_TYPE (number), cloud.py:289
loginAccount: email,
iampwd: this._hashIamPassword(loginId, password),
password: this._hashPassword(loginId, password),
Expand Down
20 changes: 20 additions & 0 deletions tests/midea_cloud.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,26 @@ test('getToken builds idBytes for both endians without RangeError', async () =>
assert.equal(calls.length, 2);
});

test('MSmartHome request body sends NUMERIC format/clientType (cloud.py types)', async () => {
const c = new cloud.MideaCloud('msmarthome');
let sent;
const origFetch = global.fetch;
global.fetch = async (_url, opts) => { sent = JSON.parse(opts.body); return { status: 200, json: async () => ({ code: '0', data: {} }) }; };
try {
await c._requestMSmart('/v1/user/login/id/get', { loginAccount: 'a@b.de' });
} finally { global.fetch = origFetch; }
// The Midea cloud rejects "2"/"1" (strings) with "value is illegal" — these must be numbers.
assert.strictEqual(sent.format, 2);
assert.strictEqual(sent.clientType, 1);
assert.equal(typeof sent.format, 'number');
assert.equal(typeof sent.clientType, 'number');
// common fields present + passthrough data merged
assert.equal(sent.appId, '1010');
assert.equal(sent.language, 'en_US');
assert.equal(sent.loginAccount, 'a@b.de');
assert.ok(sent.stamp && sent.reqId && sent.deviceId);
});

test('live login + listDevices', { skip: !process.env.GC_MIDEA_CLOUD }, async () => {
const { email, password, app } = JSON.parse(process.env.GC_MIDEA_CLOUD);
const c = new cloud.MideaCloud(app);
Expand Down
Loading