@switchbot/homebridge-switchbot 2.5.0-beta.3 → 2.5.0

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.
@@ -12,27 +12,42 @@ import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge';
12
12
  import { device, devicesConfig, serviceData, switchbot, deviceStatus, ad, HostDomain, DevicePath } from '../settings';
13
13
  import { sleep } from '../utils';
14
14
 
15
+ enum BlindTiltMappingMode {
16
+ OnlyUp = 'only_up',
17
+ OnlyDown = 'only_down',
18
+ DownAndUp = 'down_and_up',
19
+ UpAndDown = 'up_and_down',
20
+ UseTiltForDirection = 'use_tilt_for_direction',
21
+ }
22
+
15
23
  export class BlindTilt {
16
24
  // Services
17
25
  windowCoveringService: Service;
26
+ lightSensorService?: Service;
18
27
  batteryService?: Service;
19
28
 
20
29
  // Characteristic Values
21
30
  CurrentPosition!: CharacteristicValue;
22
31
  PositionState!: CharacteristicValue;
23
32
  TargetPosition!: CharacteristicValue;
24
- BatteryLevel!: CharacteristicValue;
33
+ CurrentAmbientLightLevel?: CharacteristicValue;
34
+ BatteryLevel?: CharacteristicValue;
25
35
  StatusLowBattery?: CharacteristicValue;
36
+
26
37
  CurrentHorizontalTiltAngle!: CharacteristicValue;
27
38
  TargetHorizontalTiltAngle!: CharacteristicValue;
28
39
 
29
40
  // OpenAPI Others
30
41
  deviceStatus!: any; //deviceStatusResponse;
31
42
  slidePosition: deviceStatus['slidePosition'];
32
- moving: deviceStatus['moving'];
33
- Battery: deviceStatus['battery'];
34
43
  direction: deviceStatus['direction'];
35
- version?: deviceStatus['version'];
44
+ moving: deviceStatus['moving'];
45
+ runStatus: deviceStatus['runStatus'];
46
+ brightness: deviceStatus['brightness'];
47
+ setPositionMode?: string | number;
48
+ Mode!: string;
49
+
50
+ mappingMode: BlindTiltMappingMode = BlindTiltMappingMode.OnlyUp;
36
51
 
37
52
  // BLE Others
38
53
  connected?: boolean;
@@ -44,6 +59,7 @@ export class BlindTilt {
44
59
  battery: serviceData['battery'];
45
60
  position: serviceData['position'];
46
61
  inMotion: serviceData['inMotion'];
62
+ lightLevel: serviceData['lightLevel'];
47
63
 
48
64
  // Target
49
65
  setNewTarget!: boolean;
@@ -80,6 +96,9 @@ export class BlindTilt {
80
96
  this.setupMqtt(device);
81
97
  this.context();
82
98
 
99
+ this.mappingMode = (device.blindTilt?.mappingMode as BlindTiltMappingMode) ?? BlindTiltMappingMode.OnlyUp;
100
+ this.debugLog(`Mapping mode: ${this.mappingMode}`);
101
+
83
102
  // this is subject we use to track when we need to POST changes to the SwitchBot API
84
103
  this.doBlindTiltUpdate = new Subject();
85
104
  this.blindTiltUpdateInProgress = false;
@@ -143,27 +162,28 @@ export class BlindTilt {
143
162
  })
144
163
  .onSet(this.TargetPositionSet.bind(this));
145
164
 
165
+ this.CurrentHorizontalTiltAngle = 90;
146
166
  this.windowCoveringService
147
167
  .getCharacteristic(this.platform.Characteristic.CurrentHorizontalTiltAngle)
148
168
  .setProps({
149
- minStep: 1,
150
- minValue: 0,
151
- maxValue: 1,
152
- validValues: [0, 1],
153
- validValueRanges: [0, 1],
169
+ minStep: 180,
170
+ minValue: -90,
171
+ maxValue: 90,
172
+ validValues: [-90, 90],
154
173
  })
155
174
  .onGet(() => {
175
+ // this.debugLog(`requested CurrentHorizontalTiltAngle: ${this.CurrentHorizontalTiltAngle}`);
156
176
  return this.CurrentHorizontalTiltAngle;
157
177
  });
158
178
 
179
+ this.TargetHorizontalTiltAngle = 90;
159
180
  this.windowCoveringService
160
181
  .getCharacteristic(this.platform.Characteristic.TargetHorizontalTiltAngle)
161
182
  .setProps({
162
- minStep: 1,
163
- minValue: 0,
164
- maxValue: 1,
165
- validValues: [0, 1],
166
- validValueRanges: [0, 1],
183
+ minStep: 180,
184
+ minValue: -90,
185
+ maxValue: 90,
186
+ validValues: [-90, 90],
167
187
  })
168
188
  .onSet(this.TargetHorizontalTiltAngleSet.bind(this));
169
189
 
@@ -267,7 +287,7 @@ export class BlindTilt {
267
287
  this.windowCoveringService.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
268
288
  this.debugLog(`${this.device.deviceType}: ${this.CurrentPosition} DECREASING PositionState: ${this.PositionState}`);
269
289
  } else {
270
- this.debugLog(`${this.device.deviceType}: ${this.CurrentPosition} Standby, CurrentPosition: ${this.CurrentPosition}`);
290
+ this.debugLog(`${this.device.deviceType}: ${this.CurrentPosition} Standby2, CurrentPosition: ${this.CurrentPosition}`);
271
291
  this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
272
292
  this.windowCoveringService.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
273
293
  this.debugLog(`${this.device.deviceType}: ${this.CurrentPosition} STOPPED PositionState: ${this.PositionState}`);
@@ -283,6 +303,62 @@ export class BlindTilt {
283
303
  ` TargetPosition: ${this.TargetPosition}, PositionState: ${this.PositionState},`,
284
304
  );
285
305
 
306
+ if (!this.device.curtain?.hide_lightsensor) {
307
+ this.set_minLux = this.minLux();
308
+ this.set_maxLux = this.maxLux();
309
+ this.spaceBetweenLevels = 9;
310
+
311
+ // Brightness
312
+ switch (this.lightLevel) {
313
+ case 1:
314
+ this.CurrentAmbientLightLevel = this.set_minLux;
315
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} LightLevel: ${this.lightLevel}`);
316
+ break;
317
+ case 2:
318
+ this.CurrentAmbientLightLevel = (this.set_maxLux - this.set_minLux) / this.spaceBetweenLevels;
319
+ this.debugLog(
320
+ `${this.device.deviceType}: ${this.accessory.displayName} LightLevel: ${this.lightLevel},` +
321
+ ` Calculation: ${(this.set_maxLux - this.set_minLux) / this.spaceBetweenLevels}`,
322
+ );
323
+ break;
324
+ case 3:
325
+ this.CurrentAmbientLightLevel = ((this.set_maxLux - this.set_minLux) / this.spaceBetweenLevels) * 2;
326
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} LightLevel: ${this.lightLevel}`);
327
+ break;
328
+ case 4:
329
+ this.CurrentAmbientLightLevel = ((this.set_maxLux - this.set_minLux) / this.spaceBetweenLevels) * 3;
330
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} LightLevel: ${this.lightLevel}`);
331
+ break;
332
+ case 5:
333
+ this.CurrentAmbientLightLevel = ((this.set_maxLux - this.set_minLux) / this.spaceBetweenLevels) * 4;
334
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} LightLevel: ${this.lightLevel}`);
335
+ break;
336
+ case 6:
337
+ this.CurrentAmbientLightLevel = ((this.set_maxLux - this.set_minLux) / this.spaceBetweenLevels) * 5;
338
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} LightLevel: ${this.lightLevel}`);
339
+ break;
340
+ case 7:
341
+ this.CurrentAmbientLightLevel = ((this.set_maxLux - this.set_minLux) / this.spaceBetweenLevels) * 6;
342
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} LightLevel: ${this.lightLevel}`);
343
+ break;
344
+ case 8:
345
+ this.CurrentAmbientLightLevel = ((this.set_maxLux - this.set_minLux) / this.spaceBetweenLevels) * 7;
346
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} LightLevel: ${this.lightLevel}`);
347
+ break;
348
+ case 9:
349
+ this.CurrentAmbientLightLevel = ((this.set_maxLux - this.set_minLux) / this.spaceBetweenLevels) * 8;
350
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} LightLevel: ${this.lightLevel}`);
351
+ break;
352
+ case 10:
353
+ default:
354
+ this.CurrentAmbientLightLevel = this.set_maxLux;
355
+ this.debugLog();
356
+ }
357
+ this.debugLog(
358
+ `${this.device.deviceType}: ${this.accessory.displayName} LightLevel: ${this.lightLevel},`
359
+ + ` CurrentAmbientLightLevel: ${this.CurrentAmbientLightLevel}`,
360
+ );
361
+ }
286
362
  // Battery
