Skip to content

Commit 8d4124a

Browse files
authored
add types to support Air Purifier (#833)
* add types to support Air Purifier * fix homekit type for airpurifier
1 parent b7cda86 commit 8d4124a

File tree

4 files changed

+226
-0
lines changed

4 files changed

+226
-0
lines changed

Diff for: plugins/homekit/src/types/airpurifier.ts

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { ScryptedDevice, ScryptedDeviceType, ScryptedInterface, AirPurifierStatus, AirPurifierMode, AirPurifier, FilterMaintenance } from '@scrypted/sdk';
2+
import { addSupportedType, bindCharacteristic, DummyDevice, } from '../common';
3+
import { Characteristic, CharacteristicEventTypes, CharacteristicSetCallback, CharacteristicValue, Service } from '../hap';
4+
import { makeAccessory } from './common';
5+
import type { HomeKitPlugin } from "../main";
6+
7+
addSupportedType({
8+
type: ScryptedDeviceType.AirPurifier,
9+
probe(device: DummyDevice): boolean {
10+
return device.interfaces.includes(ScryptedInterface.AirPurifier);
11+
},
12+
getAccessory: async (device: ScryptedDevice & AirPurifier & FilterMaintenance, homekitPlugin: HomeKitPlugin) => {
13+
const accessory = makeAccessory(device, homekitPlugin);
14+
15+
const service = accessory.addService(Service.AirPurifier, device.name);
16+
const nightModeService = accessory.addService(Service.Switch, `${device.name} Night Mode`)
17+
18+
/* On/Off AND mode toggle */
19+
bindCharacteristic(device, ScryptedInterface.AirPurifier, service, Characteristic.Active,
20+
() => {
21+
switch(device.airPurifierState.status) {
22+
case AirPurifierStatus.Active:
23+
return Characteristic.Active.ACTIVE;
24+
case AirPurifierStatus.ActiveNightMode:
25+
return Characteristic.Active.ACTIVE;
26+
}
27+
return Characteristic.Active.INACTIVE;
28+
});
29+
30+
service.getCharacteristic(Characteristic.Active)
31+
.on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
32+
callback();
33+
device.setAirPurifierState({
34+
status: (value as boolean) ? AirPurifierStatus.Active : AirPurifierStatus.Inactive,
35+
})
36+
});
37+
38+
/* Current State */
39+
bindCharacteristic(device, ScryptedInterface.AirPurifier, service, Characteristic.CurrentAirPurifierState,
40+
() => {
41+
switch (device.airPurifierState.status) {
42+
case AirPurifierStatus.Inactive:
43+
return Characteristic.CurrentAirPurifierState.INACTIVE;
44+
case AirPurifierStatus.Idle:
45+
return Characteristic.CurrentAirPurifierState.IDLE;
46+
}
47+
return Characteristic.CurrentAirPurifierState.PURIFYING_AIR;
48+
});
49+
50+
/* Fan Speed */
51+
bindCharacteristic(device, ScryptedInterface.AirPurifier, service, Characteristic.RotationSpeed,
52+
() => device.airPurifierState.speed);
53+
54+
service.getCharacteristic(Characteristic.RotationSpeed)
55+
.on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
56+
callback();
57+
device.setAirPurifierState({
58+
speed: value,
59+
})
60+
})
61+
62+
/* i.e. Mode: Manual/Auto slider */
63+
bindCharacteristic(device, ScryptedInterface.AirPurifier, service, Characteristic.TargetAirPurifierState,
64+
() => {
65+
if (device.airPurifierState.mode == AirPurifierMode.Automatic)
66+
return Characteristic.TargetAirPurifierState.AUTO;
67+
return Characteristic.TargetAirPurifierState.MANUAL;
68+
});
69+
70+
service.getCharacteristic(Characteristic.TargetAirPurifierState)
71+
.on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
72+
callback();
73+
device.setAirPurifierState({
74+
mode: value === Characteristic.TargetAirPurifierState.AUTO ? AirPurifierMode.Automatic : AirPurifierMode.Manual,
75+
})
76+
});
77+
78+
/* LockPhysicalControls i.e. "Child Lock: Unlocked/Locked" */
79+
bindCharacteristic(device, ScryptedInterface.AirPurifier, service, Characteristic.LockPhysicalControls,
80+
() => !!device.airPurifierState.lockPhysicalControls);
81+
82+
service.getCharacteristic(Characteristic.LockPhysicalControls)
83+
.on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
84+
callback();
85+
device.setAirPurifierState({
86+
lockPhysicalControls: (value as boolean),
87+
})
88+
})
89+
90+
/* Night mode switch */
91+
bindCharacteristic(device, ScryptedInterface.AirPurifier, nightModeService, Characteristic.On,
92+
() => !!(device.airPurifierState.status === AirPurifierStatus.ActiveNightMode));
93+
94+
nightModeService.getCharacteristic(Characteristic.On)
95+
.on(CharacteristicEventTypes.SET, (value: CharacteristicValue, callback: CharacteristicSetCallback) => {
96+
callback();
97+
device.setAirPurifierState({
98+
status: value ? AirPurifierStatus.ActiveNightMode : AirPurifierStatus.Active,
99+
})
100+
})
101+
102+
/* Optional: Filter Maintenance Service */
103+
if (device.interfaces.includes(ScryptedInterface.FilterMaintenance)) {
104+
const filterMaintenanceService = accessory.addService(Service.FilterMaintenance, device.name);
105+
106+
bindCharacteristic(device, ScryptedInterface.FilterMaintenance, filterMaintenanceService, Characteristic.FilterLifeLevel,
107+
() => device.filterLifeLevel)
108+
109+
bindCharacteristic(device, ScryptedInterface.FilterMaintenance, filterMaintenanceService, Characteristic.FilterChangeIndication,
110+
() => {
111+
if (device.filterChangeIndication)
112+
return Characteristic.FilterChangeIndication.CHANGE_FILTER;
113+
return Characteristic.FilterChangeIndication.FILTER_OK;
114+
})
115+
}
116+
117+
return accessory;
118+
}
119+
});

