@switchbot/homebridge-switchbot 2.5.0-beta.2 → 2.5.0-beta.4

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,26 +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'];
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;
35
51
 
36
52
  // BLE Others
37
53
  connected?: boolean;
@@ -43,6 +59,7 @@ export class BlindTilt {
43
59
  battery: serviceData['battery'];
44
60
  position: serviceData['position'];
45
61
  inMotion: serviceData['inMotion'];
62
+ lightLevel: serviceData['lightLevel'];
46
63
 
47
64
  // Target
48
65
  setNewTarget!: boolean;
@@ -79,6 +96,9 @@ export class BlindTilt {
79
96
  this.setupMqtt(device);
80
97
  this.context();
81
98
 
99
+ this.mappingMode = (device.blindTilt?.mappingMode as BlindTiltMappingMode) ?? BlindTiltMappingMode.OnlyUp;
100
+ this.debugLog(`Mapping mode: ${this.mappingMode}`);
101
+
82
102
  // this is subject we use to track when we need to POST changes to the SwitchBot API
83
103
  this.doBlindTiltUpdate = new Subject();
84
104
  this.blindTiltUpdateInProgress = false;
@@ -142,6 +162,7 @@ export class BlindTilt {
142
162
  })
143
163
  .onSet(this.TargetPositionSet.bind(this));
144
164
 
165
+ this.CurrentHorizontalTiltAngle = 90;
145
166
  this.windowCoveringService
146
167
  .getCharacteristic(this.platform.Characteristic.CurrentHorizontalTiltAngle)
147
168
  .setProps({
@@ -149,12 +170,13 @@ export class BlindTilt {
149
170
  minValue: -90,
150
171
  maxValue: 90,
151
172
  validValues: [-90, 90],
152
- validValueRanges: [-90, 90],
153
173
  })
154
174
  .onGet(() => {
175
+ // this.debugLog(`requested CurrentHorizontalTiltAngle: ${this.CurrentHorizontalTiltAngle}`);
155
176
  return this.CurrentHorizontalTiltAngle;
156
177
  });
157
178
 
179
+ this.TargetHorizontalTiltAngle = 90;
158
180
  this.windowCoveringService
159
181
  .getCharacteristic(this.platform.Characteristic.TargetHorizontalTiltAngle)
160
182
  .setProps({
@@ -162,7 +184,6 @@ export class BlindTilt {
162
184
  minValue: -90,
163
185
  maxValue: 90,
164
186
  validValues: [-90, 90],
165
- validValueRanges: [-90, 90],
166
187
  })
167
188
  .onSet(this.TargetHorizontalTiltAngleSet.bind(this));
168
189
 
@@ -266,7 +287,7 @@ export class BlindTilt {
266
287
  this.windowCoveringService.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
267
288
  this.debugLog(`${this.device.deviceType}: ${this.CurrentPosition} DECREASING PositionState: ${this.PositionState}`);
268
289
  } else {
269
- this.debugLog(`${this.device.deviceType}: ${this.CurrentPosition} Standby, CurrentPosition: ${this.CurrentPosition}`);
290
+ this.debugLog(`${this.device.deviceType}: ${this.CurrentPosition} Standby2, CurrentPosition: ${this.CurrentPosition}`);
270
291
  this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
271
292
  this.windowCoveringService.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
272
293
  this.debugLog(`${this.device.deviceType}: ${this.CurrentPosition} STOPPED PositionState: ${this.PositionState}`);
@@ -282,6 +303,62 @@ export class BlindTilt {
282
303
  ` TargetPosition: ${this.TargetPosition}, PositionState: ${this.PositionState},`,
283
304
  );
284
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
+ }
285
362
  // Battery
286
363
  this.BatteryLevel = Number(this.battery);
287
364
  if (this.BatteryLevel < 10) {
@@ -295,17 +372,28 @@ export class BlindTilt {
295
372
 
296
373
  async openAPIparseStatus(): Promise<void> {
297
374
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} openAPIparseStatus`);
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;
298
380
  // CurrentPosition
299
- this.CurrentPosition = 100 - Number(this.slidePosition);
300
381
  await this.setMinMax();
301
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
+
302
389
  if (this.setNewTarget) {
303
390
  this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Checking Status ...`);
304
391
  }
305
392
 
306
- if (this.setNewTarget && this.moving) {
393
+ this.debugLog(`runStatus: ${this.runStatus}`);
394
+ if (this.setNewTarget || (this.runStatus === undefined || this.runStatus !== 'static')) {
307
395
  await this.setMinMax();
308
- if (this.TargetPosition > this.CurrentPosition) {
396
+ if (this.TargetPosition > this.CurrentPosition || (homekitTiltAngle && (this.TargetHorizontalTiltAngle !== this.CurrentHorizontalTiltAngle))) {
309
397
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Closing, CurrentPosition: ${this.CurrentPosition} `);
310
398
  this.PositionState = this.platform.Characteristic.PositionState.INCREASING;
311
399
  this.windowCoveringService.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
@@ -316,29 +404,41 @@ export class BlindTilt {
316
404
  this.windowCoveringService.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
317
405
  this.debugLog(`${this.device.deviceType}: ${this.CurrentPosition} DECREASING PositionState: ${this.PositionState}`);
318
406
  } else {
319
- 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}`);
320
409
  this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
321
410
  this.windowCoveringService.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
322
411
  this.debugLog(`${this.device.deviceType}: ${this.CurrentPosition} STOPPED PositionState: ${this.PositionState}`);
323
412
  }
324
413
  } else {
325
- 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}`);
326
416
  this.TargetPosition = this.CurrentPosition;
417
+ if(homekitTiltAngle) {
418
+ this.TargetHorizontalTiltAngle = this.CurrentHorizontalTiltAngle;
419
+ }
327
420
  this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
328
421
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Stopped`);
329
422
  }
330
- if (this.direction === 'Up') {
331
- this.CurrentHorizontalTiltAngle = 90;
332
- } else if (this.direction === 'Down') {
333
- this.CurrentHorizontalTiltAngle = -90;
334
- } else {
335
- this.CurrentHorizontalTiltAngle = 0;
336
- }
337
423
  this.debugLog(
338
424
  `${this.device.deviceType}: ${this.accessory.displayName} CurrentPosition: ${this.CurrentPosition},` +
339
- ` TargetPosition: ${this.TargetPosition}, PositionState: ${this.PositionState},` +
340
- ` CurrentHorizontalTiltAngle: ${this.CurrentHorizontalTiltAngle}`,
425
+ ` TargetPosition: ${this.TargetPosition}, PositionState: ${this.PositionState},`,
341
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
+ }
342
442
  }
343
443
 
344
444
  async refreshStatus(): Promise<void> {
@@ -369,6 +469,7 @@ export class BlindTilt {
369
469
  if (switchbot !== false) {
370
470
  switchbot
371
471
  .startScan({
472
+ model: 'c',
372
473
  id: this.device.bleMac,
373
474
  })
374
475
  .then(async () => {
@@ -382,10 +483,12 @@ export class BlindTilt {
382
483
  this.battery = ad.serviceData.battery;
383
484
  this.inMotion = ad.serviceData.inMotion;
384
485
  this.position = ad.serviceData.position;
486
+ this.lightLevel = ad.serviceData.lightLevel;
385
487
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} serviceData: ${superStringify(ad.serviceData)}`);
386
488
  this.debugLog(
387
489
  `${this.device.deviceType}: ${this.accessory.displayName} calibration: ${ad.serviceData.calibration}, ` +
388
- `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}`);
389
492
 
390
493
  if (this.serviceData) {
391
494
  this.connected = true;
@@ -447,10 +550,11 @@ export class BlindTilt {
447
550
  try {
448
551
  this.deviceStatus = JSON.parse(rawData);
449
552
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} refreshStatus: ${superStringify(this.deviceStatus)}`);
450
- 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;
451
555
  this.moving = this.deviceStatus.body.moving;
452
- this.Battery = this.deviceStatus.body.battery;
453
- this.direction = this.deviceStatus.body.direction; // "Up"/"Down"
556
+ this.runStatus = this.deviceStatus.body.runStatus;
557
+ this.brightness = this.deviceStatus.body.brightness;
454
558
  this.openAPIparseStatus();
455
559
  this.updateHomeKitCharacteristics();
456
560
  } catch (e: any) {
@@ -500,6 +604,12 @@ export class BlindTilt {
500
604
  .join(':')
501
605
  .toLowerCase();
502
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}`);
503
613
  if (switchbot !== false) {
504
614
  switchbot
505
615
  .discover({ model: 'c', quick: true, id: this.device.bleMac })
@@ -508,7 +618,7 @@ export class BlindTilt {
508
618
  return await this.retry({
509
619
  max: await this.maxRetry(),
510
620
  fn: () => {
511
- return device_list[0].runToPos(100 - Number(this.TargetPosition));
621
+ return device_list[0].runToPos(100 - Number(this.TargetPosition), adjustedMode);
512
622
  },
513
623
  });
514
624
  })
@@ -567,35 +677,15 @@ export class BlindTilt {
567
677
  .digest();
568
678
  const sign = signTerm.toString('base64');
569
679
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} sign: ${sign}`);
570
- this.debugLog(`Pushing ${this.TargetPosition}`);
571
- let command = '';
572
- let parameter = '';
573
- if (this.TargetHorizontalTiltAngle === 90 && this.TargetPosition === 100) {
574
- command = 'fullyOpen';
575
- parameter = 'default';
576
- } else if (this.TargetHorizontalTiltAngle === -90 && this.TargetPosition === 100) {
577
- command = 'fullyOpen';
578
- parameter = 'default';
579
- } else if (this.TargetHorizontalTiltAngle === 90 && this.TargetPosition === 0) {
580
- command = 'closeUp';
581
- parameter = 'default';
582
- } else if (this.TargetHorizontalTiltAngle === -90 && this.TargetPosition === 0) {
583
- command = 'closeDown';
584
- parameter = 'default';
585
- } else {
586
- let direction = '';
587
- const position = this.TargetPosition;
588
- if (this.TargetHorizontalTiltAngle === 90) {
589
- direction = 'up';
590
- } else {
591
- direction = 'down';
592
- }
593
- command = 'setPosition';
594
- parameter = `${direction};${position}`;
595
- }
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}`);
596
686
  const body = superStringify({
597
- 'command': `${command}`,
598
- 'parameter': `${parameter}`,
687
+ 'command': 'setPosition',
688
+ 'parameter': `${direction};${position}`,
599
689
  'commandType': 'command',
600
690
  });
601
691
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Sending request to SwitchBot API, body: ${body},`);
@@ -639,6 +729,25 @@ export class BlindTilt {
639
729
  }
640
730
  }