287
363
  this.BatteryLevel = Number(this.battery);
288
364
  if (this.BatteryLevel < 10) {
@@ -296,24 +372,28 @@ export class BlindTilt {
296
372
 
297
373
  async openAPIparseStatus(): Promise<void> {
298
374
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIparseStatus`);
299
- // CurrentPosition
300
- if (this.direction === 'Down') {
301
- this.CurrentPosition = Number(this.slidePosition) * 2;//100 - Number(this.slidePosition);
302
- } else if (this.direction === 'Up') {
303
- this.CurrentPosition = Number(this.slidePosition) / 2;
304
- } else {
305
- this.CurrentPosition = Number(this.slidePosition);
306
- }
307
375
 
376
+ const [homekitPosition, homekitTiltAngle] = this.mapDeviceValuesToHomekitValues(Number(this.slidePosition), String(this.direction));
377
+ this.debugLog(` device: ${this.slidePosition} => HK: ${homekitPosition}`);
378
+
379
+ this.CurrentPosition = homekitPosition;
380
+ // CurrentPosition
308
381
  await this.setMinMax();
309
382
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} CurrentPosition: ${this.CurrentPosition}`);
383
+
384
+ if(homekitTiltAngle) {
385
+ this.CurrentHorizontalTiltAngle = homekitTiltAngle!;
386
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} CurrentHorizontalTiltAngle: ${this.CurrentHorizontalTiltAngle}`);
387
+ }
388
+
310
389
  if (this.setNewTarget) {
311
390
  this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Checking Status ...`);
312
391
  }
