homebridge-bond 3.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,258 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.BondPlatform = void 0;
7
+ const Bond_1 = require("./interface/Bond");
8
+ const platformAccessory_1 = require("./platformAccessory");
9
+ const config_1 = require("./interface/config");
10
+ const Device_1 = require("./interface/Device");
11
+ const settings_1 = require("./settings");
12
+ const dgram_1 = __importDefault(require("dgram"));
13
+ class BondPlatform {
14
+ constructor(log, config, api) {
15
+ this.log = log;
16
+ this.config = config;
17
+ this.api = api;
18
+ this.Service = this.api.hap.Service;
19
+ this.Characteristic = this.api.hap.Characteristic;
20
+ this.UUIDGen = this.api.hap.uuid;
21
+ this.accessories = [];
22
+ if (config === null) {
23
+ this.log.error('No config defined.');
24
+ return;
25
+ }
26
+ if (!config_1.BondPlatformConfig.isValid(this)) {
27
+ this.log.error(`Config: ${JSON.stringify(config, null, 2)}`);
28
+ return;
29
+ }
30
+ this.log.debug(`Config: ${JSON.stringify(config, null, 2)}`);
31
+ api.on('didFinishLaunching', () => {
32
+ // Delaying the initialization of bonds property because we need to
33
+ // get the device ids before doing anything
34
+ this.validateBonds();
35
+ });
36
+ }
37
+ // Validate that all of the bonds provided in the config are online and authenticaiton is working
38
+ validateBonds() {
39
+ const bonds = Bond_1.Bond.objects(this);
40
+ const validated = [];
41
+ Bond_1.Bond.validate(bonds).then(res => {
42
+ res.forEach(res => {
43
+ if (res !== undefined) {
44
+ validated.push(res);
45
+ }
46
+ });
47
+ this.setupBonds(validated);
48
+ });
49
+ }
50
+ setupBonds(bonds) {
51
+ if (bonds.length === 0) {
52
+ this.log.warn('No valid Bonds available.');
53
+ return;
54
+ }
55
+ // const bonds = Bond.objects(this);
56
+ Bond_1.Bond.updateDeviceIds(bonds).then(() => {
57
+ this.bonds = bonds;
58
+ this.log(`${this.accessories.length} cached accessories were loaded`);
59
+ this.bonds.forEach(bond => {
60
+ this.getDevices(bond);
61
+ this.setupBPUP(bond);
62
+ });
63
+ });
64
+ }
65
+ getDevices(bond) {
66
+ this.cleanupBondData(bond);
67
+ this.log(`Getting devices for this Bond (${bond.version.bondid})...`);
68
+ this.log(`${bond.deviceIds.length} devices were found on this Bond (${bond.version.bondid}).`);
69
+ const filtered = bond.deviceIds.filter(deviceId => {
70
+ const accessories = this.accessories.filter(acc => {
71
+ return acc.context.device.uniqueId === bond.uniqueDeviceId(deviceId);
72
+ });
73
+ return accessories.length === 0;
74
+ });
75
+ if (filtered.length === 0) {
76
+ this.log(`No new devices to add for this Bond (${bond.version.bondid}).`);
77
+ return;
78
+ }
79
+ this.log(`Attempting to add ${filtered.length} devices that were not previously added.`);
80
+ bond.api
81
+ .getDevices(filtered)
82
+ .then(devices => {
83
+ devices.forEach(device => {
84
+ // Set the unique id
85
+ device.uniqueId = bond.uniqueDeviceId(device.id);
86
+ // Set the bond id
87
+ device.bondId = bond.version.bondid;
88
+ });
89
+ this.addAccessories(devices);
90
+ })
91
+ .catch(error => {
92
+ this.log(`Error getting devices: ${error}`);
93
+ });
94
+ }
95
+ cleanupBondData(bond) {
96
+ // Data cleanup - Make sure all cached devices have uniqueId and bondId on them
97
+ bond.deviceIds.forEach(deviceId => {
98
+ this.accessories.forEach(accessory => {
99
+ // Only run if device does not have uniqueId
100
+ if (accessory.context.device.uniqueId === undefined
101
+ && accessory.context.device.id === deviceId) {
102
+ const uniqueId = bond.uniqueDeviceId(deviceId);
103
+ this.log.debug(`Updating device data with uniqueId ${uniqueId}`);
104
+ accessory.context.device.uniqueId = bond.uniqueDeviceId(deviceId);
105
+ accessory.context.device.bondId = bond.version.bondid;
106
+ }
107
+ });
108
+ });
109
+ }
110
+ addAccessories(devices) {
111
+ devices.forEach(device => {
112
+ this.addAccessory(device);
113
+ });
114
+ }
115
+ // Accessory
116
+ /**
117
+ * Add a new accessory that hasn't been added before.
118
+ */
119
+ addAccessory(device) {
120
+ const bond = this.bondForDevice(device);
121
+ // Make sure Bond exists
122
+ if (bond === undefined) {
123
+ this.log(`[${device.name}] Bond does not exist for device id: ${device.id}.`);
124
+ return;
125
+ }
126
+ // Make sure device shouldn't be excluded
127
+ if ((bond.config.hide_device_ids !== undefined
128
+ && bond.config.hide_device_ids.includes(device.id))) {
129
+ this.log(`[${device.name}] Excluding ${device.id}.`);
130
+ return;
131
+ }
132
+ // Make sure device has supported actions
133
+ if (!Device_1.Device.isSupported(device)) {
134
+ this.log(`[${device.name}] Device has no supported actions.`);
135
+ return;
136
+ }
137
+ const uuid = this.UUIDGen.generate(device.uniqueId);
138
+ if (this.accessoryAdded(uuid)) {
139
+ this.log(`[${device.name}] Accessory already added.`);
140
+ return;
141
+ }
142
+ const displayName = Device_1.Device.displayName(device);
143
+ const accessory = new this.api.platformAccessory(`${displayName}`, uuid);
144
+ accessory.context.device = device;
145
+ this.create(accessory);
146
+ this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
147
+ this.accessories.push(accessory);
148
+ this.log(`Adding Accessory ${accessory.displayName}`);
149
+ this.log.debug(`Device unique id: ${device.uniqueId}`);
150
+ }
151
+ removeAccessory(accessory) {
152
+ this.log(`Removing Accessory: ${accessory.displayName}`);
153
+ const index = this.accessories.indexOf(accessory);
154
+ if (index > -1) {
155
+ this.accessories.splice(index, 1);
156
+ }
157
+ this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
158
+ }
159
+ configureAccessory(accessory) {
160
+ if (this.config === null || this.config.bonds === undefined) {
161
+ return;
162
+ }
163
+ this.accessories.push(accessory);
164
+ // If bonds hasn't been initilized, attempt to configure the accessory
165
+ // after a delay
166
+ if (this.bonds) {
167
+ this.log(`Configuring Accessory: ${accessory.displayName}`);
168
+ this.create(accessory);
169
+ }
170
+ else {
171
+ const that = this;
172
+ const timer = setInterval(() => {
173
+ if (this.bonds) {
174
+ that.log(`Configuring Accessory: ${accessory.displayName}`);
175
+ that.create(accessory);
176
+ clearInterval(timer);
177
+ }
178
+ }, 500);
179
+ }
180
+ }
181
+ create(accessory) {
182
+ const device = accessory.context.device;
183
+ const bond = this.bondForDevice(device);
184
+ if (!bond) {
185
+ return;
186
+ }
187
+ if ((bond.config.hide_device_ids
188
+ && bond.config.hide_device_ids.includes(device.id))) {
189
+ this.removeAccessory(accessory);
190
+ return;
191
+ }
192
+ this.logAccessory(accessory, `actions: ${device.actions}`);
193
+ const bondAccessory = platformAccessory_1.BondAccessory.create(this, accessory, bond);
194
+ bond.accessories.push(bondAccessory);
195
+ }
196
+ setupBPUP(bond) {
197
+ const PORT = 30007;
198
+ const HOST = bond.config.ip_address;
199
+ const message = Buffer.from('');
200
+ const client = dgram_1.default.createSocket('udp4');
201
+ const log = this.log;
202
+ function send() {
203
+ client.send(message, 0, message.length, PORT, HOST, (err) => {
204
+ if (err) {
205
+ log.error(`Erorr sending UDP message: ${err}`);
206
+ throw err;
207
+ }
208
+ log.debug(`UDP message sent to ${HOST}:${PORT}`);
209
+ });
210
+ }
211
+ send();
212
+ // From Bond API Docs: The client should continue to send the Keep-Alive datagram on
213
+ // the same socket every 60 seconds to keep the connection active.
214
+ setInterval(send, 1000 * 60);
215
+ client.on('message', (message, remote) => {
216
+ const msg = message.toString().trim();
217
+ const packet = JSON.parse(msg);
218
+ log.debug(`UDP Message received from ${remote.address}:${remote.port} - ${msg}`);
219
+ bond.receivedBPUPPacket(packet);
220
+ });
221
+ client.on('close', () => {
222
+ this.log('Connection closed');
223
+ });
224
+ }
225
+ bondForDevice(device) {
226
+ if (this.bonds) {
227
+ const bond = this.bonds.find(x => x.version.bondid === device.bondId && x.deviceIds.includes(device.id));
228
+ if (bond === undefined) {
229
+ this.log.error(`No Bond found for Device: ${device.name}.
230
+ This Device may have been removed from your Bond but still exists in cachedAccessories.`);
231
+ }
232
+ return bond;
233
+ }
234
+ else {
235
+ this.log.error('config.bonds is not defined');
236
+ }
237
+ }
238
+ // Helper Methods
239
+ accessoryAdded(uuid) {
240
+ const accessories = this.accessories.filter(acc => {
241
+ return acc.UUID === uuid;
242
+ });
243
+ return accessories.length > 0;
244
+ }
245
+ debug(accessory, message) {
246
+ const device = accessory.context.device;
247
+ this.log.debug(`[${device.name}] ${message}`);
248
+ }
249
+ logAccessory(accessory, message) {
250
+ const device = accessory.context.device;
251
+ this.log(`[${device.name}] ${message}`);
252
+ }
253
+ error(accessory, message) {
254
+ const device = accessory.context.device;
255
+ this.log.error(`[${device.name}] ${message}`);
256
+ }
257
+ }
258
+ exports.BondPlatform = BondPlatform;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BondAccessory = void 0;
4
+ const CeilingFanAccessory_1 = require("./accessories/CeilingFanAccessory");
5
+ const DeviceType_1 = require("./enum/DeviceType");
6
+ const FireplaceAccessory_1 = require("./accessories/FireplaceAccessory");
7
+ const GenericAccessory_1 = require("./accessories/GenericAccessory");
8
+ const LightAccessory_1 = require("./accessories/LightAccessory");
9
+ const ShadesAccessory_1 = require("./accessories/ShadesAccessory");
10
+ // eslint-disable-next-line @typescript-eslint/no-namespace
11
+ var BondAccessory;
12
+ (function (BondAccessory) {
13
+ function create(platform, accessory, bond) {
14
+ var _a;
15
+ const device = accessory.context.device;
16
+ const service = accessory.getService(platform.Service.AccessoryInformation);
17
+ service
18
+ .setCharacteristic(platform.Characteristic.Manufacturer, (_a = bond.version.make) !== null && _a !== void 0 ? _a : `Bond (${bond.version.target})`)
19
+ // FirmwareRevision only supports semantic versioning, so the 'v' in the firmware version needs to be dropped
20
+ .setCharacteristic(platform.Characteristic.FirmwareRevision, bond.version.fw_ver.replace('v', ''))
21
+ // SerialNumber must be at least 2 characters. Use uniqueId to prevent warnings.
22
+ .setCharacteristic(platform.Characteristic.SerialNumber, device.uniqueId);
23
+ if (bond.version.model) {
24
+ service
25
+ .setCharacteristic(platform.Characteristic.Model, bond.version.model);
26
+ }
27
+ if (bond.version.mcu_ver) {
28
+ service
29
+ .setCharacteristic(platform.Characteristic.HardwareRevision, bond.version.mcu_ver);
30
+ }
31
+ switch (device.type) {
32
+ case DeviceType_1.DeviceType.CeilingFan:
33
+ return new CeilingFanAccessory_1.CeilingFanAccessory(platform, accessory, bond);
34
+ case DeviceType_1.DeviceType.Generic:
35
+ return new GenericAccessory_1.GenericAccessory(platform, accessory, bond);
36
+ case DeviceType_1.DeviceType.Fireplace:
37
+ return new FireplaceAccessory_1.FireplaceAccessory(platform, accessory, bond);
38
+ case DeviceType_1.DeviceType.Shades:
39
+ return new ShadesAccessory_1.ShadesAccessory(platform, accessory, bond);
40
+ case DeviceType_1.DeviceType.Light:
41
+ return new LightAccessory_1.LightAccessory(platform, accessory, bond);
42
+ default: {
43
+ throw 'Invalid Device Type';
44
+ }
45
+ }
46
+ }
47
+ BondAccessory.create = create;
48
+ })(BondAccessory = exports.BondAccessory || (exports.BondAccessory = {}));
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PLUGIN_NAME = exports.PLATFORM_NAME = void 0;
4
+ /**
5
+ * This is the name of the platform that users will use to register the plugin in the Homebridge config.json
6
+ */
7
+ exports.PLATFORM_NAME = 'Bond';
8
+ /**
9
+ * This must match the name of your plugin as defined the package.json
10
+ */
11
+ exports.PLUGIN_NAME = 'homebridge-bond';
Binary file
package/global.d.ts ADDED
@@ -0,0 +1 @@
1
+ declare module 'biguint-format';
Binary file
Binary file
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "homebridge-bond",
3
+ "version": "3.2.9",
4
+ "description": "A homebridge plugin to control your Bond devices over the v2 API.",
5
+ "license": "MIT",
6
+ "main": "dist/index.js",
7
+ "scripts": {
8
+ "lint": "eslint src/**.ts",
9
+ "watch": "npm run build && npm link && nodemon",
10
+ "dev": "tsc --watch & nodemon dist",
11
+ "build": "rimraf ./dist && tsc -p tsconfig-build.json",
12
+ "prepublishOnly": "npm run lint && npm run build",
13
+ "test": "mocha -r ts-node/register tests/**/*.spec.ts",
14
+ "coverage": "nyc -r lcov -e .ts -x \"*.spec.ts\" npm run test"
15
+ },
16
+ "keywords": [
17
+ "homebridge-plugin",
18
+ "bond",
19
+ "bond-home",
20
+ "rf",
21
+ "ir"
22
+ ],
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git://github.com/aarons22/homebridge-bond.git"
26
+ },
27
+ "bugs": {
28
+ "url": "http://github.com/aarons22/homebridge-bond/issues"
29
+ },
30
+ "engines": {
31
+ "node": ">=10.17.0",
32
+ "homebridge": ">=1.3.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/chai": "^4.2.14",
36
+ "@types/flake-idgen": "^0.1.30",
37
+ "@types/mocha": "^8.2.0",
38
+ "@types/node": "^14.14.31",
39
+ "@typescript-eslint/eslint-plugin": "^4.18.0",
40
+ "@typescript-eslint/parser": "^4.18.0",
41
+ "chai": "^4.2.0",
42
+ "eslint": "^7.22.0",
43
+ "homebridge": "^1.3.1",
44
+ "mocha": "^9.1.2",
45
+ "nodemon": "^2.0.7",
46
+ "nyc": "^15.1.0",
47
+ "rimraf": "^3.0.2",
48
+ "ts-node": "^9.1.1",
49
+ "typescript": "^4.2.2"
50
+ },
51
+ "dependencies": {
52
+ "axios": "^0.21.4",
53
+ "axios-retry": "^3.1.8",
54
+ "biguint-format": "^1.0.2",
55
+ "flake-idgen": "^1.4.0"
56
+ },
57
+ "homepage": "https://github.com/aarons22/homebridge-bond#readme",
58
+ "author": "Aaron Sapp"
59
+ }
@@ -0,0 +1,263 @@
1
+ import { expect } from 'chai';
2
+ import 'mocha';
3
+ import { Action } from '../src/enum/Action';
4
+ import { DeviceType } from '../src/enum/DeviceType';
5
+ import { Device } from '../src/interface/Device';
6
+ import { DeviceFactory } from './factories/device';
7
+
8
+ describe('displayName', () => {
9
+ it('is correct', () => {
10
+ const device = DeviceFactory.createDevice();
11
+ expect(Device.displayName(device)).equal('location name');
12
+ });
13
+ });
14
+
15
+ describe('isSupported', () => {
16
+ it('ceiling fan', () => {
17
+ const device = DeviceFactory.createDevice({ type: DeviceType.CeilingFan });
18
+ expect(Device.isSupported(device)).equal(true);
19
+ });
20
+
21
+ it('shades', () => {
22
+ const device = DeviceFactory.createDevice({ type: DeviceType.Shades });
23
+ expect(Device.isSupported(device)).equal(true);
24
+ });
25
+
26
+ it('generic', () => {
27
+ const device = DeviceFactory.createDevice({ type: DeviceType.Generic });
28
+ expect(Device.isSupported(device)).equal(true);
29
+ });
30
+
31
+ it('fireplace', () => {
32
+ const device = DeviceFactory.createDevice({ type: DeviceType.Fireplace });
33
+ expect(Device.isSupported(device)).equal(true);
34
+ });
35
+
36
+ it('light', () => {
37
+ const device = DeviceFactory.createDevice({ type: DeviceType.Light });
38
+ expect(Device.isSupported(device)).equal(true);
39
+ });
40
+ });
41
+
42
+ describe('HasDimmer', () => {
43
+ it('has dimmer', () => {
44
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop, Action.StartDimmer] });
45
+ expect(Device.HasDimmer(device)).equal(true);
46
+ });
47
+
48
+ it('does not have dimmer', () => {
49
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop] });
50
+ expect(Device.HasDimmer(device)).equal(false);
51
+ });
52
+ });
53
+
54
+ describe('HasSeparateDimmers', () => {
55
+ it('has dimmer', () => {
56
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop, Action.StartIncreasingBrightness, Action.StartDecreasingBrightness] });
57
+ expect(Device.HasSeparateDimmers(device)).equal(true);
58
+ });
59
+
60
+ it('does not have separate dimmers', () => {
61
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop] });
62
+ expect(Device.HasSeparateDimmers(device)).equal(false);
63
+ });
64
+ });
65
+
66
+ describe('CFhasLightbulb', () => {
67
+ it('has lightbulb', () => {
68
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop, Action.ToggleLight] });
69
+ expect(Device.CFhasLightbulb(device)).equal(true);
70
+ });
71
+
72
+ it('does not have lightbulb', () => {
73
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop] });
74
+ expect(Device.CFhasLightbulb(device)).equal(false);
75
+ });
76
+ });
77
+
78
+ describe('CFhasUpDownLight', () => {
79
+ it('has up down light', () => {
80
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop, Action.ToggleUpLight, Action.ToggleDownLight] });
81
+ expect(Device.CFhasUpDownLight(device)).equal(true);
82
+ });
83
+
84
+ it('does not have up down light', () => {
85
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop] });
86
+ expect(Device.CFhasUpDownLight(device)).equal(false);
87
+ });
88
+ });
89
+
90
+ describe('canSetSpeed', () => {
91
+ it('can set speed', () => {
92
+ const device = DeviceFactory.createDevice({
93
+ actions: [Action.Stop, Action.SetSpeed],
94
+ maxSpeed: 1,
95
+ });
96
+ expect(Device.canSetSpeed(device)).equal(true);
97
+ });
98
+
99
+ it('does not have set speed', () => {
100
+ const device = DeviceFactory.createDevice({
101
+ actions: [Action.Stop],
102
+ maxSpeed: 1,
103
+ });
104
+ expect(Device.canSetSpeed(device)).equal(false);
105
+ });
106
+
107
+ it('does not have max speed', () => {
108
+ const device = DeviceFactory.createDevice({
109
+ actions: [Action.Stop, Action.SetSpeed],
110
+ });
111
+ expect(Device.canSetSpeed(device)).equal(false);
112
+ });
113
+ });
114
+
115
+ describe('canIncreaseDecreaseSpeed', () => {
116
+ it('can increase and decrease speed', () => {
117
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop, Action.IncreaseSpeed, Action.DecreaseSpeed] });
118
+ expect(Device.canIncreaseDecreaseSpeed(device)).equal(true);
119
+ });
120
+
121
+ it('cannnot increase and decrease speed', () => {
122
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop] });
123
+ expect(Device.canIncreaseDecreaseSpeed(device)).equal(false);
124
+ });
125
+ });
126
+
127
+ describe('hasOffOn', () => {
128
+ it('has off on', () => {
129
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop, Action.TurnOff, Action.TurnOn] });
130
+ expect(Device.hasOffOn(device)).equal(true);
131
+ });
132
+
133
+ it('does not have off on', () => {
134
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop] });
135
+ expect(Device.hasOffOn(device)).equal(false);
136
+ });
137
+ });
138
+
139
+ describe('hasReverseSwitch', () => {
140
+ it('has reverse switch', () => {
141
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop, Action.ToggleDirection] });
142
+ expect(Device.hasReverseSwitch(device)).equal(true);
143
+ });
144
+
145
+ it('does not have reverse switch', () => {
146
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop] });
147
+ expect(Device.hasReverseSwitch(device)).equal(false);
148
+ });
149
+ });
150
+
151
+ describe('GXhasToggle', () => {
152
+ it('has toggle', () => {
153
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop, Action.TogglePower] });
154
+ expect(Device.GXhasToggle(device)).equal(true);
155
+ });
156
+
157
+ it('does not have toggle', () => {
158
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop] });
159
+ expect(Device.GXhasToggle(device)).equal(false);
160
+ });
161
+ });
162
+
163
+ describe('FPhasToggle', () => {
164
+ it('has toggle', () => {
165
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop, Action.TogglePower] });
166
+ expect(Device.FPhasToggle(device)).equal(true);
167
+ });
168
+
169
+ it('does not have toggle', () => {
170
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop] });
171
+ expect(Device.FPhasToggle(device)).equal(false);
172
+ });
173
+ });
174
+
175
+ describe('FPhasFlame', () => {
176
+ it('has flame and toggle power', () => {
177
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop, Action.SetFlame, Action.TogglePower] });
178
+ expect(Device.FPhasFlame(device)).equal(true);
179
+ });
180
+
181
+ it('does not have flame and toggle power', () => {
182
+ const device = DeviceFactory.createDevice({ actions: [Action.TogglePower] });
183
+ expect(Device.FPhasFlame(device)).equal(false);
184
+ });
185
+ });
186
+
187
+ describe('MShasToggle', () => {
188
+ it('has toggle', () => {
189
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop, Action.ToggleOpen] });
190
+ expect(Device.MShasToggle(device)).equal(true);
191
+ });
192
+
193
+ it('does not have toggle', () => {
194
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop] });
195
+ expect(Device.MShasToggle(device)).equal(false);
196
+ });
197
+ });
198
+
199
+ describe('MShasPreset', () => {
200
+ it('has preset', () => {
201
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop, Action.Preset] });
202
+ expect(Device.MShasPreset(device)).equal(true);
203
+ });
204
+
205
+ it('does not have preset', () => {
206
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop] });
207
+ expect(Device.MShasPreset(device)).equal(false);
208
+ });
209
+ });
210
+
211
+ describe('LThasLightbulb', () => {
212
+ it('has lightbulb', () => {
213
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop, Action.ToggleLight] });
214
+ expect(Device.LThasLightbulb(device)).equal(true);
215
+ });
216
+
217
+ it('does not have lightbulb', () => {
218
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop] });
219
+ expect(Device.LThasLightbulb(device)).equal(false);
220
+ });
221
+ });
222
+
223
+ describe('LThasBrightness', () => {
224
+ it('has brightness', () => {
225
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop, Action.SetBrightness, Action.TurnLightOff] });
226
+ expect(Device.LThasBrightness(device)).equal(true);
227
+ });
228
+
229
+ it('does not have brightness', () => {
230
+ const device = DeviceFactory.createDevice({ actions: [Action.Stop] });
231
+ expect(Device.LThasBrightness(device)).equal(false);
232
+ });
233
+ });
234
+
235
+ describe('fanSpeeds', () => {
236
+ context('has commands', () => {
237
+ it('returns correct values', () => {
238
+ const device = DeviceFactory.createFanWithSpeeds([1,2,3]);
239
+ expect(Device.fanSpeeds(device)).to.deep.equal([1,2,3]);
240
+ });
241
+
242
+ it('returns correct values', () => {
243
+ const device = DeviceFactory.createFanWithSpeeds([1,2,4]);
244
+ expect(Device.fanSpeeds(device)).to.deep.equal([1,2,4]);
245
+ });
246
+ });
247
+
248
+ context('does not have commands', () => {
249
+ context('has max speed', () => {
250
+ it('returns correct values', () => {
251
+ const device = DeviceFactory.createDevice({maxSpeed: 6});
252
+ expect(Device.fanSpeeds(device)).to.deep.equal([1,2,3,4,5,6]);
253
+ });
254
+ });
255
+
256
+ context('does not have max speed', () => {
257
+ it('returns empty array', () => {
258
+ const device = DeviceFactory.createDevice();
259
+ expect(Device.fanSpeeds(device)).to.deep.equal([]);
260
+ });
261
+ });
262
+ });
263
+ });