641
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
+
642
751
  /**
643
752
  * Handle requests to set the value of the "Target Position" characteristic
644
753
  */
@@ -653,20 +762,27 @@ export class BlindTilt {
653
762
  if (this.device.mqttURL) {
654
763
  this.mqttPublish('TargetPosition', this.TargetPosition);
655
764
  }
765
+ this.startUpdatingBlindTiltIfNeeded();
766
+ }
656
767
 
768
+ async startUpdatingBlindTiltIfNeeded(): Promise<void> {
657
769
  await this.setMinMax();
658
- if (value > this.CurrentPosition) {
770
+ this.debugLog('setMinMax');
771
+ if (this.TargetPosition > this.CurrentPosition || (this.TargetHorizontalTiltAngle !== this.CurrentHorizontalTiltAngle)) {
659
772
  this.PositionState = this.platform.Characteristic.PositionState.INCREASING;
660
773
  this.setNewTarget = true;
661
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} value: ${value}, CurrentPosition: ${this.CurrentPosition}`);
662
- } 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) {
663
777
  this.PositionState = this.platform.Characteristic.PositionState.DECREASING;
664
778
  this.setNewTarget = true;
665
- 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}`);
666
781
  } else {
667
782
  this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
668
783
  this.setNewTarget = false;
669
- 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}`);
670
786
  }
671
787
  this.windowCoveringService.setCharacteristic(this.platform.Characteristic.PositionState, this.PositionState);
672
788
  this.windowCoveringService.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
@@ -686,42 +802,24 @@ export class BlindTilt {
686
802
  this.doBlindTiltUpdate.next();
687
803
  }
688
804
 
689
- /**
690
- * Handle requests to set the value of the "Target Position" characteristic
691
- */
692
- async TargetHorizontalTiltAngleSet(value: CharacteristicValue): Promise<void> {
693
- if (this.TargetHorizontalTiltAngle === this.accessory.context.TargetHorizontalTiltAngle) {
694
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} No Changes, Set TargetHorizontalTiltAngle: ${value}`);
695
- } else {
696
- this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Set TargetHorizontalTiltAngle: ${value}`);
697
- }
698
-
699
- this.TargetHorizontalTiltAngle = value;
700
- this.accessory.context.TargetHorizontalTiltAngle = this.TargetHorizontalTiltAngle;
701
- if (this.device.mqttURL) {
702
- this.mqttPublish('TargetHorizontalTiltAngle', this.TargetHorizontalTiltAngle);
703
- }
704
-
705
- this.windowCoveringService.setCharacteristic(this.platform.Characteristic.PositionState, this.PositionState);
706
- this.windowCoveringService.getCharacteristic(this.platform.Characteristic.PositionState).updateValue(this.PositionState);
805
+ async updateHomeKitCharacteristics(): Promise<void> {
806
+ await this.setMinMax();
707
807
 
708
- /**
709
- * If Blind Tilt movement time is short, the moving flag from backend is always false.
710
- * The minimum time depends on the network control latency.
711
- */
712
- clearTimeout(this.setNewTargetTimer);
713
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateRate: ${this.updateRate}`);
714
- if (this.setNewTarget) {
715
- this.setNewTargetTimer = setTimeout(() => {
716
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} setNewTarget ${this.setNewTarget} timeout`);
717
- this.setNewTarget = false;
718
- }, 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
+ }
719
821
  }
720
- this.doBlindTiltUpdate.next();
721
- }
722
822
 
723
- async updateHomeKitCharacteristics(): Promise<void> {
724
- await this.setMinMax();
725
823
  if (this.CurrentPosition === undefined || Number.isNaN(this.CurrentPosition)) {
726
824
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} CurrentPosition: ${this.CurrentPosition}`);
727
825
  } else {
@@ -752,16 +850,18 @@ export class BlindTilt {
752
850
  this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.TargetPosition, Number(this.TargetPosition));
753
851
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic TargetPosition: ${this.TargetPosition}`);
754
852
  }
755
- if (this.TargetHorizontalTiltAngle === undefined || Number.isNaN(this.TargetHorizontalTiltAngle)) {
756
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} TargetHorizontalTiltAngle: ${this.TargetHorizontalTiltAngle}`);
757
- } else {
758
- if (this.device.mqttURL) {
759
- this.mqttPublish('TargetHorizontalTiltAngle', this.TargetHorizontalTiltAngle);
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}`);
760
864
  }
761
- this.accessory.context.TargetHorizontalTiltAngle = this.TargetHorizontalTiltAngle;
762
- this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.TargetHorizontalTiltAngle, Number(this.TargetHorizontalTiltAngle));
763
- this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} updateCharacteristic`
764
- + ` TargetHorizontalTiltAngle: ${this.TargetHorizontalTiltAngle}`);
765
865
  }