313
392
 
314
- if (this.setNewTarget && this.moving) {
393
+ this.debugLog(`runStatus: ${this.runStatus}`);
394
+ if (this.setNewTarget || (this.runStatus === undefined || this.runStatus !== 'static')) {
315
395
  await this.setMinMax();
316
- if (this.TargetPosition > this.CurrentPosition) {
396
+ if (this.TargetPosition > this.CurrentPosition || (homekitTiltAngle && (this.TargetHorizontalTiltAngle !== this.CurrentHorizontalTiltAngle))) {
317
397
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Closing, CurrentPosition: ${this.CurrentPosition} `);
318
398
  this.PositionState = this.platform.Characteristic.PositionState.INCREASING;
319
399
  this.windowCoveringService.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
@@ -324,29 +404,41 @@ export class BlindTilt {
324
404
  this.windowCoveringService.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
325
405
  this.debugLog(`${this.device.deviceType}: ${this.CurrentPosition} DECREASING PositionState: ${this.PositionState}`);
326
406
  } else {
327
- this.debugLog(`${this.device.deviceType}: ${this.CurrentPosition} Standby, CurrentPosition: ${this.CurrentPosition}`);
407
+ this.debugLog(`${this.device.deviceType}: ${this.CurrentPosition} Standby because reached position,`+
408
+ ` CurrentPosition: ${this.CurrentPosition}`);
328
409
  this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
329
410
  this.windowCoveringService.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
330
411
  this.debugLog(`${this.device.deviceType}: ${this.CurrentPosition} STOPPED PositionState: ${this.PositionState}`);
331
412
  }
332
413
  } else {
333
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Standby, CurrentPosition: ${this.CurrentPosition}`);
414
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Standby because device not moving,`+
415
+ ` CurrentPosition: ${this.CurrentPosition}`);
334
416
  this.TargetPosition = this.CurrentPosition;
417
+ if(homekitTiltAngle) {
418
+ this.TargetHorizontalTiltAngle = this.CurrentHorizontalTiltAngle;
419
+ }
335
420
  this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
336
421
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Stopped`);
337
422
  }
338
- if (this.direction === 'Up') {
339
- this.CurrentHorizontalTiltAngle = 0;
340
- } else if (this.direction === 'Down') {
341
- this.CurrentHorizontalTiltAngle = 1;
342
- } else {
343
- this.CurrentHorizontalTiltAngle = 0;
344
- }
345
423
  this.debugLog(
346
424
  `${this.device.deviceType}: ${this.accessory.displayName} CurrentPosition: ${this.CurrentPosition},` +
347
- ` TargetPosition: ${this.TargetPosition}, PositionState: ${this.PositionState},` +
348
- ` CurrentHorizontalTiltAngle: ${this.CurrentHorizontalTiltAngle}`,
425
+ ` TargetPosition: ${this.TargetPosition}, PositionState: ${this.PositionState},`,
349
426
  );
427
+
428
+ if (!this.device.curtain?.hide_lightsensor) {
429
+ this.set_minLux = this.minLux();
430
+ this.set_maxLux = this.maxLux();
431
+ // Brightness
432
+ switch (this.brightness) {
433
+ case 'dim':
434
+ this.CurrentAmbientLightLevel = this.set_minLux;
435
+ break;
436
+ case 'bright':
437
+ default:
438
+ this.CurrentAmbientLightLevel = this.set_maxLux;
439
+ }
440
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} CurrentAmbientLightLevel: ${this.CurrentAmbientLightLevel}`);
441
+ }
350
442
  }
351
443
 
352
444
  async refreshStatus(): Promise<void> {
@@ -377,6 +469,7 @@ export class BlindTilt {
377
469
  if (switchbot !== false) {
378
470
  switchbot
379
471
  .startScan({
472
+ model: 'c',
380
473
  id: this.device.bleMac,
381
474
  })
382
475
  .then(async () => {
@@ -390,10 +483,12 @@ export class BlindTilt {
390
483
  this.battery = ad.serviceData.battery;
391
484
  this.inMotion = ad.serviceData.inMotion;
392
485
  this.position = ad.serviceData.position;
486
+ this.lightLevel = ad.serviceData.lightLevel;
393
487
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} serviceData: ${superStringify(ad.serviceData)}`);
394
488
  this.debugLog(
395
489
  `${this.device.deviceType}: ${this.accessory.displayName} calibration: ${ad.serviceData.calibration}, ` +
396
- `position: ${ad.serviceData.position}, inMotion: ${ad.serviceData.inMotion}, battery: ${ad.serviceData.battery}`);
490
+ `position: ${ad.serviceData.position}, lightLevel: ${ad.serviceData.lightLevel}, battery: ${ad.serviceData.battery}, ` +
491
+ `inMotion: ${ad.serviceData.inMotion}`);
397
492
 
