Skip to content

Commit 684e4bd

Browse files
authored
Merge pull request #6 from SwitchBot-Wonderlabs/master
Add support for SwitchBot Curtain
2 parents 33cfca6 + de9950f commit 684e4bd

File tree

4 files changed

+285
-37
lines changed

4 files changed

+285
-37
lines changed

README.md

Lines changed: 151 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
node-switchbot
22
===============
33

4-
The node-switchbot is a Node.js module which allows you to move your [Switchbot (Bot)](https://www.switch-bot.com/bot)'s arm and to monitor the temperature/humidity from [SwitchBot Thermometer & Hygrometer (Meter)](https://www.switch-bot.com/meter).
4+
The node-switchbot is a Node.js module which allows you to move your [Switchbot (Bot)](https://www.switch-bot.com/bot)'s arm and [Switchbot Curtain(Curtain)](https://www.switch-bot.com/products/switchbot-curtain), also monitor the temperature/humidity from [SwitchBot Thermometer & Hygrometer (Meter)](https://www.switch-bot.com/meter).
55

66
This module is unofficial. It was developed by reference to [the official python code](https://github.com/OpenWonderLabs/python-host). But some functionalities of this module were developed through trial and error. So some information obtained from this module might be wrong.
77

@@ -40,36 +40,46 @@ $ npm install node-switchbot
4040
---------------------------------------
4141
## Table of Contents
4242

43-
* [Quick Start](#Quick-Start)
44-
* [Monitoring Advertising packets](#Quick-Start-1)
45-
* [Moving the arm of the Bot](#Quick-Start-2)
46-
* [`Switchbot` object](#Switchbot-object)
47-
* [`discover()` method](#Switchbot-discover-method)
48-
* [`ondiscover` event handler](#Switchbot-ondiscover-event-handler)
49-
* [`startScan()` method](#Switchbot-startScan-method)
50-
* [`stopScan()` method](#Switchbot-stopScan-method)
51-
* [`onadvertisement` event handler](#Switchbot-onadvertisement-event-handler)
52-
* [`wait()` method](#Switchbot-wait-method)
53-
* [`SwitchbotDevice` object](#SwitchbotDevice-object)
54-
* [Properties](#SwitchbotDevice-properties)
55-
* [`getDeviceName()` method](#SwitchbotDevice-getDeviceName-method)
56-
* [`setDeviceName()` method](#SwitchbotDevice-setDeviceName-method)
57-
* [`connect()` method](#SwitchbotDevice-connect-method)
58-
* [`disconnect()` method](#SwitchbotDevice-disconnect-method)
59-
* [`onconnect` event handler](#SwitchbotDevice-onconnect-event-handler)
60-
* [`ondisconnect` event handler](#SwitchbotDevice-ondisconnect-event-handler)
61-
* [`SwitchbotDeviceWoHand` object](#SwitchbotDeviceWoHand-object)
62-
* [`press()` method](#SwitchbotDeviceWoHand-press-method)
63-
* [`turnOn()` method](#SwitchbotDeviceWoHand-turnOn-method)
64-
* [`turnOff()` method](#SwitchbotDeviceWoHand-turnOff-method)
65-
* [`down()` method](#SwitchbotDeviceWoHand-down-method)
66-
* [`up()` method](#SwitchbotDeviceWoHand-up-method)
67-
* [Advertisement data](#Advertisement-data)
68-
* [Bot (WoHand)](#Advertisement-data-WoHand)
69-
* [Meter (WoSensorTH)](#Advertisement-data-WoSensorTH)
70-
* [Release Note](#Release-Note)
71-
* [References](#References)
72-
* [License](#License)
43+
- [node-switchbot](#node-switchbot)
44+
- [Supported OS](#supported-os)
45+
- [Dependencies](#dependencies)
46+
- [Installation](#installation)
47+
- [Table of Contents](#table-of-contents)
48+
- [<a id="Quick-Start">Quick Start</a>](#quick-start)
49+
- [<a id="Quick-Start-1">Monitoring Advertising packets</a>](#monitoring-advertising-packets)
50+
- [<a id="Quick-Start-2">Moving the arm of the Bot</a>](#moving-the-arm-of-the-bot)
51+
- [<a id="Switchbot-object">`Switchbot` object</a>](#switchbot-object)
52+
- [<a id="Switchbot-discover-method">`discover()` method</a>](#discover-method)
53+
- [<a id="Switchbot-ondiscover-event-handler">`ondiscover` event hander</a>](#ondiscover-event-hander)
54+
- [<a id="Switchbot-startScan-method">`startScan()` method</a>](#startscan-method)
55+
- [<a id="Switchbot-stopScan-method">`stopScan()` method</a>](#stopscan-method)
56+
- [<a id="Switchbot-onadvertisement-event-handler">`onadvertisement` event handler</a>](#onadvertisement-event-handler)
57+
- [<a id="Switchbot-wait-method">`wait()` method</a>](#wait-method)
58+
- [<a id="SwitchbotDevice-object">`SwitchbotDevice` object</a>](#switchbotdevice-object)
59+
- [<a id="SwitchbotDevice-properties">Properties</a>](#properties)
60+
- [<a id="SwitchbotDevice-getDeviceName-method">`getDeviceName()` method</a>](#getdevicename-method)
61+
- [<a id="SwitchbotDevice-setDeviceName-method">`setDeviceName()` method</a>](#setdevicename-method)
62+
- [<a id="SwitchbotDevice-connect-method">`connect()` method</a>](#connect-method)
63+
- [<a id="SwitchbotDevice-disconnect-method">`disconnect()` method</a>](#disconnect-method)
64+
- [<a id="SwitchbotDevice-onconnect-event-handler">`onconnect` event handler</a>](#onconnect-event-handler)
65+
- [<a id="SwitchbotDevice-ondisconnect-event-handler">`ondisconnect` event handler</a>](#ondisconnect-event-handler)
66+
- [<a id="SwitchbotDeviceWoHand-object">`SwitchbotDeviceWoHand` object</a>](#switchbotdevicewohand-object)
67+
- [<a id="SwitchbotDeviceWoHand-press-method">`press()` method</a>](#press-method)
68+
- [<a id="SwitchbotDeviceWoHand-turnOn-method">`turnOn()` method</a>](#turnon-method)
69+
- [<a id="SwitchbotDeviceWoHand-turnOff-method">`turnOff()` method</a>](#turnoff-method)
70+
- [<a id="SwitchbotDeviceWoHand-down-method">`down()` method</a>](#down-method)
71+
- [<a id="SwitchbotDeviceWoHand-up-method">`up()` method</a>](#up-method)
72+
- [<a id="SwitchbotDeviceWoCurtain-object">`SwitchbotDeviceWoCurtain` object</a>](#switchbotdevicewocurtain-object)
73+
- [<a id="SwitchbotDeviceWoCurtain-open-method">`open()` method</a>](#open-method)
74+
- [<a id="SwitchbotDeviceWoCurtain-close-method">`close()` method</a>](#close-method)
75+
- [<a id="SwitchbotDeviceWoCurtain-runToPos-method">`runToPos()` method</a>](#runtopos-method)
76+
- [<a id="Advertisement-data">Advertisement data</a>](#advertisement-data)
77+
- [<a id="Advertisement-data-WoHand">Bot (WoHand)</a>](#bot-wohand)
78+
- [<a id="Advertisement-data-WoSensorTH">Meter (WoSensorTH)</a>](#meter-wosensorth)
79+
- [<a id="Advertisement-data-WoCurtain">Curtain (WoCurtain)</a>](#curtain-wocurtain)
80+
- [<a id="Release-Note">Release Note</a>](#release-note)
81+
- [<a id="References">References</a>](#references)
82+
- [<a id="License">License</a>](#license)
7383

7484
---------------------------------------
7585
## <a id="Quick-Start">Quick Start</a>
@@ -157,6 +167,19 @@ The sample codes above will output the result as follows:
157167
"battery": 100
158168
}
159169
}
170+
{
171+
"id": "ec58c5d00111",
172+
"address": "ec:58:c5:d0:01:11",
173+
"rssi": -39,
174+
"serviceData": {
175+
"model": "c",
176+
"modelName": "WoCurtain",
177+
"calibration": true,
178+
"battery": 91,
179+
"position": 1,
180+
"lightLevel": 1
181+
}
182+
}
160183
```
161184

162185
See the section "[Advertisement data](#Advertisement-data)" for the details of the advertising packets.
@@ -650,6 +673,72 @@ switchbot.discover({ model: 'H', quick: true }).then((device_list) => {
650673
});
651674
```
652675

676+
---------------------------------------
677+
## <a id="SwitchbotDeviceWoCurtain-object">`SwitchbotDeviceWoCurtain` object</a>
678+
679+
The `SwitchbotDeviceWoCurtain` object represents an Curtain, which is created through the discovery process triggered by the [`Switchbot.discover()`](#Switchbot-discover-method) method.
680+
681+
Actually, the `SwitchbotDeviceWoCurtain` is an object inherited from the [`SwitchbotDevice`](#SwitchbotDevice-object). You can use not only the method described in this section but also the properties and methods implemented in the [`SwitchbotDevice`](#SwitchbotDevice-object) object.
682+
683+
### <a id="SwitchbotDeviceWoCurtain-open-method">`open()` method</a>
684+
685+
```javascript
686+
switchbot.discover({ model: 'c', quick: true }).then((device_list) => {
687+
return device_list[0].open();
688+
}).then(() => {
689+
console.log('Done.');
690+
}).catch((error) => {
691+
console.error(error);
692+
});
693+
```
694+
695+
### <a id="SwitchbotDeviceWoCurtain-close-method">`close()` method</a>
696+
697+
The `close()` method sends a close command to the Curtain. This method returns a `Promise` object. Nothing will be passed to the `resove()`.
698+
699+
If no connection is established with the device, this method automatically establishes a connection with the device, then finally closes the connection. You don't have to call the [`connect()`](#SwitchbotDevice-connect-method) method in advance.
700+
701+
When the Curtain receives this command, the Curtain will close the curtain (100% position). If not calibrated, the Curtain does not move.
702+
703+
```javascript
704+
switchbot.discover({ model: 'c', quick: true }).then((device_list) => {
705+
return device_list[0].close();
706+
}).then(() => {
707+
console.log('Done.');
708+
}).catch((error) => {
709+
console.error(error);
710+
});
711+
```
712+
713+
### <a id="SwitchbotDeviceWoCurtain-runToPos-method">`runToPos()` method</a>
714+
715+
The `runToPos()` method sends a position command to the Curtain. This method returns a `Promise` object. Nothing will be passed to the `resove()`.
716+
717+
If no connection is established with the device, this method automatically establishes a connection with the device, then finally closes the connection. You don't have to call the [`connect()`](#SwitchbotDevice-connect-method) method in advance.
718+
719+
When the Curtain receives this command, the Curtain will run to the percentage position. If not calibrated, the Curtain does not move.
720+
721+
722+
The `open()` method sends a open command to the Curtain. This method returns a `Promise` object. Nothing will be passed to the `resove()`.
723+
724+
If no connection is established with the device, this method automatically establishes a connection with the device, then finally closes the connection. You don't have to call the [`connect()`](#SwitchbotDevice-connect-method) method in advance.
725+
726+
When the Curtain receives this command, the Curtain will open the curtain (0% position). If not calibrated, the Curtain does not move.
727+
728+
Property | Type | Required | Description
729+
:------------|:--------|:---------|:------------
730+
`percent` | Integer | Required | The percentage of target position (`0-100`). (e.g., `50`)
731+
732+
```javascript
733+
switchbot.discover({ model: 'c', quick: true }).then((device_list) => {
734+
return device_list[0].runToPos(50);
735+
}).then(() => {
736+
console.log('Done.');
737+
}).catch((error) => {
738+
console.error(error);
739+
});
740+
```
741+
653742
---------------------------------------
654743
## <a id="Advertisement-data">Advertisement data</a>
655744

@@ -747,6 +836,37 @@ The `fahrenheit` indicates the setting on the device. Note that it does *not* in
747836

748837
The `battery` is *experimental* for now. I'm not sure whether the value is correct or not. Never trust this value for now.
749838

839+
### <a id="Advertisement-data-WoCurtain">Curtain (WoCurtain)</a>
840+
841+
Example of the advertisement data:
842+
843+
```json
844+
{
845+
"id": "ec58c5d00111",
846+
"address": "ec:58:c5:d0:01:11",
847+
"rssi": -39,
848+
"serviceData": {
849+
"model": "c",
850+
"modelName": "WoCurtain",
851+
"calibration": true,
852+
"battery": 91,
853+
"position": 1,
854+
"lightLevel": 1
855+
}
856+
}
857+
```
858+
859+
Structure of the `serviceData`:
860+
861+
Property | Type | Description
862+
:-------------|:--------|:-----------
863+
`model` | String | This value is always `"c"`, which means "Curtain (WoCurtain)".
864+
`modelName` | String | This value is always `"WoCurtain"`, which means "Curtain".
865+
`calibration` | Boolean | This value indicates the calibration status (`true` or `false`).
866+
`battery` | Integer | This value indicates the battery level (`1-100`, `%`).
867+
`position` | Integer | This value indicates the percentage of current position (`0-100`, 0 is open, `%`).
868+
`lightLevel` | Integer | This value indicates the light level of the light source currently set (`1-10`).
869+
750870
---------------------------------------
751871
## <a id="Release-Note">Release Note</a>
752872

lib/switchbot-advertising.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,21 @@ class SwitchbotAdvertising {
4949
* }
5050
* }
5151
*
52+
* WoCurtain
53+
* {
54+
* id: 'ec58c5d00111',
55+
* address: 'ec:58:c5:d0:01:11',
56+
* rssi: -39,
57+
* serviceData: {
58+
* model: 'c',
59+
* modelName: 'WoCurtain',
60+
* calibration: true,
61+
* battery: 91,
62+
* position: 1,
63+
* lightLevel: 1
64+
* }
65+
* }
66+
*
5267
* If the specified `Peripheral` does not represent any switchbot
5368
* device, this method will return `null`.
5469
* ---------------------------------------------------------------- */
@@ -72,6 +87,8 @@ class SwitchbotAdvertising {
7287
sd = this._parseServiceDataForWoHand(buf);
7388
} else if (model === 'T') { // WoSensorTH
7489
sd = this._parseServiceDataForWoSensorTH(buf);
90+
} else if (model === 'c') { // WoCurtain
91+
sd = this._parseServiceDataForWoCurtain(buf);
7592
} else {
7693
return null;
7794
}
@@ -138,6 +155,32 @@ class SwitchbotAdvertising {
138155

139156
return data;
140157
}
158+
159+
_parseServiceDataForWoCurtain(buf) {
160+
if (buf.length !== 5) {
161+
return null;
162+
}
163+
let byte1 = buf.readUInt8(1);
164+
let byte2 = buf.readUInt8(2);
165+
let byte3 = buf.readUInt8(3);
166+
let byte4 = buf.readUInt8(4);
167+
168+
let calibration = byte1 & 0b01000000; // Whether the calibration is completed
169+
let battery = byte2 & 0b01111111; // %
170+
let currPosition = byte3 & 0b01111111; // current positon %
171+
let lightLevel = (byte4 >> 4) & 0b00001111; // light sensor level (1-10)
172+
173+
let data = {
174+
model: 'c',
175+
modelName: 'WoCurtain',
176+
calibration: calibration ? true : false,
177+
battery: battery,
178+
position: currPosition,
179+
lightLevel: lightLevel
180+
};
181+
182+
return data;
183+
}
141184
}
142185

143186
module.exports = new SwitchbotAdvertising();

lib/switchbot-device-wocurtain.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/* ------------------------------------------------------------------
2+
* node-linking - switchbot-device-wocurtain.js
3+
*
4+
* Copyright (c) 2020, SwitchBot, All rights reserved.
5+
* Released under the MIT license
6+
* Date: 2020-10-26
7+
* ---------------------------------------------------------------- */
8+
'use strict';
9+
const SwitchbotDevice = require('./switchbot-device.js');
10+
11+
class SwitchbotDeviceWoCurtain extends SwitchbotDevice {
12+
13+
/* ------------------------------------------------------------------
14+
* open()
15+
* - Open the curtain
16+
*
17+
* [Arguments]
18+
* - none
19+
*
20+
* [Returen value]
21+
* - Promise object
22+
* Nothing will be passed to the `resolve()`.
23+
* ---------------------------------------------------------------- */
24+
open() {
25+
return this._operateCurtain([0x57, 0x0f, 0x45, 0x01, 0x01, 0x01, 0x00]);
26+
}
27+
28+
/* ------------------------------------------------------------------
29+
* close()
30+
* - close the curtain
31+
*
32+
* [Arguments]
33+
* - none
34+
*
35+
* [Returen value]
36+
* - Promise object
37+
* Nothing will be passed to the `resolve()`.
38+
* ---------------------------------------------------------------- */
39+
close() {
40+
return this._operateCurtain([0x57, 0x0f, 0x45, 0x01, 0x01, 0x01, 0x64]);
41+
}
42+
43+
/* ------------------------------------------------------------------
44+
* runToPos()
45+
* - run to the targe position
46+
*
47+
* [Arguments]
48+
* - percent | number | Required | the percentage of target position
49+
*
50+
* [Returen value]
51+
* - Promise object
52+
* Nothing will be passed to the `resolve()`.
53+
* ---------------------------------------------------------------- */
54+
runToPos(percent) {
55+
if (typeof percent != 'number') {
56+
return new Promise((resolve, reject) => {
57+
reject(new Error('The type of target position percentage is incorrent: ' + typeof percent));
58+
});
59+
}
60+
if (percent > 100) { percent = 100; }
61+
if (percent < 0) { percent = 0; }
62+
return this._operateCurtain([0x57, 0x0f, 0x45, 0x01, 0x01, 0x01, percent]);
63+
}
64+
65+
_operateCurtain(bytes) {
66+
return new Promise((resolve, reject) => {
67+
let req_buf = Buffer.from(bytes);
68+
this._command(req_buf).then((res_buf) => {
69+
let code = res_buf.readUInt8(0);
70+
if (res_buf.length === 3 && code === 0x01) {
71+
resolve();
72+
} else {
73+
reject(new Error('The device returned an error: 0x' + res_buf.toString('hex')));
74+
}
75+
}).catch((error) => {
76+
reject(error);
77+
});
78+
});
79+
}
80+
}
81+
82+
module.exports = SwitchbotDeviceWoCurtain;

0 commit comments

Comments
 (0)