Skip to content

Commit 647d4d8

Browse files
authored
HomeKit: Handle state only shutters (#2342)
1 parent 2b45792 commit 647d4d8

File tree

2 files changed

+119
-0
lines changed

2 files changed

+119
-0
lines changed

server/services/homekit/lib/buildService.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,38 @@ function buildService(device, features, categoryMapping, subtype) {
199199
const { features: updatedFeatures } = await this.gladys.device.getBySelector(device.selector);
200200
callback(undefined, coverStateMapping[updatedFeatures.find((feat) => feat.id === feature.id).last_value]);
201201
});
202+
203+
if (
204+
!features.find((f) =>
205+
[
206+
`${DEVICE_FEATURE_CATEGORIES.CURTAIN}:${DEVICE_FEATURE_TYPES.CURTAIN.POSITION}`,
207+
`${DEVICE_FEATURE_CATEGORIES.SHUTTER}:${DEVICE_FEATURE_TYPES.SHUTTER.POSITION}`,
208+
].includes(`${f.category}:${f.type}`),
209+
)
210+
) {
211+
const targetPosCharacteristic = service.getCharacteristic(
212+
Characteristic[categoryMapping.capabilities[DEVICE_FEATURE_TYPES.CURTAIN.POSITION].characteristics[1]],
213+
);
214+
targetPosCharacteristic.on(CharacteristicEventTypes.SET, async (value, callback) => {
215+
const action = {
216+
type: ACTIONS.DEVICE.SET_VALUE,
217+
status: ACTIONS_STATUS.PENDING,
218+
value: Math.round(
219+
normalize(
220+
value,
221+
targetPosCharacteristic.props.minValue,
222+
targetPosCharacteristic.props.maxValue,
223+
feature.min,
224+
feature.max,
225+
),
226+
),
227+
device: device.selector,
228+
device_feature: feature.selector,
229+
};
230+
this.gladys.event.emit(EVENTS.ACTION.TRIGGERED, action);
231+
callback();
232+
});
233+
}
202234
break;
203235
}
204236
default:

server/test/services/homekit/lib/buildService.test.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,4 +575,91 @@ describe('Build service', () => {
575575
device_feature: features[1].selector,
576576
});
577577
});
578+
579+
it('should build shutter/curtain service without real position', async () => {
580+
homekitHandler.gladys.device.getBySelector = stub().resolves({
581+
features: [
582+
{
583+
id: '31c6a4a7-9710-4951-bf34-04eeae5b9ff7',
584+
name: 'Shutter State',
585+
selector: 'shutter-state',
586+
category: DEVICE_FEATURE_CATEGORIES.SHUTTER,
587+
type: DEVICE_FEATURE_TYPES.SHUTTER.STATE,
588+
last_value: 0,
589+
},
590+
],
591+
});
592+
homekitHandler.gladys.event.emit = stub();
593+
const on = stub();
594+
const getCharacteristic = stub()
595+
.onCall(0)
596+
.returns({
597+
on,
598+
props: {
599+
perms: ['PAIRED_READ'],
600+
},
601+
})
602+
.onCall(1)
603+
.returns({
604+
on,
605+
props: {
606+
perms: ['PAIRED_READ', 'PAIRED_WRITE'],
607+
minValue: 0,
608+
maxValue: 100,
609+
},
610+
});
611+
const WindowCovering = stub().returns({
612+
getCharacteristic,
613+
});
614+
615+
homekitHandler.hap = {
616+
Characteristic: {
617+
PositionState: 'POSITIONSTATE',
618+
TargetPosition: 'TARGETPOSITION',
619+
},
620+
CharacteristicEventTypes: stub(),
621+
Perms: {
622+
PAIRED_READ: 'PAIRED_READ',
623+
PAIRED_WRITE: 'PAIRED_WRITE',
624+
},
625+
Service: {
626+
WindowCovering,
627+
},
628+
};
629+
const device = {
630+
name: 'Shutter',
631+
selector: 'shutter',
632+
};
633+
const features = [
634+
{
635+
id: '31c6a4a7-9710-4951-bf34-04eeae5b9ff7',
636+
selector: 'shutter-state',
637+
name: 'Shutter State',
638+
category: DEVICE_FEATURE_CATEGORIES.SHUTTER,
639+
type: DEVICE_FEATURE_TYPES.SHUTTER.STATE,
640+
min: -1,
641+
max: 1,
642+
},
643+
];
644+
645+
const cb = stub();
646+
647+
await homekitHandler.buildService(device, features, mappings[DEVICE_FEATURE_CATEGORIES.SHUTTER]);
648+
await on.args[0][1](cb);
649+
await on.args[1][1](70, cb);
650+
651+
expect(WindowCovering.args[0][0]).to.equal('Shutter');
652+
expect(on.callCount).to.equal(2);
653+
expect(getCharacteristic.args[0][0]).to.equal('POSITIONSTATE');
654+
expect(getCharacteristic.args[1][0]).to.equal('TARGETPOSITION');
655+
expect(cb.args[0][1]).to.equal(2);
656+
expect(homekitHandler.gladys.event.emit.args[0][1]).to.eql({
657+
type: ACTIONS.DEVICE.SET_VALUE,
658+
status: ACTIONS_STATUS.PENDING,
659+
// we don't have a real position, it's normalized and rounded between -1 and 1
660+
value: 0,
661+
device: device.selector,
662+
device_feature: features[0].selector,
663+
});
664+
});
578665
});

0 commit comments

Comments
 (0)