398
493
  if (this.serviceData) {
399
494
  this.connected = true;
@@ -455,15 +550,11 @@ export class BlindTilt {
455
550
  try {
456
551
  this.deviceStatus = JSON.parse(rawData);
457
552
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} refreshStatus: ${superStringify(this.deviceStatus)}`);
458
- this.slidePosition = this.deviceStatus.body.slidePosition;// "slidePosition": 0 = Closed Down | 100 = Closed Up | 50 = Fully Open
553
+ this.slidePosition = this.deviceStatus.body.slidePosition;
554
+ this.direction = this.deviceStatus.body.direction;
459
555
  this.moving = this.deviceStatus.body.moving;
460
- this.Battery = this.deviceStatus.body.battery;
461
- this.direction = this.deviceStatus.body.direction; // "Up"/"Down"
462
- this.version = this.deviceStatus.body.version;
463
- if (this.version) {
464
- const firmware = this.version * .10;
465
- this.accessory.context.FirmwareRevision = firmware;
466
- }
556
+ this.runStatus = this.deviceStatus.body.runStatus;
557
+ this.brightness = this.deviceStatus.body.brightness;
467
558
  this.openAPIparseStatus();
468
559
  this.updateHomeKitCharacteristics();
469
560
  } catch (e: any) {
@@ -513,6 +604,12 @@ export class BlindTilt {
513
604
  .join(':')
514
605
  .toLowerCase();
515
606
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} BLE Address: ${this.device.bleMac}`);
607
+ this.SilentPerformance();
608
+ const adjustedMode = this.setPositionMode || null;
609
+ if (adjustedMode === null) {
610
+ this.Mode = 'Default Mode';
611
+ }
612
+ this.debugLog(`${this.accessory.displayName} Mode: ${this.Mode}`);
516
613
  if (switchbot !== false) {
517
614
  switchbot
518
615
  .discover({ model: 'c', quick: true, id: this.device.bleMac })
@@ -521,7 +618,7 @@ export class BlindTilt {
521
618
  return await this.retry({
522
619
  max: await this.maxRetry(),
523
620
  fn: () => {
524
- return device_list[0].runToPos(100 - Number(this.TargetPosition));
621
+ return device_list[0].runToPos(100 - Number(this.TargetPosition), adjustedMode);
525
622
  },
526
623
  });
527
624
  })
@@ -580,35 +677,15 @@ export class BlindTilt {
580
677
  .digest();
581
678
  const sign = signTerm.toString('base64');
582
679
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} sign: ${sign}`);
583
- this.debugLog(`Pushing ${this.TargetPosition}`);
584
- let command = '';
585
- let parameter = '';
586
- if (this.TargetHorizontalTiltAngle === 90 && this.TargetPosition === 100) {
587
- command = 'fullyOpen';
588
- parameter = 'default';
589
- } else if (this.TargetHorizontalTiltAngle === -90 && this.TargetPosition === 100) {
590
- command = 'fullyOpen';
591
- parameter = 'default';
592
- } else if (this.TargetHorizontalTiltAngle === 90 && this.TargetPosition === 0) {
593
- command = 'closeUp';
594
- parameter = 'default';
595
- } else if (this.TargetHorizontalTiltAngle === -90 && this.TargetPosition === 0) {
596
- command = 'closeDown';
597
- parameter = 'default';
598
- } else {
599
- let direction = '';
600
- const position = this.TargetPosition;
601
- if (this.TargetHorizontalTiltAngle === 90) {
602
- direction = 'up';
603
- } else {
604
- direction = 'down';
605
- }
606
- command = 'setPosition';
607
- parameter = `${direction};${position}`;
608
- }
680
+
681
+
682
+ const [direction, position] = this.mapHomekitValuesToDeviceValues(Number(this.TargetPosition), Number(this.TargetHorizontalTiltAngle));
683
+ this.debugLog(`Pushing ${this.TargetPosition} (device = ${direction};${position})`);
684
+
685
+ this.debugLog(`${this.accessory.displayName} Mode: ${this.Mode}`);
609
686
  const body = superStringify({
610
- 'command': `${command}`,
611
- 'parameter': `${parameter}`,
687
+ 'command': 'setPosition',
688
+ 'parameter': `${direction};${position}`,
612
689
  'commandType': 'command',
613
690
  });
614
691
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Sending request to SwitchBot API, body: ${body},`);
@@ -652,6 +729,25 @@ export class BlindTilt {
652
729
  }
653
730
  }
654
731
 
732
+ /**
733
+ * Handle requests to set the value of the "Target Horizontal Tilt" characteristic
734
+ */
735
+ async TargetHorizontalTiltAngleSet(value: CharacteristicValue): Promise<void> {
736
+ if (this.TargetHorizontalTiltAngle === this.accessory.context.TargetHorizontalTiltAngle) {
737
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} No Changes, Set TargetHorizontalTiltAngle: ${value}`);
738
+ } else {
739
+ this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Set TargetHorizontalTiltAngle: ${value}`);
740
+ }
741
+
742
+ //value = value < 0 ? -90 : 90;
743
+ this.TargetHorizontalTiltAngle = value;
744
+ if(this.device.mqttURL) {
745
+ this.mqttPublish('TargetHorizontalTiltAngle', this.TargetHorizontalTiltAngle);
746
+ }
747
+
748
+ this.startUpdatingBlindTiltIfNeeded();
749
+ }
750
+
655
751
  /**
656
752
  * Handle requests to set the value of the "Target Position" characteristic
657
753
  */
