Skip to content

Commit 06a5261

Browse files
committed
support air purifier v3
1 parent b9d7721 commit 06a5261

File tree

2 files changed

+242
-1
lines changed

2 files changed

+242
-1
lines changed

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: 2 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');
@@ -37,7 +38,7 @@ module.exports = {
3738
'zhimi.airpurifier.mc2': AirPurifier,
3839

3940
// Air Purifier 3
40-
'zhimi.airpurifier.mb3': AirPurifier,
41+
'zhimi.airpurifier.mb3': AirPurifier3,
4142

4243
'zhimi.humidifier.v1': Humidifier,
4344
'zhimi.humidifier.ca1': Humidifier,

0 commit comments

Comments
 (0)