766
866
  if (this.BLE) {
767
867
  if (this.BatteryLevel === undefined) {
@@ -862,6 +962,26 @@ export class BlindTilt {
862
962
  }
863
963
  }
864
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
+
865
985
  async setMinMax(): Promise<void> {
866
986
  if (this.device.curtain?.set_min) {
867
987
  if (this.CurrentPosition <= this.device.curtain?.set_min) {
@@ -873,13 +993,17 @@ export class BlindTilt {
873
993
  this.CurrentPosition = 100;
874
994
  }
875
995
  }
996
+
997
+ if(this.mappingMode === BlindTiltMappingMode.UseTiltForDirection) {
998
+ this.CurrentHorizontalTiltAngle = this.CurrentHorizontalTiltAngle < 0 ? -90 : 90;
999
+ }
876
1000
  }
877
1001
 
878
1002
  minStep(device: device & devicesConfig): number {
879
1003
  if (device.curtain?.set_minStep) {
880
1004
  this.set_minStep = device.curtain?.set_minStep;
881
1005
  } else {
882
- this.set_minStep = 90;
1006
+ this.set_minStep = 1;
883
1007
  }
884
1008
  return this.set_minStep;
885
1009
  }
@@ -976,6 +1100,9 @@ export class BlindTilt {
976
1100
  this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.CurrentPosition, e);
977
1101
  this.windowCoveringService.updateCharacteristic(this.platform.Characteristic.PositionState, e);
978
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
+ }
979
1106
  if (this.BLE) {
980
1107
  this.batteryService?.updateCharacteristic(this.platform.Characteristic.BatteryLevel, e);
981
1108
  this.batteryService?.updateCharacteristic(this.platform.Characteristic.StatusLowBattery, e);
@@ -1012,12 +1139,6 @@ export class BlindTilt {
1012
1139
  this.TargetPosition = this.accessory.context.TargetPosition;
1013
1140
  }
1014
1141
 
1015
- if (this.TargetHorizontalTiltAngle === undefined) {
1016
- this.TargetHorizontalTiltAngle = 0;
1017
- } else {
1018
- this.TargetHorizontalTiltAngle = this.accessory.context.TargetHorizontalTiltAngle;
1019
- }
1020
-
1021
1142
  if (this.PositionState === undefined) {
1022
1143
  this.PositionState = this.platform.Characteristic.PositionState.STOPPED;
1023
1144
  } else {
@@ -1025,6 +1146,126 @@ export class BlindTilt {
1025
1146
  }
1026
1147
  }
1027
1148
 
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
+ }
1201
+ }
1202
+ }
1203
+
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
+ }
1266
+ }
1267
+ }
1268
+
1028
1269
  async refreshRate(device: device & devicesConfig): Promise<void> {
1029
1270
  // refreshRate
1030
1271
  if (device.refreshRate) {
@@ -1035,19 +1276,19 @@ export class BlindTilt {
1035
1276
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Platform Config refreshRate: ${this.deviceRefreshRate}`);
1036
1277
  }
1037
1278
  // updateRate
1038
- if (device?.curtain?.updateRate) {
1039
- this.updateRate = device?.curtain?.updateRate;
1279
+ if (device?.blindTilt?.updateRate) {
1280
+ this.updateRate = device?.blindTilt?.updateRate;
1040
1281
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Device Config Blind Tilt updateRate: ${this.updateRate}`);
1041
1282
  } else {
1042
- this.updateRate = 7;
1283
+ this.updateRate = 2;
1043
1284
  this.debugLog(`${this.device.deviceType}: ${this.accessory.displayName} Using Default Blind Tilt updateRate: ${this.updateRate}`);
1044
1285
  }
1045
1286
  }