@@ -666,20 +762,27 @@ export class BlindTilt {
666
762
  if (this.device.mqttURL) {
667
763
  this.mqttPublish('TargetPosition', this.TargetPosition);
668
764
  }
765
+ this.startUpdatingBlindTiltIfNeeded();
766
+ }
669
767
 
768
+ async startUpdatingBlindTiltIfNeeded(): Promise<void> {
670
769
  await this.setMinMax();
671
- if (value > this.CurrentPosition) {
770
+ this.debugLog('setMinMax');
771
+ if (this.TargetPosition > this.CurrentPosition || (this.TargetHorizontalTiltAngle !== this.CurrentHorizontalTiltAngle)) {
672
772
  this.PositionState = this.platform.Characteristic.PositionState.INCREASING;
673
773
  this.setNewTarget = true;
674
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} value: ${value}, CurrentPosition: ${this.CurrentPosition}`);
675
- } else if (value < this.CurrentPosition) {
774
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} value: ${this.CurrentPosition},`+
775
+ ` CurrentPosition: ${this.CurrentPosition}`);
776
+ } else if (this.TargetPosition < this.CurrentPosition) {
676
777
  this.PositionState = this.platform.Characteristic.PositionState.DECREASING;
677
778
  this.setNewTarget = true;
678
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} value: ${value}, CurrentPosition: ${this.CurrentPosition}`);
779
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} value: ${this.CurrentPosition},`+
780
+ ` CurrentPosition: ${this.CurrentPosition}`);
679
781
  } else {
680
782
  this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
681
783
  this.setNewTarget = false;
682
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} value: ${value}, CurrentPosition: ${this.CurrentPosition}`);
784
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} value: ${this.CurrentPosition},`+
785
+ ` CurrentPosition: ${this.CurrentPosition}`);
683
786
  }
684
787
  this.windowCoveringService.setCharacteristic(this.platform.Characteristic.PositionState, this.PositionState);
685
788
  this.windowCoveringService.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
@@ -699,42 +802,24 @@ export class BlindTilt {
699
802
  this.doBlindTiltUpdate.next();
700
803
  }
701
804
 
702
- /**
703
- * Handle requests to set the value of the "Target Position" characteristic
704
- */
705
- async TargetHorizontalTiltAngleSet(value: CharacteristicValue): Promise<void> {
706
- if (this.TargetHorizontalTiltAngle === this.accessory.context.TargetHorizontalTiltAngle) {
707
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} No Changes, Set TargetHorizontalTiltAngle: ${value}`);
708
- } else {
709
- this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Set TargetHorizontalTiltAngle: ${value}`);
710
- }
711
- this.TargetHorizontalTiltAngle = value;
712
- this.CurrentHorizontalTiltAngle = this.TargetHorizontalTiltAngle;
713
- this.accessory.context.TargetHorizontalTiltAngle = this.TargetHorizontalTiltAngle;
714
- if (this.device.mqttURL) {
715
- this.mqttPublish('TargetHorizontalTiltAngle', this.TargetHorizontalTiltAngle);
716
- }
717
-
718
- this.windowCoveringService.setCharacteristic(this.platform.Characteristic.PositionState, this.PositionState);
719
- this.windowCoveringService.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
805
+ async updateHomeKitCharacteristics(): Promise<void> {
806
+ await this.setMinMax();
720
807
 
721
- /**
722
- * If Blind Tilt movement time is short, the moving flag from backend is always false.
723
- * The minimum time depends on the network control latency.
724
- */
725
- clearTimeout(this.setNewTargetTimer);
726
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateRate: ${this.updateRate}`);
727
- if (this.setNewTarget) {
728
- this.setNewTargetTimer = setTimeout(() => {
729
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} setNewTarget ${this.setNewTarget} timeout`);
730
- this.setNewTarget = false;
731
- }, this.updateRate * 1000);
808
+ if(this.mappingMode === BlindTiltMappingMode.UseTiltForDirection) {
809
+ if (this.CurrentHorizontalTiltAngle === undefined || Number.isNaN(this.CurrentHorizontalTiltAngle)) {
810
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} CurrentHorizontalTiltAngle: ${this.CurrentHorizontalTiltAngle}`);
811
+ } else {
812
+ if (this.device.mqttURL) {
813
+ this.mqttPublish('CurrentHorizontalTiltAngle', this.CurrentHorizontalTiltAngle);
814
+ }
815
+ this.accessory.context.CurrentHorizontalTiltAngle = this.CurrentHorizontalTiltAngle;
816
+ this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.CurrentHorizontalTiltAngle,
817
+ Number(this.CurrentHorizontalTiltAngle));
818
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName}
819
+ updateCharacteristic CurrentHorizontalTiltAngle: ${this.CurrentHorizontalTiltAngle}`);
820
+ }
732
821
  }