Diff for: plugins/homekit/src/types/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ import './vacuum';
1515
import './outlet';
1616
import './notifier';
1717
import './windowcovering'
18+
import './airpurifier'

Diff for: sdk/types/scrypted_python/scrypted_sdk/types.py

+73
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@
99
from .other import *
1010

1111

12+
class AirPurifierMode(Enum):
13+
Automatic = "Automatic"
14+
Manual = "Manual"
15+
16+
class AirPurifierStatus(Enum):
17+
Active = "Active"
18+
ActiveNightMode = "ActiveNightMode"
19+
Idle = "Idle"
20+
Inactive = "Inactive"
21+
1222
class AirQuality(Enum):
1323
Excellent = "Excellent"
1424
Fair = "Fair"
@@ -49,6 +59,7 @@ class PanTiltZoomMovement(Enum):
4959

5060
class ScryptedDeviceType(Enum):
5161
API = "API"
62+
AirPurifier = "AirPurifier"
5263
Automation = "Automation"
5364
Builtin = "Builtin"
5465
Camera = "Camera"
@@ -83,6 +94,7 @@ class ScryptedDeviceType(Enum):
8394
WindowCovering = "WindowCovering"
8495

8596
class ScryptedInterface(Enum):
97+
AirPurifier = "AirPurifier"
8698
AirQualitySensor = "AirQualitySensor"
8799
AmbientLightSensor = "AmbientLightSensor"
88100
AudioSensor = "AudioSensor"
@@ -106,6 +118,7 @@ class ScryptedInterface(Enum):
106118
EntrySensor = "EntrySensor"
107119
EventRecorder = "EventRecorder"
108120
Fan = "Fan"
121+
FilterMaintenance = "FilterMaintenance"
109122
FloodSensor = "FloodSensor"
110123
HttpRequestHandler = "HttpRequestHandler"
111124
HumiditySensor = "HumiditySensor"
@@ -319,6 +332,13 @@ class AdoptDevice(TypedDict):
319332
settings: DeviceCreatorSettings
320333
pass
321334

335+
class AirPurifierState(TypedDict):
336+
lockPhysicalControls: bool
337+
mode: AirPurifierMode
338+
speed: float
339+
status: AirPurifierStatus
340+
pass
341+
322342
class ColorHsv(TypedDict):
323343
h: float
324344
s: float
@@ -733,6 +753,12 @@ class VideoFrameGeneratorOptions(TypedDict):
733753
class TamperState(TypedDict):
734754
pass
735755

756+
class AirPurifier:
757+
airPurifierState: AirPurifierState
758+
async def setAirPurifierState(self, state: AirPurifierState) -> None:
759+
pass
760+
pass
761+
736762
class AirQualitySensor:
737763
airQuality: AirQuality
738764
pass
@@ -864,6 +890,11 @@ async def setFan(self, fan: FanState) -> None:
864890
pass
865891
pass
866892

