Skip to content

Commit 96014f3

Browse files
authored
Merge pull request #4 from salamwaddah/purifier-3
Support Purifier 3
2 parents 8df2bad + 06a5261 commit 96014f3

File tree

3 files changed

+269
-3
lines changed

3 files changed

+269
-3
lines changed

docs/devices/README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,25 +90,35 @@ __Note:__ This table does not include Aqara (Smart Home Gateway) devices as thei
9090
Id | Type | Auto-token | Support | Note
9191
--------------------------|-------------------|------------|--------------|------
9292
`zhimi.airpurifier.m1` | Air Purifier | Yes | ✅ Good |
93-
`zhimi.airpurifier.v1` | Air Purifier` | Yes | ✅ Good |
93+
`zhimi.airpurifier.v1` | Air Purifier | Yes | ✅ Good |
9494
`zhimi.airpurifier.v2` | Air Purifier | Yes | ✅ Good |
9595
`zhimi.airpurifier.v3` | Air Purifier | Unknown | ⚠️ Untested |
9696
`zhimi.airpurifier.v4` | - | Unknown | ⚠️ Generic | Testing needed to check compatibility.
9797
`zhimi.airpurifier.v5` | - | Unknown | ⚠️ Generic | Testing needed to check compatibility.
9898
`zhimi.airpurifier.v6` | Air Purifier | Yes | ✅ Basic |
9999
`zhimi.humidifier.v1` | Humidifier | Unknown | ⚠️ Untested |
100+
`zhimi.humidifier.ca1` | Humidifier | Unknown | ⚠️ Untested |
100101
`chuangmi.plug.m1` | Power plug | Yes | ✅ Good |
101102
`chuangmi.plug.v1` | Power plug | Yes | ✅ Good |
102103
`chuangmi.plug.v2` | Power plug | Yes | ✅ Good |
104+
`chuangmi.plug.v3` | Power plug | Unknown | ⚠️ Untested |
103105
`qmi.powerstrip.v1` | Power strip | Yes | ⚠️ Untested |
104106
`zimi.powerstrip.v2` | Power strip | Yes | ⚠️ Untested |
105107
`rockrobo.vaccum.v1` | Vacuum | No | ✅ Basic | DND, timers and mapping features are not supported.
106108
`rockrobo.vaccum.s5` | Vacuum | No | ✅ Basic | DND, timers and mapping features are not supported.
109+
`roborock.vacuum.s5e` | Vacuum | No | ⚠️ Untested |
110+
`roborock.vacuum.a10` | Vacuum | No | ⚠️ Untested |
111+
`roborock.vacuum.m1s` | Vacuum | No | ⚠️ Untested |
107112
`lumi.gateway.v1` | Generic | Yes | ⚠️ Generic | API used to access sub devices not supported.
108113
`lumi.gateway.v2` | Gateway | Yes | ✅ Basic |
109114
`lumi.gateway.v3` | Gateway | Yes | ✅ Basic |
110115
`yeelink.light.lamp1` | Light | No | ✅ Good |
116+
`yeelink.light.lamp2` | Light | No | ⚠️ Untested |
111117
`yeelink.light.mono1` | Light | No | ✅ Good |
112118
`yeelink.light.color1` | Light | No | ✅ Good |
119+
`yeelink.light.color2` | Light | No | ⚠️ Untested |
120+
`yeelink.light.color3` | Light | No | ⚠️ Untested |
121+
`yeelink.light.color4` | Light | No | ⚠️ Untested |
113122
`yeelink.light.color5` | Light | No | ✅ Good | Verified with real device.
114-
`yeelink.light.strip1` | Light | No | ⚠️ Untested | Support added, verification with real device needed.
123+
`yeelink.light.strip1` | Light | No | ⚠️ Untested |
124+
`yeelink.light.strip2` | Light | No | ⚠️ Untested |

lib/devices/air-purifier3.js

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
'use strict';
2+
3+
const { AirPurifier } = require('abstract-things/climate');
4+
const MiioApi = require('../device');
5+
6+
const Power = require('./capabilities/power');
7+
const Mode = require('./capabilities/mode');
8+
const SwitchableLED = require('./capabilities/switchable-led');
9+
const LEDBrightness = require('./capabilities/changeable-led-brightness');
10+
const Buzzer = require('./capabilities/buzzer');
11+
const { Temperature, Humidity, AQI } = require('./capabilities/sensor');
12+
13+
/**
14+
* Abstraction over a Mi Air Purifier.
15+
*
16+
* Air Purifiers have a mode that indicates if is on or not. Changing the mode
17+
* to `idle` will power off the device, all other modes will power on the
18+
* device.
19+
*/
20+
module.exports = class extends AirPurifier
21+
.with(MiioApi, Power, Mode, Temperature, Humidity, AQI,
22+
SwitchableLED, LEDBrightness, Buzzer)
23+
{
24+
25+
get serviceMapping() {
26+
return {
27+
power: { siid: 2, piid: 2 },
28+
mode: {
29+
siid: 2,
30+
piid: 5,
31+
mapping: (mode) => {
32+
switch (mode) {
33+
case 'auto': return 0;
34+
case 'sleep': return 1;
35+
case 'favorite': return 2;
36+
case 'idle:': return 3;
37+
default:
38+
return 0;
39+
40+
}
41+
}
42+
},
43+
temp_dec: { siid: 3, piid: 8 },
44+
humidity: { siid: 3, piid: 7 },
45+
aqi: { siid: 3, piid: 6 },
46+
favorite_level: {
47+
siid: 10,
48+
piid: 10,
49+
mapping: (level) => {
50+
return Math.round(level/16*14);
51+
}
52+
},
53+
filter1_life: { siid: 4, piid: 3 },
54+
f1_hour_used: { siid: 4, piid: 5 },
55+
use_time: { siid: 12, piid: 1 },
56+
led: { siid: 6, piid: 6},
57+
led_b: { siid: 6, piid: 1 },
58+
buzzer: { siid: 5, piid: 1 }
59+
};
60+
}
61+
62+
getServiceProperty(prop) {
63+
return {
64+
did: String(this.handle.api.id),
65+
siid: this.serviceMapping[prop].siid,
66+
piid: this.serviceMapping[prop].piid
67+
};
68+
}
69+
70+
static get type() {
71+
return 'miio:air-purifier';
72+
}
73+
74+
loadProperties(props) {
75+
// Rewrite property names to device internal ones
76+
props = props.map(key => this._reversePropertyDefinitions[key] || key);
77+
78+
const propObjects = props.filter(prop => this.serviceMapping[prop]).map(this.getServiceProperty.bind(this));
79+
80+
return this.call('get_properties', propObjects).then(result => {
81+
const obj = {};
82+
for(let i=0; i<result.length; i++) {
83+
this._pushProperty(obj, props[i], result[i].value);
84+
}
85+
return obj;
86+
});
87+
}
88+
89+
constructor(options) {
90+
super(options);
91+
92+
// Define the power property
93+
this.defineProperty('power');
94+
95+
// Set the mode property and supported modes
96+
this.defineProperty('mode', {
97+
mapper: v => {
98+
switch(v) {
99+
case 0: return 'auto';
100+
case 1: return 'silent';
101+
case 2: return 'favorite';
102+
case 3: return 'idle';
103+
}
104+
}
105+
});
106+
this.updateModes([
107+
'idle',
108+
'auto',
109+
'silent',
110+
'favorite'
111+
]);
112+
113+
// Sensor value for Temperature capability
114+
this.defineProperty('temp_dec', {
115+
name: 'temperature'
116+
});
117+
118+
// Sensor value for RelativeHumidity capability
119+
this.defineProperty('humidity');
120+
121+
// Sensor value used for AQI (PM2.5) capability
122+
this.defineProperty('aqi');
123+
124+
// The favorite level
125+
this.defineProperty('favorite_level', {
126+
name: 'favoriteLevel',
127+
mapper: v => Math.round(v/14*16)
128+
});
129+
130+
// Info about usage
131+
this.defineProperty('filter1_life', {
132+
name: 'filterLifeRemaining'
133+
});
134+
this.defineProperty('f1_hour_used', {
135+
name: 'filterHoursUsed'
136+
});
137+
this.defineProperty('use_time', {
138+
name: 'useTime'
139+
});
140+
141+
// State for SwitchableLED capability
142+
this.defineProperty('led');
143+
144+
this.defineProperty('led_b', {
145+
name: 'ledBrightness',
146+
mapper: v => {
147+
switch(v) {
148+
case 0:
149+
return 'bright';
150+
case 1:
151+
return 'dim';
152+
case 2:
153+
return 'off';
154+
default:
155+
return 'unknown';
156+
}
157+
}
158+
});
159+
160+
// Buzzer and beeping
161+
this.defineProperty('buzzer');
162+
}
163+
164+
changePower(power) {
165+
const attributes = [];
166+
167+
if (!power) {
168+
// change mode to idle when turning off
169+
attributes.push(Object.assign(this.getServiceProperty('mode'), { value: 3 }));
170+
}
171+
172+
attributes.push(Object.assign({ value: power }, this.getServiceProperty('power')));
173+
174+
return this.call('set_properties', attributes, {
175+
refresh: [ 'power', 'mode' ],
176+
refreshDelay: 200
177+
});
178+
}
179+
180+
/**
181+
* Perform a mode change as requested by `mode(string)` or
182+
* `setMode(string)`.
183+
*/
184+
changeMode(mode) {
185+
const realMode = this.serviceMapping['mode'].mapping(mode);
186+
187+
return this.call('set_properties', [ Object.assign({ value: realMode }, this.getServiceProperty('mode')) ], {
188+
refresh: [ 'power', 'mode' ],
189+
refreshDelay: 200
190+
})
191+
.then(MiioApi.checkOk)
192+
.catch(err => {
193+
throw err.code === -5001 ? new Error('Mode `' + mode + '` not supported') : err;
194+
});
195+
}
196+
197+
/**
198+
* Get the favorite level used when the mode is `favorite`. Between 0 and 16.
199+
*/
200+
favoriteLevel(level=undefined) {
201+
if(typeof level === 'undefined') {
202+
return Promise.resolve(this.property('favoriteLevel'));
203+
}
204+
205+
return this.setFavoriteLevel(level);
206+
}
207+
208+
/**
209+
* Set the favorite level used when the mode is `favorite`, should be
210+
* between 0 and 16.
211+
*/
212+
setFavoriteLevel(level) {
213+
const realFavoriteLevel = this.serviceMapping['favorite_level'].mapping(level);
214+
215+
return this.call('set_properties', [ Object.assign({ value: realFavoriteLevel }, this.getServiceProperty('favorite_level')) ])
216+
.then(() => null);
217+
}
218+
219+
/**
220+
* Set the LED brightness to either `bright`, `dim` or `off`.
221+
*/
222+
changeLEDBrightness(level) {
223+
switch(level) {
224+
case 'bright':
225+
level = 0;
226+
break;
227+
case 'dim':
228+
level = 1;
229+
break;
230+
case 'off':
231+
level = 2;
232+
break;
233+
default:
234+
return Promise.reject(new Error('Invalid LED brigthness: ' + level));
235+
}
236+
237+
return this.call('set_properties', [ Object.assign({ value: level }, this.getServiceProperty('led_b')) ])
238+
.then(() => null);
239+
}
240+
};

lib/models.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
const AirMonitor = require('./devices/air-monitor');
77
const AirPurifier = require('./devices/air-purifier');
8+
const AirPurifier3 = require('./devices/air-purifier3');
89
const Gateway = require('./devices/gateway');
910

1011
const Vacuum = require('./devices/vacuum');
@@ -33,23 +34,33 @@ module.exports = {
3334
// Air Purifier 2S
3435
'zhimi.airpurifier.ma2': AirPurifier,
3536

37+
// Air Purifier 2H
38+
'zhimi.airpurifier.mc2': AirPurifier,
39+
3640
// Air Purifier 3
37-
'zhimi.airpurifier.mb3': AirPurifier,
41+
'zhimi.airpurifier.mb3': AirPurifier3,
3842

3943
'zhimi.humidifier.v1': Humidifier,
44+
'zhimi.humidifier.ca1': Humidifier,
4045

4146
// Deerma Antibacterial Humidifier
4247
'deerma.humidifier.jsq': Humidifier,
4348

4449
'chuangmi.plug.m1': PowerPlug,
4550
'chuangmi.plug.v1': require('./devices/chuangmi.plug.v1'),
4651
'chuangmi.plug.v2': PowerPlug,
52+
'chuangmi.plug.m3': PowerPlug,
53+
'chuangmi.plug.hmi206': PowerPlug,
4754

4855
'rockrobo.vacuum.v1': Vacuum,
4956
'roborock.vacuum.s5': Vacuum,
57+
'roborock.vacuum.s5e': Vacuum,
58+
'roborock.vacuum.a10': Vacuum,
59+
'roborock.vacuum.m1s': Vacuum,
5060

5161
'lumi.gateway.v2': Gateway.WithLightAndSensor,
5262
'lumi.gateway.v3': Gateway.WithLightAndSensor,
63+
'lumi.gateway.mgl03': Gateway.WithLightAndSensor,
5364
'lumi.acpartner.v1': Gateway.Basic,
5465
'lumi.acpartner.v2': Gateway.Basic,
5566
'lumi.acpartner.v3': Gateway.Basic,
@@ -58,10 +69,15 @@ module.exports = {
5869
'zimi.powerstrip.v2': PowerStrip,
5970

6071
'yeelink.light.lamp1': YeelightMono,
72+
'yeelink.light.lamp2': YeelightMono,
6173
'yeelink.light.mono1': YeelightMono,
6274
'yeelink.light.color1': YeelightColor,
75+
'yeelink.light.color2': YeelightColor,
76+
'yeelink.light.color3': YeelightColor,
77+
'yeelink.light.color4': YeelightColor,
6378
'yeelink.light.color5': YeelightColor,
6479
'yeelink.light.strip1': YeelightColor,
80+
'yeelink.light.strip2': YeelightColor,
6581

6682
'philips.light.sread1': require('./devices/eyecare-lamp2'),
6783
'philips.light.bulb': require('./devices/philips-light-bulb')

0 commit comments

Comments
 (0)