733
- this.doBlindTiltUpdate.next();
734
- }
735
822
 
736
- async updateHomeKitCharacteristics(): Promise<void> {
737
- await this.setMinMax();
738
823
  if (this.CurrentPosition === undefined || Number.isNaN(this.CurrentPosition)) {
739
824
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} CurrentPosition: ${this.CurrentPosition}`);
740
825
  } else {
@@ -765,28 +850,18 @@ export class BlindTilt {
765
850
  this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.TargetPosition, Number(this.TargetPosition));
766
851
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic TargetPosition: ${this.TargetPosition}`);
767
852
  }
768
- if (this.TargetHorizontalTiltAngle === undefined || Number.isNaN(this.TargetHorizontalTiltAngle)) {
769
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} TargetHorizontalTiltAngle: ${this.TargetHorizontalTiltAngle}`);
770
- } else {
771
- if (this.device.mqttURL) {
772
- this.mqttPublish('TargetHorizontalTiltAngle', this.TargetHorizontalTiltAngle);
773
- }
774
- this.accessory.context.TargetHorizontalTiltAngle = this.TargetHorizontalTiltAngle;
775
- this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.TargetHorizontalTiltAngle, Number(this.TargetHorizontalTiltAngle));
776
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic`
777
- + ` TargetHorizontalTiltAngle: ${this.TargetHorizontalTiltAngle}`);
778
- }
779
- if (this.CurrentHorizontalTiltAngle === undefined || Number.isNaN(this.CurrentHorizontalTiltAngle)) {
780
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} CurrentHorizontalTiltAngle: ${this.CurrentHorizontalTiltAngle}`);
781
- } else {
782
- if (this.device.mqttURL) {
783
- this.mqttPublish('CurrentHorizontalTiltAngle', this.CurrentHorizontalTiltAngle);
853
+ if (!this.device.curtain?.hide_lightsensor) {
854
+ if (this.CurrentAmbientLightLevel === undefined || Number.isNaN(this.CurrentAmbientLightLevel)) {
855
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} CurrentAmbientLightLevel: ${this.CurrentAmbientLightLevel}`);
856
+ } else {
857
+ if (this.device.mqttURL) {
858
+ this.mqttPublish('CurrentAmbientLightLevel', this.CurrentAmbientLightLevel);
859
+ }
860
+ this.accessory.context.CurrentAmbientLightLevel = this.CurrentAmbientLightLevel;
861
+ this.lightSensorService?.updateCharacteristic(this.platform.Characteristic.CurrentAmbientLightLevel, this.CurrentAmbientLightLevel);
862
+ this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName}`
863
+ + ` updateCharacteristic CurrentAmbientLightLevel: ${this.CurrentAmbientLightLevel}`);
784
864
  }
785
- this.accessory.context.CurrentHorizontalTiltAngle = this.CurrentHorizontalTiltAngle;
786
- this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.CurrentHorizontalTiltAngle,
787
- Number(this.CurrentHorizontalTiltAngle));
788
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic`
789
- + ` CurrentHorizontalTiltAngle: ${this.CurrentHorizontalTiltAngle}`);
790
865
  }