1046
1287
 
1047
1288
  async config(device: device & devicesConfig): Promise<void> {
1048
1289
  let config = {};
1049
- if (device.curtain) {
1050
- config = device.curtain;
1290
+ if (device.blindTilt) {
1291
+ config = device.blindTilt;
1051
1292
  }
1052
1293
  if (device.connectionType !== undefined) {
1053
1294
  config['connectionType'] = device.connectionType;
@@ -1070,6 +1311,13 @@ export class BlindTilt {
1070
1311
  if (device.maxRetry !== undefined) {
1071
1312
  config['maxRetry'] = device.maxRetry;
1072
1313
  }
1314
+
1315
+ if(device.blindTilt?.mappingMode === undefined) {
1316
+ config['mappingMode'] = BlindTiltMappingMode.OnlyUp;
1317
+ } else {
1318
+ config['mappingMode'] = device.blindTilt?.mappingMode;
1319
+ }
1320
+
1073
1321
  if (Object.entries(config).length !== 0) {
1074
1322
  this.infoLog(`${this.device.deviceType}: ${this.accessory.displayName} Config: ${superStringify(config)}`);
1075
1323
  }
@@ -1141,4 +1389,4 @@ export class BlindTilt {
1141
1389
  enablingDeviceLogging(): boolean {
1142
1390
  return this.deviceLogging.includes('debug') || this.deviceLogging === 'standard';
1143
1391
  }
1144
- }
1392
+ }