893+
class FilterMaintenance:
894+
filterChangeIndication: bool
895+
filterLifeLevel: float
896+
pass
897+
867898
class FloodSensor:
868899
flooded: bool
869900
pass
@@ -1400,6 +1431,9 @@ class ScryptedInterfaceProperty(Enum):
14001431
noxDensity = "noxDensity"
14011432
co2ppm = "co2ppm"
14021433
airQuality = "airQuality"
1434+
airPurifierState = "airPurifierState"
1435+
filterChangeIndication = "filterChangeIndication"
1436+
filterLifeLevel = "filterLifeLevel"
14031437
humiditySetting = "humiditySetting"
14041438
fan = "fan"
14051439
applicationInfo = "applicationInfo"
@@ -1481,6 +1515,7 @@ class ScryptedInterfaceMethods(Enum):
14811515
putSetting = "putSetting"
14821516
armSecuritySystem = "armSecuritySystem"
14831517
disarmSecuritySystem = "disarmSecuritySystem"
1518+
setAirPurifierState = "setAirPurifierState"
14841519
getReadmeMarkdown = "getReadmeMarkdown"
14851520
getOauthUrl = "getOauthUrl"
14861521
onOauthCallback = "onOauthCallback"
@@ -1912,6 +1947,27 @@ def airQuality(self) -> AirQuality:
19121947
def airQuality(self, value: AirQuality):
19131948
self.setScryptedProperty("airQuality", value)
19141949

1950+
@property
1951+
def airPurifierState(self) -> AirPurifierState:
1952+
return self.getScryptedProperty("airPurifierState")
1953+
@airPurifierState.setter
1954+
def airPurifierState(self, value: AirPurifierState):
1955+
self.setScryptedProperty("airPurifierState", value)
1956+
1957+
@property
1958+
def filterChangeIndication(self) -> bool:
1959+
return self.getScryptedProperty("filterChangeIndication")
1960+
@filterChangeIndication.setter
1961+
def filterChangeIndication(self, value: bool):
1962+
self.setScryptedProperty("filterChangeIndication", value)
1963+
1964+
@property
1965+
def filterLifeLevel(self) -> float:
1966+
return self.getScryptedProperty("filterLifeLevel")
1967+
@filterLifeLevel.setter
1968+
def filterLifeLevel(self, value: float):
1969+
self.setScryptedProperty("filterLifeLevel", value)
1970+
19151971
@property
19161972
def humiditySetting(self) -> HumiditySettingStatus:
19171973
return self.getScryptedProperty("humiditySetting")
@@ -2431,6 +2487,23 @@ def applicationInfo(self, value: LauncherApplicationInfo):
24312487
"airQuality"
24322488
]
24332489
},
2490+
"AirPurifier": {
2491+
"name": "AirPurifier",
2492+
"methods": [
2493+
"setAirPurifierState"
2494+
],
2495+
"properties": [
2496+
"airPurifierState"
2497+
]
2498+
},
2499+
"FilterMaintenance": {
2500+
"name": "FilterMaintenance",
2501+
"methods": [],
2502+
"properties": [
2503+
"filterChangeIndication",
2504+
"filterLifeLevel"
2505+
]
2506+
},
24342507
"Readme": {
24352508
"name": "Readme",
24362509
"methods": [

Diff for: sdk/types/src/types.input.ts

+33
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ export enum ScryptedDeviceType {
131131
SecuritySystem = "SecuritySystem",
132132
WindowCovering = "WindowCovering",
133133
Siren = "Siren",
134+
AirPurifier = "AirPurifier",
134135
Unknown = "Unknown",
135136
}
136137
/**
@@ -1181,6 +1182,36 @@ export interface Position {
11811182
export interface PositionSensor {
11821183
position?: Position;
11831184
}
1185+
export enum AirPurifierStatus {
1186+
Inactive = "Inactive",
1187+
Idle = "Idle",
1188+
Active = "Active",
1189+
ActiveNightMode = "ActiveNightMode",
1190+
}
1191+
1192+
export enum AirPurifierMode {
1193+
Manual = "Manual",
1194+
Automatic = "Automatic",
1195+
}
1196+
1197+
export interface AirPurifierState {
1198+
speed?: number;
1199+
status?: AirPurifierStatus,
1200+
mode?: AirPurifierMode,
1201+
lockPhysicalControls?: boolean,
1202+
}
1203+
1204+
export interface AirPurifier {
1205+
airPurifierState?: AirPurifierState;
1206+
1207+
setAirPurifierState(state: AirPurifierState): Promise<void>;
1208+
}
1209+
1210+
export interface FilterMaintenance {
1211+
filterLifeLevel?: number,
1212+
filterChangeIndication?: boolean,
1213+
}
1214+
11841215
export interface PM10Sensor {
11851216
pm10Density?: number;
11861217
}
@@ -1950,6 +1981,8 @@ export enum ScryptedInterface {
19501981
NOXSensor = "NOXSensor",
19511982
CO2Sensor = "CO2Sensor",
19521983
AirQualitySensor = "AirQualitySensor",
1984+
AirPurifier = "AirPurifier",
1985+
FilterMaintenance = "FilterMaintenance",
19531986
Readme = "Readme",
19541987
OauthClient = "OauthClient",
19551988
MixinProvider = "MixinProvider",

0 commit comments

Comments
 (0)