791
866
  if (this.BLE) {
792
867
  if (this.BatteryLevel === undefined) {
@@ -887,6 +962,26 @@ export class BlindTilt {
887
962
  }
888
963
  }
889
964
 
965
+ async SilentPerformance() {
966
+ if (this.TargetPosition > 50) {
967
+ if (this.device.curtain?.setOpenMode === '1') {
968
+ this.setPositionMode = 1;
969
+ this.Mode = 'Silent Mode';
970
+ } else {
971
+ this.setPositionMode = 0;
972
+ this.Mode = 'Performance Mode';
973
+ }
974
+ } else {
975
+ if (this.device.curtain?.setCloseMode === '1') {
976
+ this.setPositionMode = 1;
977
+ this.Mode = 'Silent Mode';
978
+ } else {
979
+ this.setPositionMode = 0;
980
+ this.Mode = 'Performance Mode';
981
+ }
982
+ }
983
+ }
984
+
890
985
  async setMinMax(): Promise<void> {
891
986
  if (this.device.curtain?.set_min) {
892
987
  if (this.CurrentPosition <= this.device.curtain?.set_min) {
@@ -898,6 +993,10 @@ export class BlindTilt {
898
993
  this.CurrentPosition = 100;
899
994
  }
900
995
  }
996
+
997
+ if(this.mappingMode === BlindTiltMappingMode.UseTiltForDirection) {
998
+ this.CurrentHorizontalTiltAngle = this.CurrentHorizontalTiltAngle < 0 ? -90 : 90;
999
+ }
901
1000
  }
902
1001
 
903
1002
  minStep(device: device & devicesConfig): number {
@@ -1001,6 +1100,9 @@ export class BlindTilt {
1001
1100
  this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.CurrentPosition, e);
1002
1101
  this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.PositionState, e);
1003
1102
  this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.TargetPosition, e);
1103
+ if (!this.device.curtain?.hide_lightsensor) {
1104
+ this.lightSensorService?.updateCharacteristic(this.platform.Characteristic.CurrentAmbientLightLevel, e);
1105
+ }
1004
1106
  if (this.BLE) {
1005
1107
  this.batteryService?.updateCharacteristic(this.platform.Characteristic.BatteryLevel, e);
1006
1108
  this.batteryService?.updateCharacteristic(this.platform.Characteristic.StatusLowBattery, e);
@@ -1014,15 +1116,10 @@ export class BlindTilt {
1014
1116
  + ` accessory.context.FirmwareRevision: ${accessory.context.FirmwareRevision}`);
1015
1117
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} device.firmware: ${device.firmware}`);
1016
1118
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} this.platform.version: ${this.platform.version}`);
1017
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} API version: ${this.version}`);
1018
1119
  if (accessory.context.FirmwareRevision) {
1019
1120
  FirmwareRevision = accessory.context.FirmwareRevision;
1020
1121
  } else if (device.firmware) {
1021
1122
  FirmwareRevision = device.firmware;
1022
- } else if (this.version) {
1023
- const firmware = this.version * .10;
1024
- accessory.context.FirmwareRevision = firmware;
1025
- FirmwareRevision = superStringify(firmware);
1026
1123
  } else {
1027
1124
  FirmwareRevision = this.platform.version;
1028
1125
  }
@@ -1042,22 +1139,130 @@ export class BlindTilt {
1042
1139
  this.TargetPosition = this.accessory.context.TargetPosition;
1043
1140
  }
1044
1141
 
1045
- if (this.TargetHorizontalTiltAngle === undefined) {
1046
- this.TargetHorizontalTiltAngle = 0;
1142
+ if (this.PositionState === undefined) {
1143
+ this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
1047
1144
  } else {
1048
- this.TargetHorizontalTiltAngle = this.accessory.context.TargetHorizontalTiltAngle;
1145
+ this.PositionState = this.accessory.context.PositionState;
1049
1146
  }
1147
+ }
1050
1148
 
1051
- if (this.CurrentHorizontalTiltAngle === undefined) {
1052
- this.CurrentHorizontalTiltAngle = 0;
1053
- } else {
1054
- this.CurrentHorizontalTiltAngle = this.accessory.context.CurrentHorizontalTiltAngle;
1149
+ /**
1150
+ * Maps device values to homekit values
1151
+ *
1152
+ * @param devicePosition the position as reported by the devide
1153
+ * @param direction the direction as reported by the device
1154
+ * @returns [homekit position, homekit tiltAngle]
1155
+ */
1156
+ mapDeviceValuesToHomekitValues(devicePosition: number, deviceDirection: string): [CharacteristicValue, CharacteristicValue?] {
1157
+ // device position 0 => closed down
1158
+ // device position 50 => open
1159
+ // device position 100 => closed up
1160
+
1161
+ // homekit position 0 => closed
1162
+ // homekit position 100 => open
1163
+ deviceDirection;
1164
+ switch(this.mappingMode) {
1165
+ case BlindTiltMappingMode.OnlyUp:
1166
+ // we only close upwards, so we see anything that is tilted downwards(<50) as open
1167
+ if(devicePosition < 50) {
1168
+ return [100, undefined]; // fully open in homekit
1169
+ } else {
1170
+ // we range from 50->100, with 100 being closed, so map to homekit by scaling to 0..100 and then reversing
1171
+ return [100 - (devicePosition - 50) * 2, undefined];
1172
+ }
1173
+
1174
+ case BlindTiltMappingMode.OnlyDown:
1175
+ // we only close downwards, so we see anything that is tilted upwards(>50) as upwards
1176
+ if(devicePosition > 50) {
1177
+ return [100, undefined]; // fully open in homekit
1178
+ } else {
1179
+ // we range from 0..50 so scale to homekit and then reverse
1180
+ return [devicePosition * 2, undefined];
1181
+ }
1182
+
1183
+ case BlindTiltMappingMode.DownAndUp:
1184
+ // we close both ways with closed downwards being 0 in homekit and closed upwards in homekit being 100. Open is 50 in homekit
1185
+ return [devicePosition, undefined];
1186
+
1187
+ case BlindTiltMappingMode.UpAndDown:
1188
+ // we close both ways with closed downwards being 1000 in homekit and closed upwards in homekit being 0. Open is 50 in homekit.,
1189
+ // so we reverse the value
1190
+ return [100 - devicePosition, undefined];
1191
+
1192
+ case BlindTiltMappingMode.UseTiltForDirection:
1193
+ // we use tilt for direction, so being closed downwards is 0 in homekit with -90 tilt, while being closed upwards is 0 with 90 tilt.
1194
+ if(devicePosition <= 50) {
1195
+ // downwards tilted, so we range from 0..50, with 0 being closed and 50 being open, so scale.
1196
+ return [devicePosition * 2, -90];
1197
+ } else {
1198
+ // upwards tilted, so we range from 50..100, with 50 being open and 100 being closed, so scale and rever
1199
+ return [100 - (devicePosition - 50) * 2, 90];
1200
+ }
1055
1201
  }
1202
+ }
1056
1203
 
1057
- if (this.PositionState === undefined) {
1058
- this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
1059
- } else {
1060
- this.PositionState = this.accessory.context.PositionState;
1204
+ /**
1205
+ * Maps homekit values to device values
1206
+ *
1207
+ * @param homekitPosition the position as reported by homekit
1208
+ * @param homekitTiltAngle? the tilt angle as reported by homekit
1209
+ * @returns [device position, device direction]
1210
+ */
1211
+ mapHomekitValuesToDeviceValues(homekitPosition: number, homekitTiltAngle: number): [string, number] {
1212
+ // homekit position 0 => closed
1213
+ // homekit position 100 => open
1214
+
1215
+ // device position [up, 0] = closed upwards
1216
+ // device position [down, 0] = closed downwards
1217
+ // device position [up, 100] = open
1218
+ // device position [down, 100] = open
1219
+
1220
+ switch(this.mappingMode) {
1221
+ case BlindTiltMappingMode.OnlyUp:
1222
+ // invert
1223
+ return ['up', homekitPosition];
1224
+ case BlindTiltMappingMode.OnlyDown:
1225
+ // invert
1226
+ return ['down', homekitPosition];
1227
+
1228
+ case BlindTiltMappingMode.DownAndUp:
1229
+ // homekit 0 = downwards closed,
1230
+ // homekit 50 = open,
1231
+ // homekit 100 = upwards closed
1232
+ if(homekitPosition <= 50) {
1233
+ // homekit 0..50 -> device 100..0 so scale and invert
1234
+ return ['down', 100 - homekitPosition * 2];
1235
+ } else {
1236
+ // homekit 50..100 -> device 0..100, so rebase, scale and invert
1237
+ return ['up', (homekitPosition - 50) * 2];
1238
+ }
1239
+
1240
+ case BlindTiltMappingMode.UpAndDown:
1241
+ // homekit 0 = upwards closed,
1242
+ // homekit 50 = open,
1243
+ // homekit 100 = upwards closed
1244
+ if(homekitPosition <= 50) {
1245
+ // homekit 0..50 -> device 0..100 so scale and invert
1246
+ return ['up', homekitPosition * 2];
1247
+ } else {
1248
+ // homekit 50..100 -> device 100...0 so scale
1249
+ return ['down', 100 - homekitPosition * 2];
1250
+ }
1251
+
1252
+ case BlindTiltMappingMode.UseTiltForDirection:
1253
+ // tilt -90, homekit 0 = closed downwards
1254
+ // tilt -90, homekit 100 = open
1255
+ // tilt 90, homekit 0 = closed upwards
1256
+ // tilt 90, homekit 100 = open
1257
+ if(homekitTiltAngle! <= 0) {
1258
+ // downwards
1259
+ // homekit 0..100 -> device 0..100, so invert
1260
+ return ['down', homekitPosition];
1261
+ } else {
1262
+ // upwards
1263
+ // homekit 0..100 -> device 0..100, so invert
1264
+ return ['up,', homekitPosition];
1265
+ }
1061
1266
  }
1062
1267
  }
1063
1268
 
@@ -1071,19 +1276,19 @@ export class BlindTilt {
1071
1276
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Platform Config refreshRate: ${this.deviceRefreshRate}`);
1072
1277
  }
1073
1278
  // updateRate
1074
- if (device?.curtain?.updateRate) {
1075
- this.updateRate = device?.curtain?.updateRate;
1279
+ if (device?.blindTilt?.updateRate) {
1280
+ this.updateRate = device?.blindTilt?.updateRate;
1076
1281
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Device Config Blind Tilt updateRate: ${this.updateRate}`);
1077
1282
  } else {
1078
- this.updateRate = 7;
1283
+ this.updateRate = 2;
1079
1284
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Default Blind Tilt updateRate: ${this.updateRate}`);
1080
1285
  }
1081
1286
  }
1082
1287
 
1083
1288
  async config(device: device & devicesConfig): Promise<void> {
1084
1289
  let config = {};
1085
- if (device.curtain) {
1086
- config = device.curtain;
1290
+ if (device.blindTilt) {
1291
+ config = device.blindTilt;
1087
1292
  }
1088
1293
  if (device.connectionType !== undefined) {
1089
1294
  config['connectionType'] = device.connectionType;
@@ -1106,6 +1311,13 @@ export class BlindTilt {
1106
1311
  if (device.maxRetry !== undefined) {
1107
1312
  config['maxRetry'] = device.maxRetry;
1108
1313
  }
1314
+
1315
+ if(device.blindTilt?.mappingMode === undefined) {
1316
+ config['mappingMode'] = BlindTiltMappingMode.OnlyUp;
1317
+ } else {
1318
+ config['mappingMode'] = device.blindTilt?.mappingMode;
1319
+ }
1320
+
1109
1321
  if (Object.entries(config).length !== 0) {
1110
1322
  this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Config: ${superStringify(config)}`);
1111
1323
  }
@@ -1177,4 +1389,4 @@ export class BlindTilt {
1177
1389
  enablingDeviceLogging(): boolean {
1178
1390
  return this.deviceLogging.includes('debug') || this.deviceLogging === 'standard';
1179
1391
  }
1180
- }
1392
+ }