added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
32
bundles/org.openhab.io.homekit/.classpath
Normal file
32
bundles/org.openhab.io.homekit/.classpath
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
23
bundles/org.openhab.io.homekit/.project
Normal file
23
bundles/org.openhab.io.homekit/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.io.homekit</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
50
bundles/org.openhab.io.homekit/NOTICE
Normal file
50
bundles/org.openhab.io.homekit/NOTICE
Normal file
@@ -0,0 +1,50 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
||||
|
||||
== Third-party Content
|
||||
|
||||
HAP-Java
|
||||
* License: MIT License
|
||||
* Project: https://github.com/beowulfe/HAP-Java
|
||||
* Source: https://github.com/beowulfe/HAP-Java
|
||||
|
||||
nimbus-srp
|
||||
* License: Apache 2.0 license
|
||||
* Project: http://connect2id.com/products/nimbus-srp
|
||||
* Source: http://connect2id.com/products/nimbus-srp
|
||||
|
||||
netty
|
||||
* License: Apache 2.0 license
|
||||
* Project: http://netty.io
|
||||
* Source: https://github.com/netty/netty
|
||||
|
||||
jsonp
|
||||
* License: GPLv2 or CDDL 1.1 license
|
||||
* Project: https://jsonp.java.net
|
||||
* Source: https://github.com/eclipse-ee4j/jsonp
|
||||
|
||||
ed25519-java
|
||||
* License: GPLv2 or CDDL 1.1 license
|
||||
* Project: https://github.com/str4d/ed25519-java
|
||||
* Source: https://github.com/str4d/ed25519-java
|
||||
|
||||
bc-java
|
||||
* License: MIT License
|
||||
* Project: https://www.bouncycastle.org
|
||||
* Source: https://github.com/bcgit/bc-java
|
||||
|
||||
curve25519-java
|
||||
* License: Apache 2.0 license
|
||||
* Project: https://code.google.com/archive/p/curve25519-java
|
||||
* Source: https://github.com/trevorbernard/curve25519-java
|
||||
684
bundles/org.openhab.io.homekit/README.md
Normal file
684
bundles/org.openhab.io.homekit/README.md
Normal file
@@ -0,0 +1,684 @@
|
||||
# HomeKit Add-on
|
||||
|
||||
This is an add-on that exposes your openHAB system as a bridge over the HomeKit protocol.
|
||||
|
||||
Using this add-on, you will be able to control your openHAB system using Apple's Siri, or any of a number of HomeKit enabled iOS apps.
|
||||
In order to do so, you will need to make some configuration changes.
|
||||
HomeKit organizes your home into "accessories" that are made up of a number of "characteristics".
|
||||
Some accessory types require a specific set of characteristics.
|
||||
|
||||
HomeKit integration supports following accessory types:
|
||||
- Switchable
|
||||
- Outlet
|
||||
- Lighting (simple, dimmable, color)
|
||||
- Fan
|
||||
- Thermostat
|
||||
- Heater / Cooler
|
||||
- Lock
|
||||
- Security System
|
||||
- Garage Door Opener
|
||||
- Motorized Door
|
||||
- Motorized Window
|
||||
- Window Covering/Blinds
|
||||
- Valve
|
||||
- Air Quality Sensor
|
||||
- Contact Sensor
|
||||
- Leak Sensor
|
||||
- Motion Sensor
|
||||
- Occupancy Sensor
|
||||
- Smoke Sensor
|
||||
- Temperature Sensor
|
||||
- Humidity Sensor
|
||||
- Light Sensor
|
||||
- Carbon Dioxide Sensor
|
||||
- Carbon Monoxide Sensor
|
||||
|
||||
**Attention: Some tags have been renamed. Old style may not be supported in future versions. See below for details.**
|
||||
|
||||
## Global Configuration
|
||||
|
||||
Your first step will be to create the `homekit.cfg` in your `$OPENHAB_CONF/services` folder.
|
||||
At the very least, you will need to define a pin number for the bridge.
|
||||
This will be used in iOS when pairing. The pin code is in the form "###-##-###".
|
||||
Requirements beyond this are not clear, and Apple enforces limitations on eligible pins within iOS.
|
||||
At the very least, you cannot use repeating (111-11-111) or sequential (123-45-678) pin codes.
|
||||
If your home network is secure, a good starting point is the pin code used in most sample applications: 031-45-154.
|
||||
Check for typos in the pin-code if you encounter "Bad Client Credential" errors during pairing.
|
||||
|
||||
Other settings, such as using Fahrenheit temperatures, customizing the thermostat heat/cool/auto modes, and specifying the interface to advertise the HomeKit bridge (which can be edited in Paper UI standard mode) are also illustrated in the following sample:
|
||||
|
||||
```
|
||||
org.openhab.homekit:port=9124
|
||||
org.openhab.homekit:pin=031-45-154
|
||||
org.openhab.homekit:useFahrenheitTemperature=true
|
||||
org.openhab.homekit:thermostatTargetModeCool=CoolOn
|
||||
org.openhab.homekit:thermostatTargetModeHeat=HeatOn
|
||||
org.openhab.homekit:thermostatTargetModeAuto=Auto
|
||||
org.openhab.homekit:thermostatTargetModeOff=Off
|
||||
org.openhab.homekit:networkInterface=192.168.0.6
|
||||
```
|
||||
|
||||
The following additional settings can be added or edited in Paper UI after switching to expert mode:
|
||||
|
||||
```
|
||||
org.openhab.homekit:name=openHAB
|
||||
org.openhab.homekit:minimumTemperature=-100
|
||||
org.openhab.homekit:maximumTemperature=100
|
||||
```
|
||||
|
||||
### Overview of all settings
|
||||
|
||||
| Setting | Description | Default value |
|
||||
|:-------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------|
|
||||
| networkInterface | IP address or domain name under which the HomeKit bridge can be reached. If no value is configured, the add-on uses the first network adapter address. | (none) |
|
||||
| port | Port under which the HomeKit bridge can be reached. | 9123 |
|
||||
| pin | Pin code used for pairing with iOS devices. Apparently, pin codes are provided by Apple and represent specific device types, so they cannot be chosen freely. The pin code 031-45-154 is used in sample applications and known to work. | 031-45-154 |
|
||||
| startDelay | HomeKit start delay in seconds in case the number of accessories is lower than last time. This helps to avoid resetting home app in case not all items have been initialised properly before HomeKit integration start. | 30 |
|
||||
| useFahrenheitTemperature | Set to true to use Fahrenheit degrees, or false to use Celsius degrees. | false |
|
||||
| thermostatTargetModeCool | Word used for activating the cooling mode of the device (if applicable). | CoolOn |
|
||||
| thermostatTargetModeHeat | Word used for activating the heating mode of the device (if applicable). | HeatOn |
|
||||
| thermostatTargetModeAuto | Word used for activating the automatic mode of the device (if applicable). | Auto |
|
||||
| thermostatTargetModeOff | Word used to set the thermostat mode of the device to off (if applicable). | Off |
|
||||
| minimumTemperature | Lower bound of possible temperatures, used in the user interface of the iOS device to display the allowed temperature range. Note that this setting applies to all devices in HomeKit. | -100 |
|
||||
| maximumTemperature | Upper bound of possible temperatures, used in the user interface of the iOS device to display the allowed temperature range. Note that this setting applies to all devices in HomeKit. | 100 |
|
||||
| name | Name under which this HomeKit bridge is announced on the network. This is also the name displayed on the iOS device when searching for available bridges. | openHAB |
|
||||
|
||||
## Item Configuration
|
||||
|
||||
After setting the global configuration, you will need to tag your [openHAB items](https://www.openhab.org/docs/configuration/items.html) for HomeKit with accessory type.
|
||||
For our purposes, you may consider HomeKit accessories to be of two types: simple and complex.
|
||||
|
||||
A simple accessory will be mapped to a single openHAB item, e.g. HomeKit lighting can represent an openHAB Switch, Dimmer, or Color item.
|
||||
A complex accessory will be made up of multiple openHAB items, e.g. HomeKit Thermostat can be composed of mode, and current & target temperature.
|
||||
Complex accessories require a tag on a Group Item indicating the accessory type, as well as tags on the items it composes.
|
||||
|
||||
A HomeKit accessory has mandatory and optional characteristics (listed below in the table).
|
||||
The mapping between openHAB items and HomeKit accessory and characteristics is done by means of tagging.
|
||||
You can tag openHAB items using:
|
||||
|
||||
- [tags](https://www.openhab.org/docs/configuration/items.html#tags) (deprecated)
|
||||
- [metadata](https://www.openhab.org/docs/concepts/items.html#item-metadata)
|
||||
|
||||
e.g.
|
||||
|
||||
```xtend
|
||||
Switch leaksensor_tag "Leak Sensor with Tag" [ "LeakSensor" ]
|
||||
Switch leaksensor_metadata "Leak Sensor" {homekit="LeakSensor"}
|
||||
```
|
||||
|
||||
The HomeKit integration currently supports both options. You can mix both options in the same configuration file.
|
||||
If an openHAB item has both, tags and metadata, then HomeKit integration will use only metadata and ignore tags.
|
||||
In general, the `tag` way is considered legacy and may be removed in future releases.
|
||||
|
||||
You can link one openHAB item to one or more HomeKit accessory, e.g.
|
||||
|
||||
```xtend
|
||||
Switch occupancy_and_motion_sensor "Occupancy and Motion Sensor Tag" {homekit="OccupancySensor,MotionSensor"}
|
||||
```
|
||||
|
||||
The tag can be:
|
||||
|
||||
- full qualified: i.e. with accessory type and characteristic, e.g. "LeakSensor.LeakDetectedState"
|
||||
- shorthand version: with only either accessory type or characteristic, e.g. "LeakSensor", "LeakDetectedState".
|
||||
|
||||
if shorthand version has only accessory type, then HomeKit will automatically link *all* mandatory characteristics of this accessory type to the openHAB item.
|
||||
e.g. HomeKit window covering has 3 mandatory characteristics: CurrentPosition, TargetPosition, PositionState.
|
||||
Following are equal configuration:
|
||||
|
||||
```xtend
|
||||
Rollershutter window_covering "Window Rollershutter" {homekit="WindowCovering"}
|
||||
Rollershutter window_covering "Window Rollershutter" {homekit="WindowCovering, WindowCovering.CurrentPosition, WindowCovering.TargetPosition, WindowCovering.PositionState"}
|
||||
```
|
||||
|
||||
If the shorthand version has only a characteristic then it must be a part of a group which has a HomeKit accessory type.
|
||||
You can use openHAB group to define complex accessories. The group item must indicate the HomeKit accessory type,
|
||||
e.g. LeakSensor definition using tags
|
||||
|
||||
```xtend
|
||||
Group gLegacy_leaksensor "Legacy Leak sensor Group" [ "LeakSensor" ]
|
||||
Switch legacy_leaksensor "Legacy Leak sensor" (gLegacy_Leaksensor) [ "LeakDetectedState" ]
|
||||
Switch legacy_leaksensor_battery "Legacy Leak sensor battery status" (gLegacy_Leaksensor) [ "homekit:BatteryLowStatus" ]
|
||||
```
|
||||
|
||||
using metadata
|
||||
|
||||
```xtend
|
||||
Group gLeakSensor "Leak Sensor Group" {homekit="LeakSensor"}
|
||||
Switch leaksensor "Leak Sensor" (gLeakSensor) {homekit="LeakSensor.LeakDetectedState"}
|
||||
Switch leaksensor_battery "Leak Sensor Battery" (gLeakSensor) {homekit="LeakSensor.BatteryLowStatus"}
|
||||
```
|
||||
|
||||
|
||||
You can use openHAB group to manage state of multiple items. (see [Group items](https://www.openhab.org/docs/configuration/items.html#derive-group-state-from-member-items))
|
||||
In this case, you can assign HomeKit accessory type to the group and to the group items
|
||||
Following example defines 3 HomeKit accessories of type Lighting:
|
||||
|
||||
- "Light 1" and "Light 2" as independent lights
|
||||
- "Light Group" that controls "Light 1" and "Light 2" as group
|
||||
|
||||
```xtend
|
||||
Group:Switch:OR(ON,OFF) gLight "Light Group" {homekit="Lighting"}
|
||||
Switch light1 "Light 1" (gLight) {homekit="Lighting.OnState"}
|
||||
Switch light2 "Light 2" (gLight) {homekit="Lighting.OnState"}
|
||||
```
|
||||
|
||||
## Supported accessory type
|
||||
|
||||
| Accessory Tag | Mandatory Characteristics | Optional Characteristics | Supported OH items | Description |
|
||||
|:---------------------|:----------------------------|:-----------------------------|:-------------------------|:-----------------------------------------------------------------|
|
||||
| AirQualitySensor | | | | Air Quality Sensor which can measure different parameters |
|
||||
| | AirQuality | | String | Air quality state, possible values (UNKNOWN,EXCELLENT,GOOD,FAIR,INFERIOR,POOR). Custom mapping can be defined at item level, e.g. [EXCELLENT="BEST", POOR="BAD"] |
|
||||
| | | OzoneDensity | Number | Ozone density in micrograms/m3, max 1000 |
|
||||
| | | NitrogenDioxideDensity | Number | NO2 density in micrograms/m3, max 1000 |
|
||||
| | | SulphurDioxideDensity | Number | SO2 density in micrograms/m3, max 1000 |
|
||||
| | | PM25Density | Number | PM2.5 micrometer particulate density in micrograms/m3, max 1000 |
|
||||
| | | PM10Density | Number | PM10 micrometer particulate density in micrograms/m3, max 1000 |
|
||||
| | | VOCDensity | Number | VOC Density in micrograms/m3, max 1000 |
|
||||
| | | Name | String | Name of the sensor |
|
||||
| | | ActiveStatus | Switch, Contact | Working status |
|
||||
| | | FaultStatus | Switch, Contact | Fault status |
|
||||
| | | TamperedStatus | Switch, Contact | Tampered status |
|
||||
| | | BatteryLowStatus | Switch, Contact | Battery status |
|
||||
| LeakSensor | | | | Leak Sensor |
|
||||
| | LeakDetectedState | | Switch, Contact | Leak sensor state (ON=Leak Detected, OFF=no leak) |
|
||||
| | | Name | String | Name of the sensor |
|
||||
| | | ActiveStatus | Switch, Contact | Working status |
|
||||
| | | FaultStatus | Switch, Contact | Fault status |
|
||||
| | | TamperedStatus | Switch, Contact | Tampered status |
|
||||
| | | BatteryLowStatus | Switch, Contact | Battery status |
|
||||
| MotionSensor | | | | Motion Sensor |
|
||||
| | MotionDetectedState | | Switch, Contact | Motion sensor state (ON=motion detected, OFF=no motion) |
|
||||
| | | Name | String | Name of the sensor |
|
||||
| | | ActiveStatus | Switch, Contact | Working status |
|
||||
| | | FaultStatus | Switch, Contact | Fault status |
|
||||
| | | TamperedStatus | Switch, Contact | Tampered status |
|
||||
| | | BatteryLowStatus | Switch, Contact | Battery status |
|
||||
| OccupancySensor | | | | Occupancy Sensor |
|
||||
| | OccupancyDetectedState | | Switch, Contact | Occupancy sensor state (ON=occupied, OFF=not occupied) |
|
||||
| | | Name | String | Name of the sensor |
|
||||
| | | ActiveStatus | Switch, Contact | Working status |
|
||||
| | | FaultStatus | Switch, Contact | Fault status |
|
||||
| | | TamperedStatus | Switch, Contact | Tampered status |
|
||||
| | | BatteryLowStatus | Switch, Contact | Battery status |
|
||||
| ContactSensor | | | | Contact Sensor,An accessory with on/off state that can be viewed in HomeKit but not changed such as a contact sensor for a door or window |
|
||||
| | ContactSensorState | | Switch, Contact | Contact sensor state (ON=open, OFF=closed) |
|
||||
| | | Name | String | Name of the sensor |
|
||||
| | | ActiveStatus | Switch, Contact | Working status |
|
||||
| | | FaultStatus | Switch, Contact | Fault status |
|
||||
| | | TamperedStatus | Switch, Contact | Tampered status |
|
||||
| | | BatteryLowStatus | Switch, Contact | Battery status |
|
||||
| SmokeSensor | | | | Smoke Sensor |
|
||||
| | SmokeDetectedState | | Switch, Contact | Smoke sensor state (ON=smoke detected, OFF=no smoke) |
|
||||
| | | Name | String | Name of the sensor |
|
||||
| | | ActiveStatus | Switch, Contact | Working status |
|
||||
| | | FaultStatus | Switch, Contact | Fault status |
|
||||
| | | TamperedStatus | Switch, Contact | Tampered status |
|
||||
| | | BatteryLowStatus | Switch, Contact | Battery status |
|
||||
| LightSensor | | | | Light sensor |
|
||||
| | LightLevel | | Number | Light level in lux |
|
||||
| | | Name | String | Name of the sensor |
|
||||
| | | ActiveStatus | Switch, Contact | Working status |
|
||||
| | | FaultStatus | Switch, Contact | Fault status |
|
||||
| | | TamperedStatus | Switch, Contact | Tampered status |
|
||||
| | | BatteryLowStatus | Switch, Contact | Battery status |
|
||||
| HumiditySensor | | | | Relative Humidity Sensor providing read-only values |
|
||||
| | RelativeHumidity | | Number | Relative humidity in % between 0 and 100. additional configuration homekitMultiplicator = <number to multiply result with>. |
|
||||
| | | Name | String | Name of the sensor |
|
||||
| | | ActiveStatus | Switch, Contact | Working status |
|
||||
| | | FaultStatus | Switch, Contact | Fault status |
|
||||
| | | TamperedStatus | Switch, Contact | Tampered status |
|
||||
| | | BatteryLowStatus | Switch, Contact | Battery status |
|
||||
| TemperatureSensor | | | | Temperature sensor |
|
||||
| | CurrentTemperature | | Number | current temperature. supported configuration: minValue, maxValue, step. |
|
||||
| | | Name | String | Name of the sensor |
|
||||
| | | ActiveStatus | Switch, Contact | Working status |
|
||||
| | | FaultStatus | Switch, Contact | Fault status |
|
||||
| | | TamperedStatus | Switch, Contact | Tampered status |
|
||||
| | | BatteryLowStatus | Switch, Contact | Battery status |
|
||||
| CarbonDioxideSensor | | | | Carbon Dioxide Sensor |
|
||||
| | CarbonDioxideDetectedState | | Switch, Contact | carbon dioxide sensor state (ON- abnormal level of carbon dioxide detected, OFF - level is normal) |
|
||||
| | | CarbonDioxideLevel | Number | Carbon dioxide level in ppm, max 100000 |
|
||||
| | | CarbonDioxidePeakLevel | Number | highest detected level (ppm) of carbon dioxide detected by a sensor, max 100000 |
|
||||
| | | Name | String | Name of the sensor |
|
||||
| | | ActiveStatus | Switch, Contact | Working status |
|
||||
| | | FaultStatus | Switch, Contact | Fault status |
|
||||
| | | TamperedStatus | Switch, Contact | Tampered status |
|
||||
| | | BatteryLowStatus | Switch, Contact | Battery status |
|
||||
| CarbonMonoxideSensor | | | | Carbon monoxide Sensor |
|
||||
| | CarbonMonoxideDetectedState | | Switch, Contact | Carbon monoxide sensor state (ON- abnormal level of carbon monoxide detected, OFF - level is normal) |
|
||||
| | | CarbonMonoxideLevel | Number | Carbon monoxide level in ppm, max 100 |
|
||||
| | | CarbonMonoxidePeakLevel | Number | highest detected level (ppm) of carbon monoxide detected by a sensor, max 100 |
|
||||
| | | Name | String | Name of the sensor |
|
||||
| | | ActiveStatus | Switch, Contact | Working status |
|
||||
| | | FaultStatus | Switch, Contact | Fault status |
|
||||
| | | TamperedStatus | Switch, Contact | Tampered status |
|
||||
| | | BatteryLowStatus | Switch, Contact | Battery status |
|
||||
| Door | | | | Motorized door. One Rollershutter item covers all mandatory characteristics. see examples below. |
|
||||
| | CurrentPosition | | Rollershutter | Current position of motorized door |
|
||||
| | TargetPosition | | Rollershutter | Target position of motorized door |
|
||||
| | PositionState | | Rollershutter | Position state. Supported states: DECREASING, INCREASING, STOPPED. Mapping can be redefined at item level, e.g. [DECREASING="Down", INCREASING="Up"]. If no state provided, "STOPPED" is used. |
|
||||
| | | Name | String | Name of the motorized door |
|
||||
| | | HoldPosition | Switch | Motorized door should stop at its current position. A value of ON must hold the state of the accessory. A value of OFF should be ignored. |
|
||||
| | | ObstructionStatus | Switch, Contact | Current status of obstruction sensor. ON-obstruction detected, OFF - no obstruction |
|
||||
| Window | | | | Motorized window. One Rollershutter item covers all mandatory characteristics. see examples below. |
|
||||
| | CurrentPosition | | Rollershutter | Current position of motorized window |
|
||||
| | TargetPosition | | Rollershutter | Target position of motorized window |
|
||||
| | PositionState | | Rollershutter | Position state. Supported states: DECREASING, INCREASING, STOPPED. Mapping can be redefined at item level, e.g. [DECREASING="Down", INCREASING="Up"]. If no state provided, "STOPPED" is used. |
|
||||
| | | Name | String | Name of the motorized window |
|
||||
| | | HoldPosition | Switch | Motorized door should stop at its current position. A value of ON must hold the state of the accessory. A value of OFF should be ignored. |
|
||||
| | | ObstructionStatus | Switch, Contact | Current status of obstruction sensor. ON-obstruction detected, OFF - no obstruction |
|
||||
| WindowCovering | | | | Window covering / blinds. One Rollershutter item covers all mandatory characteristics. see examples below. |
|
||||
| | CurrentPosition | | Rollershutter | Current position of window covering |
|
||||
| | TargetPosition | | Rollershutter | Target position of window covering |
|
||||
| | PositionState | | Rollershutter | current only "STOPPED" is supported. |
|
||||
| | | Name | String | Name of the windows covering |
|
||||
| | | HoldPosition | Switch | Window covering should stop at its current position. A value of ON must hold the state of the accessory. A value of OFF should be ignored. |
|
||||
| | | ObstructionStatus | Switch, Contact | Current status of obstruction sensor. ON-obstruction detected, OFF - no obstruction |
|
||||
| | | CurrentHorizontalTiltAngle | Number | current angle of horizontal slats for accessories windows. values -90 to 90. A value of 0 indicates that the slats are rotated to a fully open position. A value of -90 indicates that the slats are rotated all the way in a direction where the user-facing edge is higher than the window-facing edge. |
|
||||
| | | TargetHorizontalTiltAngle | Number | target angle of horizontal slats |
|
||||
| | | CurrentVerticalTiltAngle | Number | current angle of vertical slats |
|
||||
| | | TargetVerticalTiltAngle | Number | target angle of vertical slats |
|
||||
| Switchable | | | | An accessory that can be turned off and on. While similar to a lightbulb, this will be presented differently in the Siri grammar and iOS apps |
|
||||
| | OnState | | Switch | State of the switch - ON/OFF |
|
||||
| | | Name | String | Name of the switch |
|
||||
| Outlet | | | | An accessory that can be turned off and on. While similar to a lightbulb, this will be presented differently in the Siri grammar and iOS apps |
|
||||
| | OnState | | Switch | State of the outlet - ON/OFF |
|
||||
| | InUseStatus | | Switch | indicates whether current flowing through the outlet |
|
||||
| | | Name | String | Name of the switch |
|
||||
| Lighting | | | | A lightbulb, can have further optional parameters for brightness, hue, etc |
|
||||
| | OnState | | Switch | State of the light - ON/OFF |
|
||||
| | | Name | String | Name of the light |
|
||||
| | | Hue | Dimmer, Color | Hue |
|
||||
| | | Saturation | Dimmer, Color | Saturation in % (1-100) |
|
||||
| | | Brightness | Dimmer, Color | Brightness in % (1-100). See "Usage of dimmer modes" for configuration details. |
|
||||
| | | ColorTemperature | Number | Color temperature which is represented in reciprocal megaKelvin, values - 50 to 400. should not be used in combination with hue, saturation and brightness |
|
||||
| Fan | | | | Fan |
|
||||
| | ActiveStatus | | Switch | accessory current working status. A value of "ON"/"OPEN" indicates that the accessory is active and is functioning without any errors. |
|
||||
| | | CurrentFanState | Number | current fan state. values: 0=INACTIVE, 1=IDLE, 2=BLOWING AIR |
|
||||
| | | TargetFanState | Number | target fan state. values: 0=MANUAL, 1=AUTO |
|
||||
| | | RotationDirection | Number, Switch | rotation direction. values: 0/OFF=CLOCKWISE, 1/ON=COUNTER CLOCKWISE |
|
||||
| | | RotationSpeed | Number, Dimmer | fan rotation speed in % (1-100) |
|
||||
| | | SwingMode | Number, Switch | swing mode. values: 0/OFF=SWING DISABLED, 1/ON=SWING ENABLED |
|
||||
| | | LockControl | Number, Switch | status of physical control lock. values: 0/OFF=CONTROL LOCK DISABLED, 1/ON=CONTROL LOCK ENABLED |
|
||||
| Thermostat | | | | A thermostat requires all mandatory characteristics defined below |
|
||||
| | CurrentTemperature | | Number | current temperature. supported configuration: minValue, maxValue, step |
|
||||
| | TargetTemperature | | Number | target temperature. supported configuration: minValue, maxValue, step |
|
||||
| | CurrentHeatingCoolingMode | | String | Current heating cooling mode (OFF, AUTO, HEAT, COOL). for mapping see homekit settings above. |
|
||||
| | TargetHeatingCoolingMode | | String | Target heating cooling mode (OFF, AUTO, HEAT, COOL). for mapping see homekit settings above. |
|
||||
| | | Name | String | Name of the thermostat |
|
||||
| | | CoolingThresholdTemperature | Number | maximum temperature that must be reached before cooling is turned on. min/max/step can configured at item level, e.g. minValue=10.5, maxValue=50, step=2] |
|
||||
| | | HeatingThresholdTemperature | Number | minimum temperature that must be reached before heating is turned on. min/max/step can configured at item level, e.g. minValue=10.5, maxValue=50, step=2] |
|
||||
| HeaterCooler | | | | Heater or/and cooler device |
|
||||
| | ActiveStatus | | Switch | accessory current working status. A value of "ON"/"OPEN" indicates that the accessory is active and is functioning without any errors. |
|
||||
| | CurrentTemperature | | Number | current temperature. supported configuration: minValue, maxValue, step |
|
||||
| | CurrentHeaterCoolerState | | String | current heater/cooler mode (INACTIVE, IDLE, HEATING, COOLING). Mapping can be redefined at item level, e.g. [HEATING="HEAT", COOLING="COOL"] |
|
||||
| | TargetHeaterCoolerState | | String | target heater/cooler mode (AUTO, HEAT, COOL). Mapping can be redefined at item level, e.g. [AUTO="AUTOMATIC"] |
|
||||
| | | Name | String | Name of the heater/cooler |
|
||||
| | | RotationSpeed | Number | fan rotation speed in % (1-100) |
|
||||
| | | SwingMode | Number, Switch | swing mode. values: 0/OFF=SWING DISABLED, 1/ON=SWING ENABLED |
|
||||
| | | LockControl | Number, Switch | status of physical control lock. values: 0/OFF=CONTROL LOCK DISABLED, 1/ON=CONTROL LOCK ENABLED |
|
||||
| | | CoolingThresholdTemperature | Number | maximum temperature that must be reached before cooling is turned on. min/max/step can configured at item level, e.g. minValue=10.5, maxValue=50, step=2] |
|
||||
| | | HeatingThresholdTemperature | Number | minimum temperature that must be reached before heating is turned on. min/max/step can configured at item level, e.g. minValue=10.5, maxValue=50, step=2] |
|
||||
| Lock | | | | A Lock Mechanism |
|
||||
| | LockCurrentState | | Switch, Number | current state of lock mechanism (1/ON=SECURED, 0/OFF=UNSECURED, 2=JAMMED, 3=UNKNOWN) |
|
||||
| | LockTargetState | | Switch | target state of lock mechanism (ON=SECURED, OFF=UNSECURED) |
|
||||
| | | Name | String | Name of the lock |
|
||||
| Valve | | | | Valve. additional configuration: homekitValveType = ["Generic", "Irrigation", "Shower", "Faucet"] |
|
||||
| | ActiveStatus | | Switch | accessory current working status. A value of "ON"/"OPEN" indicates that the accessory is active and is functioning without any errors. |
|
||||
| | InUseStatus | | Switch | indicates whether fluid flowing through the valve. A value of "ON"/"OPEN" indicates that fluid is flowing. |
|
||||
| | | Duration | Number | defines how long a valve should be set to ʼIn Useʼ in second. You can define the default duration via configuration homekitDefaultDuration = <default duration in seconds> |
|
||||
| | | RemainingDuration | Number | describes the remaining duration on the accessory. the remaining duration increases/decreases from the accessoryʼs usual countdown. i.e. changes from 90 to 80 in a second. |
|
||||
| | | Name | String | Name of the lock |
|
||||
| | | FaultStatus | Switch, Contact | accessory fault status. "ON"/"OPEN" value indicates that the accessory has experienced a fault that may be interfering with its intended functionality. A value of "OFF"/"CLOSED" indicates that there is no fault. |
|
||||
| SecuritySystem | | | | Security system. |
|
||||
| | CurrentSecuritySystemState | | String | Current state of the security system. STAY_ARM / AWAY_ARM / NIGHT_ARM / DISARMED / TRIGGERED. Mapping can be redefined at item level, e.g. [AWAY_ARM="AWAY", NIGHT_ARM="NIGHT" ] |
|
||||
| | TargetSecuritySystemState | | String | Requested state of the security system. STAY_ARM / AWAY_ARM / NIGHT_ARM / DISARM. While the requested state is not DISARM, and the current state is DISARMED, HomeKit will display "Arming...", for example during an exit delay. Mapping can be redefined at item level, e.g. [AWAY_ARM="AWAY", NIGHT_ARM="NIGHT" ] |
|
||||
| | | Name | String | Name of the security system |
|
||||
| | | FaultStatus | Switch, Contact | accessory fault status. "ON"/"OPEN" value indicates that the accessory has experienced a fault that may be interfering with its intended functionality. A value of "OFF"/"CLOSED" indicates that there is no fault. |
|
||||
| | | TamperedStatus | Switch, Contact | accessory tampered status. A status of "ON"/"OPEN" indicates that the accessory has been tampered with. Value should return to "OFF"/"CLOSED" when the accessory has been reset to a non-tampered state. |
|
||||
| GarageDoorOpener | | | | A garage door opener. |
|
||||
| | ObstructionStatus | | Switch | Current status of obstruction sensor. ON-obstruction detected, OFF - no obstruction |
|
||||
| | CurrentDoorState | | String | Current door state. Possible values: OPEN, OPENING, CLOSED, CLOSING, STOPPED |
|
||||
| | TargetDoorState | | Switch, String | Target door state. ON/"OPEN" = open door, OFF/"CLOSED" = closed door |
|
||||
| | | Name | String | Name of the garage door |
|
||||
| | | LockCurrentState | Switch | current states of lock mechanism (OFF=SECURED, ON=UNSECURED) |
|
||||
| | | LockTargetState | Switch | target states of lock mechanism (OFF=SECURED, ON=UNSECURED) |
|
||||
|
||||
### Legacy tags
|
||||
|
||||
Following tags are still supported but could be removed in the future releases. Please consider replacing them with the new style.
|
||||
|
||||
| Old (tag style) | New (metadata style) |
|
||||
|:---------------------------------|:------------------------------------------------|
|
||||
| homekit:HeatingCoolingMode | CurrentHeatingCoolingMode |
|
||||
| homekit:TargetHeatingCoolingMode | TargetHeatingCoolingMode |
|
||||
| homekit:TargetTemperature | TargetTemperature |
|
||||
| homekit:BatteryLowStatus | BatteryLowStatus |
|
||||
| homekit:BatteryLevel | mapping to BatteryLowStatus |
|
||||
| CurrentHumidity | RelativeHumidity |
|
||||
| Blinds | WindowCovering |
|
||||
| DimmableLighting | Lighting with characteristic Brightness |
|
||||
| ColorfulLighting | Lighting with characteristic Brightness and Hue |
|
||||
|
||||
|
||||
### Examples
|
||||
|
||||
See the sample below for example items:
|
||||
|
||||
#### Using "tag"
|
||||
|
||||
```xtend
|
||||
Color legacy_color_light_single "Legacy Color Light Single" [ "Lighting" ]
|
||||
Color legacy_color_light_dimmable "Legacy Color Light Dimmable" [ "DimmableLighting" ]
|
||||
Color legacy_color_light_hue "Legacy Color Light Hue" [ "ColorfulLighting" ]
|
||||
|
||||
Rollershutter legacy_window_covering "Legacy Window Rollershutter" [ "WindowCovering" ]
|
||||
Switch legacy_switch_single "Legacy Switch single" [ "Switchable" ]
|
||||
Switch legacy_contactsensor_single "Legacy Contact Sensor single" [ "ContactSensor" ]
|
||||
Switch legacy_leaksensor_single "Legacy Leak Sensor single" [ "LeakSensor" ]
|
||||
Switch legacy_leaksensor_single2 "Legacy Leak Sensor single 2" [ "LeakSensor", "LeakSensor.LeakDetectedState" ]
|
||||
Switch legacy_motionsensor_single "Legacy Motion Sensor" [ "MotionSensor" ]
|
||||
Switch legacy_occupancy_single "Legacy Occupanncy Sensor" [ "OccupancySensor" ]
|
||||
Switch legacy_smoke_single "Legacy Smoke Sensor" [ "SmokeSensor" ]
|
||||
Number legacy_humidity_single "Legacy Humidity Sensor" [ "CurrentHumidity" ]
|
||||
Number legacy_temperature_sensor "Temperature Sensor" ["TemperatureSensor"]
|
||||
|
||||
Switch legacy_lock "Legacy Lock single" [ "Lock" ]
|
||||
|
||||
Switch legacy_valve_single "Legacy Valve Single" [ "Valve" ]
|
||||
|
||||
Group gLegacy_Valve "Legacy Valve Group" [ "Valve" ]
|
||||
Switch legacy_valve_active "Legacy Valve active" (gLegacy_Valve) [ "Active" ]
|
||||
Number legacy_valve_duration "Legacy Valve duration" (gLegacy_Valve) [ "Duration" ]
|
||||
Number legacy_valve_remaining_duration "Legacy Valve remaining duration" (gLegacy_Valve) [ "RemainingDuration" ]
|
||||
|
||||
Group gLegacy_Thermo "Legacy Thermostat" [ "Thermostat" ]
|
||||
Number legacy_thermostat_current_temp "L Therm. Cur. Temp. [%.1f C]" (gLegacy_Thermo) [ "CurrentTemperature" ]
|
||||
Number legacy_thermostat_target_temp "L Therm. Target Temp.[%.1f C]" (gLegacy_Thermo) [ "homekit:TargetTemperature" ]
|
||||
String legacy_thermostat_current_mode "Legacy Thermostat Current Mode" (gLegacy_Thermo) [ "homekit:CurrentHeatingCoolingMode" ]
|
||||
String legacy_thermostat_target_mode "Thermostat Target Mode" (gLegacy_Thermo) [ "homekit:TargetHeatingCoolingMode" ]
|
||||
|
||||
Group gLegacy_Leaksensor "Legacy Leak Sensor Group" [ "LeakSensor" ]
|
||||
Switch legacy_leaksensor "Legacy Leak Sensor" (gLegacy_Leaksensor) [ "LeakSensor" ]
|
||||
Switch legacy_leaksensor_bat "Legacy Leak sensor battery status" (gLegacy_Leaksensor) [ "homekit:BatteryLowStatus" ]
|
||||
Switch legacy_leaksensor_fault "Legacy Leak sensor fault" (gLegacy_Leaksensor) [ "FaultStatus" ]
|
||||
|
||||
Group gLegacy_Security "Legacy Security System Group" [ "SecuritySystem" ]
|
||||
String legacy_SecurityCurrentState "Security Current State" (gLegacy_Security) [ "CurrentSecuritySystemState" ]
|
||||
String legacy_SecurityTargetState "Security Target State" (gLegacy_Security) [ "TargetSecuritySystemState" ]
|
||||
```
|
||||
|
||||
#### Using "metadata"
|
||||
|
||||
```xtend
|
||||
Color color_light_single "Color Light Single" {homekit="Lighting"}
|
||||
Color color_light_dimmable "Legacy Color Light Dimmable" {homekit="Lighting, Lighting.Brightness"}
|
||||
Color color_light_hue "Legacy Color Light Hue" {homekit="Lighting, Lighting.Hue, Lighting.Brightness, Lighting.Saturation"}
|
||||
|
||||
Rollershutter window_covering "Window Rollershutter" {homekit="WindowCovering"}
|
||||
Rollershutter window_covering_long "Window Rollershutter long" {homekit="WindowCovering, WindowCovering.CurrentPosition, WindowCovering.TargetPosition, WindowCovering.PositionState"}
|
||||
|
||||
Switch leaksensor_single "Leak Sensor single" {homekit="LeakSensor"}
|
||||
Switch lock "Lock single" {homekit="Lock"}
|
||||
Switch valve_single "Valve single" {homekit="Valve" [homekitValveType="Shower"]}
|
||||
|
||||
Number temperature_sensor "Temperature Sensor [%.1f C]" {homekit="TemperatureSensor" [minValue=10.5, maxValue=27] }
|
||||
Number light_sensor "Light Sensor" {homekit="LightSensor"}
|
||||
|
||||
Group gValve "Valve Group" {homekit="Valve" [homekitValveType="Irrigation"]}
|
||||
Switch valve_active "Valve active" (gValve) {homekit="Valve.ActiveStatus, Valve.InUseStatus"}
|
||||
Number valve_duration "Valve duration" (gValve) {homekit="Valve.Duration"}
|
||||
Number valve_remaining_duration "Valve remaining duration" (gValve) {homekit="Valve.RemainingDuration"}
|
||||
|
||||
Group gThermostat "Thermostat" {homekit="Thermostat"}
|
||||
Number thermostat_current_temp "Thermostat Current Temp [%.1f C]" (gThermostat) {homekit="Thermostat.CurrentTemperature" [minValue=0, maxValue=40]}
|
||||
Number thermostat_target_temp "Thermostat Target Temp[%.1f C]" (gThermostat) {homekit="Thermostat.TargetTemperature" [minValue=10.5, maxValue=27]}
|
||||
String thermostat_current_mode "Thermostat Current Mode" (gThermostat) {homekit="Thermostat.CurrentHeatingCoolingMode"}
|
||||
String thermostat_target_mode "Thermostat Target Mode" (gThermostat) {homekit="Thermostat.TargetHeatingCoolingMode"}
|
||||
|
||||
Group gLeakSensor "Leak Sensor Group" {homekit="LeakSensor"}
|
||||
Switch leaksensor "Leak Sensor" (gLeakSensor) {homekit="LeakDetectedState"}
|
||||
String leaksensor_name "Leak Sensor Name" (gLeakSensor) {homekit="Name"}
|
||||
Switch leaksensor_bat "Leak Sensor Battery" (gLeakSensor) {homekit="BatteryLowStatus"}
|
||||
Switch leaksensor_active "Leak Sensor Active" (gLeakSensor) {homekit="ActiveStatus"}
|
||||
Switch leaksensor_fault "Leak Sensor Fault" (gLeakSensor) {homekit="FaultStatus"}
|
||||
Switch leaksensor_tampered "Leak Sensor Tampered" (gLeakSensor) {homekit="TamperedStatus"}
|
||||
|
||||
Group gMotionSensor "Motion Sensor Group" {homekit="MotionSensor"}
|
||||
Switch motionsensor "Motion Sensor" (gMotionSensor) {homekit="MotionSensor.MotionDetectedState"}
|
||||
Switch motionsensor_bat "Motion Sensor Battery" (gMotionSensor) {homekit="MotionSensor.BatteryLowStatus"}
|
||||
Switch motionsensor_active "Motion Sensor Active" (gMotionSensor) {homekit="MotionSensor.ActiveStatus"}
|
||||
Switch motionsensor_fault "Motion Sensor Fault" (gMotionSensor) {homekit="MotionSensor.FaultStatus"}
|
||||
Switch motionsensor_tampered "Motion Sensor Tampered" (gMotionSensor) {homekit="MotionSensor.TamperedStatus"}
|
||||
|
||||
Group gOccupancySensor "Occupancy Sensor Group" {homekit="OccupancySensor"}
|
||||
Switch occupancysensor "Occupancy Sensor" (gOccupancySensor) {homekit="OccupancyDetectedState"}
|
||||
Switch occupancysensor_bat "Occupancy Sensor Battery" (gOccupancySensor) {homekit="BatteryLowStatus"}
|
||||
Switch occupancysensor_active "Occupancy Sensor Active" (gOccupancySensor) {homekit="OccupancySensor.ActiveStatus"}
|
||||
Switch occupancysensor_fault "Occupancy Sensor Fault" (gOccupancySensor) {homekit="OccupancySensor.FaultStatus"}
|
||||
Switch occupancysensor_tampered "Occupancy Sensor Tampered" (gOccupancySensor) {homekit="OccupancySensor.TamperedStatus"}
|
||||
|
||||
Group gContactSensor "Contact Sensor Group" {homekit="ContactSensor"}
|
||||
Contact contactsensor "Contact Sensor" (gContactSensor) {homekit="ContactSensor.ContactSensorState"}
|
||||
Switch contactsensor_bat "Contact Sensor Battery" (gContactSensor) {homekit="ContactSensor.BatteryLowStatus"}
|
||||
Switch contactsensor_active "Contact Sensor Active" (gContactSensor) {homekit="ContactSensor.ActiveStatus"}
|
||||
Switch contactsensor_fault "Contact Sensor Fault" (gContactSensor) {homekit="ContactSensor.FaultStatus"}
|
||||
Switch contactsensor_tampered "Contact Sensor Tampered" (gContactSensor) {homekit="ContactSensor.TamperedStatus"}
|
||||
|
||||
Group gAirQualitySensor "Air Quality Sensor" {homekit="AirQualitySensor"}
|
||||
String airquality "Air Quality" (gAirQualitySensor) {homekit="AirQuality"}
|
||||
Number ozone "Ozone Density" (gAirQualitySensor) {homekit="OzoneDensity"}
|
||||
Number voc "VOC Density" (gAirQualitySensor) {homekit="VOCDensity"}
|
||||
Number nitrogen "Nitrogen Density" (gAirQualitySensor) {homekit="NitrogenDioxideDensity"}
|
||||
Number sulphur "Sulphur Density" (gAirQualitySensor) {homekit="SulphurDioxideDensity"}
|
||||
Number pm25 "PM25 Density" (gAirQualitySensor) {homekit="PM25Density"}
|
||||
Number pm10 "PM10 Density" (gAirQualitySensor) {homekit="PM10Density"}
|
||||
|
||||
Group gSecuritySystem "Security System Group" {homekit="SecuritySystem"}
|
||||
String security_current_state "Security Current State" (gSecuritySystem) {homekit="SecuritySystem.CurrentSecuritySystemState"}
|
||||
String security_target_state "Security Target State" (gSecuritySystem) {homekit="SecuritySystem.TargetSecuritySystemState"}
|
||||
|
||||
Group gCooler "Cooler Group" {homekit="HeaterCooler"}
|
||||
Switch cooler_active "Cooler Active" (gCooler) {homekit="ActiveStatus"}
|
||||
Number cooler_current_temp "Cooler Current Temp [%.1f C]" (gCooler) {homekit="CurrentTemperature"}
|
||||
String cooler_current_mode "Cooler Current Mode" (gCooler) {homekit="CurrentHeaterCoolerState" [HEATING="HEAT", COOLING="COOL"]}
|
||||
String cooler_target_mode "Cooler Target Mode" (gCooler) {homekit="TargetHeaterCoolerState"}
|
||||
Number cooler_cool_thrs "Cooler Cool Threshold Temp [%.1f C]" (gCooler) {homekit="CoolingThresholdTemperature" [minValue=10.5, maxValue=50]}
|
||||
Number cooler_heat_thrs "Cooler Heat Threshold Temp [%.1f C]" (gCooler) {homekit="HeatingThresholdTemperature" [minValue=0.5, maxValue=20]}
|
||||
```
|
||||
|
||||
## Accessory Configuration Details
|
||||
|
||||
### Dimmers
|
||||
|
||||
The way HomeKit handles dimmer devices can be different to the actual dimmers' way of working.
|
||||
HomeKit home app sends following commands/update:
|
||||
|
||||
- On brightness change home app sends "ON" event along with target brightness, e.g. "Brightness = 50%" + "State = ON".
|
||||
- On "ON" event home app sends "ON" along with brightness 100%, i.e. "Brightness = 100%" + "State = ON"
|
||||
- On "OFF" event home app sends "OFF" without brightness information.
|
||||
|
||||
However, some dimmer devices for example do not expect brightness on "ON" event, some others do not expect "ON" upon brightness change.
|
||||
In order to support different devices HomeKit integration can filter some events. Which events should be filtered is defined via dimmerMode configuration.
|
||||
|
||||
```xtend
|
||||
Dimmer dimmer_light "Dimmer Light" {homekit="Lighting, Lighting.Brightness" [dimmerMode="<mode>"]}
|
||||
```
|
||||
|
||||
Following modes are supported:
|
||||
|
||||
- "normal" - no filtering. The commands will be sent to device as received from HomeKit. This is default mode.
|
||||
- "filterOn" - ON events are filtered out. only OFF events and brightness information are sent
|
||||
- "filterBrightness100" - only Brightness=100% is filtered out. everything else sent unchanged. This allows custom logic for soft launch in devices.
|
||||
- "filterOnExceptBrightness100" - ON events are filtered out in all cases except of brightness = 100%.
|
||||
|
||||
Examples:
|
||||
|
||||
```xtend
|
||||
Dimmer dimmer_light_1 "Dimmer Light 1" {homekit="Lighting, Lighting.Brightness" [dimmerMode="filterOn"]}
|
||||
Dimmer dimmer_light_2 "Dimmer Light 2" {homekit="Lighting, Lighting.Brightness" [dimmerMode="filterBrightness100"]}
|
||||
Dimmer dimmer_light_3 "Dimmer Light 3" {homekit="Lighting, Lighting.Brightness" [dimmerMode="filterOnExceptBrightness100"]}
|
||||
```
|
||||
### Windows Covering (Blinds) / Window / Door
|
||||
|
||||
HomeKit Windows Covering, Window and Door accessory types have following mandatory characteristics:
|
||||
|
||||
- CurrentPosition (0-100% of current window covering position)
|
||||
- TargetPosition (0-100% of target position)
|
||||
- PositionState (DECREASING,INCREASING or STOPPED as state). If no state provided, HomeKit will send STOPPED
|
||||
|
||||
These characteristics can be mapped to a single openHAB rollershutter item. In such case currentPosition will always equal target position, means if you request to close a blind/window/door, HomeKit will immediately report that the blind/window/door is closed.
|
||||
As discussed above, one can use full or shorthand definition. Following two definitions are equal:
|
||||
|
||||
```xtend
|
||||
Rollershutter window "Window" {homekit = "Window"}
|
||||
Rollershutter door "Door" {homekit = "Door"}
|
||||
Rollershutter window_covering "Window Rollershutter" {homekit = "WindowCovering"}
|
||||
Rollershutter window_covering_long "Window Rollershutter long" {homekit = "WindowCovering, WindowCovering.CurrentPosition, WindowCovering.TargetPosition, WindowCovering.PositionState"}
|
||||
```
|
||||
|
||||
openHAB Rollershutter is defined by default as:
|
||||
|
||||
- OPEN if position is 0%,
|
||||
- CLOSED if position is 100%.
|
||||
|
||||
In contrast, HomeKit window covering/door/window have inverted mapping
|
||||
- OPEN if position 100%
|
||||
- CLOSED if position is 0%
|
||||
|
||||
Therefore, HomeKit integration inverts by default the values between openHAB and HomeKit, e.g. if openHAB current position is 30% then it will send 70% to HomeKit app.
|
||||
In case you need to disable this logic you can do it with configuration parameter inverted="false", e.g.
|
||||
|
||||
```xtend
|
||||
Rollershutter window_covering "Window Rollershutter" {homekit = "WindowCovering" [inverted="false"]}
|
||||
Rollershutter window "Window" {homekit = "Window" [inverted="false"]}
|
||||
Rollershutter door "Door" {homekit = "Door" [inverted="false"]}
|
||||
|
||||
```
|
||||
|
||||
Window covering can have a number of optional characteristics like horizontal & vertical tilt, obstruction status and hold position trigger.
|
||||
If your blind supports tilt, and you want to control tilt via HomeKit you need to define blind as a group.
|
||||
e.g.
|
||||
|
||||
```xtend
|
||||
Group gBlind "Blind with tilt" {homekit = "WindowCovering"}
|
||||
Rollershutter window_covering "Blind" (gBlind) {homekit = "WindowCovering"}
|
||||
Dimmer window_covering_htilt "Blind horizontal tilt" (gBlind) {homekit = "WindowCovering.CurrentHorizontalTiltAngle, WindowCovering.TargetHorizontalTiltAngle"}
|
||||
Dimmer window_covering_vtilt "Blind vertical tilt" (gBlind) {homekit = "WindowCovering.CurrentVerticalTiltAngle, WindowCovering.TargetVerticalTiltAngle"}
|
||||
```
|
||||
|
||||
### Valve
|
||||
|
||||
The HomeKit valve accessory supports following 2 optional characteristics:
|
||||
|
||||
- duration: this describes how long the valve should set "InUse" once it is activated. The duration changes will apply to the next operation. If valve is already active then duration changes have no effect.
|
||||
|
||||
- remaining duration: this describes the remaining duration on the valve. Notifications on this characteristic must only be used if the remaining duration increases/decreases from the accessoryʼs usual countdown of remaining duration.
|
||||
|
||||
Upon valve activation in home app, home app starts to count down from the "duration" to "0" without contacting the server. Home app also does not trigger any action if it remaining duration get 0.
|
||||
It is up to valve to have an own timer and stop valve once the timer is over.
|
||||
Some valves have such timer, e.g. pretty common for sprinklers.
|
||||
In case the valve has no timer capability, openHAB can take care on this - start an internal timer and send "Off" command to the valve once the timer is over.
|
||||
|
||||
configuration for these two cases looks as follow:
|
||||
|
||||
- valve with timer:
|
||||
|
||||
```xtend
|
||||
Group gValve "Valve Group" {homekit="Valve" [homekitValveType="Irrigation"]}
|
||||
Switch valve_active "Valve active" (gValve) {homekit = "Valve.ActiveStatus, Valve.InUseStatus"}
|
||||
Number valve_duration "Valve duration" (gValve) {homekit = "Valve.Duration"}
|
||||
Number valve_remaining_duration "Valve remaining duration" (gValve) {homekit = "Valve.RemainingDuration"}
|
||||
```
|
||||
|
||||
- valve without timer (no item for remaining duration required)
|
||||
|
||||
```xtend
|
||||
Group gValve "Valve Group" {homekit="Valve" [homekitValveType="Irrigation", homekitTimer="true]}
|
||||
Switch valve_active "Valve active" (gValve) {homekit = "Valve.ActiveStatus, Valve.InUseStatus"}
|
||||
Number valve_duration "Valve duration" (gValve) {homekit = "Valve.Duration" [homekitDefaultDuration = 1800]}
|
||||
```
|
||||
|
||||
### Sensors
|
||||
Sensors have typically one mandatory characteristic, e.g. temperature or lead trigger, and several optional characteristics which are typically used for battery powered sensors and/or wireless sensors.
|
||||
Following table summarizes the optional characteristics supported by sensors.
|
||||
|
||||
| Characteristics | Supported openHAB items | Description |
|
||||
|:-----------------------------|:-------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Name | String | Name of the sensor. This characteristic is interesting only for very specific cases in which the name of accessory is dynamic. if you not sure then you don't need it. |
|
||||
| ActiveStatus | Switch, Contact | Accessory current working status. "ON"/"OPEN" indicates that the accessory is active and is functioning without any errors. |
|
||||
| FaultStatus | Switch, Contact | Accessory fault status. "ON"/"OPEN" value indicates that the accessory has experienced a fault that may be interfering with its intended functionality. A value of "OFF"/"CLOSED" indicates that there is no fault. |
|
||||
| TamperedStatus | Switch, Contact | Accessory tampered status. "ON"/"OPEN" indicates that the accessory has been tampered. Value should return to "OFF"/"CLOSED" when the accessory has been reset to a non-tampered state. |
|
||||
| BatteryLowStatus | Switch, Contact | Accessory battery status. "ON"/"OPEN" indicates that the battery level of the accessory is low. Value should return to "OFF"/"CLOSED" when the battery charges to a level thats above the low threshold. |
|
||||
|
||||
Examples of sensor definitions.
|
||||
Sensors without optional characteristics:
|
||||
|
||||
```xtend
|
||||
Switch leaksensor_single "Leak Sensor" {homekit="LeakSensor"}
|
||||
Number light_sensor "Light Sensor" {homekit="LightSensor"}
|
||||
Number temperature_sensor "Temperature Sensor [%.1f C]" {homekit="TemperatureSensor"}
|
||||
Contact contact_sensor "Contact Sensor" {homekit="ContactSensor"}
|
||||
Switch occupancy_sensor "Occupancy Sensor" {homekit="OccupancyDetectedState"}
|
||||
Switch motion_sensor "Motion Sensor" {homekit="MotionSensor"}
|
||||
Number humidity_sensor "Humidity Sensor" {homekit="HumiditySensor"}
|
||||
```
|
||||
|
||||
Sensors with optional characteristics:
|
||||
|
||||
```xtend
|
||||
Group gLeakSensor "Leak Sensor" {homekit="LeakSensor"}
|
||||
Switch leaksensor "Leak Sensor State" (gLeakSensor) {homekit="LeakDetectedState"}
|
||||
Switch leaksensor_bat "Leak Sensor Battery" (gLeakSensor) {homekit="BatteryLowStatus"}
|
||||
Switch leaksensor_active "Leak Sensor Active" (gLeakSensor) {homekit="ActiveStatus"}
|
||||
Switch leaksensor_fault "Leak Sensor Fault" (gLeakSensor) {homekit="FaultStatus"}
|
||||
Switch leaksensor_tampered "Leak Sensor Tampered" (gLeakSensor) {homekit="TamperedStatus"}
|
||||
|
||||
Group gMotionSensor "Motion Sensor" {homekit="MotionSensor"}
|
||||
Switch motionsensor "Motion Sensor State" (gMotionSensor) {homekit="MotionDetectedState"}
|
||||
Switch motionsensor_bat "Motion Sensor Battery" (gMotionSensor) {homekit="BatteryLowStatus"}
|
||||
Switch motionsensor_active "Motion Sensor Active" (gMotionSensor) {homekit="ActiveStatus"}
|
||||
Switch motionsensor_fault "Motion Sensor Fault" (gMotionSensor) {homekit="FaultStatus"}
|
||||
Switch motionsensor_tampered "Motion Sensor Tampered" (gMotionSensor) {homekit="TamperedStatus"}
|
||||
```
|
||||
|
||||
## Common Problems
|
||||
|
||||
**openHAB HomeKit hub shows up when I manually scan for devices, but Home app reports "can't connect to device"**
|
||||
|
||||
If you see this error in the Home app, and don't see any log messages, it could be because your IP address in the `networkInterface` setting is misconfigured.
|
||||
The openHAB HomeKit hub is advertised via mDNS.
|
||||
If you register an IP address that isn't reachable from your phone (such as `localhost`, `0.0.0.0`, `127.0.0.1`, etc.), then Home will be unable to reach openHAB.
|
||||
|
||||
## Additional Notes
|
||||
|
||||
HomeKit allows only a single pairing to be established with the bridge.
|
||||
This pairing is normally shared across devices via iCloud.
|
||||
If you need to establish a new pairing, you will need to clear the existing pairings.
|
||||
To do this, you can issue the command `smarthome:homekit clearPairings` from the [OSGi console](https://www.openhab.org/docs/administration/console.html).
|
||||
After doing this, you may need to remove the file `$OPENHAB_USERDATA/jsondb/homekit.json` and restart openHAB.
|
||||
|
||||
HomeKit requires a unique identifier for each accessory advertised by the bridge.
|
||||
This unique identifier is hashed from the Item's name.
|
||||
For that reason, it is important that the name of your Items exposed to HomeKit remain consistent.
|
||||
|
||||
HomeKit listens by default on port 9124.
|
||||
Java prefers the IPv6 network stack by default.
|
||||
If you have connection or detection problems, you can configure Java to prefer the IPv4 network stack instead.
|
||||
To prefer the IPv4 network stack, adapt the Java command line arguments to include: `-Djava.net.preferIPv4Stack=true`
|
||||
Depending on the openHAB installation method, you should modify `start.sh`, `start_debug.sh`, `start.bat`, or `start_debug.bat` (standalone/manual installation) or `EXTRA_JAVA_OPTS` in `/etc/default/openhab2` (Debian installation).
|
||||
|
||||
If you encounter any issues with the add-on and need support, it may be important to get detailed logs of your device's communication with openHAB.
|
||||
In order to get logs from the underlying library used to implement the HomeKit protocol, enable trace logging using the following commands at [the console](https://www.openhab.org/docs/administration/console.html):
|
||||
|
||||
```
|
||||
openhab> log:set TRACE io.github.hapjava
|
||||
openhab> log:tail io.github.hapjava
|
||||
```
|
||||
|
||||
## Console commands
|
||||
|
||||
`smarthome:homekit list` - list all HomeKit accessories currently advertised to the HomeKit clients.
|
||||
|
||||
`smarthome:homekit show <accessory_id | name>` - print additional details of the accessories which partially match provided ID or name.
|
||||
|
||||
108
bundles/org.openhab.io.homekit/pom.xml
Normal file
108
bundles/org.openhab.io.homekit/pom.xml
Normal file
@@ -0,0 +1,108 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.io.homekit</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: IO :: Homekit</name>
|
||||
|
||||
<properties>
|
||||
<dep.noembedding>javax.json,javax.json-api,netty-common,netty-buffer,netty-transport,netty-handler,netty-codec,netty-codec-http,netty-resolver</dep.noembedding>
|
||||
<netty.version>4.1.42.Final</netty.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.github.j-n-k</groupId>
|
||||
<artifactId>hap-java</artifactId>
|
||||
<version>2.0.0.OH2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish</groupId>
|
||||
<artifactId>javax.json</artifactId>
|
||||
<version>1.0.4</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.json</groupId>
|
||||
<artifactId>javax.json-api</artifactId>
|
||||
<version>1.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-common</artifactId>
|
||||
<version>${netty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-buffer</artifactId>
|
||||
<version>${netty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-transport</artifactId>
|
||||
<version>${netty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-handler</artifactId>
|
||||
<version>${netty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-codec</artifactId>
|
||||
<version>${netty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-codec-http</artifactId>
|
||||
<version>${netty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-resolver</artifactId>
|
||||
<version>${netty.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.nimbusds</groupId>
|
||||
<artifactId>srp6a</artifactId>
|
||||
<version>1.5.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.vrallev.ecc</groupId>
|
||||
<artifactId>ecc-25519-java</artifactId>
|
||||
<version>1.0.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<version>1.51</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.zeromq</groupId>
|
||||
<artifactId>curve25519-java</artifactId>
|
||||
<version>0.1.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
13
bundles/org.openhab.io.homekit/src/main/feature/feature.xml
Normal file
13
bundles/org.openhab.io.homekit/src/main/feature/feature.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.io.homekit-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-misc-homekit" description="HomeKit Integration" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature>openhab-transport-mdns</feature>
|
||||
<feature>openhab.tp-netty</feature>
|
||||
<bundle dependency="true">mvn:org.glassfish/javax.json/1.0.4</bundle>
|
||||
<bundle dependency="true">mvn:javax.json/javax.json-api/1.0</bundle>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.io.homekit/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import io.github.hapjava.accessories.HomekitAccessory;
|
||||
|
||||
/**
|
||||
* HomeKit integration API
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface Homekit {
|
||||
|
||||
/**
|
||||
* Refreshes the saved authentication info from the underlying storage service. If you
|
||||
* make changes to the saved authentication info, call this.
|
||||
*
|
||||
* @throws IOException exception in case new auth info could not be published via mDNS
|
||||
*/
|
||||
void refreshAuthInfo() throws IOException;
|
||||
|
||||
/**
|
||||
* HomeKit requests normally require authentication via the pairing mechanism. Use this
|
||||
* method to bypass that check and enable unauthenticated requests. This can be useful
|
||||
* when debugging.
|
||||
*
|
||||
* @param allow boolean indicating whether or not to allow unauthenticated requests
|
||||
*/
|
||||
void allowUnauthenticatedRequests(boolean allow);
|
||||
|
||||
/**
|
||||
* returns list of HomeKit accessories registered at bridge.
|
||||
*/
|
||||
List<HomekitAccessory> getAccessories();
|
||||
|
||||
/**
|
||||
* clear all pairings with HomeKit clients
|
||||
*/
|
||||
void clearHomekitPairings();
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Highly performant generic debouncer
|
||||
*
|
||||
* @author Tim Harper - Initial contribution
|
||||
*
|
||||
*/
|
||||
class Debouncer {
|
||||
|
||||
private final Clock clock;
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final Long delayMs;
|
||||
private final Runnable action;
|
||||
private final AtomicBoolean pending = new AtomicBoolean(false);
|
||||
private final AtomicInteger calls = new AtomicInteger(0);
|
||||
private final String name;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(Debouncer.class);
|
||||
|
||||
private volatile Long lastCallAttempt;
|
||||
private ScheduledFuture<?> feature;
|
||||
|
||||
/**
|
||||
* Highly performant generic debouncer
|
||||
*
|
||||
* Note: Debounced calls are filtered synchronously, in the caller thread, without the need for locks, context
|
||||
* switches, or heap allocations. We use AtomicBoolean to resolve concurrent races; the probability of
|
||||
* contending on an AtomicBoolean transition is very low.
|
||||
*
|
||||
* @param name The name of this debouncer
|
||||
* @param scheduler The scheduler implementation to use
|
||||
* @param delay The time after which to invoke action; each time [[Debouncer.call]] is invoked, this delay is
|
||||
* reset
|
||||
* @param clock The source from which we get the current time. This input should use the same source. Specified
|
||||
* for testing purposes
|
||||
* @param action The action to invoke
|
||||
*/
|
||||
Debouncer(String name, ScheduledExecutorService scheduler, Duration delay, Clock clock, Runnable action) {
|
||||
this.name = name;
|
||||
this.scheduler = scheduler;
|
||||
this.action = action;
|
||||
|
||||
this.delayMs = delay.toMillis();
|
||||
this.clock = clock;
|
||||
this.lastCallAttempt = clock.millis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register that the provided action should be called according to the debounce logic
|
||||
*/
|
||||
void call() {
|
||||
lastCallAttempt = clock.millis();
|
||||
calls.incrementAndGet();
|
||||
if (pending.compareAndSet(false, true)) {
|
||||
feature = scheduler.schedule(this::tryActionOrPostpone, delayMs, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
logger.trace("stop debouncer");
|
||||
if (feature != null) {
|
||||
feature.cancel(true);
|
||||
calls.set(0);
|
||||
pending.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void tryActionOrPostpone() {
|
||||
long now = clock.millis();
|
||||
|
||||
boolean delaySurpassed = ((now - lastCallAttempt) >= delayMs);
|
||||
|
||||
if (delaySurpassed) {
|
||||
if (pending.compareAndSet(true, false)) {
|
||||
int foldedCalls = calls.getAndSet(0);
|
||||
logger.trace("Debouncer action {} invoked after delay {} ({} calls)", name, delayMs, foldedCalls);
|
||||
try {
|
||||
action.run();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Debouncer {} action resulted in error: {}", name, e.getMessage());
|
||||
}
|
||||
} else {
|
||||
logger.warn("Invalid state in debouncer. Should not have reached here!");
|
||||
}
|
||||
} else {
|
||||
// reschedule at origLastInvocation + delayMs
|
||||
// Note: we use Math.max as there's a _very_ small chance lastCallAttempt could advance in another thread,
|
||||
// and result in a negative calculation
|
||||
long delay = Math.max(1, lastCallAttempt - now + delayMs);
|
||||
feature = scheduler.schedule(this::tryActionOrPostpone, delay, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.github.hapjava.accessories.HomekitAccessory;
|
||||
import io.github.hapjava.server.impl.HomekitRoot;
|
||||
|
||||
/**
|
||||
* Stores the created HomekitAccessories. GroupedAccessories are also held here
|
||||
* in a pre-created pending state until all required characteristics are found.
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
class HomekitAccessoryRegistry {
|
||||
private @Nullable HomekitRoot bridge;
|
||||
private final Map<String, HomekitAccessory> createdAccessories = new HashMap<>();
|
||||
private int configurationRevision = 1;
|
||||
private final Logger logger = LoggerFactory.getLogger(HomekitAccessoryRegistry.class);
|
||||
|
||||
public void setConfigurationRevision(int revision) {
|
||||
configurationRevision = revision;
|
||||
}
|
||||
|
||||
public int getConfigurationRevision() {
|
||||
return configurationRevision;
|
||||
}
|
||||
|
||||
public int makeNewConfigurationRevision() {
|
||||
configurationRevision = (configurationRevision + 1) % 65535;
|
||||
final HomekitRoot bridge = this.bridge;
|
||||
try {
|
||||
if (bridge != null) {
|
||||
bridge.setConfigurationIndex(configurationRevision);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("Could not update configuration revision number", e);
|
||||
}
|
||||
return configurationRevision;
|
||||
}
|
||||
|
||||
public synchronized void remove(String itemName) {
|
||||
if (createdAccessories.containsKey(itemName)) {
|
||||
HomekitAccessory accessory = createdAccessories.remove(itemName);
|
||||
logger.trace("Removed accessory {} for taggedItem {}", accessory, itemName);
|
||||
final HomekitRoot bridge = this.bridge;
|
||||
if (bridge != null) {
|
||||
bridge.removeAccessory(accessory);
|
||||
} else {
|
||||
logger.warn("trying to remove {} but bridge is null", accessory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void clear() {
|
||||
final HomekitRoot bridge = this.bridge;
|
||||
if (bridge != null) {
|
||||
createdAccessories.values().forEach(bridge::removeAccessory);
|
||||
} else {
|
||||
logger.warn("trying to clear accessories but bridge is null");
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void setBridge(HomekitRoot bridge) {
|
||||
this.bridge = bridge;
|
||||
createdAccessories.values().forEach(bridge::addAccessory);
|
||||
}
|
||||
|
||||
public synchronized void unsetBridge() {
|
||||
final HomekitRoot oldBridge = bridge;
|
||||
if (oldBridge != null) {
|
||||
createdAccessories.values().forEach(oldBridge::removeAccessory);
|
||||
}
|
||||
bridge = null;
|
||||
}
|
||||
|
||||
public synchronized void addRootAccessory(String itemName, HomekitAccessory accessory) {
|
||||
createdAccessories.put(itemName, accessory);
|
||||
final HomekitRoot bridge = this.bridge;
|
||||
if (bridge != null) {
|
||||
bridge.addAccessory(accessory);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, HomekitAccessory> getAllAccessories() {
|
||||
return this.createdAccessories;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Enum of the possible device types. The defined tag string can be used
|
||||
* as a tag on an item to enable it for Homekit.
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
public enum HomekitAccessoryType {
|
||||
HUMIDITY_SENSOR("HumiditySensor"),
|
||||
LIGHTBULB("Lighting"),
|
||||
SWITCH("Switchable"),
|
||||
TEMPERATURE_SENSOR("TemperatureSensor"),
|
||||
THERMOSTAT("Thermostat"),
|
||||
CONTACT_SENSOR("ContactSensor"),
|
||||
VALVE("Valve"),
|
||||
LEAK_SENSOR("LeakSensor"),
|
||||
MOTION_SENSOR("MotionSensor"),
|
||||
OCCUPANCY_SENSOR("OccupancySensor"),
|
||||
WINDOW_COVERING("WindowCovering"),
|
||||
DOOR("Door"),
|
||||
WINDOW("Window"),
|
||||
SMOKE_SENSOR("SmokeSensor"),
|
||||
CARBON_MONOXIDE_SENSOR("CarbonMonoxideSensor"),
|
||||
CARBON_DIOXIDE_SENSOR("CarbonDioxideSensor"),
|
||||
FAN("Fan"),
|
||||
LOCK("Lock"),
|
||||
SECURITY_SYSTEM("SecuritySystem"),
|
||||
OUTLET("Outlet"),
|
||||
SPEAKER("Speaker"),
|
||||
GARAGE_DOOR_OPENER("GarageDoorOpener"),
|
||||
HEATER_COOLER("HeaterCooler"),
|
||||
LIGHT_SENSOR("LightSensor"),
|
||||
AIR_QUALITY_SENSOR("AirQualitySensor"),
|
||||
DUMMY("Dummy"),
|
||||
@Deprecated()
|
||||
BLINDS("Blinds"),
|
||||
@Deprecated()
|
||||
OLD_DIMMABLE_LIGHTBULB("DimmableLighting"),
|
||||
@Deprecated()
|
||||
OLD_HUMIDITY_SENSOR("CurrentHumidity"),
|
||||
@Deprecated()
|
||||
OLD_COLORFUL_LIGHTBULB("ColorfulLighting");
|
||||
|
||||
private static final Map<String, HomekitAccessoryType> TAG_MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (HomekitAccessoryType type : HomekitAccessoryType.values()) {
|
||||
TAG_MAP.put(type.tag, type);
|
||||
}
|
||||
}
|
||||
|
||||
private final String tag;
|
||||
|
||||
private HomekitAccessoryType(String tag) {
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
public String getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* get accessoryType from String
|
||||
*
|
||||
* @param tag the tag string
|
||||
* @return accessoryType or Optional.empty if no accessory type for the tag was found
|
||||
*/
|
||||
public static Optional<HomekitAccessoryType> valueOfTag(String tag) {
|
||||
return Optional.ofNullable(TAG_MAP.get(tag));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.items.GenericItem;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.items.StateChangeListener;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
|
||||
/**
|
||||
* Subscribes and unsubscribes from Item changes to enable notification to HomeKit
|
||||
* clients. Each item/key pair (key is optional) should be unique, as the underlying
|
||||
* HomeKit library takes care of insuring only a single subscription exists for
|
||||
* each accessory.
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
public class HomekitAccessoryUpdater {
|
||||
private final Logger logger = LoggerFactory.getLogger(HomekitAccessoryUpdater.class);
|
||||
private final ConcurrentMap<ItemKey, Subscription> subscriptionsByName = new ConcurrentHashMap<>();
|
||||
|
||||
public void subscribe(GenericItem item, HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(item, null, callback);
|
||||
}
|
||||
|
||||
public void subscribe(GenericItem item, String key, HomekitCharacteristicChangeCallback callback) {
|
||||
logger.trace("Received subscription request for {} / {}", item, key);
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
if (callback == null) {
|
||||
logger.trace("The received subscription contains a null callback, skipping");
|
||||
return;
|
||||
}
|
||||
ItemKey itemKey = new ItemKey(item, key);
|
||||
subscriptionsByName.compute(itemKey, (k, v) -> {
|
||||
if (v != null) {
|
||||
logger.debug("Received duplicate subscription for {} / {}", item, key);
|
||||
unsubscribe(item, key);
|
||||
}
|
||||
logger.trace("Adding subscription for {} / {}", item, key);
|
||||
Subscription subscription = (changedItem, oldState, newState) -> callback.changed();
|
||||
item.addStateChangeListener(subscription);
|
||||
return subscription;
|
||||
});
|
||||
}
|
||||
|
||||
public void unsubscribe(GenericItem item) {
|
||||
unsubscribe(item, null);
|
||||
}
|
||||
|
||||
public void unsubscribe(GenericItem item, String key) {
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
subscriptionsByName.computeIfPresent(new ItemKey(item, key), (k, v) -> {
|
||||
logger.trace("Removing existing subscription for {} / {}", item, key);
|
||||
item.removeStateChangeListener(v);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
@NonNullByDefault
|
||||
private interface Subscription extends StateChangeListener {
|
||||
|
||||
@Override
|
||||
void stateChanged(Item item, State oldState, State newState);
|
||||
|
||||
@Override
|
||||
default void stateUpdated(Item item, State state) {
|
||||
// Do nothing on non-change update
|
||||
}
|
||||
}
|
||||
|
||||
private static class ItemKey {
|
||||
public final GenericItem item;
|
||||
public final String key;
|
||||
|
||||
public ItemKey(GenericItem item, String key) {
|
||||
this.item = item;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((item == null) ? 0 : item.hashCode());
|
||||
result = prime * result + ((key == null) ? 0 : key.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ItemKey other = (ItemKey) obj;
|
||||
if (item == null) {
|
||||
if (other.item != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!item.equals(other.item)) {
|
||||
return false;
|
||||
}
|
||||
if (key == null) {
|
||||
if (other.key != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!key.equals(other.key)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.storage.Storage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.github.hapjava.server.HomekitAuthInfo;
|
||||
import io.github.hapjava.server.impl.HomekitServer;
|
||||
|
||||
/**
|
||||
* Provides a mechanism to store authenticated HomeKit client details inside the
|
||||
* ESH StorageService, by implementing HomekitAuthInfo.
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
public class HomekitAuthInfoImpl implements HomekitAuthInfo {
|
||||
private final Logger logger = LoggerFactory.getLogger(HomekitAuthInfoImpl.class);
|
||||
public static final String STORAGE_KEY = "homekit";
|
||||
private static final String STORAGE_MAC = "mac";
|
||||
private static final String STORAGE_SALT = "salt";
|
||||
private static final String STORAGE_PRIVATE_KEY = "privateKey";
|
||||
private static final String STORAGE_USER_PREFIX = "user_";
|
||||
|
||||
private final Storage<String> storage;
|
||||
private String mac;
|
||||
private BigInteger salt;
|
||||
private byte[] privateKey;
|
||||
private final String pin;
|
||||
|
||||
public HomekitAuthInfoImpl(Storage<String> storage, String pin) throws InvalidAlgorithmParameterException {
|
||||
this.storage = storage;
|
||||
this.pin = pin;
|
||||
initializeStorage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createUser(String username, byte[] publicKey) {
|
||||
logger.trace("Create user {}", username);
|
||||
storage.put(createUserKey(username), Base64.getEncoder().encodeToString(publicKey));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMac() {
|
||||
return mac;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPin() {
|
||||
return pin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger getSalt() {
|
||||
return salt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getUserPublicKey(String username) {
|
||||
final String encodedKey = storage.get(createUserKey(username));
|
||||
if (encodedKey != null) {
|
||||
return Base64.getDecoder().decode(encodedKey);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUser(String username) {
|
||||
logger.trace("Remove user {}", username);
|
||||
storage.remove(createUserKey(username));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasUser() {
|
||||
Collection<String> keys = storage.getKeys();
|
||||
return keys.stream().anyMatch(this::isUserKey);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
logger.trace("Clear all users");
|
||||
for (String key : new HashSet<>(storage.getKeys())) {
|
||||
if (isUserKey(key)) {
|
||||
storage.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String createUserKey(String username) {
|
||||
return STORAGE_USER_PREFIX + username;
|
||||
}
|
||||
|
||||
private boolean isUserKey(String key) {
|
||||
return key.startsWith(STORAGE_USER_PREFIX);
|
||||
}
|
||||
|
||||
private void initializeStorage() throws InvalidAlgorithmParameterException {
|
||||
mac = storage.get(STORAGE_MAC);
|
||||
final @Nullable Object saltConfig = storage.get(STORAGE_SALT);
|
||||
final @Nullable Object privateKeyConfig = storage.get(STORAGE_PRIVATE_KEY);
|
||||
if (mac == null) {
|
||||
logger.warn(
|
||||
"Could not find existing MAC in {}. Generating new MAC. This will require re-pairing of iOS devices.",
|
||||
storage.getClass().getName());
|
||||
mac = HomekitServer.generateMac();
|
||||
storage.put(STORAGE_MAC, mac);
|
||||
}
|
||||
if (saltConfig == null) {
|
||||
salt = HomekitServer.generateSalt();
|
||||
storage.put(STORAGE_SALT, salt.toString());
|
||||
} else {
|
||||
salt = new BigInteger(saltConfig.toString());
|
||||
}
|
||||
if (privateKeyConfig == null) {
|
||||
privateKey = HomekitServer.generateKey();
|
||||
storage.put(STORAGE_PRIVATE_KEY, Base64.getEncoder().encodeToString(privateKey));
|
||||
} else {
|
||||
privateKey = Base64.getDecoder().decode(privateKeyConfig.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.common.ThreadPoolManager;
|
||||
import org.openhab.core.items.GroupItem;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.items.ItemNotFoundException;
|
||||
import org.openhab.core.items.ItemRegistry;
|
||||
import org.openhab.core.items.ItemRegistryChangeListener;
|
||||
import org.openhab.core.items.MetadataRegistry;
|
||||
import org.openhab.core.storage.Storage;
|
||||
import org.openhab.core.storage.StorageService;
|
||||
import org.openhab.io.homekit.internal.accessories.HomekitAccessoryFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.github.hapjava.accessories.HomekitAccessory;
|
||||
import io.github.hapjava.server.impl.HomekitRoot;
|
||||
|
||||
/**
|
||||
* Listens for changes to the item registry. When changes are detected, check
|
||||
* for HomeKit tags and, if present, add the items to the HomekitAccessoryRegistry.
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HomekitChangeListener implements ItemRegistryChangeListener {
|
||||
private final Logger logger = LoggerFactory.getLogger(HomekitChangeListener.class);
|
||||
private final static String REVISION_CONFIG = "revision";
|
||||
private final static String ACCESSORY_COUNT = "accessory_count";
|
||||
private final ItemRegistry itemRegistry;
|
||||
private final HomekitAccessoryRegistry accessoryRegistry = new HomekitAccessoryRegistry();
|
||||
private final MetadataRegistry metadataRegistry;
|
||||
private final Storage<String> storage;
|
||||
private HomekitAccessoryUpdater updater = new HomekitAccessoryUpdater();
|
||||
private HomekitSettings settings;
|
||||
private int lastAccessoryCount;
|
||||
|
||||
private final Set<String> pendingUpdates = new HashSet<>();
|
||||
|
||||
private final ScheduledExecutorService scheduler = ThreadPoolManager
|
||||
.getScheduledPool(ThreadPoolManager.THREAD_POOL_NAME_COMMON);
|
||||
|
||||
/**
|
||||
* Rather than reacting to item added/removed/modified changes directly, we mark them as dirty (and the groups to
|
||||
* which they belong)
|
||||
*
|
||||
* We wait for a second to pass until no more items are changed. This allows us to add a group of items all at once,
|
||||
* rather than for each update at a time, preventing us from showing an error message with each addition until the
|
||||
* group is complete.
|
||||
*/
|
||||
private final Debouncer applyUpdatesDebouncer;
|
||||
|
||||
HomekitChangeListener(ItemRegistry itemRegistry, HomekitSettings settings, MetadataRegistry metadataRegistry,
|
||||
StorageService storageService) {
|
||||
this.itemRegistry = itemRegistry;
|
||||
this.settings = settings;
|
||||
this.metadataRegistry = metadataRegistry;
|
||||
storage = storageService.getStorage(HomekitAuthInfoImpl.STORAGE_KEY);
|
||||
this.applyUpdatesDebouncer = new Debouncer("update-homekit-devices", scheduler, Duration.ofMillis(1000),
|
||||
Clock.systemUTC(), this::applyUpdates);
|
||||
|
||||
itemRegistry.addRegistryChangeListener(this);
|
||||
itemRegistry.getItems().forEach(this::createRootAccessories);
|
||||
initialiseRevision();
|
||||
logger.info("Created {} HomeKit items.", accessoryRegistry.getAllAccessories().size());
|
||||
}
|
||||
|
||||
private void initialiseRevision() {
|
||||
int revision;
|
||||
try {
|
||||
revision = Integer.parseInt(storage.get(REVISION_CONFIG));
|
||||
} catch (NumberFormatException e) {
|
||||
revision = 1;
|
||||
storage.put(REVISION_CONFIG, "" + revision);
|
||||
}
|
||||
try {
|
||||
lastAccessoryCount = Integer.parseInt(storage.get(ACCESSORY_COUNT));
|
||||
} catch (NumberFormatException e) {
|
||||
lastAccessoryCount = 0;
|
||||
storage.put(ACCESSORY_COUNT, "" + accessoryRegistry.getAllAccessories().size());
|
||||
}
|
||||
accessoryRegistry.setConfigurationRevision(revision);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void added(Item item) {
|
||||
markDirty(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void allItemsChanged(Collection<String> oldItemNames) {
|
||||
clearAccessories();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark an item as dirty, plus any accessory groups to which it pertains, so that after a debounce period the
|
||||
* accessory update can be applied.
|
||||
*
|
||||
* @param item The item that has been changed or removed.
|
||||
*/
|
||||
private synchronized void markDirty(Item item) {
|
||||
logger.trace("Mark dirty item {}", item.getName());
|
||||
pendingUpdates.add(item.getName());
|
||||
/*
|
||||
* If findMyAccessoryGroups fails because the accessory group has already been deleted, then we can count on a
|
||||
* later update telling us that the accessory group was removed.
|
||||
*/
|
||||
for (Item accessoryGroup : HomekitAccessoryFactory.getAccessoryGroups(item, itemRegistry, metadataRegistry)) {
|
||||
pendingUpdates.add(accessoryGroup.getName());
|
||||
}
|
||||
applyUpdatesDebouncer.call();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void removed(Item item) {
|
||||
markDirty(item);
|
||||
}
|
||||
|
||||
private Optional<Item> getItemOptional(String name) {
|
||||
try {
|
||||
return Optional.of(itemRegistry.getItem(name));
|
||||
} catch (ItemNotFoundException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public void makeNewConfigurationRevision() {
|
||||
final int newRevision = accessoryRegistry.makeNewConfigurationRevision();
|
||||
lastAccessoryCount = accessoryRegistry.getAllAccessories().size();
|
||||
logger.trace("make new configuration revision. new revision number {}, number of accessories {}", newRevision,
|
||||
lastAccessoryCount);
|
||||
storage.put(REVISION_CONFIG, "" + newRevision);
|
||||
storage.put(ACCESSORY_COUNT, "" + lastAccessoryCount);
|
||||
}
|
||||
|
||||
private synchronized void applyUpdates() {
|
||||
logger.trace("apply updates");
|
||||
for (final String name : pendingUpdates) {
|
||||
accessoryRegistry.remove(name);
|
||||
logger.trace(" add items {}", name);
|
||||
getItemOptional(name).ifPresent(this::createRootAccessories);
|
||||
}
|
||||
if (!pendingUpdates.isEmpty()) {
|
||||
makeNewConfigurationRevision();
|
||||
pendingUpdates.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updated(Item oldElement, Item element) {
|
||||
markDirty(oldElement);
|
||||
markDirty(element);
|
||||
}
|
||||
|
||||
public int getLastAccessoryCount() {
|
||||
return lastAccessoryCount;
|
||||
}
|
||||
|
||||
public synchronized void clearAccessories() {
|
||||
accessoryRegistry.clear();
|
||||
}
|
||||
|
||||
public synchronized void setBridge(HomekitRoot bridge) {
|
||||
accessoryRegistry.setBridge(bridge);
|
||||
}
|
||||
|
||||
public synchronized void unsetBridge() {
|
||||
applyUpdatesDebouncer.stop();
|
||||
accessoryRegistry.unsetBridge();
|
||||
}
|
||||
|
||||
public void setUpdater(HomekitAccessoryUpdater updater) {
|
||||
this.updater = updater;
|
||||
}
|
||||
|
||||
public void updateSettings(HomekitSettings settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
this.itemRegistry.removeRegistryChangeListener(this);
|
||||
}
|
||||
|
||||
public Map<String, HomekitAccessory> getAccessories() {
|
||||
return this.accessoryRegistry.getAllAccessories();
|
||||
}
|
||||
|
||||
public int getConfigurationRevision() {
|
||||
return this.accessoryRegistry.getConfigurationRevision();
|
||||
}
|
||||
|
||||
/**
|
||||
* creates one or more HomeKit items for given openhab item.
|
||||
* one OpenHAB item can linked to several HomeKit accessories or characteristics.
|
||||
* OpenHAB Item is a good candidate for homeKit accessory IF
|
||||
* - it has HomeKit accessory types, i.e. HomeKit accessory tag AND
|
||||
* - has no group with HomeKit tag, i.e. single line accessory ODER
|
||||
* - has groups with HomeKit tag, but all groups are with baseItem, e.g. Group:Switch,
|
||||
* so that the groups already complete accessory and group members can be a standalone HomeKit accessory.
|
||||
* In contrast, items which are part of groups without BaseItem are additional HomeKit characteristics of the
|
||||
* accessory defined by that group and dont need to be created as RootAccessory here.
|
||||
*
|
||||
* Examples:
|
||||
* // Single Line HomeKit Accessory
|
||||
* Switch light "Light" {homekit="Lighting"}
|
||||
*
|
||||
* // One HomeKit accessory defined using group
|
||||
* Group gLight "Light Group" {homekit="Lighting"}
|
||||
* Switch light "Light" (gLight) {homekit="Lighting.OnState"}
|
||||
*
|
||||
* // 2 HomeKit accessories: one is switch attached to group, another one a single switch
|
||||
* Group:Switch:OR(ON,OFF) gLight "Light Group " {homekit="Lighting"}
|
||||
* Switch light "Light" (gLight) {homekit="Lighting.OnState"}
|
||||
*
|
||||
* @param item openHAB item
|
||||
*/
|
||||
private void createRootAccessories(Item item) {
|
||||
final List<Entry<HomekitAccessoryType, HomekitCharacteristicType>> accessoryTypes = HomekitAccessoryFactory
|
||||
.getAccessoryTypes(item, metadataRegistry);
|
||||
final List<GroupItem> groups = HomekitAccessoryFactory.getAccessoryGroups(item, itemRegistry, metadataRegistry);
|
||||
if (!accessoryTypes.isEmpty()
|
||||
&& (groups.isEmpty() || groups.stream().noneMatch(g -> g.getBaseItem() == null))) {
|
||||
logger.trace("Item {} is a HomeKit accessory of types {}", item.getName(), accessoryTypes);
|
||||
final HomekitOHItemProxy itemProxy = new HomekitOHItemProxy(item);
|
||||
accessoryTypes.forEach(rootAccessory -> createRootAccessory(new HomekitTaggedItem(itemProxy,
|
||||
rootAccessory.getKey(), HomekitAccessoryFactory.getItemConfiguration(item, metadataRegistry))));
|
||||
}
|
||||
}
|
||||
|
||||
private void createRootAccessory(HomekitTaggedItem taggedItem) {
|
||||
try {
|
||||
accessoryRegistry.addRootAccessory(taggedItem.getName(),
|
||||
HomekitAccessoryFactory.create(taggedItem, metadataRegistry, updater, settings));
|
||||
} catch (HomekitException e) {
|
||||
logger.warn("Could not add device {}: {}", taggedItem.getItem().getUID(), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Characteristics are used by complex accessories that can't be represented by
|
||||
* a single item (i.e. a thermostat)
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
public enum HomekitCharacteristicType {
|
||||
/*
|
||||
* It is illegal to have a characteristic type also be a device type
|
||||
*/
|
||||
EMPTY("Empty"), // used in case only accessory type but no characteristic provided
|
||||
NAME("Name"),
|
||||
BATTERY_LOW_STATUS("BatteryLowStatus"),
|
||||
ACTIVE_STATUS("ActiveStatus"),
|
||||
ISCONFIGURED("IsConfigured"),
|
||||
SERVICELABELIDX("ServiceLabelIndex"),
|
||||
INUSE_STATUS("InUseStatus"),
|
||||
FAULT_STATUS("FaultStatus"),
|
||||
TAMPERED_STATUS("TamperedStatus"),
|
||||
OBSTRUCTION_STATUS("ObstructionStatus"),
|
||||
ON_STATE("OnState"),
|
||||
CONTACT_SENSOR_STATE("ContactSensorState"),
|
||||
|
||||
MOTION_DETECTED_STATE("MotionDetectedState"),
|
||||
OCCUPANCY_DETECTED_STATE("OccupancyDetectedState"),
|
||||
SMOKE_DETECTED_STATE("SmokeDetectedState"),
|
||||
|
||||
CARBON_MONOXIDE_DETECTED_STATE("CarbonMonoxideDetectedState"),
|
||||
CARBON_MONOXIDE_LEVEL("CarbonMonoxideLevel"),
|
||||
CARBON_MONOXIDE_PEAK_LEVEL("CarbonMonoxidePeakLevel"),
|
||||
|
||||
CARBON_DIOXIDE_DETECTED_STATE("CarbonDioxideDetectedState"),
|
||||
CARBON_DIOXIDE_LEVEL("CarbonDioxideLevel"),
|
||||
CARBON_DIOXIDE_PEAK_LEVEL("CarbonDioxidePeakLevel"),
|
||||
|
||||
RELATIVE_HUMIDITY("RelativeHumidity"),
|
||||
LEAK_DETECTED_STATE("LeakDetectedState"),
|
||||
HOLD_POSITION("HoldPosition"),
|
||||
|
||||
TARGET_POSITION("TargetPosition"),
|
||||
CURRENT_POSITION("CurrentPosition"),
|
||||
POSITION_STATE("PositionState"),
|
||||
CURRENT_HORIZONTAL_TILT_ANGLE("CurrentHorizontalTiltAngle"),
|
||||
CURRENT_VERTICAL_TILT_ANGLE("CurrentVerticalTiltAngle"),
|
||||
TARGET_HORIZONTAL_TILT_ANGLE("TargetHorizontalTiltAngle"),
|
||||
TARGET_VERTICAL_TILT_ANGLE("TargetVerticalTiltAngle"),
|
||||
|
||||
HUE("Hue"),
|
||||
BRIGHTNESS("Brightness"),
|
||||
SATURATION("Saturation"),
|
||||
COLOR_TEMPERATURE("ColorTemperature"),
|
||||
|
||||
CURRENT_FAN_STATE("CurrentFanState"),
|
||||
TARGET_FAN_STATE("TargetFanState"),
|
||||
ROTATION_DIRECTION("RotationDirection"),
|
||||
ROTATION_SPEED("RotationSpeed"),
|
||||
SWING_MODE("SwingMode"),
|
||||
LOCK_CONTROL("LockControl"),
|
||||
|
||||
CURRENT_TEMPERATURE("CurrentTemperature"),
|
||||
TARGET_HEATING_COOLING_STATE("TargetHeatingCoolingMode"),
|
||||
CURRENT_HEATING_COOLING_STATE("CurrentHeatingCoolingMode"),
|
||||
TARGET_TEMPERATURE("TargetTemperature"),
|
||||
TEMPERATURE_UNIT("TemperatureUnit"),
|
||||
|
||||
LOCK_CURRENT_STATE("LockCurrentState"),
|
||||
LOCK_TARGET_STATE("LockTargetState"),
|
||||
|
||||
DURATION("Duration"),
|
||||
REMAINING_DURATION("RemainingDuration"),
|
||||
|
||||
SECURITY_SYSTEM_CURRENT_STATE("CurrentSecuritySystemState"),
|
||||
SECURITY_SYSTEM_TARGET_STATE("TargetSecuritySystemState"),
|
||||
|
||||
VOLUME("Volume"),
|
||||
MUTE("Mute"),
|
||||
LIGHT_LEVEL("LightLevel"),
|
||||
|
||||
CURRENT_DOOR_STATE("CurrentDoorState"),
|
||||
TARGET_DOOR_STATE("TargetDoorState"),
|
||||
TARGET_HEATER_COOLER_STATE("TargetHeaterCoolerState"),
|
||||
CURRENT_HEATER_COOLER_STATE("CurrentHeaterCoolerState"),
|
||||
COOLING_THRESHOLD_TEMPERATURE("CoolingThresholdTemperature"),
|
||||
HEATING_THRESHOLD_TEMPERATURE("HeatingThresholdTemperature"),
|
||||
|
||||
AIR_QUALITY("AirQuality"),
|
||||
OZONE_DENSITY("OzoneDensity"),
|
||||
NITROGEN_DIOXIDE_DENSITY("NitrogenDioxideDensity"),
|
||||
SULPHUR_DIOXIDE_DENSITY("SulphurDioxideDensity"),
|
||||
PM25_DENSITY("PM25Density"),
|
||||
PM10_DENSITY("PM10Density"),
|
||||
VOC_DENSITY("VOCDensity"),
|
||||
|
||||
@Deprecated()
|
||||
OLD_BATTERY_LEVEL("homekit:BatteryLevel"),
|
||||
@Deprecated()
|
||||
OLD_BATTERY_LOW_STATUS("homekit:BatteryLowStatus"),
|
||||
@Deprecated()
|
||||
VERY_OLD_TARGET_HEATING_COOLING_MODE("homekit:HeatingCoolingMode"),
|
||||
@Deprecated()
|
||||
OLD_TARGET_HEATING_COOLING_MODE("homekit:TargetHeatingCoolingMode"),
|
||||
@Deprecated()
|
||||
OLD_CURRENT_HEATING_COOLING_STATE("homekit:CurrentHeatingCoolingMode"),
|
||||
@Deprecated()
|
||||
OLD_TARGET_TEMPERATURE("homekit:TargetTemperature");
|
||||
|
||||
private static final Map<String, HomekitCharacteristicType> TAG_MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (HomekitCharacteristicType type : HomekitCharacteristicType.values()) {
|
||||
TAG_MAP.put(type.tag, type);
|
||||
}
|
||||
}
|
||||
|
||||
private final String tag;
|
||||
|
||||
private HomekitCharacteristicType(String tag) {
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
public String getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* get characteristicType from String
|
||||
*
|
||||
* @param tag the tag string
|
||||
* @return characteristicType or null if not found
|
||||
*/
|
||||
public static Optional<HomekitCharacteristicType> valueOfTag(String tag) {
|
||||
return Optional.ofNullable(TAG_MAP.get(tag));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.io.console.Console;
|
||||
import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
|
||||
import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
|
||||
import org.openhab.io.homekit.Homekit;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Console commands for interacting with the HomeKit integration
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
@Component(service = ConsoleCommandExtension.class)
|
||||
@NonNullByDefault
|
||||
public class HomekitCommandExtension extends AbstractConsoleCommandExtension {
|
||||
private static final String SUBCMD_CLEAR_PAIRINGS = "clearPairings";
|
||||
private static final String SUBCMD_LIST_ACCESSORIES = "list";
|
||||
private static final String SUBCMD_PRINT_ACCESSORY = "show";
|
||||
private static final String SUBCMD_ALLOW_UNAUTHENTICATED = "allowUnauthenticated";
|
||||
@Deprecated
|
||||
private static final String LEGACY_SUBCMD_LIST_ACCESSORIES = "listAccessories";
|
||||
@Deprecated
|
||||
private static final String LEGACY_SUBCMD_PRINT_ACCESSORY = "printAccessory";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(HomekitCommandExtension.class);
|
||||
|
||||
private @NonNullByDefault({}) Homekit homekit;
|
||||
|
||||
public HomekitCommandExtension() {
|
||||
super("homekit", "Interact with the HomeKit integration.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(String[] args, Console console) {
|
||||
if (args.length > 0) {
|
||||
String subCommand = args[0];
|
||||
switch (subCommand) {
|
||||
case SUBCMD_CLEAR_PAIRINGS:
|
||||
clearHomekitPairings(console);
|
||||
break;
|
||||
|
||||
case SUBCMD_ALLOW_UNAUTHENTICATED:
|
||||
if (args.length > 1) {
|
||||
boolean allow = Boolean.parseBoolean(args[1]);
|
||||
allowUnauthenticatedHomekitRequests(allow, console);
|
||||
} else {
|
||||
console.println("true/false is required as an argument");
|
||||
}
|
||||
break;
|
||||
case SUBCMD_LIST_ACCESSORIES:
|
||||
case LEGACY_SUBCMD_LIST_ACCESSORIES:
|
||||
listAccessories(console);
|
||||
if (subCommand.equalsIgnoreCase(LEGACY_SUBCMD_LIST_ACCESSORIES)) {
|
||||
console.println("");
|
||||
console.println("Hey, you can use the shorter command 'homekit list'");
|
||||
}
|
||||
break;
|
||||
case SUBCMD_PRINT_ACCESSORY:
|
||||
case LEGACY_SUBCMD_PRINT_ACCESSORY:
|
||||
if (args.length > 1) {
|
||||
printAccessory(args[1], console);
|
||||
} else {
|
||||
console.println("accessory id or name is required as an argument");
|
||||
}
|
||||
if (subCommand.equalsIgnoreCase(LEGACY_SUBCMD_PRINT_ACCESSORY)) {
|
||||
console.println("");
|
||||
console.println("Hey, you can use the shorter command 'homekit show <accessory_id|name>'");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.println("Unknown command '" + subCommand + "'");
|
||||
printUsage(console);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
printUsage(console);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getUsages() {
|
||||
return Arrays.asList(buildCommandUsage(SUBCMD_LIST_ACCESSORIES, "list all HomeKit accessories"),
|
||||
buildCommandUsage(SUBCMD_PRINT_ACCESSORY + " <accessory id | accessory name>",
|
||||
"print additional details of the accessories which partially match provided ID or name."),
|
||||
buildCommandUsage(SUBCMD_CLEAR_PAIRINGS, "removes all pairings with HomeKit clients."),
|
||||
buildCommandUsage(SUBCMD_ALLOW_UNAUTHENTICATED + " <boolean>",
|
||||
"enables or disables unauthenticated access to facilitate debugging"));
|
||||
}
|
||||
|
||||
@Reference
|
||||
public void setHomekit(Homekit homekit) {
|
||||
this.homekit = homekit;
|
||||
}
|
||||
|
||||
private void clearHomekitPairings(Console console) {
|
||||
homekit.clearHomekitPairings();
|
||||
console.println("Cleared HomeKit pairings");
|
||||
}
|
||||
|
||||
private void allowUnauthenticatedHomekitRequests(boolean allow, Console console) {
|
||||
homekit.allowUnauthenticatedRequests(allow);
|
||||
console.println((allow ? "Enabled " : "Disabled ") + "unauthenticated HomeKit access");
|
||||
}
|
||||
|
||||
private void listAccessories(Console console) {
|
||||
homekit.getAccessories().forEach(v -> {
|
||||
try {
|
||||
console.println(v.getId() + " " + v.getName().get());
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
logger.warn("Cannot list accessories", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void printAccessory(String id, Console console) {
|
||||
homekit.getAccessories().forEach(v -> {
|
||||
try {
|
||||
if (("" + v.getId()).contains(id) || ((v.getName().get() != null)
|
||||
&& (v.getName().get().toUpperCase().contains(id.toUpperCase())))) {
|
||||
console.println(v.getId() + " " + v.getName().get());
|
||||
console.println("Services:");
|
||||
v.getServices().forEach(s -> {
|
||||
console.println(" Service Type: " + s.getType());
|
||||
console.println(" Characteristics: ");
|
||||
s.getCharacteristics().forEach(c -> console.println(" : " + c.getClass()));
|
||||
});
|
||||
console.println("");
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
logger.warn("Cannot print accessory", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
*
|
||||
* Different command types supported by HomekitOHItemProxy.
|
||||
*
|
||||
* @author Eugen Freiter - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public enum HomekitCommandType {
|
||||
HUE_COMMAND,
|
||||
SATURATION_COMMAND,
|
||||
BRIGHTNESS_COMMAND,
|
||||
ON_COMMAND
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Dimmer commands are handled differently by different devices.
|
||||
* Some devices expect only the brightness updates, some other expect brightness as well as "On/Off" commands.
|
||||
* This enum describes different modes of dimmer handling in the context of HomeKit binding.
|
||||
*
|
||||
* Following modes are supported:
|
||||
* DIMMER_MODE_NORMAL - no filtering. The commands will be sent to device as received from HomeKit.
|
||||
* DIMMER_MODE_FILTER_ON - ON events are filtered out. only OFF and brightness information are sent
|
||||
* DIMMER_MODE_FILTER_BRIGHTNESS_100 - only Brightness=100% is filtered out. everything else unchanged. This allows
|
||||
* custom logic for soft launch in devices.
|
||||
* DIMMER_MODE_FILTER_ON_EXCEPT_BRIGHTNESS_100 - ON events are filtered out in all cases except of Brightness = 100%.
|
||||
*
|
||||
* @author Eugen Freiter - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public enum HomekitDimmerMode {
|
||||
DIMMER_MODE_NORMAL("normal"),
|
||||
DIMMER_MODE_FILTER_ON("filterOn"),
|
||||
DIMMER_MODE_FILTER_BRIGHTNESS_100("filterBrightness100"),
|
||||
DIMMER_MODE_FILTER_ON_EXCEPT_BRIGHTNESS_100("filterOnExceptBrightness100");
|
||||
|
||||
private static final Map<String, HomekitDimmerMode> TAG_MAP = Arrays.stream(HomekitDimmerMode.values())
|
||||
.collect(Collectors.toMap(type -> type.tag.toUpperCase(), type -> type));
|
||||
|
||||
private final String tag;
|
||||
|
||||
private HomekitDimmerMode(String tag) {
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
public String getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
public static Optional<HomekitDimmerMode> valueOfTag(String tag) {
|
||||
return Optional.ofNullable(TAG_MAP.get(tag.toUpperCase()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link HomekitException} class defines an exception for handling HomekitException
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HomekitException extends Exception {
|
||||
private static final long serialVersionUID = -8178227920946730286L;
|
||||
|
||||
public HomekitException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.common.ThreadPoolManager;
|
||||
import org.openhab.core.config.core.ConfigurableService;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.items.ItemRegistry;
|
||||
import org.openhab.core.items.MetadataRegistry;
|
||||
import org.openhab.core.net.NetworkAddressService;
|
||||
import org.openhab.core.storage.StorageService;
|
||||
import org.openhab.io.homekit.Homekit;
|
||||
import org.osgi.framework.Constants;
|
||||
import org.osgi.framework.FrameworkUtil;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
import org.osgi.service.component.annotations.Modified;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.github.hapjava.accessories.HomekitAccessory;
|
||||
import io.github.hapjava.server.impl.HomekitRoot;
|
||||
import io.github.hapjava.server.impl.HomekitServer;
|
||||
|
||||
/**
|
||||
* Provides access to openHAB items via the HomeKit API
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
@Component(immediate = true, service = { Homekit.class }, configurationPid = "org.openhab.homekit", property = {
|
||||
Constants.SERVICE_PID + "=org.openhab.homekit",
|
||||
ConfigurableService.SERVICE_PROPERTY_DESCRIPTION_URI + "=io:homekit",
|
||||
ConfigurableService.SERVICE_PROPERTY_CATEGORY + "=io",
|
||||
ConfigurableService.SERVICE_PROPERTY_LABEL + "=HomeKit Integration", "port:Integer=9123" })
|
||||
@NonNullByDefault
|
||||
public class HomekitImpl implements Homekit {
|
||||
private final Logger logger = LoggerFactory.getLogger(HomekitImpl.class);
|
||||
private final NetworkAddressService networkAddressService;
|
||||
private final HomekitChangeListener changeListener;
|
||||
|
||||
private HomekitSettings settings;
|
||||
private @Nullable InetAddress networkInterface;
|
||||
private @Nullable HomekitServer homekitServer;
|
||||
private @Nullable HomekitRoot bridge;
|
||||
private final HomekitAuthInfoImpl authInfo;
|
||||
|
||||
private final ScheduledExecutorService scheduler = ThreadPoolManager
|
||||
.getScheduledPool(ThreadPoolManager.THREAD_POOL_NAME_COMMON);
|
||||
|
||||
@Activate
|
||||
public HomekitImpl(@Reference StorageService storageService, @Reference ItemRegistry itemRegistry,
|
||||
@Reference NetworkAddressService networkAddressService, Map<String, Object> config,
|
||||
@Reference MetadataRegistry metadataRegistry) throws IOException, InvalidAlgorithmParameterException {
|
||||
this.networkAddressService = networkAddressService;
|
||||
this.settings = processConfig(config);
|
||||
this.changeListener = new HomekitChangeListener(itemRegistry, settings, metadataRegistry, storageService);
|
||||
authInfo = new HomekitAuthInfoImpl(storageService.getStorage(HomekitAuthInfoImpl.STORAGE_KEY), settings.pin);
|
||||
startHomekitServer();
|
||||
}
|
||||
|
||||
private HomekitSettings processConfig(Map<String, Object> config) {
|
||||
HomekitSettings settings = (new Configuration(config)).as(HomekitSettings.class);
|
||||
settings.process();
|
||||
if (settings.networkInterface == null) {
|
||||
settings.networkInterface = networkAddressService.getPrimaryIpv4HostAddress();
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
@Modified
|
||||
protected synchronized void modified(Map<String, Object> config) {
|
||||
try {
|
||||
HomekitSettings oldSettings = settings;
|
||||
settings = processConfig(config);
|
||||
changeListener.updateSettings(settings);
|
||||
if (!oldSettings.networkInterface.equals(settings.networkInterface) || oldSettings.port != settings.port) {
|
||||
// the HomeKit server settings changed. we do a complete re-init
|
||||
stopHomekitServer();
|
||||
startHomekitServer();
|
||||
} else if (!oldSettings.name.equals(settings.name) || !oldSettings.pin.equals(settings.pin)) {
|
||||
// we change the root bridge only
|
||||
stopBridge();
|
||||
startBridge();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("Could not initialize HomeKit connector: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void stopBridge() {
|
||||
final @Nullable HomekitRoot bridge = this.bridge;
|
||||
if (bridge != null) {
|
||||
changeListener.unsetBridge();
|
||||
bridge.stop();
|
||||
this.bridge = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void startBridge() throws IOException {
|
||||
final @Nullable HomekitServer homekitServer = this.homekitServer;
|
||||
if (homekitServer != null && bridge == null) {
|
||||
final HomekitRoot bridge = homekitServer.createBridge(authInfo, settings.name, HomekitSettings.MANUFACTURER,
|
||||
HomekitSettings.MODEL, HomekitSettings.SERIAL_NUMBER,
|
||||
FrameworkUtil.getBundle(getClass()).getVersion().toString(), HomekitSettings.HARDWARE_REVISION);
|
||||
changeListener.setBridge(bridge);
|
||||
this.bridge = bridge;
|
||||
bridge.setConfigurationIndex(changeListener.getConfigurationRevision());
|
||||
|
||||
final int lastAccessoryCount = changeListener.getLastAccessoryCount();
|
||||
int currentAccessoryCount = changeListener.getAccessories().size();
|
||||
if (currentAccessoryCount < lastAccessoryCount) {
|
||||
logger.debug(
|
||||
"It looks like not all items were initialized yet. Old configuration had {} accessories, the current one has only {} accessories. Delay HomeKit bridge start for {} seconds.",
|
||||
lastAccessoryCount, currentAccessoryCount, settings.startDelay);
|
||||
scheduler.schedule(() -> {
|
||||
if (currentAccessoryCount < lastAccessoryCount) {
|
||||
// the number of items is still different, maybe it is desired.
|
||||
// make new configuration revision.
|
||||
changeListener.makeNewConfigurationRevision();
|
||||
}
|
||||
bridge.start();
|
||||
}, settings.startDelay, TimeUnit.SECONDS);
|
||||
} else { // start bridge immediately.
|
||||
bridge.start();
|
||||
}
|
||||
} else {
|
||||
logger.warn(
|
||||
"trying to start bridge but HomeKit server is not initialized or bridge is already initialized");
|
||||
}
|
||||
}
|
||||
|
||||
private void startHomekitServer() throws IOException {
|
||||
if (homekitServer == null) {
|
||||
networkInterface = InetAddress.getByName(settings.networkInterface);
|
||||
homekitServer = new HomekitServer(networkInterface, settings.port);
|
||||
startBridge();
|
||||
} else {
|
||||
logger.warn("trying to start HomeKit server but it is already initialized");
|
||||
}
|
||||
}
|
||||
|
||||
private void stopHomekitServer() {
|
||||
final @Nullable HomekitServer homekit = this.homekitServer;
|
||||
if (homekit != null) {
|
||||
if (bridge != null) {
|
||||
stopBridge();
|
||||
}
|
||||
homekit.stop();
|
||||
this.homekitServer = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
protected void deactivate() {
|
||||
changeListener.clearAccessories();
|
||||
stopHomekitServer();
|
||||
changeListener.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshAuthInfo() throws IOException {
|
||||
final @Nullable HomekitRoot bridge = this.bridge;
|
||||
if (bridge != null) {
|
||||
bridge.refreshAuthInfo();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void allowUnauthenticatedRequests(boolean allow) {
|
||||
final @Nullable HomekitRoot bridge = this.bridge;
|
||||
if (bridge != null) {
|
||||
bridge.allowUnauthenticatedRequests(allow);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HomekitAccessory> getAccessories() {
|
||||
return new ArrayList<>(this.changeListener.getAccessories().values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearHomekitPairings() {
|
||||
try {
|
||||
authInfo.clear();
|
||||
refreshAuthInfo();
|
||||
} catch (Exception e) {
|
||||
logger.warn("Could not clear HomeKit pairings", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
|
||||
package org.openhab.io.homekit.internal;
|
||||
|
||||
import static org.openhab.io.homekit.internal.HomekitCommandType.*;
|
||||
import static org.openhab.io.homekit.internal.HomekitDimmerMode.*;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.common.ThreadPoolManager;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.library.items.ColorItem;
|
||||
import org.openhab.core.library.items.DimmerItem;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* Proxy class that can collect multiple commands for the same openHAB item and merge them to one command.
|
||||
* e.g. Hue and Saturation update for Color Item
|
||||
*
|
||||
* @author Eugen Freiter - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HomekitOHItemProxy {
|
||||
private final Logger logger = LoggerFactory.getLogger(HomekitOHItemProxy.class);
|
||||
private static final int DEFAULT_DELAY = 50; // in ms
|
||||
private final Item item;
|
||||
private final Map<HomekitCommandType, State> commandCache = new ConcurrentHashMap<>();
|
||||
private final ScheduledExecutorService scheduler = ThreadPoolManager
|
||||
.getScheduledPool(ThreadPoolManager.THREAD_POOL_NAME_COMMON);
|
||||
private @NonNullByDefault({}) ScheduledFuture<?> future;
|
||||
private HomekitDimmerMode dimmerMode = DIMMER_MODE_NORMAL;
|
||||
// delay, how long wait for further commands. in ms.
|
||||
private int delay = DEFAULT_DELAY;
|
||||
|
||||
public HomekitOHItemProxy(Item item) {
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
public Item getItem() {
|
||||
return item;
|
||||
}
|
||||
|
||||
public void setDimmerMode(HomekitDimmerMode mode) {
|
||||
dimmerMode = mode;
|
||||
}
|
||||
|
||||
public void setDelay(int delay) {
|
||||
this.delay = delay;
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
private void sendCommand() {
|
||||
if (!(item instanceof DimmerItem)) {
|
||||
// currently supports only DimmerItem and ColorItem (which extends DimmerItem)
|
||||
logger.debug("unexpected item type {}. Only DimmerItem and ColorItem are supported.", item);
|
||||
return;
|
||||
}
|
||||
final OnOffType on = (OnOffType) commandCache.remove(ON_COMMAND);
|
||||
final PercentType brightness = (PercentType) commandCache.remove(BRIGHTNESS_COMMAND);
|
||||
final DecimalType hue = (DecimalType) commandCache.remove(HUE_COMMAND);
|
||||
final PercentType saturation = (PercentType) commandCache.remove(SATURATION_COMMAND);
|
||||
final @Nullable OnOffType currentOnState = ((DimmerItem) item).getStateAs(OnOffType.class);
|
||||
if (on != null) {
|
||||
// always sends OFF.
|
||||
// sends ON only if
|
||||
// - DIMMER_MODE_NONE is enabled OR
|
||||
// - DIMMER_MODE_FILTER_BRIGHTNESS_100 is enabled OR
|
||||
// - DIMMER_MODE_FILTER_ON_EXCEPT100 is not enabled and brightness is null or below 100
|
||||
if ((on == OnOffType.OFF) || (dimmerMode == DIMMER_MODE_NORMAL)
|
||||
|| (dimmerMode == DIMMER_MODE_FILTER_BRIGHTNESS_100)
|
||||
|| ((dimmerMode == DIMMER_MODE_FILTER_ON_EXCEPT_BRIGHTNESS_100) && (currentOnState != OnOffType.ON)
|
||||
&& ((brightness == null) || (brightness.intValue() == 100)))) {
|
||||
logger.trace("send OnOff command for item {} with value {}", item, on);
|
||||
((DimmerItem) item).send(on);
|
||||
}
|
||||
}
|
||||
|
||||
// if hue or saturation present, send an HSBType state update. no filter applied for HUE & Saturation
|
||||
if ((hue != null) || (saturation != null)) {
|
||||
if (item instanceof ColorItem) {
|
||||
// logic for ColorItem = combine hue, saturation and brightness update to one command
|
||||
final HSBType currentState = item.getState() instanceof UnDefType ? HSBType.BLACK
|
||||
: (HSBType) item.getState();
|
||||
((ColorItem) item).send(new HSBType(hue != null ? hue : currentState.getHue(),
|
||||
saturation != null ? saturation : currentState.getSaturation(),
|
||||
brightness != null ? brightness : currentState.getBrightness()));
|
||||
logger.trace("send HSB command for item {} with following values hue={} saturation={} brightness={}",
|
||||
item, hue, saturation, brightness);
|
||||
}
|
||||
} else if ((brightness != null) && (item instanceof DimmerItem)) {
|
||||
// sends brightness:
|
||||
// - DIMMER_MODE_NONE
|
||||
// - DIMMER_MODE_FILTER_ON
|
||||
// - other modes (DIMMER_MODE_FILTER_BRIGHTNESS_100 or DIMMER_MODE_FILTER_ON_EXCEPT_BRIGHTNESS_100) and
|
||||
// <100%.
|
||||
if ((dimmerMode == DIMMER_MODE_NORMAL) || (dimmerMode == DIMMER_MODE_FILTER_ON)
|
||||
|| (brightness.intValue() < 100) || (currentOnState == OnOffType.ON)) {
|
||||
logger.trace("send Brightness command for item {} with value {}", item, brightness);
|
||||
((DimmerItem) item).send(brightness);
|
||||
}
|
||||
}
|
||||
commandCache.clear();
|
||||
}
|
||||
|
||||
public synchronized void sendCommandProxy(HomekitCommandType commandType, State state) {
|
||||
commandCache.put(commandType, state);
|
||||
logger.trace("add command to command cache: item {}, command type {}, command state {}. cache state after: {}",
|
||||
this, commandType, state, commandCache);
|
||||
// if cache has already HUE+SATURATION or BRIGHTNESS+ON then we don't expect any further relevant command
|
||||
if (((item instanceof ColorItem) && commandCache.containsKey(HUE_COMMAND)
|
||||
&& commandCache.containsKey(SATURATION_COMMAND))
|
||||
|| (commandCache.containsKey(BRIGHTNESS_COMMAND) && commandCache.containsKey(ON_COMMAND))) {
|
||||
if (future != null) {
|
||||
future.cancel(false);
|
||||
}
|
||||
sendCommand();
|
||||
return;
|
||||
}
|
||||
// if timer is not already set, create a new one to ensure that the command command is send even if no follow up
|
||||
// commands are received.
|
||||
if (future == null || future.isDone()) {
|
||||
future = scheduler.schedule(() -> {
|
||||
logger.trace("timer of {} ms is over, sending the command", delay);
|
||||
sendCommand();
|
||||
}, delay, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal;
|
||||
|
||||
/**
|
||||
* Provides the configured and static settings for the Homekit addon
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
public class HomekitSettings {
|
||||
public static final String MANUFACTURER = "openHAB Community";
|
||||
public static final String SERIAL_NUMBER = "none";
|
||||
public static final String MODEL = "openHAB";
|
||||
public static final String HARDWARE_REVISION = "2.5";
|
||||
|
||||
public String name = "openHAB";
|
||||
public int port = 9123;
|
||||
public String pin = "031-45-154";
|
||||
public int startDelay = 30;
|
||||
public boolean useFahrenheitTemperature = false;
|
||||
public double minimumTemperature = -100;
|
||||
public double maximumTemperature = 100;
|
||||
public String thermostatTargetModeHeat = "HeatOn";
|
||||
public String thermostatTargetModeCool = "CoolOn";
|
||||
public String thermostatTargetModeAuto = "Auto";
|
||||
public String thermostatTargetModeOff = "Off";
|
||||
public String thermostatCurrentModeHeating = "HeatOn";
|
||||
public String thermostatCurrentModeCooling = "CoolOn";
|
||||
public String thermostatCurrentModeOff = "Off";
|
||||
public String doorCurrentStateOpen = "OPEN";
|
||||
public String doorCurrentStateOpening = "OPENING";
|
||||
public String doorCurrentStateClosed = "CLOSED";
|
||||
public String doorCurrentStateClosing = "CLOSING";
|
||||
public String doorCurrentStateStopped = "STOPPED";
|
||||
public String doorTargetStateClosed = "CLOSED";
|
||||
public String doorTargetStateOpen = "OPEN";
|
||||
public String networkInterface;
|
||||
|
||||
@Deprecated
|
||||
public String thermostatHeatMode;
|
||||
@Deprecated
|
||||
public String thermostatCoolMode;
|
||||
@Deprecated
|
||||
public String thermostatAutoMode;
|
||||
@Deprecated
|
||||
public String thermostatOffMode;
|
||||
|
||||
public void process() {
|
||||
if (thermostatHeatMode /* legacy setting */ != null) {
|
||||
this.thermostatTargetModeHeat = thermostatHeatMode;
|
||||
}
|
||||
if (thermostatCoolMode /* legacy setting */ != null) {
|
||||
this.thermostatTargetModeCool = thermostatCoolMode;
|
||||
}
|
||||
if (thermostatAutoMode /* legacy setting */ != null) {
|
||||
this.thermostatTargetModeAuto = thermostatAutoMode;
|
||||
}
|
||||
if (thermostatOffMode /* legacy setting */ != null) {
|
||||
this.thermostatTargetModeOff = thermostatOffMode;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
long temp;
|
||||
temp = Double.doubleToLongBits(maximumTemperature);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
temp = Double.doubleToLongBits(minimumTemperature);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
result = prime * result + ((pin == null) ? 0 : pin.hashCode());
|
||||
result = prime * result + port;
|
||||
result = prime * result + ((thermostatTargetModeAuto == null) ? 0 : thermostatTargetModeAuto.hashCode());
|
||||
result = prime * result + ((thermostatTargetModeCool == null) ? 0 : thermostatTargetModeCool.hashCode());
|
||||
result = prime * result + ((thermostatTargetModeHeat == null) ? 0 : thermostatTargetModeHeat.hashCode());
|
||||
result = prime * result + ((thermostatTargetModeOff == null) ? 0 : thermostatTargetModeOff.hashCode());
|
||||
result = prime * result + (useFahrenheitTemperature ? 1231 : 1237);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
HomekitSettings other = (HomekitSettings) obj;
|
||||
if (Double.doubleToLongBits(maximumTemperature) != Double.doubleToLongBits(other.maximumTemperature)) {
|
||||
return false;
|
||||
}
|
||||
if (Double.doubleToLongBits(minimumTemperature) != Double.doubleToLongBits(other.minimumTemperature)) {
|
||||
return false;
|
||||
}
|
||||
if (pin == null) {
|
||||
if (other.pin != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!pin.equals(other.pin)) {
|
||||
return false;
|
||||
}
|
||||
if (port != other.port) {
|
||||
return false;
|
||||
}
|
||||
if (thermostatTargetModeAuto == null) {
|
||||
if (other.thermostatTargetModeAuto != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!thermostatTargetModeAuto.equals(other.thermostatTargetModeAuto)) {
|
||||
return false;
|
||||
}
|
||||
if (thermostatTargetModeCool == null) {
|
||||
if (other.thermostatTargetModeCool != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!thermostatTargetModeCool.equals(other.thermostatTargetModeCool)) {
|
||||
return false;
|
||||
}
|
||||
if (thermostatTargetModeHeat == null) {
|
||||
if (other.thermostatTargetModeHeat != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!thermostatTargetModeHeat.equals(other.thermostatTargetModeHeat)) {
|
||||
return false;
|
||||
}
|
||||
if (thermostatTargetModeOff == null) {
|
||||
if (other.thermostatTargetModeOff != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!thermostatTargetModeOff.equals(other.thermostatTargetModeOff)) {
|
||||
return false;
|
||||
}
|
||||
if (useFahrenheitTemperature != other.useFahrenheitTemperature) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal;
|
||||
|
||||
import static org.openhab.io.homekit.internal.HomekitAccessoryType.DUMMY;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.items.GroupItem;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Wraps an Item with data derived from supported tags defined.
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HomekitTaggedItem {
|
||||
private final Logger logger = LoggerFactory.getLogger(HomekitTaggedItem.class);
|
||||
|
||||
/** configuration keywords at items level **/
|
||||
public final static String MIN_VALUE = "minValue";
|
||||
public final static String MAX_VALUE = "maxValue";
|
||||
public final static String STEP = "step";
|
||||
public final static String DIMMER_MODE = "dimmerMode";
|
||||
public final static String DELAY = "commandDelay";
|
||||
public final static String INVERTED = "inverted";
|
||||
|
||||
private static final Map<Integer, String> CREATED_ACCESSORY_IDS = new ConcurrentHashMap<>();
|
||||
|
||||
// proxy item used to group commands for complex item types like Color or Dimmer
|
||||
private final HomekitOHItemProxy proxyItem;
|
||||
|
||||
// type of HomeKit accessory/service, e.g. TemperatureSensor
|
||||
private final HomekitAccessoryType homekitAccessoryType;
|
||||
|
||||
// type of HomeKit characteristic, e.g. CurrentTemperature
|
||||
private HomekitCharacteristicType homekitCharacteristicType;
|
||||
|
||||
// configuration attached to the openHAB Item, e.g. minValue, maxValue, valveType
|
||||
private final @Nullable Map<String, Object> configuration;
|
||||
|
||||
// link to the groupItem if item is part of a group
|
||||
private @Nullable GroupItem parentGroupItem;
|
||||
|
||||
// HomeKit accessory id (aid) which is generated from item name
|
||||
private final int id;
|
||||
|
||||
public HomekitTaggedItem(HomekitOHItemProxy item, HomekitAccessoryType homekitAccessoryType,
|
||||
@Nullable Map<String, Object> configuration) {
|
||||
this.proxyItem = item;
|
||||
this.parentGroupItem = null;
|
||||
this.configuration = configuration;
|
||||
this.homekitAccessoryType = homekitAccessoryType;
|
||||
this.homekitCharacteristicType = HomekitCharacteristicType.EMPTY;
|
||||
if (homekitAccessoryType != DUMMY) {
|
||||
this.id = calculateId(item.getItem());
|
||||
} else {
|
||||
this.id = 0;
|
||||
}
|
||||
parseConfiguration();
|
||||
}
|
||||
|
||||
public HomekitTaggedItem(HomekitOHItemProxy item, HomekitAccessoryType homekitAccessoryType,
|
||||
HomekitCharacteristicType homekitCharacteristicType, @Nullable Map<String, Object> configuration) {
|
||||
this(item, homekitAccessoryType, configuration);
|
||||
this.homekitCharacteristicType = homekitCharacteristicType;
|
||||
}
|
||||
|
||||
public HomekitTaggedItem(HomekitOHItemProxy item, HomekitAccessoryType homekitAccessoryType,
|
||||
HomekitCharacteristicType homekitCharacteristicType, @Nullable GroupItem parentGroup,
|
||||
@Nullable Map<String, Object> configuration) {
|
||||
this(item, homekitAccessoryType, homekitCharacteristicType, configuration);
|
||||
this.parentGroupItem = parentGroup;
|
||||
}
|
||||
|
||||
public boolean isGroup() {
|
||||
return (isAccessory() && (proxyItem.getItem() instanceof GroupItem)
|
||||
&& ((GroupItem) proxyItem.getItem()).getBaseItem() == null);
|
||||
}
|
||||
|
||||
public HomekitAccessoryType getAccessoryType() {
|
||||
return homekitAccessoryType;
|
||||
}
|
||||
|
||||
public HomekitCharacteristicType getCharacteristicType() {
|
||||
return homekitCharacteristicType;
|
||||
}
|
||||
|
||||
public @Nullable Map<String, Object> getConfiguration() {
|
||||
return configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not this item refers to an item that fully specifies a HomeKit accessory. Mutually
|
||||
* exclusive
|
||||
* to isCharacteristic(). Primary devices must belong to a root accessory group.
|
||||
*/
|
||||
public boolean isAccessory() {
|
||||
return homekitAccessoryType != DUMMY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not this item is in a group that specifies a HomeKit accessory. It is not possible to be a
|
||||
* characteristic and an accessory. Further, all characteristics belong to a
|
||||
* root deviceGroup.
|
||||
*/
|
||||
public boolean isCharacteristic() {
|
||||
return homekitCharacteristicType != HomekitCharacteristicType.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* return openHAB item responsible for the HomeKit item
|
||||
*
|
||||
* @return openHAB item
|
||||
*/
|
||||
public Item getItem() {
|
||||
return proxyItem.getItem();
|
||||
}
|
||||
|
||||
/**
|
||||
* return proxy item which is used to group commands.
|
||||
*
|
||||
* @return proxy item
|
||||
*/
|
||||
public HomekitOHItemProxy getProxyItem() {
|
||||
return proxyItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* send openHAB item command via proxy item, which allows to group commands.
|
||||
* e.g. sendCommandProxy(hue), sendCommandProxy(brightness) would lead to one openHAB command that updates hue and
|
||||
* brightness at once
|
||||
*
|
||||
* @param commandType type of the command, e.g. HomekitCommandType.HUE_COMMAND
|
||||
* @param command command/state
|
||||
*/
|
||||
public void sendCommandProxy(HomekitCommandType commandType, State command) {
|
||||
proxyItem.sendCommandProxy(commandType, command);
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return proxyItem.getItem().getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RootDevice GroupItem to which this item belongs.
|
||||
* Returns null if not in a group.
|
||||
*/
|
||||
public @Nullable GroupItem getRootDeviceGroupItem() {
|
||||
return parentGroupItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not this item belongs to a HomeKit accessory group.
|
||||
*
|
||||
* Characteristic devices must belong to a HomeKit accessory group.
|
||||
*/
|
||||
public boolean isMemberOfAccessoryGroup() {
|
||||
return parentGroupItem != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* return object from item configuration for given key or default if not found
|
||||
*
|
||||
* @param key configuration key
|
||||
* @param defaultValue default value
|
||||
* @param <T> expected class
|
||||
* @return value
|
||||
*/
|
||||
@SuppressWarnings({ "null", "unchecked" })
|
||||
public <T> T getConfiguration(String key, T defaultValue) {
|
||||
if (configuration != null) {
|
||||
final @Nullable Object value = configuration.get(key);
|
||||
if (value != null && value.getClass().equals(defaultValue.getClass())) {
|
||||
return (T) value;
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* return configuration as double if exists otherwise return defaultValue
|
||||
*
|
||||
* @param key configuration key
|
||||
* @param defaultValue default value
|
||||
* @return value
|
||||
*/
|
||||
public double getConfigurationAsDouble(String key, double defaultValue) {
|
||||
return getConfiguration(key, BigDecimal.valueOf(defaultValue)).doubleValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* parse and apply item configuration.
|
||||
*/
|
||||
private void parseConfiguration() {
|
||||
if (configuration != null) {
|
||||
final @Nullable Object dimmerModeConfig = configuration.get(DIMMER_MODE);
|
||||
if (dimmerModeConfig instanceof String) {
|
||||
HomekitDimmerMode.valueOfTag((String) dimmerModeConfig).ifPresent(proxyItem::setDimmerMode);
|
||||
}
|
||||
final @Nullable Object delayConfig = configuration.get(DELAY);
|
||||
if (delayConfig instanceof Number) {
|
||||
proxyItem.setDelay(((Number) delayConfig).intValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int calculateId(Item item) {
|
||||
// magic number 629 is the legacy from apache HashCodeBuilder (17*37)
|
||||
int id = 629 + item.getName().hashCode();
|
||||
if (id < 0) {
|
||||
id += Integer.MAX_VALUE;
|
||||
}
|
||||
if (id < 2) {
|
||||
id = 2; // 0 and 1 are reserved
|
||||
}
|
||||
if (CREATED_ACCESSORY_IDS.containsKey(id)) {
|
||||
if (!CREATED_ACCESSORY_IDS.get(id).equals(item.getName())) {
|
||||
logger.warn(
|
||||
"Could not create HomeKit accessory {} because its hash conflicts with {}. This is a 1:1,000,000 chance occurrence. Change one of the names and consider playing the lottery. See https://github.com/openhab/openhab-addons/issues/257#issuecomment-125886562",
|
||||
item.getName(), CREATED_ACCESSORY_IDS.get(id));
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
CREATED_ACCESSORY_IDS.put(id, item.getName());
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "Item:" + proxyItem.getItem() + " HomeKit type:" + homekitAccessoryType + " HomeKit characteristic:"
|
||||
+ homekitCharacteristicType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import javax.measure.Quantity;
|
||||
import javax.measure.Unit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.items.GenericItem;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.ImperialUnits;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitCharacteristicType;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.github.hapjava.accessories.HomekitAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.services.Service;
|
||||
|
||||
/**
|
||||
* Abstract class for Homekit Accessory implementations, this provides the
|
||||
* accessory metadata using information from the underlying Item.
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
abstract class AbstractHomekitAccessoryImpl implements HomekitAccessory {
|
||||
private final Logger logger = LoggerFactory.getLogger(AbstractHomekitAccessoryImpl.class);
|
||||
private final List<HomekitTaggedItem> characteristics;
|
||||
private final HomekitTaggedItem accessory;
|
||||
private final HomekitAccessoryUpdater updater;
|
||||
private final HomekitSettings settings;
|
||||
private final List<Service> services;
|
||||
|
||||
public AbstractHomekitAccessoryImpl(HomekitTaggedItem accessory, List<HomekitTaggedItem> characteristics,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) {
|
||||
this.characteristics = characteristics;
|
||||
this.accessory = accessory;
|
||||
this.updater = updater;
|
||||
this.services = new ArrayList<>();
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
@NonNullByDefault
|
||||
protected Optional<HomekitTaggedItem> getCharacteristic(HomekitCharacteristicType type) {
|
||||
return characteristics.stream().filter(c -> c.getCharacteristicType() == type).findAny();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return accessory.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<String> getName() {
|
||||
return CompletableFuture.completedFuture(accessory.getItem().getLabel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<String> getManufacturer() {
|
||||
return CompletableFuture.completedFuture("none");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<String> getModel() {
|
||||
return CompletableFuture.completedFuture("none");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<String> getSerialNumber() {
|
||||
return CompletableFuture.completedFuture("none");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<String> getFirmwareRevision() {
|
||||
return CompletableFuture.completedFuture("none");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void identify() {
|
||||
// We're not going to support this for now
|
||||
}
|
||||
|
||||
public HomekitTaggedItem getRootAccessory() {
|
||||
return accessory;
|
||||
}
|
||||
|
||||
public Collection<Service> getServices() {
|
||||
return this.services;
|
||||
}
|
||||
|
||||
protected HomekitAccessoryUpdater getUpdater() {
|
||||
return updater;
|
||||
}
|
||||
|
||||
protected HomekitSettings getSettings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
@NonNullByDefault
|
||||
protected void subscribe(HomekitCharacteristicType characteristicType,
|
||||
HomekitCharacteristicChangeCallback callback) {
|
||||
final Optional<HomekitTaggedItem> characteristic = getCharacteristic(characteristicType);
|
||||
if (characteristic.isPresent()) {
|
||||
getUpdater().subscribe((GenericItem) characteristic.get().getItem(), characteristicType.getTag(), callback);
|
||||
} else {
|
||||
logger.warn("Missing mandatory characteristic {}", characteristicType);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNullByDefault
|
||||
protected void unsubscribe(HomekitCharacteristicType characteristicType) {
|
||||
final Optional<HomekitTaggedItem> characteristic = getCharacteristic(characteristicType);
|
||||
if (characteristic.isPresent()) {
|
||||
getUpdater().unsubscribe((GenericItem) characteristic.get().getItem(), characteristicType.getTag());
|
||||
} else {
|
||||
logger.warn("Missing mandatory characteristic {}", characteristicType);
|
||||
}
|
||||
}
|
||||
|
||||
protected @Nullable <T extends State> T getStateAs(HomekitCharacteristicType characteristic, Class<T> type) {
|
||||
final Optional<HomekitTaggedItem> taggedItem = getCharacteristic(characteristic);
|
||||
if (taggedItem.isPresent()) {
|
||||
final State state = taggedItem.get().getItem().getStateAs(type);
|
||||
if (state != null) {
|
||||
return state.as(type);
|
||||
}
|
||||
}
|
||||
logger.debug("State for characteristic {} at accessory {} cannot be retrieved.", characteristic,
|
||||
accessory.getName());
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNullByDefault
|
||||
protected <T extends Item> Optional<T> getItem(HomekitCharacteristicType characteristic, Class<T> type) {
|
||||
final Optional<HomekitTaggedItem> taggedItem = getCharacteristic(characteristic);
|
||||
if (taggedItem.isPresent()) {
|
||||
final Item item = taggedItem.get().getItem();
|
||||
if (type.isInstance(item)) {
|
||||
return Optional.of((T) item);
|
||||
} else {
|
||||
logger.warn("Unsupported item type for characteristic {} at accessory {}. Expected {}, got {}",
|
||||
characteristic, accessory.getItem().getName(), type, taggedItem.get().getItem().getClass());
|
||||
}
|
||||
} else {
|
||||
logger.warn("Mandatory characteristic {} not found at accessory {}. ", characteristic,
|
||||
accessory.getItem().getName());
|
||||
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* return configuration attached to the root accessory, e.g. groupItem.
|
||||
* Note: result will be casted to the type of the default value.
|
||||
* The type for number is BigDecimal.
|
||||
*
|
||||
* @param key configuration key
|
||||
* @param defaultValue default value
|
||||
* @param <T> expected type
|
||||
* @return configuration value
|
||||
*/
|
||||
@NonNullByDefault
|
||||
protected <T> T getAccessoryConfiguration(String key, T defaultValue) {
|
||||
return accessory.getConfiguration(key, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* return configuration of the characteristic item, e.g. currentTemperature.
|
||||
* Note: result will be casted to the type of the default value.
|
||||
* The type for number is BigDecimal.
|
||||
*
|
||||
* @param characteristicType characteristic type
|
||||
* @param key configuration key
|
||||
* @param defaultValue default value
|
||||
* @param <T> expected type
|
||||
* @return configuration value
|
||||
*/
|
||||
@NonNullByDefault
|
||||
protected <T> T getAccessoryConfiguration(HomekitCharacteristicType characteristicType, String key,
|
||||
T defaultValue) {
|
||||
return getCharacteristic(characteristicType)
|
||||
.map(homekitTaggedItem -> homekitTaggedItem.getConfiguration(key, defaultValue)).orElse(defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* update mapping with values from item configuration.
|
||||
* it checks for all keys from the mapping whether there is configuration at item with the same key and if yes,
|
||||
* replace the value.
|
||||
*
|
||||
* @param characteristicType characteristicType to identify item
|
||||
* @param map mapping to update
|
||||
*/
|
||||
@NonNullByDefault
|
||||
protected void updateMapping(HomekitCharacteristicType characteristicType, Map<?, String> map) {
|
||||
getCharacteristic(characteristicType).ifPresent(c -> {
|
||||
final Map<String, Object> configuration = c.getConfiguration();
|
||||
if (configuration != null) {
|
||||
map.replaceAll((k, current_value) -> {
|
||||
final Object new_value = configuration.get(current_value);
|
||||
return (new_value instanceof String) ? (String) new_value : current_value;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* takes item state as value and retrieves the key for that value from mapping.
|
||||
* e.g. used to map StringItem value to HomeKit Enum
|
||||
*
|
||||
* @param characteristicType characteristicType to identify item
|
||||
* @param mapping mapping
|
||||
* @param defaultValue default value if nothing found in mapping
|
||||
* @param <T> type of the result derived from
|
||||
* @return key for the value
|
||||
*/
|
||||
@NonNullByDefault
|
||||
protected <T> T getKeyFromMapping(HomekitCharacteristicType characteristicType, Map<T, String> mapping,
|
||||
T defaultValue) {
|
||||
final Optional<HomekitTaggedItem> c = getCharacteristic(characteristicType);
|
||||
if (c.isPresent()) {
|
||||
final State state = c.get().getItem().getState();
|
||||
logger.trace("getKeyFromMapping: characteristic {}, state {}, mapping {}", characteristicType.getTag(),
|
||||
state, mapping);
|
||||
if (state instanceof StringType) {
|
||||
return mapping.entrySet().stream().filter(entry -> state.toString().equalsIgnoreCase(entry.getValue()))
|
||||
.findAny().map(Entry::getKey).orElseGet(() -> {
|
||||
logger.warn(
|
||||
"Wrong value {} for {} characteristic of the item {}. Expected one of following {}. Returning {}.",
|
||||
state.toString(), characteristicType.getTag(), c.get().getName(), mapping.values(),
|
||||
defaultValue);
|
||||
return defaultValue;
|
||||
});
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@NonNullByDefault
|
||||
protected void addCharacteristic(HomekitTaggedItem characteristic) {
|
||||
characteristics.add(characteristic);
|
||||
}
|
||||
|
||||
@NonNullByDefault
|
||||
private <T extends Quantity<T>> double convertAndRound(double value, Unit<T> from, Unit<T> to) {
|
||||
double rawValue = from == to ? value : from.getConverterTo(to).convert(value);
|
||||
return new BigDecimal(rawValue).setScale(1, RoundingMode.HALF_UP).doubleValue();
|
||||
}
|
||||
|
||||
@NonNullByDefault
|
||||
protected double convertToCelsius(double degrees) {
|
||||
return convertAndRound(degrees,
|
||||
getSettings().useFahrenheitTemperature ? ImperialUnits.FAHRENHEIT : SIUnits.CELSIUS, SIUnits.CELSIUS);
|
||||
}
|
||||
|
||||
@NonNullByDefault
|
||||
protected double convertFromCelsius(double degrees) {
|
||||
return convertAndRound(degrees,
|
||||
getSettings().useFahrenheitTemperature ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT,
|
||||
ImperialUnits.FAHRENHEIT);
|
||||
}
|
||||
|
||||
@NonNullByDefault
|
||||
protected BooleanItemReader createBooleanReader(HomekitCharacteristicType characteristicType,
|
||||
OnOffType trueOnOffValue, OpenClosedType trueOpenClosedValue) throws IncompleteAccessoryException {
|
||||
return new BooleanItemReader(
|
||||
getItem(characteristicType, GenericItem.class)
|
||||
.orElseThrow(() -> new IncompleteAccessoryException(characteristicType)),
|
||||
trueOnOffValue, trueOpenClosedValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.CURRENT_POSITION;
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.POSITION_STATE;
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.TARGET_POSITION;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.items.RollershutterItem;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitCharacteristicType;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.characteristics.impl.windowcovering.PositionStateEnum;
|
||||
|
||||
/**
|
||||
* Common methods for Door, Window and WindowCovering.
|
||||
*
|
||||
* @author Eugen Freiter - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
abstract class AbstractHomekitPositionAccessoryImpl extends AbstractHomekitAccessoryImpl {
|
||||
protected int closedPosition;
|
||||
protected int openPosition;
|
||||
private final Map<PositionStateEnum, String> positionStateMapping;
|
||||
|
||||
public AbstractHomekitPositionAccessoryImpl(HomekitTaggedItem taggedItem,
|
||||
List<HomekitTaggedItem> mandatoryCharacteristics, HomekitAccessoryUpdater updater,
|
||||
HomekitSettings settings) {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
final String invertedConfig = getAccessoryConfiguration(HomekitTaggedItem.INVERTED, "true");
|
||||
final boolean inverted = invertedConfig.equalsIgnoreCase("yes") || invertedConfig.equalsIgnoreCase("true");
|
||||
closedPosition = inverted ? 0 : 100;
|
||||
openPosition = inverted ? 100 : 0;
|
||||
positionStateMapping = new EnumMap<>(PositionStateEnum.class);
|
||||
positionStateMapping.put(PositionStateEnum.DECREASING, "DECREASING");
|
||||
positionStateMapping.put(PositionStateEnum.INCREASING, "INCREASING");
|
||||
positionStateMapping.put(PositionStateEnum.STOPPED, "STOPPED");
|
||||
updateMapping(POSITION_STATE, positionStateMapping);
|
||||
}
|
||||
|
||||
public CompletableFuture<Integer> getCurrentPosition() {
|
||||
return CompletableFuture.completedFuture(convertPositionState(CURRENT_POSITION, openPosition, closedPosition));
|
||||
}
|
||||
|
||||
public CompletableFuture<PositionStateEnum> getPositionState() {
|
||||
return CompletableFuture
|
||||
.completedFuture(getKeyFromMapping(POSITION_STATE, positionStateMapping, PositionStateEnum.STOPPED));
|
||||
}
|
||||
|
||||
public CompletableFuture<Integer> getTargetPosition() {
|
||||
return CompletableFuture.completedFuture(convertPositionState(TARGET_POSITION, openPosition, closedPosition));
|
||||
}
|
||||
|
||||
@NonNullByDefault({})
|
||||
public CompletableFuture<Void> setTargetPosition(int value) {
|
||||
getItem(TARGET_POSITION, RollershutterItem.class)
|
||||
.ifPresent(item -> item.send(new PercentType(convertPosition(value, openPosition))));
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
public void subscribeCurrentPosition(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(CURRENT_POSITION, callback);
|
||||
}
|
||||
|
||||
public void subscribePositionState(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(POSITION_STATE, callback);
|
||||
}
|
||||
|
||||
public void subscribeTargetPosition(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(TARGET_POSITION, callback);
|
||||
}
|
||||
|
||||
public void unsubscribeCurrentPosition() {
|
||||
unsubscribe(CURRENT_POSITION);
|
||||
}
|
||||
|
||||
public void unsubscribePositionState() {
|
||||
unsubscribe(POSITION_STATE);
|
||||
}
|
||||
|
||||
public void unsubscribeTargetPosition() {
|
||||
unsubscribe(TARGET_POSITION);
|
||||
}
|
||||
|
||||
/**
|
||||
* convert/invert position of door/window/blinds.
|
||||
* openHAB Rollershutter is:
|
||||
* - completely open if position is 0%,
|
||||
* - completely closed if position is 100%.
|
||||
* HomeKit mapping has inverted mapping
|
||||
* From Specification: "For blinds/shades/awnings, a value of 0 indicates a position that permits the least light
|
||||
* and a value
|
||||
* of 100 indicates a position that allows most light.", i.e.
|
||||
* HomeKit Blinds is
|
||||
* - completely open if position is 100%,
|
||||
* - completely closed if position is 0%.
|
||||
*
|
||||
* As openHAB rollershutter item is typically used for window covering, the binding has by default inverting
|
||||
* mapping.
|
||||
* One can override this default behaviour with inverted="false/no" flag. in this cases, openHAB item value will be
|
||||
* sent to HomeKit with no changes.
|
||||
*
|
||||
* @param value source value
|
||||
* @return target value
|
||||
*/
|
||||
protected int convertPosition(int value, int openPosition) {
|
||||
return Math.abs(openPosition - value);
|
||||
}
|
||||
|
||||
protected int convertPositionState(HomekitCharacteristicType type, int openPosition, int closedPosition) {
|
||||
final @Nullable DecimalType value = getStateAs(type, PercentType.class);
|
||||
return value != null ? convertPosition(value.intValue(), openPosition) : closedPosition;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.items.GroupItem;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.library.items.ContactItem;
|
||||
import org.openhab.core.library.items.StringItem;
|
||||
import org.openhab.core.library.items.SwitchItem;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Wraps either a SwitchItem or a ContactItem, interpreting the open / closed states accordingly.
|
||||
*
|
||||
* @author Tim Harper - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class BooleanItemReader {
|
||||
private final Item item;
|
||||
private final OnOffType trueOnOffValue;
|
||||
private final OpenClosedType trueOpenClosedValue;
|
||||
private final Logger logger = LoggerFactory.getLogger(BooleanItemReader.class);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param item The item to read
|
||||
* @param trueOnOffValue If OnOffType, then consider true if this value
|
||||
* @param trueOpenClosedValue if OpenClosedType, then consider true if this value
|
||||
*/
|
||||
BooleanItemReader(Item item, OnOffType trueOnOffValue, OpenClosedType trueOpenClosedValue) {
|
||||
this.item = item;
|
||||
this.trueOnOffValue = trueOnOffValue;
|
||||
this.trueOpenClosedValue = trueOpenClosedValue;
|
||||
if (!(item instanceof SwitchItem) && !(item instanceof ContactItem) && !(item instanceof StringItem)) {
|
||||
logger.warn("Item {} is a {} instead of the expected SwitchItem, ContactItem or StringItem", item.getName(),
|
||||
item.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
boolean getValue() {
|
||||
final State state = item.getState();
|
||||
if (state instanceof OnOffType) {
|
||||
return state.equals(trueOnOffValue);
|
||||
} else if (state instanceof OpenClosedType) {
|
||||
return state.equals(trueOpenClosedValue);
|
||||
} else if (state instanceof StringType) {
|
||||
return state.toString().equalsIgnoreCase("Open") || state.toString().equalsIgnoreCase("Opened");
|
||||
} else {
|
||||
logger.debug("Unexpected item state, returning false. Item {}, State {}", item.getName(), state);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private OnOffType getOffValue(OnOffType onValue) {
|
||||
return onValue == OnOffType.ON ? OnOffType.OFF : OnOffType.ON;
|
||||
}
|
||||
|
||||
void setValue(Boolean value) {
|
||||
if (item instanceof SwitchItem) {
|
||||
((SwitchItem) item).send(value ? trueOnOffValue : getOffValue(trueOnOffValue));
|
||||
} else if (item instanceof GroupItem) {
|
||||
((GroupItem) item).send(value ? trueOnOffValue : getOffValue(trueOnOffValue));
|
||||
} else {
|
||||
logger.debug("Cannot set value {} for item {}. Only Switch and Group items are supported.", value, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import static org.openhab.io.homekit.internal.HomekitAccessoryType.*;
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.*;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.AbstractMap.SimpleEntry;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.items.GenericItem;
|
||||
import org.openhab.core.items.GroupItem;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.items.ItemRegistry;
|
||||
import org.openhab.core.items.Metadata;
|
||||
import org.openhab.core.items.MetadataKey;
|
||||
import org.openhab.core.items.MetadataRegistry;
|
||||
import org.openhab.core.library.items.ColorItem;
|
||||
import org.openhab.core.library.items.DimmerItem;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryType;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitCharacteristicType;
|
||||
import org.openhab.io.homekit.internal.HomekitException;
|
||||
import org.openhab.io.homekit.internal.HomekitOHItemProxy;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.github.hapjava.accessories.HomekitAccessory;
|
||||
import io.github.hapjava.characteristics.Characteristic;
|
||||
import io.github.hapjava.services.Service;
|
||||
|
||||
/**
|
||||
* Creates a HomekitAccessory for a given HomekitTaggedItem.
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
* @author Eugen Freiter - refactoring for optional characteristics
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@SuppressWarnings("deprecation")
|
||||
public class HomekitAccessoryFactory {
|
||||
private static final Logger logger = LoggerFactory.getLogger(HomekitAccessoryFactory.class);
|
||||
public final static String METADATA_KEY = "homekit"; // prefix for HomeKit meta information in items.xml
|
||||
|
||||
/** List of mandatory attributes for each accessory type. **/
|
||||
private final static Map<HomekitAccessoryType, HomekitCharacteristicType[]> MANDATORY_CHARACTERISTICS = new HashMap<HomekitAccessoryType, HomekitCharacteristicType[]>() {
|
||||
{
|
||||
put(LEAK_SENSOR, new HomekitCharacteristicType[] { LEAK_DETECTED_STATE });
|
||||
put(MOTION_SENSOR, new HomekitCharacteristicType[] { MOTION_DETECTED_STATE });
|
||||
put(OCCUPANCY_SENSOR, new HomekitCharacteristicType[] { OCCUPANCY_DETECTED_STATE });
|
||||
put(CONTACT_SENSOR, new HomekitCharacteristicType[] { CONTACT_SENSOR_STATE });
|
||||
put(SMOKE_SENSOR, new HomekitCharacteristicType[] { SMOKE_DETECTED_STATE });
|
||||
put(HUMIDITY_SENSOR, new HomekitCharacteristicType[] { RELATIVE_HUMIDITY });
|
||||
put(AIR_QUALITY_SENSOR, new HomekitCharacteristicType[] { AIR_QUALITY });
|
||||
put(SWITCH, new HomekitCharacteristicType[] { ON_STATE });
|
||||
put(CARBON_DIOXIDE_SENSOR, new HomekitCharacteristicType[] { CARBON_DIOXIDE_DETECTED_STATE });
|
||||
put(CARBON_MONOXIDE_SENSOR, new HomekitCharacteristicType[] { CARBON_MONOXIDE_DETECTED_STATE });
|
||||
put(WINDOW_COVERING, new HomekitCharacteristicType[] { TARGET_POSITION, CURRENT_POSITION, POSITION_STATE });
|
||||
put(LIGHTBULB, new HomekitCharacteristicType[] { ON_STATE });
|
||||
put(FAN, new HomekitCharacteristicType[] { ACTIVE_STATUS });
|
||||
put(LIGHT_SENSOR, new HomekitCharacteristicType[] { LIGHT_LEVEL });
|
||||
put(TEMPERATURE_SENSOR, new HomekitCharacteristicType[] { CURRENT_TEMPERATURE });
|
||||
put(THERMOSTAT, new HomekitCharacteristicType[] { CURRENT_HEATING_COOLING_STATE,
|
||||
TARGET_HEATING_COOLING_STATE, CURRENT_TEMPERATURE, TARGET_TEMPERATURE });
|
||||
put(LOCK, new HomekitCharacteristicType[] { LOCK_CURRENT_STATE, LOCK_TARGET_STATE });
|
||||
put(VALVE, new HomekitCharacteristicType[] { ACTIVE_STATUS, INUSE_STATUS });
|
||||
put(SECURITY_SYSTEM,
|
||||
new HomekitCharacteristicType[] { SECURITY_SYSTEM_CURRENT_STATE, SECURITY_SYSTEM_TARGET_STATE });
|
||||
put(OUTLET, new HomekitCharacteristicType[] { ON_STATE, INUSE_STATUS });
|
||||
put(SPEAKER, new HomekitCharacteristicType[] { MUTE });
|
||||
put(GARAGE_DOOR_OPENER,
|
||||
new HomekitCharacteristicType[] { CURRENT_DOOR_STATE, TARGET_DOOR_STATE, OBSTRUCTION_STATUS });
|
||||
put(HEATER_COOLER, new HomekitCharacteristicType[] { ACTIVE_STATUS, CURRENT_HEATER_COOLER_STATE,
|
||||
TARGET_HEATER_COOLER_STATE, CURRENT_TEMPERATURE });
|
||||
// LEGACY
|
||||
put(BLINDS, new HomekitCharacteristicType[] { TARGET_POSITION, CURRENT_POSITION, POSITION_STATE });
|
||||
put(WINDOW, new HomekitCharacteristicType[] { CURRENT_POSITION, TARGET_POSITION, POSITION_STATE });
|
||||
put(DOOR, new HomekitCharacteristicType[] { CURRENT_POSITION, TARGET_POSITION, POSITION_STATE });
|
||||
|
||||
put(OLD_HUMIDITY_SENSOR, new HomekitCharacteristicType[] { RELATIVE_HUMIDITY });
|
||||
put(OLD_DIMMABLE_LIGHTBULB, new HomekitCharacteristicType[] { ON_STATE });
|
||||
put(OLD_COLORFUL_LIGHTBULB, new HomekitCharacteristicType[] { ON_STATE });
|
||||
}
|
||||
};
|
||||
|
||||
/** List of service implementation for each accessory type. **/
|
||||
private final static Map<HomekitAccessoryType, Class<? extends AbstractHomekitAccessoryImpl>> SERVICE_IMPL_MAP = new HashMap<HomekitAccessoryType, Class<? extends AbstractHomekitAccessoryImpl>>() {
|
||||
{
|
||||
put(LEAK_SENSOR, HomekitLeakSensorImpl.class);
|
||||
put(MOTION_SENSOR, HomekitMotionSensorImpl.class);
|
||||
put(OCCUPANCY_SENSOR, HomekitOccupancySensorImpl.class);
|
||||
put(CONTACT_SENSOR, HomekitContactSensorImpl.class);
|
||||
put(SMOKE_SENSOR, HomekitSmokeSensorImpl.class);
|
||||
put(HUMIDITY_SENSOR, HomekitHumiditySensorImpl.class);
|
||||
put(AIR_QUALITY_SENSOR, HomekitAirQualitySensorImpl.class);
|
||||
put(SWITCH, HomekitSwitchImpl.class);
|
||||
put(CARBON_DIOXIDE_SENSOR, HomekitCarbonDioxideSensorImpl.class);
|
||||
put(CARBON_MONOXIDE_SENSOR, HomekitCarbonMonoxideSensorImpl.class);
|
||||
put(WINDOW_COVERING, HomekitWindowCoveringImpl.class);
|
||||
put(LIGHTBULB, HomekitLightbulbImpl.class);
|
||||
put(FAN, HomekitFanImpl.class);
|
||||
put(LIGHT_SENSOR, HomekitLightSensorImpl.class);
|
||||
put(TEMPERATURE_SENSOR, HomekitTemperatureSensorImpl.class);
|
||||
put(THERMOSTAT, HomekitThermostatImpl.class);
|
||||
put(LOCK, HomekitLockImpl.class);
|
||||
put(VALVE, HomekitValveImpl.class);
|
||||
put(SECURITY_SYSTEM, HomekitSecuritySystemImpl.class);
|
||||
put(OUTLET, HomekitOutletImpl.class);
|
||||
put(SPEAKER, HomekitSpeakerImpl.class);
|
||||
put(GARAGE_DOOR_OPENER, HomekitGarageDoorOpenerImpl.class);
|
||||
put(BLINDS, HomekitWindowCoveringImpl.class);
|
||||
put(DOOR, HomekitDoorImpl.class);
|
||||
put(WINDOW, HomekitWindowImpl.class);
|
||||
put(HEATER_COOLER, HomekitHeaterCoolerImpl.class);
|
||||
put(OLD_HUMIDITY_SENSOR, HomekitHumiditySensorImpl.class);
|
||||
put(OLD_DIMMABLE_LIGHTBULB, HomekitLightbulbImpl.class);
|
||||
put(OLD_COLORFUL_LIGHTBULB, HomekitLightbulbImpl.class);
|
||||
}
|
||||
};
|
||||
|
||||
/** mapping of legacy attributes to new attributes. **/
|
||||
private final static Map<HomekitCharacteristicType, HomekitCharacteristicType> LEGACY_CHARACTERISTICS_MAPPING = new HashMap<HomekitCharacteristicType, HomekitCharacteristicType>() {
|
||||
{
|
||||
put(OLD_CURRENT_HEATING_COOLING_STATE, CURRENT_HEATING_COOLING_STATE);
|
||||
put(OLD_TARGET_HEATING_COOLING_MODE, TARGET_HEATING_COOLING_STATE);
|
||||
put(OLD_TARGET_TEMPERATURE, TARGET_TEMPERATURE);
|
||||
put(OLD_BATTERY_LOW_STATUS, BATTERY_LOW_STATUS);
|
||||
put(VERY_OLD_TARGET_HEATING_COOLING_MODE, CURRENT_HEATING_COOLING_STATE);
|
||||
}
|
||||
};
|
||||
|
||||
/** list of optional implicit optional characteristics. mainly used for legacy accessory type */
|
||||
private final static Map<HomekitAccessoryType, HomekitCharacteristicType[]> IMPLICIT_OPTIONAL_CHARACTERISTICS = new HashMap<HomekitAccessoryType, HomekitCharacteristicType[]>() {
|
||||
{
|
||||
put(OLD_DIMMABLE_LIGHTBULB, new HomekitCharacteristicType[] { BRIGHTNESS });
|
||||
put(OLD_COLORFUL_LIGHTBULB, new HomekitCharacteristicType[] { HUE, SATURATION, BRIGHTNESS });
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* creates HomeKit accessory for a openhab item.
|
||||
*
|
||||
* @param taggedItem openhab item tagged as HomeKit item
|
||||
* @param metadataRegistry openhab metadata registry required to get item meta information
|
||||
* @param updater OH HomeKit update class that ensure the status sync between OH item and corresponding HomeKit
|
||||
* characteristic.
|
||||
* @param settings OH settings
|
||||
* @return HomeKit accessory
|
||||
* @throws HomekitException exception in case HomeKit accessory could not be created, e.g. due missing mandatory
|
||||
* characteristic
|
||||
*/
|
||||
@SuppressWarnings("null")
|
||||
public static HomekitAccessory create(HomekitTaggedItem taggedItem, MetadataRegistry metadataRegistry,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) throws HomekitException {
|
||||
final HomekitAccessoryType accessoryType = taggedItem.getAccessoryType();
|
||||
logger.trace("Constructing {} of accessory type {}", taggedItem.getName(), accessoryType.getTag());
|
||||
final List<HomekitTaggedItem> requiredCharacteristics = getMandatoryCharacteristics(taggedItem,
|
||||
metadataRegistry);
|
||||
final HomekitCharacteristicType[] mandatoryCharacteristics = MANDATORY_CHARACTERISTICS.get(accessoryType);
|
||||
if ((mandatoryCharacteristics != null) && (requiredCharacteristics.size() < mandatoryCharacteristics.length)) {
|
||||
logger.warn("Accessory of type {} must have following characteristics {}. Found only {}",
|
||||
accessoryType.getTag(), mandatoryCharacteristics, requiredCharacteristics);
|
||||
throw new HomekitException("Missing mandatory characteristics");
|
||||
}
|
||||
AbstractHomekitAccessoryImpl accessoryImpl;
|
||||
try {
|
||||
final @Nullable Class<? extends AbstractHomekitAccessoryImpl> accessoryImplClass = SERVICE_IMPL_MAP
|
||||
.get(accessoryType);
|
||||
if (accessoryImplClass != null) {
|
||||
accessoryImpl = accessoryImplClass
|
||||
.getConstructor(HomekitTaggedItem.class, List.class, HomekitAccessoryUpdater.class,
|
||||
HomekitSettings.class)
|
||||
.newInstance(taggedItem, requiredCharacteristics, updater, settings);
|
||||
addOptionalCharacteristics(taggedItem, accessoryImpl, metadataRegistry);
|
||||
return accessoryImpl;
|
||||
} else {
|
||||
logger.warn("Unsupported HomeKit type: {}", accessoryType.getTag());
|
||||
throw new HomekitException("Unsupported HomeKit type: " + accessoryType);
|
||||
}
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException
|
||||
| InvocationTargetException e) {
|
||||
logger.warn("Cannot instantiate accessory implementation for accessory {}", accessoryType.getTag(), e);
|
||||
throw new HomekitException("Cannot instantiate accessory implementation for accessory " + accessoryType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return HomeKit accessory types for a OH item based on meta data
|
||||
*
|
||||
* @param item OH item
|
||||
* @param metadataRegistry meta data registry
|
||||
* @return list of HomeKit accessory types and characteristics.
|
||||
*/
|
||||
public static List<Entry<HomekitAccessoryType, HomekitCharacteristicType>> getAccessoryTypes(Item item,
|
||||
MetadataRegistry metadataRegistry) {
|
||||
final List<Entry<HomekitAccessoryType, HomekitCharacteristicType>> accessories = new ArrayList<>();
|
||||
final @Nullable Metadata metadata = metadataRegistry.get(new MetadataKey(METADATA_KEY, item.getUID()));
|
||||
boolean legacyMode = metadata == null;
|
||||
String[] tags = !legacyMode ? metadata.getValue().split(",") : item.getTags().toArray(new String[0]); // fallback
|
||||
for (String tag : tags) {
|
||||
final String[] meta = tag.split("\\.");
|
||||
Optional<HomekitAccessoryType> accessoryType = HomekitAccessoryType.valueOfTag(meta[0].trim());
|
||||
if (accessoryType.isPresent()) { // it accessory, check for characteristic
|
||||
HomekitAccessoryType type = accessoryType.get();
|
||||
if ((legacyMode) && (type.equals(LIGHTBULB))) { // support old smart logic to convert Lighting to
|
||||
// DimmableLighting or ColorfulLighting depending on
|
||||
// item type
|
||||
if (item instanceof ColorItem) {
|
||||
type = OLD_COLORFUL_LIGHTBULB;
|
||||
} else if (item instanceof DimmerItem) {
|
||||
type = OLD_DIMMABLE_LIGHTBULB;
|
||||
}
|
||||
}
|
||||
if (meta.length > 1) {
|
||||
// it has characteristic as well
|
||||
accessories.add(new SimpleEntry<>(type,
|
||||
HomekitCharacteristicType.valueOfTag(meta[1].trim()).orElse(EMPTY)));
|
||||
} else {// it has no characteristic
|
||||
accessories.add(new SimpleEntry<>(type, EMPTY));
|
||||
}
|
||||
} else { // it is no accessory, so, maybe it is a characteristic
|
||||
HomekitCharacteristicType.valueOfTag(meta[0].trim())
|
||||
.ifPresent(c -> accessories.add(new SimpleEntry<>(DUMMY, c)));
|
||||
}
|
||||
}
|
||||
return accessories;
|
||||
}
|
||||
|
||||
public static @Nullable Map<String, Object> getItemConfiguration(Item item, MetadataRegistry metadataRegistry) {
|
||||
final @Nullable Metadata metadata = metadataRegistry.get(new MetadataKey(METADATA_KEY, item.getUID()));
|
||||
return metadata != null ? metadata.getConfiguration() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* return list of HomeKit relevant groups linked to an accessory
|
||||
*
|
||||
* @param item OH item
|
||||
* @param itemRegistry item registry
|
||||
* @param metadataRegistry metadata registry
|
||||
* @return list of relevant group items
|
||||
*/
|
||||
public static List<GroupItem> getAccessoryGroups(Item item, ItemRegistry itemRegistry,
|
||||
MetadataRegistry metadataRegistry) {
|
||||
return item.getGroupNames().stream().flatMap(name -> {
|
||||
final @Nullable Item groupItem = itemRegistry.get(name);
|
||||
if ((groupItem instanceof GroupItem) && ((GroupItem) groupItem).getBaseItem() == null) {
|
||||
return Stream.of((GroupItem) groupItem);
|
||||
} else {
|
||||
return Stream.empty();
|
||||
}
|
||||
}).filter(groupItem -> !getAccessoryTypes(groupItem, metadataRegistry).isEmpty()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* collect all mandatory characteristics for a given tagged item, e.g. collect all mandatory HomeKit items from a
|
||||
* GroupItem
|
||||
*
|
||||
* @param taggedItem HomeKit tagged item
|
||||
* @param metadataRegistry meta data registry
|
||||
* @return list of mandatory
|
||||
*/
|
||||
private static List<HomekitTaggedItem> getMandatoryCharacteristics(HomekitTaggedItem taggedItem,
|
||||
MetadataRegistry metadataRegistry) {
|
||||
List<HomekitTaggedItem> collectedCharacteristics = new ArrayList<>();
|
||||
if (taggedItem.isGroup()) {
|
||||
for (Item item : ((GroupItem) taggedItem.getItem()).getAllMembers()) {
|
||||
addMandatoryCharacteristics(taggedItem, collectedCharacteristics, item, metadataRegistry);
|
||||
}
|
||||
} else {
|
||||
addMandatoryCharacteristics(taggedItem, collectedCharacteristics, taggedItem.getItem(), metadataRegistry);
|
||||
}
|
||||
logger.trace("Mandatory characteristics for item {} characteristics {}", taggedItem.getName(),
|
||||
collectedCharacteristics);
|
||||
return collectedCharacteristics;
|
||||
}
|
||||
|
||||
/**
|
||||
* add mandatory HomeKit items for a given main item to a list of characteristics.
|
||||
* Main item is use only to determine, which characteristics are mandatory.
|
||||
* The characteristics are added to item.
|
||||
* e.g. mainItem could be a group tagged as "thermostat" and item could be item linked to the group and marked as
|
||||
* TargetTemperature
|
||||
*
|
||||
* @param mainItem main item
|
||||
* @param characteristics list of characteristics
|
||||
* @param item current item
|
||||
* @param metadataRegistry meta date registry
|
||||
*/
|
||||
private static void addMandatoryCharacteristics(HomekitTaggedItem mainItem, List<HomekitTaggedItem> characteristics,
|
||||
Item item, MetadataRegistry metadataRegistry) {
|
||||
// get list of mandatory characteristics
|
||||
HomekitCharacteristicType[] mandatoryCharacteristics = MANDATORY_CHARACTERISTICS
|
||||
.get(mainItem.getAccessoryType());
|
||||
if ((mandatoryCharacteristics == null) || (mandatoryCharacteristics.length == 0)) {
|
||||
// no mandatory characteristics linked to accessory type of mainItem. we are done
|
||||
return;
|
||||
}
|
||||
// check whether we adding characteristic to the main item, and if yes, use existing item proxy.
|
||||
// if we adding no to the main item (typical for groups), create new proxy item.
|
||||
final HomekitOHItemProxy itemProxy = mainItem.getItem().equals(item) ? mainItem.getProxyItem()
|
||||
: new HomekitOHItemProxy(item);
|
||||
// an item can have several tags, e.g. "ActiveStatus, InUse". we iterate here over all his tags
|
||||
for (Entry<HomekitAccessoryType, HomekitCharacteristicType> accessory : getAccessoryTypes(item,
|
||||
metadataRegistry)) {
|
||||
// if the item has only accessory tag, e.g. TemperatureSensor,
|
||||
// the we will link all mandatory characteristic to this item,
|
||||
// e.g. we will link CurrentTemperature in case of TemperatureSensor.
|
||||
if (isRootAccessory(accessory)) {
|
||||
Arrays.stream(mandatoryCharacteristics)
|
||||
.forEach(c -> characteristics.add(new HomekitTaggedItem(itemProxy, accessory.getKey(), c,
|
||||
mainItem.isGroup() ? (GroupItem) mainItem.getItem() : null,
|
||||
HomekitAccessoryFactory.getItemConfiguration(item, metadataRegistry))));
|
||||
} else {
|
||||
// item has characteristic tag on it, so, adding it as that characteristic.
|
||||
|
||||
// check whether it is a legacy characteristic, i.e. old tag was used, and replaced by a new one
|
||||
final HomekitCharacteristicType characteristic = legacyCheck(accessory.getValue());
|
||||
|
||||
// check whether it is a mandatory characteristic. optional will be added later by another method.
|
||||
if (isMandatoryCharacteristic(mainItem.getAccessoryType(), characteristic)) {
|
||||
characteristics.add(new HomekitTaggedItem(itemProxy, accessory.getKey(), characteristic,
|
||||
mainItem.isGroup() ? (GroupItem) mainItem.getItem() : null,
|
||||
HomekitAccessoryFactory.getItemConfiguration(item, metadataRegistry)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* add optional characteristic for given accessory.
|
||||
*
|
||||
* @param taggedItem main item
|
||||
* @param accessory accessory
|
||||
* @param metadataRegistry metadata registry
|
||||
*/
|
||||
private static void addOptionalCharacteristics(HomekitTaggedItem taggedItem, AbstractHomekitAccessoryImpl accessory,
|
||||
MetadataRegistry metadataRegistry) {
|
||||
Map<HomekitCharacteristicType, GenericItem> characteristics = getOptionalCharacteristics(
|
||||
accessory.getRootAccessory(), metadataRegistry);
|
||||
Service service = accessory.getPrimaryService();
|
||||
HashMap<String, HomekitOHItemProxy> proxyItems = new HashMap<>();
|
||||
proxyItems.put(taggedItem.getItem().getUID(), taggedItem.getProxyItem());
|
||||
// an accessory can have multiple optional characteristics. iterate over them.
|
||||
characteristics.forEach((type, item) -> {
|
||||
try {
|
||||
// check whether a proxyItem already exists, if not create one.
|
||||
final HomekitOHItemProxy proxyItem = proxyItems.computeIfAbsent(item.getUID(),
|
||||
k -> new HomekitOHItemProxy(item));
|
||||
final HomekitTaggedItem optionalItem = new HomekitTaggedItem(proxyItem,
|
||||
accessory.getRootAccessory().getAccessoryType(), type,
|
||||
accessory.getRootAccessory().getRootDeviceGroupItem(),
|
||||
getItemConfiguration(item, metadataRegistry));
|
||||
final Characteristic characteristic = HomekitCharacteristicFactory.createCharacteristic(optionalItem,
|
||||
accessory.getUpdater());
|
||||
// find the corresponding add method at service and call it.
|
||||
service.getClass().getMethod("addOptionalCharacteristic", characteristic.getClass()).invoke(service,
|
||||
characteristic);
|
||||
accessory.addCharacteristic(optionalItem);
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | HomekitException e) {
|
||||
logger.warn("Unsupported optional HomeKit characteristic: service type {}, characteristic type {}",
|
||||
service.getType(), type.getTag());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* collect optional HomeKit characteristics for a OH item.
|
||||
*
|
||||
* @param taggedItem main OH item
|
||||
* @param metadataRegistry OH metadata registry
|
||||
* @return a map with characteristics and corresponding OH items
|
||||
*/
|
||||
private static Map<HomekitCharacteristicType, GenericItem> getOptionalCharacteristics(HomekitTaggedItem taggedItem,
|
||||
MetadataRegistry metadataRegistry) {
|
||||
Map<HomekitCharacteristicType, GenericItem> characteristicItems = new HashMap<>();
|
||||
if (taggedItem.isGroup()) {
|
||||
GroupItem groupItem = (GroupItem) taggedItem.getItem();
|
||||
groupItem.getMembers().forEach(item -> getAccessoryTypes(item, metadataRegistry).stream()
|
||||
.filter(c -> !isRootAccessory(c))
|
||||
.filter(c -> !isMandatoryCharacteristic(taggedItem.getAccessoryType(), legacyCheck(c.getValue())))
|
||||
.forEach(characteristic -> characteristicItems.put(legacyCheck(characteristic.getValue()),
|
||||
(GenericItem) item)));
|
||||
} else {
|
||||
getAccessoryTypes(taggedItem.getItem(), metadataRegistry).stream().filter(c -> !isRootAccessory(c))
|
||||
.filter(c -> !isMandatoryCharacteristic(taggedItem.getAccessoryType(), legacyCheck(c.getValue())))
|
||||
.forEach(characteristic -> characteristicItems.put(legacyCheck(characteristic.getValue()),
|
||||
(GenericItem) taggedItem.getItem()));
|
||||
final HomekitCharacteristicType[] implicitOptionalCharacteristics = IMPLICIT_OPTIONAL_CHARACTERISTICS
|
||||
.get(taggedItem.getAccessoryType());
|
||||
if (implicitOptionalCharacteristics != null) {
|
||||
Arrays.stream(implicitOptionalCharacteristics)
|
||||
.filter(c -> !isMandatoryCharacteristic(taggedItem.getAccessoryType(), c))
|
||||
.forEach(characteristic -> characteristicItems.put(legacyCheck(characteristic),
|
||||
(GenericItem) taggedItem.getItem()));
|
||||
}
|
||||
}
|
||||
logger.trace("Optional characteristics for item {} characteristics {}", taggedItem.getName(),
|
||||
characteristicItems);
|
||||
return Collections.unmodifiableMap(characteristicItems);
|
||||
}
|
||||
|
||||
/**
|
||||
* return true is characteristic is a mandatory characteristic for the accessory.
|
||||
*
|
||||
* @param accessory accessory
|
||||
* @param characteristic characteristic
|
||||
* @return true if characteristic is mandatory, false if not mandatory
|
||||
*/
|
||||
private static boolean isMandatoryCharacteristic(HomekitAccessoryType accessory,
|
||||
HomekitCharacteristicType characteristic) {
|
||||
return MANDATORY_CHARACTERISTICS.containsKey(accessory)
|
||||
&& Arrays.asList(MANDATORY_CHARACTERISTICS.get(accessory)).contains(characteristic);
|
||||
}
|
||||
|
||||
/**
|
||||
* check whether accessory is root accessory, i.e. without characteristic tag.
|
||||
*
|
||||
* @param accessory accessory
|
||||
* @return true if accessory has not characteristic.
|
||||
*/
|
||||
private static boolean isRootAccessory(Entry<HomekitAccessoryType, HomekitCharacteristicType> accessory) {
|
||||
return ((accessory.getValue() == null) || (accessory.getValue() == EMPTY));
|
||||
}
|
||||
|
||||
/**
|
||||
* check whether it is legacy characteristic and return new name in such case. otherwise return the input parameter
|
||||
* unchanged.
|
||||
*
|
||||
* @param characteristicType characteristic to check
|
||||
* @return new characteristic type
|
||||
*/
|
||||
private static HomekitCharacteristicType legacyCheck(HomekitCharacteristicType characteristicType) {
|
||||
if (LEGACY_CHARACTERISTICS_MAPPING.containsKey(characteristicType))
|
||||
return LEGACY_CHARACTERISTICS_MAPPING.get(characteristicType);
|
||||
return characteristicType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.AIR_QUALITY;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
|
||||
import io.github.hapjava.accessories.AirQualityAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.characteristics.impl.airquality.AirQualityEnum;
|
||||
import io.github.hapjava.services.impl.AirQualityService;
|
||||
|
||||
/**
|
||||
* Air Quality sensor accessory.
|
||||
*
|
||||
* @author Eugen Freiter - Initial contribution
|
||||
*/
|
||||
public class HomekitAirQualitySensorImpl extends AbstractHomekitAccessoryImpl implements AirQualityAccessory {
|
||||
private final Map<AirQualityEnum, String> qualityStateMapping = new EnumMap<AirQualityEnum, String>(
|
||||
AirQualityEnum.class) {
|
||||
{
|
||||
put(AirQualityEnum.UNKNOWN, "UNKNOWN");
|
||||
put(AirQualityEnum.EXCELLENT, "EXCELLENT");
|
||||
put(AirQualityEnum.GOOD, "GOOD");
|
||||
put(AirQualityEnum.FAIR, "FAIR");
|
||||
put(AirQualityEnum.INFERIOR, "INFERIOR");
|
||||
put(AirQualityEnum.POOR, "POOR");
|
||||
}
|
||||
};
|
||||
|
||||
public HomekitAirQualitySensorImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) throws IncompleteAccessoryException {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
updateMapping(AIR_QUALITY, qualityStateMapping);
|
||||
getServices().add(new AirQualityService(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<AirQualityEnum> getAirQuality() {
|
||||
return CompletableFuture
|
||||
.completedFuture(getKeyFromMapping(AIR_QUALITY, qualityStateMapping, AirQualityEnum.UNKNOWN));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeAirQuality(final HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(AIR_QUALITY, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeAirQuality() {
|
||||
unsubscribe(AIR_QUALITY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.CARBON_DIOXIDE_DETECTED_STATE;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
|
||||
import io.github.hapjava.accessories.CarbonDioxideSensorAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.characteristics.impl.carbondioxidesensor.CarbonDioxideDetectedEnum;
|
||||
import io.github.hapjava.services.impl.CarbonDioxideSensorService;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Cody Cutrer - Initial contribution
|
||||
*/
|
||||
public class HomekitCarbonDioxideSensorImpl extends AbstractHomekitAccessoryImpl
|
||||
implements CarbonDioxideSensorAccessory {
|
||||
private final BooleanItemReader carbonDioxideDetectedReader;
|
||||
|
||||
public HomekitCarbonDioxideSensorImpl(HomekitTaggedItem taggedItem,
|
||||
List<HomekitTaggedItem> mandatoryCharacteristics, HomekitAccessoryUpdater updater, HomekitSettings settings)
|
||||
throws IncompleteAccessoryException {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
carbonDioxideDetectedReader = createBooleanReader(CARBON_DIOXIDE_DETECTED_STATE, OnOffType.ON,
|
||||
OpenClosedType.OPEN);
|
||||
getServices().add(new CarbonDioxideSensorService(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<CarbonDioxideDetectedEnum> getCarbonDioxideDetectedState() {
|
||||
return CompletableFuture
|
||||
.completedFuture(carbonDioxideDetectedReader.getValue() ? CarbonDioxideDetectedEnum.ABNORMAL
|
||||
: CarbonDioxideDetectedEnum.NORMAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeCarbonDioxideDetectedState(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(CARBON_DIOXIDE_DETECTED_STATE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeCarbonDioxideDetectedState() {
|
||||
unsubscribe(CARBON_DIOXIDE_DETECTED_STATE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.CARBON_MONOXIDE_DETECTED_STATE;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
|
||||
import io.github.hapjava.accessories.CarbonMonoxideSensorAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.characteristics.impl.carbonmonoxidesensor.CarbonMonoxideDetectedEnum;
|
||||
import io.github.hapjava.services.impl.CarbonMonoxideSensorService;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Cody Cutrer - Initial contribution
|
||||
*/
|
||||
public class HomekitCarbonMonoxideSensorImpl extends AbstractHomekitAccessoryImpl
|
||||
implements CarbonMonoxideSensorAccessory {
|
||||
private final BooleanItemReader carbonMonoxideDetectedReader;
|
||||
|
||||
public HomekitCarbonMonoxideSensorImpl(HomekitTaggedItem taggedItem,
|
||||
List<HomekitTaggedItem> mandatoryCharacteristics, HomekitAccessoryUpdater updater, HomekitSettings settings)
|
||||
throws IncompleteAccessoryException {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
carbonMonoxideDetectedReader = createBooleanReader(CARBON_MONOXIDE_DETECTED_STATE, OnOffType.ON,
|
||||
OpenClosedType.OPEN);
|
||||
getServices().add(new CarbonMonoxideSensorService(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<CarbonMonoxideDetectedEnum> getCarbonMonoxideDetectedState() {
|
||||
return CompletableFuture
|
||||
.completedFuture(carbonMonoxideDetectedReader.getValue() ? CarbonMonoxideDetectedEnum.ABNORMAL
|
||||
: CarbonMonoxideDetectedEnum.NORMAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeCarbonMonoxideDetectedState(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(CARBON_MONOXIDE_DETECTED_STATE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeCarbonMonoxideDetectedState() {
|
||||
unsubscribe(CARBON_MONOXIDE_DETECTED_STATE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,631 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.items.GenericItem;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.library.items.ColorItem;
|
||||
import org.openhab.core.library.items.DimmerItem;
|
||||
import org.openhab.core.library.items.NumberItem;
|
||||
import org.openhab.core.library.items.SwitchItem;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitCharacteristicType;
|
||||
import org.openhab.io.homekit.internal.HomekitCommandType;
|
||||
import org.openhab.io.homekit.internal.HomekitException;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.github.hapjava.characteristics.Characteristic;
|
||||
import io.github.hapjava.characteristics.CharacteristicEnum;
|
||||
import io.github.hapjava.characteristics.ExceptionalConsumer;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.characteristics.impl.airquality.NitrogenDioxideDensityCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.airquality.OzoneDensityCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.airquality.PM10DensityCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.airquality.PM25DensityCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.airquality.SulphurDioxideDensityCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.airquality.VOCDensityCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.audio.VolumeCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.battery.StatusLowBatteryCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.battery.StatusLowBatteryEnum;
|
||||
import io.github.hapjava.characteristics.impl.carbondioxidesensor.CarbonDioxideLevelCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.carbondioxidesensor.CarbonDioxidePeakLevelCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.carbonmonoxidesensor.CarbonMonoxideLevelCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.carbonmonoxidesensor.CarbonMonoxidePeakLevelCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.common.NameCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.common.ObstructionDetectedCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.common.StatusActiveCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.common.StatusFaultCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.common.StatusFaultEnum;
|
||||
import io.github.hapjava.characteristics.impl.common.StatusTamperedCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.common.StatusTamperedEnum;
|
||||
import io.github.hapjava.characteristics.impl.fan.*;
|
||||
import io.github.hapjava.characteristics.impl.lightbulb.BrightnessCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.lightbulb.ColorTemperatureCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.lightbulb.HueCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.lightbulb.SaturationCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.thermostat.CoolingThresholdTemperatureCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.thermostat.HeatingThresholdTemperatureCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.valve.RemainingDurationCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.valve.SetDurationCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.windowcovering.CurrentHorizontalTiltAngleCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.windowcovering.CurrentVerticalTiltAngleCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.windowcovering.HoldPositionCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.windowcovering.TargetHorizontalTiltAngleCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.windowcovering.TargetVerticalTiltAngleCharacteristic;
|
||||
|
||||
/**
|
||||
* Creates a optional characteristics .
|
||||
*
|
||||
* @author Eugen Freiter - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@SuppressWarnings("deprecation")
|
||||
public class HomekitCharacteristicFactory {
|
||||
private static final Logger logger = LoggerFactory.getLogger(HomekitCharacteristicFactory.class);
|
||||
|
||||
// List of optional characteristics and corresponding method to create them.
|
||||
private final static Map<HomekitCharacteristicType, BiFunction<HomekitTaggedItem, HomekitAccessoryUpdater, Characteristic>> optional = new HashMap<HomekitCharacteristicType, BiFunction<HomekitTaggedItem, HomekitAccessoryUpdater, Characteristic>>() {
|
||||
{
|
||||
put(NAME, HomekitCharacteristicFactory::createNameCharacteristic);
|
||||
put(BATTERY_LOW_STATUS, HomekitCharacteristicFactory::createStatusLowBatteryCharacteristic);
|
||||
put(FAULT_STATUS, HomekitCharacteristicFactory::createStatusFaultCharacteristic);
|
||||
put(TAMPERED_STATUS, HomekitCharacteristicFactory::createStatusTamperedCharacteristic);
|
||||
put(ACTIVE_STATUS, HomekitCharacteristicFactory::createStatusActiveCharacteristic);
|
||||
put(CARBON_MONOXIDE_LEVEL, HomekitCharacteristicFactory::createCarbonMonoxideLevelCharacteristic);
|
||||
put(CARBON_MONOXIDE_PEAK_LEVEL, HomekitCharacteristicFactory::createCarbonMonoxidePeakLevelCharacteristic);
|
||||
put(CARBON_DIOXIDE_LEVEL, HomekitCharacteristicFactory::createCarbonDioxideLevelCharacteristic);
|
||||
put(CARBON_DIOXIDE_PEAK_LEVEL, HomekitCharacteristicFactory::createCarbonDioxidePeakLevelCharacteristic);
|
||||
put(HOLD_POSITION, HomekitCharacteristicFactory::createHoldPositionCharacteristic);
|
||||
put(OBSTRUCTION_STATUS, HomekitCharacteristicFactory::createObstructionDetectedCharacteristic);
|
||||
put(CURRENT_HORIZONTAL_TILT_ANGLE,
|
||||
HomekitCharacteristicFactory::createCurrentHorizontalTiltAngleCharacteristic);
|
||||
put(CURRENT_VERTICAL_TILT_ANGLE,
|
||||
HomekitCharacteristicFactory::createCurrentVerticalTiltAngleCharacteristic);
|
||||
put(TARGET_HORIZONTAL_TILT_ANGLE,
|
||||
HomekitCharacteristicFactory::createTargetHorizontalTiltAngleCharacteristic);
|
||||
put(TARGET_VERTICAL_TILT_ANGLE, HomekitCharacteristicFactory::createTargetVerticalTiltAngleCharacteristic);
|
||||
put(HUE, HomekitCharacteristicFactory::createHueCharacteristic);
|
||||
put(BRIGHTNESS, HomekitCharacteristicFactory::createBrightnessCharacteristic);
|
||||
put(SATURATION, HomekitCharacteristicFactory::createSaturationCharacteristic);
|
||||
put(COLOR_TEMPERATURE, HomekitCharacteristicFactory::createColorTemperatureCharacteristic);
|
||||
put(CURRENT_FAN_STATE, HomekitCharacteristicFactory::createCurrentFanStateCharacteristic);
|
||||
put(TARGET_FAN_STATE, HomekitCharacteristicFactory::createTargetFanStateCharacteristic);
|
||||
put(ROTATION_DIRECTION, HomekitCharacteristicFactory::createRotationDirectionCharacteristic);
|
||||
put(ROTATION_SPEED, HomekitCharacteristicFactory::createRotationSpeedCharacteristic);
|
||||
put(SWING_MODE, HomekitCharacteristicFactory::createSwingModeCharacteristic);
|
||||
put(LOCK_CONTROL, HomekitCharacteristicFactory::createLockPhysicalControlsCharacteristic);
|
||||
put(DURATION, HomekitCharacteristicFactory::createDurationCharacteristic);
|
||||
put(VOLUME, HomekitCharacteristicFactory::createVolumeCharacteristic);
|
||||
put(COOLING_THRESHOLD_TEMPERATURE, HomekitCharacteristicFactory::createCoolingThresholdCharacteristic);
|
||||
put(HEATING_THRESHOLD_TEMPERATURE, HomekitCharacteristicFactory::createHeatingThresholdCharacteristic);
|
||||
put(REMAINING_DURATION, HomekitCharacteristicFactory::createRemainingDurationCharacteristic);
|
||||
put(OZONE_DENSITY, HomekitCharacteristicFactory::createOzoneDensityCharacteristic);
|
||||
put(NITROGEN_DIOXIDE_DENSITY, HomekitCharacteristicFactory::createNitrogenDioxideDensityCharacteristic);
|
||||
put(SULPHUR_DIOXIDE_DENSITY, HomekitCharacteristicFactory::createSulphurDioxideDensityCharacteristic);
|
||||
put(PM25_DENSITY, HomekitCharacteristicFactory::createPM25DensityCharacteristic);
|
||||
put(PM10_DENSITY, HomekitCharacteristicFactory::createPM10DensityCharacteristic);
|
||||
put(VOC_DENSITY, HomekitCharacteristicFactory::createVOCDensityCharacteristic);
|
||||
// LEGACY
|
||||
put(OLD_BATTERY_LOW_STATUS, HomekitCharacteristicFactory::createStatusLowBatteryCharacteristic);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* create optional HomeKit characteristic
|
||||
*
|
||||
* @param item corresponding OH item
|
||||
* @param updater update to keep OH item and HomeKit characteristic in sync
|
||||
* @return HomeKit characteristic
|
||||
*/
|
||||
public static Characteristic createCharacteristic(HomekitTaggedItem item, HomekitAccessoryUpdater updater)
|
||||
throws HomekitException {
|
||||
final @Nullable HomekitCharacteristicType type = item.getCharacteristicType();
|
||||
logger.trace("CreateCharacteristic, type {} item {}", type, item);
|
||||
if (optional.containsKey(type)) {
|
||||
return optional.get(type).apply(item, updater);
|
||||
}
|
||||
logger.warn("Unsupported optional characteristic. Accessory type {}, characteristic type {}",
|
||||
item.getAccessoryType(), type);
|
||||
throw new HomekitException("Unsupported optional characteristic. Characteristic type \"" + type + "\"");
|
||||
}
|
||||
|
||||
// METHODS TO CREATE SINGLE CHARACTERISTIC FROM OH ITEM
|
||||
|
||||
// supporting methods
|
||||
private static <T extends CharacteristicEnum> CompletableFuture<T> getEnumFromItem(HomekitTaggedItem item,
|
||||
T offEnum, T onEnum, T defaultEnum) {
|
||||
final State state = item.getItem().getState();
|
||||
if (state instanceof OnOffType) {
|
||||
return CompletableFuture.completedFuture(state.equals(OnOffType.OFF) ? offEnum : onEnum);
|
||||
} else if (state instanceof OpenClosedType) {
|
||||
return CompletableFuture.completedFuture(state.equals(OpenClosedType.CLOSED) ? offEnum : onEnum);
|
||||
} else if (state instanceof DecimalType) {
|
||||
return CompletableFuture.completedFuture(((DecimalType) state).intValue() == 0 ? offEnum : onEnum);
|
||||
} else if (state instanceof UnDefType) {
|
||||
return CompletableFuture.completedFuture(defaultEnum);
|
||||
}
|
||||
logger.warn(
|
||||
"Item state {} is not supported. Only OnOffType,OpenClosedType and Decimal (0/1) are supported. Ignore item {}",
|
||||
state, item.getName());
|
||||
return CompletableFuture.completedFuture(defaultEnum);
|
||||
}
|
||||
|
||||
private static void setValueFromEnum(HomekitTaggedItem taggedItem, CharacteristicEnum value,
|
||||
CharacteristicEnum offEnum, CharacteristicEnum onEnum) {
|
||||
if (taggedItem.getItem() instanceof SwitchItem) {
|
||||
if (value.equals(offEnum)) {
|
||||
((SwitchItem) taggedItem.getItem()).send(OnOffType.OFF);
|
||||
} else if (value.equals(onEnum)) {
|
||||
((SwitchItem) taggedItem.getItem()).send(OnOffType.ON);
|
||||
} else {
|
||||
logger.warn("Enum value {} is not supported. Only following values are supported: {},{}", value,
|
||||
offEnum, onEnum);
|
||||
}
|
||||
} else if (taggedItem.getItem() instanceof NumberItem) {
|
||||
((NumberItem) taggedItem.getItem()).send(new DecimalType(value.getCode()));
|
||||
} else {
|
||||
logger.warn("Item type {} is not supported. Only Switch and Number item types are supported.",
|
||||
taggedItem.getItem().getType());
|
||||
}
|
||||
}
|
||||
|
||||
private static int getIntFromItem(HomekitTaggedItem taggedItem) {
|
||||
int value = 0;
|
||||
final State state = taggedItem.getItem().getState();
|
||||
if (state instanceof PercentType) {
|
||||
value = ((PercentType) state).intValue();
|
||||
} else if (state instanceof DecimalType) {
|
||||
value = ((DecimalType) state).intValue();
|
||||
} else if (state instanceof UnDefType) {
|
||||
logger.debug("Item state {} is UNDEF {}.", state, taggedItem.getName());
|
||||
} else {
|
||||
logger.warn(
|
||||
"Item state {} is not supported for {}. Only PercentType and DecimalType (0/100) are supported.",
|
||||
state, taggedItem.getName());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static Supplier<CompletableFuture<Integer>> getIntSupplier(HomekitTaggedItem taggedItem) {
|
||||
return () -> CompletableFuture.completedFuture(getIntFromItem(taggedItem));
|
||||
}
|
||||
|
||||
private static ExceptionalConsumer<Integer> setIntConsumer(HomekitTaggedItem taggedItem) {
|
||||
return (value) -> {
|
||||
if (taggedItem.getItem() instanceof NumberItem) {
|
||||
((NumberItem) taggedItem.getItem()).send(new DecimalType(value));
|
||||
} else {
|
||||
logger.warn("Item type {} is not supported for {}. Only NumberItem is supported.",
|
||||
taggedItem.getItem().getType(), taggedItem.getName());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static ExceptionalConsumer<Integer> setPercentConsumer(HomekitTaggedItem taggedItem) {
|
||||
return (value) -> {
|
||||
if (taggedItem.getItem() instanceof NumberItem) {
|
||||
((NumberItem) taggedItem.getItem()).send(new DecimalType(value));
|
||||
} else if (taggedItem.getItem() instanceof DimmerItem) {
|
||||
((DimmerItem) taggedItem.getItem()).send(new PercentType(value));
|
||||
} else {
|
||||
logger.warn("Item type {} is not supported for {}. Only DimmerItem and NumberItem are supported.",
|
||||
taggedItem.getItem().getType(), taggedItem.getName());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static Supplier<CompletableFuture<Double>> getDoubleSupplier(HomekitTaggedItem taggedItem) {
|
||||
return () -> {
|
||||
final @Nullable DecimalType value = taggedItem.getItem().getStateAs(DecimalType.class);
|
||||
return CompletableFuture.completedFuture(value != null ? value.doubleValue() : 0.0);
|
||||
};
|
||||
}
|
||||
|
||||
private static ExceptionalConsumer<Double> setDoubleConsumer(HomekitTaggedItem taggedItem) {
|
||||
return (value) -> {
|
||||
if (taggedItem.getItem() instanceof NumberItem) {
|
||||
((NumberItem) taggedItem.getItem()).send(new DecimalType(value));
|
||||
} else {
|
||||
logger.warn("Item type {} is not supported for {}. Only Number type is supported.",
|
||||
taggedItem.getItem().getType(), taggedItem.getName());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected static Consumer<HomekitCharacteristicChangeCallback> getSubscriber(HomekitTaggedItem taggedItem,
|
||||
HomekitCharacteristicType key, HomekitAccessoryUpdater updater) {
|
||||
return (callback) -> updater.subscribe((GenericItem) taggedItem.getItem(), key.getTag(), callback);
|
||||
}
|
||||
|
||||
protected static Runnable getUnsubscriber(HomekitTaggedItem taggedItem, HomekitCharacteristicType key,
|
||||
HomekitAccessoryUpdater updater) {
|
||||
return () -> updater.unsubscribe((GenericItem) taggedItem.getItem(), key.getTag());
|
||||
}
|
||||
|
||||
// create method for characteristic
|
||||
private static StatusLowBatteryCharacteristic createStatusLowBatteryCharacteristic(HomekitTaggedItem taggedItem,
|
||||
HomekitAccessoryUpdater updater) {
|
||||
return new StatusLowBatteryCharacteristic(
|
||||
() -> getEnumFromItem(taggedItem, StatusLowBatteryEnum.NORMAL, StatusLowBatteryEnum.LOW,
|
||||
StatusLowBatteryEnum.NORMAL),
|
||||
getSubscriber(taggedItem, BATTERY_LOW_STATUS, updater),
|
||||
getUnsubscriber(taggedItem, BATTERY_LOW_STATUS, updater));
|
||||
}
|
||||
|
||||
private static StatusFaultCharacteristic createStatusFaultCharacteristic(HomekitTaggedItem taggedItem,
|
||||
HomekitAccessoryUpdater updater) {
|
||||
return new StatusFaultCharacteristic(
|
||||
() -> getEnumFromItem(taggedItem, StatusFaultEnum.NO_FAULT, StatusFaultEnum.GENERAL_FAULT,
|
||||
StatusFaultEnum.NO_FAULT),
|
||||
getSubscriber(taggedItem, FAULT_STATUS, updater), getUnsubscriber(taggedItem, FAULT_STATUS, updater));
|
||||
}
|
||||
|
||||
private static StatusTamperedCharacteristic createStatusTamperedCharacteristic(HomekitTaggedItem taggedItem,
|
||||
HomekitAccessoryUpdater updater) {
|
||||
return new StatusTamperedCharacteristic(
|
||||
() -> getEnumFromItem(taggedItem, StatusTamperedEnum.NOT_TAMPERED, StatusTamperedEnum.TAMPERED,
|
||||
StatusTamperedEnum.NOT_TAMPERED),
|
||||
getSubscriber(taggedItem, TAMPERED_STATUS, updater),
|
||||
getUnsubscriber(taggedItem, TAMPERED_STATUS, updater));
|
||||
}
|
||||
|
||||
private static ObstructionDetectedCharacteristic createObstructionDetectedCharacteristic(
|
||||
HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
|
||||
return new ObstructionDetectedCharacteristic(
|
||||
() -> CompletableFuture.completedFuture(taggedItem.getItem().getState() == OnOffType.ON
|
||||
|| taggedItem.getItem().getState() == OpenClosedType.OPEN),
|
||||
getSubscriber(taggedItem, OBSTRUCTION_STATUS, updater),
|
||||
getUnsubscriber(taggedItem, OBSTRUCTION_STATUS, updater));
|
||||
}
|
||||
|
||||
private static StatusActiveCharacteristic createStatusActiveCharacteristic(HomekitTaggedItem taggedItem,
|
||||
HomekitAccessoryUpdater updater) {
|
||||
return new StatusActiveCharacteristic(
|
||||
() -> CompletableFuture.completedFuture(taggedItem.getItem().getState() == OnOffType.ON
|
||||
|| taggedItem.getItem().getState() == OpenClosedType.OPEN),
|
||||
getSubscriber(taggedItem, ACTIVE_STATUS, updater), getUnsubscriber(taggedItem, ACTIVE_STATUS, updater));
|
||||
}
|
||||
|
||||
private static NameCharacteristic createNameCharacteristic(HomekitTaggedItem taggedItem,
|
||||
HomekitAccessoryUpdater updater) {
|
||||
return new NameCharacteristic(() -> {
|
||||
final State state = taggedItem.getItem().getState();
|
||||
return CompletableFuture.completedFuture(state instanceof UnDefType ? "" : state.toString());
|
||||
});
|
||||
}
|
||||
|
||||
private static HoldPositionCharacteristic createHoldPositionCharacteristic(HomekitTaggedItem taggedItem,
|
||||
HomekitAccessoryUpdater updater) {
|
||||
return new HoldPositionCharacteristic(value -> ((SwitchItem) taggedItem.getItem()).send(OnOffType.from(value)));
|
||||
}
|
||||
|
||||
private static CarbonMonoxideLevelCharacteristic createCarbonMonoxideLevelCharacteristic(
|
||||
HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
|
||||
return new CarbonMonoxideLevelCharacteristic(getDoubleSupplier(taggedItem),
|
||||
getSubscriber(taggedItem, CARBON_DIOXIDE_LEVEL, updater),
|
||||
getUnsubscriber(taggedItem, CARBON_DIOXIDE_LEVEL, updater));
|
||||
}
|
||||
|
||||
private static CarbonMonoxidePeakLevelCharacteristic createCarbonMonoxidePeakLevelCharacteristic(
|
||||
HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
|
||||
return new CarbonMonoxidePeakLevelCharacteristic(getDoubleSupplier(taggedItem),
|
||||
getSubscriber(taggedItem, CARBON_DIOXIDE_PEAK_LEVEL, updater),
|
||||
getUnsubscriber(taggedItem, CARBON_DIOXIDE_PEAK_LEVEL, updater));
|
||||
}
|
||||
|
||||
private static CarbonDioxideLevelCharacteristic createCarbonDioxideLevelCharacteristic(HomekitTaggedItem taggedItem,
|
||||
HomekitAccessoryUpdater updater) {
|
||||
return new CarbonDioxideLevelCharacteristic(getDoubleSupplier(taggedItem),
|
||||
getSubscriber(taggedItem, CARBON_MONOXIDE_LEVEL, updater),
|
||||
getUnsubscriber(taggedItem, CARBON_MONOXIDE_LEVEL, updater));
|
||||
}
|
||||
|
||||
private static CarbonDioxidePeakLevelCharacteristic createCarbonDioxidePeakLevelCharacteristic(
|
||||
HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
|
||||
return new CarbonDioxidePeakLevelCharacteristic(getDoubleSupplier(taggedItem),
|
||||
getSubscriber(taggedItem, CARBON_MONOXIDE_PEAK_LEVEL, updater),
|
||||
getUnsubscriber(taggedItem, CARBON_MONOXIDE_PEAK_LEVEL, updater));
|
||||
}
|
||||
|
||||
private static CurrentHorizontalTiltAngleCharacteristic createCurrentHorizontalTiltAngleCharacteristic(
|
||||
HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
|
||||
return new CurrentHorizontalTiltAngleCharacteristic(getIntSupplier(taggedItem),
|
||||
getSubscriber(taggedItem, CURRENT_HORIZONTAL_TILT_ANGLE, updater),
|
||||
getUnsubscriber(taggedItem, CURRENT_HORIZONTAL_TILT_ANGLE, updater));
|
||||
}
|
||||
|
||||
private static CurrentVerticalTiltAngleCharacteristic createCurrentVerticalTiltAngleCharacteristic(
|
||||
HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
|
||||
return new CurrentVerticalTiltAngleCharacteristic(getIntSupplier(taggedItem),
|
||||
getSubscriber(taggedItem, CURRENT_VERTICAL_TILT_ANGLE, updater),
|
||||
getUnsubscriber(taggedItem, CURRENT_VERTICAL_TILT_ANGLE, updater));
|
||||
}
|
||||
|
||||
private static TargetHorizontalTiltAngleCharacteristic createTargetHorizontalTiltAngleCharacteristic(
|
||||
HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
|
||||
return new TargetHorizontalTiltAngleCharacteristic(getIntSupplier(taggedItem), setIntConsumer(taggedItem),
|
||||
getSubscriber(taggedItem, TARGET_HORIZONTAL_TILT_ANGLE, updater),
|
||||
getUnsubscriber(taggedItem, TARGET_HORIZONTAL_TILT_ANGLE, updater));
|
||||
}
|
||||
|
||||
private static TargetVerticalTiltAngleCharacteristic createTargetVerticalTiltAngleCharacteristic(
|
||||
HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
|
||||
return new TargetVerticalTiltAngleCharacteristic(getIntSupplier(taggedItem), setIntConsumer(taggedItem),
|
||||
getSubscriber(taggedItem, TARGET_HORIZONTAL_TILT_ANGLE, updater),
|
||||
getUnsubscriber(taggedItem, TARGET_HORIZONTAL_TILT_ANGLE, updater));
|
||||
}
|
||||
|
||||
private static HueCharacteristic createHueCharacteristic(HomekitTaggedItem taggedItem,
|
||||
HomekitAccessoryUpdater updater) {
|
||||
return new HueCharacteristic(() -> {
|
||||
double value = 0.0;
|
||||
State state = taggedItem.getItem().getState();
|
||||
if (state instanceof HSBType) {
|
||||
value = ((HSBType) state).getHue().doubleValue();
|
||||
}
|
||||
return CompletableFuture.completedFuture(value);
|
||||
}, (hue) -> {
|
||||
if (taggedItem.getItem() instanceof ColorItem) {
|
||||
taggedItem.sendCommandProxy(HomekitCommandType.HUE_COMMAND, new DecimalType(hue));
|
||||
} else {
|
||||
logger.warn("Item type {} is not supported for {}. Only Color type is supported.",
|
||||
taggedItem.getItem().getType(), taggedItem.getName());
|
||||
}
|
||||
}, getSubscriber(taggedItem, HUE, updater), getUnsubscriber(taggedItem, HUE, updater));
|
||||
}
|
||||
|
||||
private static BrightnessCharacteristic createBrightnessCharacteristic(HomekitTaggedItem taggedItem,
|
||||
HomekitAccessoryUpdater updater) {
|
||||
return new BrightnessCharacteristic(() -> {
|
||||
int value = 0;
|
||||
final State state = taggedItem.getItem().getState();
|
||||
if (state instanceof HSBType) {
|
||||
value = ((HSBType) state).getBrightness().intValue();
|
||||
} else if (state instanceof PercentType) {
|
||||
value = ((PercentType) state).intValue();
|
||||
}
|
||||
return CompletableFuture.completedFuture(value);
|
||||
}, (brightness) -> {
|
||||
final Item item = taggedItem.getItem();
|
||||
if (item instanceof DimmerItem) {
|
||||
taggedItem.sendCommandProxy(HomekitCommandType.BRIGHTNESS_COMMAND, new PercentType(brightness));
|
||||
} else {
|
||||
logger.warn("Item type {} is not supported for {}. Only ColorItem and DimmerItem are supported.",
|
||||
item.getType(), taggedItem.getName());
|
||||
}
|
||||
}, getSubscriber(taggedItem, BRIGHTNESS, updater), getUnsubscriber(taggedItem, BRIGHTNESS, updater));
|
||||
}
|
||||
|
||||
private static SaturationCharacteristic createSaturationCharacteristic(HomekitTaggedItem taggedItem,
|
||||
HomekitAccessoryUpdater updater) {
|
||||
return new SaturationCharacteristic(() -> {
|
||||
double value = 0.0;
|
||||
State state = taggedItem.getItem().getState();
|
||||
if (state instanceof HSBType) {
|
||||
value = ((HSBType) state).getSaturation().doubleValue();
|
||||
} else if (state instanceof PercentType) {
|
||||
value = ((PercentType) state).doubleValue();
|
||||
}
|
||||
return CompletableFuture.completedFuture(value);
|
||||
}, (saturation) -> {
|
||||
if (taggedItem.getItem() instanceof ColorItem) {
|
||||
taggedItem.sendCommandProxy(HomekitCommandType.SATURATION_COMMAND,
|
||||
new PercentType(saturation.intValue()));
|
||||
} else {
|
||||
logger.warn("Item type {} is not supported for {}. Only Color type is supported.",
|
||||
taggedItem.getItem().getType(), taggedItem.getName());
|
||||
}
|
||||
}, getSubscriber(taggedItem, SATURATION, updater), getUnsubscriber(taggedItem, SATURATION, updater));
|
||||
}
|
||||
|
||||
private static ColorTemperatureCharacteristic createColorTemperatureCharacteristic(HomekitTaggedItem taggedItem,
|
||||
HomekitAccessoryUpdater updater) {
|
||||
return new ColorTemperatureCharacteristic(getIntSupplier(taggedItem), setIntConsumer(taggedItem),
|
||||
getSubscriber(taggedItem, COLOR_TEMPERATURE, updater),
|
||||
getUnsubscriber(taggedItem, COLOR_TEMPERATURE, updater));
|
||||
}
|
||||
|
||||
private static CurrentFanStateCharacteristic createCurrentFanStateCharacteristic(HomekitTaggedItem taggedItem,
|
||||
HomekitAccessoryUpdater updater) {
|
||||
return new CurrentFanStateCharacteristic(() -> {
|
||||
final @Nullable DecimalType value = taggedItem.getItem().getStateAs(DecimalType.class);
|
||||
@Nullable
|
||||
CurrentFanStateEnum currentFanStateEnum = value != null ? CurrentFanStateEnum.fromCode(value.intValue())
|
||||
: null;
|
||||
if (currentFanStateEnum == null) {
|
||||
currentFanStateEnum = CurrentFanStateEnum.INACTIVE;
|
||||
}
|
||||
return CompletableFuture.completedFuture(currentFanStateEnum);
|
||||
}, getSubscriber(taggedItem, CURRENT_FAN_STATE, updater),
|
||||
getUnsubscriber(taggedItem, CURRENT_FAN_STATE, updater));
|
||||
}
|
||||
|
||||
private static TargetFanStateCharacteristic createTargetFanStateCharacteristic(HomekitTaggedItem taggedItem,
|
||||
HomekitAccessoryUpdater updater) {
|
||||
return new TargetFanStateCharacteristic(() -> {
|
||||
final @Nullable DecimalType value = taggedItem.getItem().getStateAs(DecimalType.class);
|
||||
@Nullable
|
||||
TargetFanStateEnum targetFanStateEnum = value != null ? TargetFanStateEnum.fromCode(value.intValue())
|
||||
: null;
|
||||
if (targetFanStateEnum == null) {
|
||||
targetFanStateEnum = TargetFanStateEnum.AUTO;
|
||||
}
|
||||
return CompletableFuture.completedFuture(targetFanStateEnum);
|
||||
}, (targetState) -> {
|
||||
if (taggedItem.getItem() instanceof NumberItem) {
|
||||
((NumberItem) taggedItem.getItem()).send(new DecimalType(targetState.getCode()));
|
||||
} else {
|
||||
logger.warn("Item type {} is not supported for {}. Only Number type is supported.",
|
||||
taggedItem.getItem().getType(), taggedItem.getName());
|
||||
}
|
||||
}, getSubscriber(taggedItem, TARGET_FAN_STATE, updater),
|
||||
getUnsubscriber(taggedItem, TARGET_FAN_STATE, updater));
|
||||
}
|
||||
|
||||
private static RotationDirectionCharacteristic createRotationDirectionCharacteristic(HomekitTaggedItem taggedItem,
|
||||
HomekitAccessoryUpdater updater) {
|
||||
return new RotationDirectionCharacteristic(
|
||||
() -> getEnumFromItem(taggedItem, RotationDirectionEnum.CLOCKWISE,
|
||||
RotationDirectionEnum.COUNTER_CLOCKWISE, RotationDirectionEnum.CLOCKWISE),
|
||||
(value) -> setValueFromEnum(taggedItem, value, RotationDirectionEnum.CLOCKWISE,
|
||||
RotationDirectionEnum.COUNTER_CLOCKWISE),
|
||||
getSubscriber(taggedItem, ROTATION_DIRECTION, updater),
|
||||
getUnsubscriber(taggedItem, ROTATION_DIRECTION, updater));
|
||||
}
|
||||
|
||||
private static SwingModeCharacteristic createSwingModeCharacteristic(HomekitTaggedItem taggedItem,
|
||||
HomekitAccessoryUpdater updater) {
|
||||
return new SwingModeCharacteristic(
|
||||
() -> getEnumFromItem(taggedItem, SwingModeEnum.SWING_DISABLED, SwingModeEnum.SWING_ENABLED,
|
||||
SwingModeEnum.SWING_DISABLED),
|
||||
(value) -> setValueFromEnum(taggedItem, value, SwingModeEnum.SWING_DISABLED,
|
||||
SwingModeEnum.SWING_ENABLED),
|
||||
getSubscriber(taggedItem, SWING_MODE, updater), getUnsubscriber(taggedItem, SWING_MODE, updater));
|
||||
}
|
||||
|
||||
private static LockPhysicalControlsCharacteristic createLockPhysicalControlsCharacteristic(
|
||||
HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
|
||||
return new LockPhysicalControlsCharacteristic(
|
||||
() -> getEnumFromItem(taggedItem, LockPhysicalControlsEnum.CONTROL_LOCK_DISABLED,
|
||||
LockPhysicalControlsEnum.CONTROL_LOCK_ENABLED, LockPhysicalControlsEnum.CONTROL_LOCK_DISABLED),
|
||||
(value) -> setValueFromEnum(taggedItem, value, LockPhysicalControlsEnum.CONTROL_LOCK_DISABLED,
|
||||
LockPhysicalControlsEnum.CONTROL_LOCK_ENABLED),
|
||||
getSubscriber(taggedItem, LOCK_CONTROL, updater), getUnsubscriber(taggedItem, LOCK_CONTROL, updater));
|
||||
}
|
||||
|
||||
private static RotationSpeedCharacteristic createRotationSpeedCharacteristic(HomekitTaggedItem item,
|
||||
HomekitAccessoryUpdater updater) {
|
||||
return new RotationSpeedCharacteristic(getIntSupplier(item), setPercentConsumer(item),
|
||||
getSubscriber(item, ROTATION_SPEED, updater), getUnsubscriber(item, ROTATION_SPEED, updater));
|
||||
}
|
||||
|
||||
private static SetDurationCharacteristic createDurationCharacteristic(HomekitTaggedItem taggedItem,
|
||||
HomekitAccessoryUpdater updater) {
|
||||
return new SetDurationCharacteristic(() -> {
|
||||
int value = getIntFromItem(taggedItem);
|
||||
final @Nullable Map<String, Object> itemConfiguration = taggedItem.getConfiguration();
|
||||
if ((value == 0) && (itemConfiguration != null)) { // check for default duration
|
||||
final Object duration = itemConfiguration.get(HomekitValveImpl.CONFIG_DEFAULT_DURATION);
|
||||
if (duration instanceof BigDecimal) {
|
||||
value = ((BigDecimal) duration).intValue();
|
||||
if (taggedItem.getItem() instanceof NumberItem) {
|
||||
((NumberItem) taggedItem.getItem()).setState(new DecimalType(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
return CompletableFuture.completedFuture(value);
|
||||
}, setIntConsumer(taggedItem), getSubscriber(taggedItem, DURATION, updater),
|
||||
getUnsubscriber(taggedItem, DURATION, updater));
|
||||
}
|
||||
|
||||
private static RemainingDurationCharacteristic createRemainingDurationCharacteristic(HomekitTaggedItem taggedItem,
|
||||
HomekitAccessoryUpdater updater) {
|
||||
return new RemainingDurationCharacteristic(getIntSupplier(taggedItem),
|
||||
getSubscriber(taggedItem, REMAINING_DURATION, updater),
|
||||
getUnsubscriber(taggedItem, REMAINING_DURATION, updater));
|
||||
}
|
||||
|
||||
private static VolumeCharacteristic createVolumeCharacteristic(HomekitTaggedItem taggedItem,
|
||||
HomekitAccessoryUpdater updater) {
|
||||
return new VolumeCharacteristic(getIntSupplier(taggedItem),
|
||||
(volume) -> ((NumberItem) taggedItem.getItem()).send(new DecimalType(volume)),
|
||||
getSubscriber(taggedItem, DURATION, updater), getUnsubscriber(taggedItem, DURATION, updater));
|
||||
}
|
||||
|
||||
private static CoolingThresholdTemperatureCharacteristic createCoolingThresholdCharacteristic(
|
||||
HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
|
||||
return new CoolingThresholdTemperatureCharacteristic(
|
||||
taggedItem.getConfigurationAsDouble(HomekitTaggedItem.MIN_VALUE,
|
||||
CoolingThresholdTemperatureCharacteristic.DEFAULT_MIN_VALUE),
|
||||
taggedItem.getConfigurationAsDouble(HomekitTaggedItem.MAX_VALUE,
|
||||
CoolingThresholdTemperatureCharacteristic.DEFAULT_MAX_VALUE),
|
||||
taggedItem.getConfigurationAsDouble(HomekitTaggedItem.STEP,
|
||||
CoolingThresholdTemperatureCharacteristic.DEFAULT_STEP),
|
||||
getDoubleSupplier(taggedItem), setDoubleConsumer(taggedItem),
|
||||
getSubscriber(taggedItem, COOLING_THRESHOLD_TEMPERATURE, updater),
|
||||
getUnsubscriber(taggedItem, COOLING_THRESHOLD_TEMPERATURE, updater));
|
||||
}
|
||||
|
||||
private static HeatingThresholdTemperatureCharacteristic createHeatingThresholdCharacteristic(
|
||||
HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
|
||||
return new HeatingThresholdTemperatureCharacteristic(
|
||||
taggedItem.getConfigurationAsDouble(HomekitTaggedItem.MIN_VALUE,
|
||||
HeatingThresholdTemperatureCharacteristic.DEFAULT_MIN_VALUE),
|
||||
taggedItem.getConfigurationAsDouble(HomekitTaggedItem.MAX_VALUE,
|
||||
HeatingThresholdTemperatureCharacteristic.DEFAULT_MAX_VALUE),
|
||||
taggedItem.getConfigurationAsDouble(HomekitTaggedItem.STEP,
|
||||
HeatingThresholdTemperatureCharacteristic.DEFAULT_STEP),
|
||||
getDoubleSupplier(taggedItem), setDoubleConsumer(taggedItem),
|
||||
getSubscriber(taggedItem, HEATING_THRESHOLD_TEMPERATURE, updater),
|
||||
getUnsubscriber(taggedItem, HEATING_THRESHOLD_TEMPERATURE, updater));
|
||||
}
|
||||
|
||||
private static OzoneDensityCharacteristic createOzoneDensityCharacteristic(final HomekitTaggedItem taggedItem,
|
||||
HomekitAccessoryUpdater updater) {
|
||||
return new OzoneDensityCharacteristic(getDoubleSupplier(taggedItem),
|
||||
getSubscriber(taggedItem, OZONE_DENSITY, updater), getUnsubscriber(taggedItem, OZONE_DENSITY, updater));
|
||||
}
|
||||
|
||||
private static NitrogenDioxideDensityCharacteristic createNitrogenDioxideDensityCharacteristic(
|
||||
final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
|
||||
return new NitrogenDioxideDensityCharacteristic(getDoubleSupplier(taggedItem),
|
||||
getSubscriber(taggedItem, NITROGEN_DIOXIDE_DENSITY, updater),
|
||||
getUnsubscriber(taggedItem, NITROGEN_DIOXIDE_DENSITY, updater));
|
||||
}
|
||||
|
||||
private static SulphurDioxideDensityCharacteristic createSulphurDioxideDensityCharacteristic(
|
||||
final HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater) {
|
||||
return new SulphurDioxideDensityCharacteristic(getDoubleSupplier(taggedItem),
|
||||
getSubscriber(taggedItem, SULPHUR_DIOXIDE_DENSITY, updater),
|
||||
getUnsubscriber(taggedItem, SULPHUR_DIOXIDE_DENSITY, updater));
|
||||
}
|
||||
|
||||
private static PM25DensityCharacteristic createPM25DensityCharacteristic(final HomekitTaggedItem taggedItem,
|
||||
HomekitAccessoryUpdater updater) {
|
||||
return new PM25DensityCharacteristic(getDoubleSupplier(taggedItem),
|
||||
getSubscriber(taggedItem, PM25_DENSITY, updater), getUnsubscriber(taggedItem, PM25_DENSITY, updater));
|
||||
}
|
||||
|
||||
private static PM10DensityCharacteristic createPM10DensityCharacteristic(final HomekitTaggedItem taggedItem,
|
||||
HomekitAccessoryUpdater updater) {
|
||||
return new PM10DensityCharacteristic(getDoubleSupplier(taggedItem),
|
||||
getSubscriber(taggedItem, PM10_DENSITY, updater), getUnsubscriber(taggedItem, PM10_DENSITY, updater));
|
||||
}
|
||||
|
||||
private static VOCDensityCharacteristic createVOCDensityCharacteristic(final HomekitTaggedItem taggedItem,
|
||||
HomekitAccessoryUpdater updater) {
|
||||
return new VOCDensityCharacteristic(getDoubleSupplier(taggedItem),
|
||||
getSubscriber(taggedItem, VOC_DENSITY, updater), getUnsubscriber(taggedItem, VOC_DENSITY, updater));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.CONTACT_SENSOR_STATE;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
|
||||
import io.github.hapjava.accessories.ContactSensorAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.characteristics.impl.contactsensor.ContactStateEnum;
|
||||
import io.github.hapjava.services.impl.ContactSensorService;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Philipp Arndt - Initial contribution
|
||||
*/
|
||||
public class HomekitContactSensorImpl extends AbstractHomekitAccessoryImpl implements ContactSensorAccessory {
|
||||
private final BooleanItemReader contactSensedReader;
|
||||
|
||||
public HomekitContactSensorImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) throws IncompleteAccessoryException {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
contactSensedReader = createBooleanReader(CONTACT_SENSOR_STATE, OnOffType.ON, OpenClosedType.OPEN);
|
||||
getServices().add(new ContactSensorService(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<ContactStateEnum> getCurrentState() {
|
||||
return CompletableFuture.completedFuture(
|
||||
contactSensedReader.getValue() ? ContactStateEnum.NOT_DETECTED : ContactStateEnum.DETECTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeContactState(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(CONTACT_SENSOR_STATE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeContactState() {
|
||||
unsubscribe(CONTACT_SENSOR_STATE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
|
||||
import io.github.hapjava.accessories.DoorAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.characteristics.impl.windowcovering.PositionStateEnum;
|
||||
import io.github.hapjava.services.impl.DoorService;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Eugen Freiter - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HomekitDoorImpl extends AbstractHomekitPositionAccessoryImpl implements DoorAccessory {
|
||||
|
||||
public HomekitDoorImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
getServices().add(new DoorService(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public CompletableFuture<Integer> getCurrentPosition() {
|
||||
return super.getCurrentPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public CompletableFuture<PositionStateEnum> getPositionState() {
|
||||
return super.getPositionState();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public CompletableFuture<Integer> getTargetPosition() {
|
||||
return super.getTargetPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public CompletableFuture<Void> setTargetPosition(Integer value) {
|
||||
return super.setTargetPosition(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public void subscribeCurrentPosition(HomekitCharacteristicChangeCallback callback) {
|
||||
super.subscribeCurrentPosition(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public void subscribePositionState(HomekitCharacteristicChangeCallback callback) {
|
||||
super.subscribePositionState(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public void subscribeTargetPosition(HomekitCharacteristicChangeCallback callback) {
|
||||
super.subscribeTargetPosition(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public void unsubscribeCurrentPosition() {
|
||||
super.unsubscribeCurrentPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public void unsubscribePositionState() {
|
||||
super.unsubscribePositionState();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public void unsubscribeTargetPosition() {
|
||||
super.unsubscribeTargetPosition();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.ACTIVE_STATUS;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
|
||||
import io.github.hapjava.accessories.FanAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.services.impl.FanService;
|
||||
|
||||
/**
|
||||
* Implements Fan using an Item that provides an On/Off state
|
||||
*
|
||||
* @author Eugen Freiter - Initial contribution
|
||||
*/
|
||||
class HomekitFanImpl extends AbstractHomekitAccessoryImpl implements FanAccessory {
|
||||
private final BooleanItemReader activeReader;
|
||||
|
||||
public HomekitFanImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) throws IncompleteAccessoryException {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
activeReader = createBooleanReader(ACTIVE_STATUS, OnOffType.ON, OpenClosedType.OPEN);
|
||||
this.getServices().add(new FanService(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> isActive() {
|
||||
return CompletableFuture.completedFuture(activeReader.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setActive(boolean state) {
|
||||
activeReader.setValue(state);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeActive(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(ACTIVE_STATUS, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeActive() {
|
||||
unsubscribe(ACTIVE_STATUS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.CURRENT_DOOR_STATE;
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.OBSTRUCTION_STATUS;
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.TARGET_DOOR_STATE;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.library.items.StringItem;
|
||||
import org.openhab.core.library.items.SwitchItem;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.github.hapjava.accessories.GarageDoorOpenerAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.characteristics.impl.garagedoor.CurrentDoorStateEnum;
|
||||
import io.github.hapjava.characteristics.impl.garagedoor.TargetDoorStateEnum;
|
||||
import io.github.hapjava.services.impl.GarageDoorOpenerService;
|
||||
|
||||
/**
|
||||
* Implements Garage Door Opener
|
||||
*
|
||||
* @author Eugen Freiter - Initial contribution
|
||||
*/
|
||||
public class HomekitGarageDoorOpenerImpl extends AbstractHomekitAccessoryImpl implements GarageDoorOpenerAccessory {
|
||||
private final Logger logger = LoggerFactory.getLogger(HomekitGarageDoorOpenerImpl.class);
|
||||
private final BooleanItemReader obstructionReader;
|
||||
|
||||
public HomekitGarageDoorOpenerImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) throws IncompleteAccessoryException {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
obstructionReader = createBooleanReader(OBSTRUCTION_STATUS, OnOffType.ON, OpenClosedType.OPEN);
|
||||
getServices().add(new GarageDoorOpenerService(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<CurrentDoorStateEnum> getCurrentDoorState() {
|
||||
final Optional<HomekitTaggedItem> characteristic = getCharacteristic(CURRENT_DOOR_STATE);
|
||||
final HomekitSettings settings = getSettings();
|
||||
String stringValue = settings.doorCurrentStateClosed;
|
||||
if (characteristic.isPresent()) {
|
||||
stringValue = characteristic.get().getItem().getState().toString();
|
||||
} else {
|
||||
logger.warn("Missing mandatory characteristic {}", CURRENT_DOOR_STATE);
|
||||
}
|
||||
CurrentDoorStateEnum mode;
|
||||
|
||||
if (stringValue.equalsIgnoreCase(settings.doorCurrentStateClosed)) {
|
||||
mode = CurrentDoorStateEnum.CLOSED;
|
||||
} else if (stringValue.equalsIgnoreCase(settings.doorCurrentStateClosing)) {
|
||||
mode = CurrentDoorStateEnum.CLOSING;
|
||||
} else if (stringValue.equalsIgnoreCase(settings.doorCurrentStateOpen)) {
|
||||
mode = CurrentDoorStateEnum.OPEN;
|
||||
} else if (stringValue.equalsIgnoreCase(settings.doorCurrentStateOpening)) {
|
||||
mode = CurrentDoorStateEnum.OPENING;
|
||||
} else if (stringValue.equalsIgnoreCase(settings.doorCurrentStateStopped)) {
|
||||
mode = CurrentDoorStateEnum.SOPPED;
|
||||
} else if (stringValue.equals("UNDEF") || stringValue.equals("NULL")) {
|
||||
logger.warn("Current door state not available. Relaying value of CLOSED to HomeKit");
|
||||
mode = CurrentDoorStateEnum.CLOSED;
|
||||
} else {
|
||||
logger.warn("Unrecognized current door state: {}. Expected {}, {}, {}, {} or {} strings in value.",
|
||||
stringValue, settings.doorCurrentStateClosed, settings.doorCurrentStateClosing,
|
||||
settings.doorCurrentStateOpen, settings.doorCurrentStateOpening, settings.doorCurrentStateStopped);
|
||||
mode = CurrentDoorStateEnum.CLOSED;
|
||||
}
|
||||
return CompletableFuture.completedFuture(mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<TargetDoorStateEnum> getTargetDoorState() {
|
||||
final Optional<HomekitTaggedItem> characteristic = getCharacteristic(TARGET_DOOR_STATE);
|
||||
Item item;
|
||||
|
||||
if (characteristic.isPresent()) {
|
||||
item = characteristic.get().getItem();
|
||||
} else {
|
||||
logger.warn("Missing mandatory characteristic {}", TARGET_DOOR_STATE);
|
||||
return CompletableFuture.completedFuture(TargetDoorStateEnum.CLOSED);
|
||||
}
|
||||
TargetDoorStateEnum mode;
|
||||
|
||||
if (item instanceof SwitchItem) {
|
||||
mode = item.getState() == OnOffType.ON ? TargetDoorStateEnum.OPEN : TargetDoorStateEnum.CLOSED;
|
||||
} else if (item instanceof StringItem) {
|
||||
final HomekitSettings settings = getSettings();
|
||||
final String stringValue = item.getState().toString();
|
||||
if (stringValue.equalsIgnoreCase(settings.doorTargetStateClosed)) {
|
||||
mode = TargetDoorStateEnum.CLOSED;
|
||||
} else if (stringValue.equalsIgnoreCase(settings.doorTargetStateOpen)) {
|
||||
mode = TargetDoorStateEnum.OPEN;
|
||||
} else {
|
||||
logger.warn(
|
||||
"Unsupported value {} for {}. Only {} and {} supported. Check HomeKit settings if you want to change the mapping",
|
||||
stringValue, item.getName(), settings.doorTargetStateClosed, settings.doorTargetStateOpen);
|
||||
mode = TargetDoorStateEnum.CLOSED;
|
||||
}
|
||||
} else {
|
||||
logger.warn("Unsupported item type {} for {}. Only Switch and String are supported", item.getType(),
|
||||
item.getName());
|
||||
mode = TargetDoorStateEnum.CLOSED;
|
||||
}
|
||||
return CompletableFuture.completedFuture(mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> getObstructionDetected() {
|
||||
return CompletableFuture.completedFuture(obstructionReader.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setTargetDoorState(TargetDoorStateEnum targetDoorStateEnum) {
|
||||
final Optional<HomekitTaggedItem> characteristic = getCharacteristic(TARGET_DOOR_STATE);
|
||||
Item item;
|
||||
if (characteristic.isPresent()) {
|
||||
item = characteristic.get().getItem();
|
||||
} else {
|
||||
logger.warn("Missing mandatory characteristic {}", TARGET_DOOR_STATE);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
if (item instanceof SwitchItem) {
|
||||
((SwitchItem) item).send(OnOffType.from(targetDoorStateEnum == TargetDoorStateEnum.OPEN));
|
||||
} else if (item instanceof StringItem) {
|
||||
final HomekitSettings settings = getSettings();
|
||||
((StringItem) item)
|
||||
.send(new StringType(targetDoorStateEnum == TargetDoorStateEnum.OPEN ? settings.doorTargetStateOpen
|
||||
: settings.doorTargetStateClosed));
|
||||
} else {
|
||||
logger.warn("Unsupported item type {} for {}. Only Switch and String are supported", item.getType(),
|
||||
item.getName());
|
||||
}
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeCurrentDoorState(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(CURRENT_DOOR_STATE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeTargetDoorState(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(TARGET_DOOR_STATE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeObstructionDetected(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(OBSTRUCTION_STATUS, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeCurrentDoorState() {
|
||||
unsubscribe(CURRENT_DOOR_STATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeTargetDoorState() {
|
||||
unsubscribe(TARGET_DOOR_STATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeObstructionDetected() {
|
||||
unsubscribe(OBSTRUCTION_STATUS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.ACTIVE_STATUS;
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.CURRENT_HEATER_COOLER_STATE;
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.TARGET_HEATER_COOLER_STATE;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.items.GenericItem;
|
||||
import org.openhab.core.library.items.StringItem;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitCharacteristicType;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.github.hapjava.accessories.HeaterCoolerAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.characteristics.impl.heatercooler.CurrentHeaterCoolerStateEnum;
|
||||
import io.github.hapjava.characteristics.impl.heatercooler.TargetHeaterCoolerStateEnum;
|
||||
import io.github.hapjava.characteristics.impl.thermostat.TemperatureDisplayUnitCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.thermostat.TemperatureDisplayUnitEnum;
|
||||
import io.github.hapjava.services.impl.HeaterCoolerService;
|
||||
|
||||
/**
|
||||
* Implements Heater Cooler
|
||||
*
|
||||
* @author Eugen Freiter - Initial contribution
|
||||
*/
|
||||
|
||||
public class HomekitHeaterCoolerImpl extends AbstractHomekitAccessoryImpl implements HeaterCoolerAccessory {
|
||||
private final Logger logger = LoggerFactory.getLogger(HomekitHeaterCoolerImpl.class);
|
||||
private final BooleanItemReader activeReader;
|
||||
private final Map<CurrentHeaterCoolerStateEnum, String> currentStateMapping = new EnumMap<CurrentHeaterCoolerStateEnum, String>(
|
||||
CurrentHeaterCoolerStateEnum.class) {
|
||||
{
|
||||
put(CurrentHeaterCoolerStateEnum.INACTIVE, "INACTIVE");
|
||||
put(CurrentHeaterCoolerStateEnum.IDLE, "IDLE");
|
||||
put(CurrentHeaterCoolerStateEnum.HEATING, "HEATING");
|
||||
put(CurrentHeaterCoolerStateEnum.COOLING, "COOLING");
|
||||
}
|
||||
};
|
||||
private final Map<TargetHeaterCoolerStateEnum, String> targetStateMapping = new EnumMap<TargetHeaterCoolerStateEnum, String>(
|
||||
TargetHeaterCoolerStateEnum.class) {
|
||||
{
|
||||
put(TargetHeaterCoolerStateEnum.AUTO, "AUTO");
|
||||
put(TargetHeaterCoolerStateEnum.HEAT, "HEAT");
|
||||
put(TargetHeaterCoolerStateEnum.COOL, "COOL");
|
||||
}
|
||||
};
|
||||
|
||||
public HomekitHeaterCoolerImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) throws IncompleteAccessoryException {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
activeReader = new BooleanItemReader(getItem(ACTIVE_STATUS, GenericItem.class)
|
||||
.orElseThrow(() -> new IncompleteAccessoryException(ACTIVE_STATUS)), OnOffType.ON, OpenClosedType.OPEN);
|
||||
updateMapping(CURRENT_HEATER_COOLER_STATE, currentStateMapping);
|
||||
updateMapping(TARGET_HEATER_COOLER_STATE, targetStateMapping);
|
||||
final HeaterCoolerService service = new HeaterCoolerService(this);
|
||||
service.addOptionalCharacteristic(new TemperatureDisplayUnitCharacteristic(this::getTemperatureDisplayUnit,
|
||||
this::setTemperatureDisplayUnit, this::subscribeTemperatureDisplayUnit,
|
||||
this::unsubscribeTemperatureDisplayUnit));
|
||||
getServices().add(service);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Double> getCurrentTemperature() {
|
||||
final @Nullable DecimalType state = getStateAs(HomekitCharacteristicType.CURRENT_TEMPERATURE,
|
||||
DecimalType.class);
|
||||
return CompletableFuture.completedFuture(state != null ? convertToCelsius(state.doubleValue()) : 0.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> isActive() {
|
||||
return CompletableFuture.completedFuture(activeReader.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setActive(boolean state) {
|
||||
activeReader.setValue(state);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<CurrentHeaterCoolerStateEnum> getCurrentHeaterCoolerState() {
|
||||
return CompletableFuture.completedFuture(getKeyFromMapping(CURRENT_HEATER_COOLER_STATE, currentStateMapping,
|
||||
CurrentHeaterCoolerStateEnum.INACTIVE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<TargetHeaterCoolerStateEnum> getTargetHeaterCoolerState() {
|
||||
return CompletableFuture.completedFuture(
|
||||
getKeyFromMapping(TARGET_HEATER_COOLER_STATE, targetStateMapping, TargetHeaterCoolerStateEnum.AUTO));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setTargetHeaterCoolerState(TargetHeaterCoolerStateEnum state) {
|
||||
final Optional<HomekitTaggedItem> characteristic = getCharacteristic(
|
||||
HomekitCharacteristicType.TARGET_HEATER_COOLER_STATE);
|
||||
if (characteristic.isPresent()) {
|
||||
((StringItem) characteristic.get().getItem()).send(new StringType(targetStateMapping.get(state)));
|
||||
} else {
|
||||
logger.warn("Missing mandatory characteristic {}",
|
||||
HomekitCharacteristicType.TARGET_HEATING_COOLING_STATE.getTag());
|
||||
}
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
public CompletableFuture<TemperatureDisplayUnitEnum> getTemperatureDisplayUnit() {
|
||||
return CompletableFuture
|
||||
.completedFuture(getSettings().useFahrenheitTemperature ? TemperatureDisplayUnitEnum.FAHRENHEIT
|
||||
: TemperatureDisplayUnitEnum.CELSIUS);
|
||||
}
|
||||
|
||||
public void setTemperatureDisplayUnit(TemperatureDisplayUnitEnum value) {
|
||||
// temperature unit set globally via binding setting and cannot be changed at item level.
|
||||
// this method is intentionally empty.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeCurrentHeaterCoolerState(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(HomekitCharacteristicType.CURRENT_HEATER_COOLER_STATE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeCurrentHeaterCoolerState() {
|
||||
unsubscribe(HomekitCharacteristicType.CURRENT_HEATER_COOLER_STATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeTargetHeaterCoolerState(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(HomekitCharacteristicType.TARGET_HEATER_COOLER_STATE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeTargetHeaterCoolerState() {
|
||||
unsubscribe(HomekitCharacteristicType.TARGET_HEATER_COOLER_STATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeActive(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(ACTIVE_STATUS, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeActive() {
|
||||
unsubscribe(ACTIVE_STATUS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeCurrentTemperature(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(HomekitCharacteristicType.CURRENT_TEMPERATURE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeCurrentTemperature() {
|
||||
unsubscribe(HomekitCharacteristicType.CURRENT_TEMPERATURE);
|
||||
}
|
||||
|
||||
public void subscribeTemperatureDisplayUnit(HomekitCharacteristicChangeCallback callback) {
|
||||
// temperature unit set globally via binding setting and cannot be changed at item level.
|
||||
// this method is intentionally empty
|
||||
}
|
||||
|
||||
public void unsubscribeTemperatureDisplayUnit() {
|
||||
// temperature unit set globally via binding setting and cannot be changed at item level.
|
||||
// this method is intentionally empty
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitCharacteristicType;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
|
||||
import io.github.hapjava.accessories.HumiditySensorAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.services.impl.HumiditySensorService;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
public class HomekitHumiditySensorImpl extends AbstractHomekitAccessoryImpl implements HumiditySensorAccessory {
|
||||
private final static String CONFIG_MULTIPLICATOR = "homekitMultiplicator";
|
||||
|
||||
public HomekitHumiditySensorImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
getServices().add(new HumiditySensorService(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Double> getCurrentRelativeHumidity() {
|
||||
final @Nullable DecimalType state = getStateAs(HomekitCharacteristicType.RELATIVE_HUMIDITY, DecimalType.class);
|
||||
if (state != null) {
|
||||
BigDecimal multiplicator = getAccessoryConfiguration(CONFIG_MULTIPLICATOR, BigDecimal.valueOf(1.0));
|
||||
return CompletableFuture.completedFuture((state.toBigDecimal().multiply(multiplicator)).doubleValue());
|
||||
}
|
||||
return CompletableFuture.completedFuture(0.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeCurrentRelativeHumidity(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(HomekitCharacteristicType.RELATIVE_HUMIDITY, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeCurrentRelativeHumidity() {
|
||||
unsubscribe(HomekitCharacteristicType.RELATIVE_HUMIDITY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.LEAK_DETECTED_STATE;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
|
||||
import io.github.hapjava.accessories.LeakSensorAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.characteristics.impl.leaksensor.LeakDetectedStateEnum;
|
||||
import io.github.hapjava.services.impl.LeakSensorService;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Tim Harper - Initial contribution
|
||||
*/
|
||||
public class HomekitLeakSensorImpl extends AbstractHomekitAccessoryImpl implements LeakSensorAccessory {
|
||||
private final BooleanItemReader leakDetectedReader;
|
||||
|
||||
public HomekitLeakSensorImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) throws IncompleteAccessoryException {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
leakDetectedReader = createBooleanReader(LEAK_DETECTED_STATE, OnOffType.ON, OpenClosedType.OPEN);
|
||||
getServices().add(new LeakSensorService(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<LeakDetectedStateEnum> getLeakDetected() {
|
||||
return CompletableFuture.completedFuture(leakDetectedReader.getValue() ? LeakDetectedStateEnum.LEAK_DETECTED
|
||||
: LeakDetectedStateEnum.LEAK_NOT_DETECTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeLeakDetected(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(LEAK_DETECTED_STATE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeLeakDetected() {
|
||||
unsubscribe(LEAK_DETECTED_STATE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.LIGHT_LEVEL;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
|
||||
import io.github.hapjava.accessories.LightSensorAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.services.impl.LightSensorService;
|
||||
|
||||
/**
|
||||
* HomeKit light sensor implementation.
|
||||
*
|
||||
* @author Eugen Freiter - Initial contribution
|
||||
*/
|
||||
public class HomekitLightSensorImpl extends AbstractHomekitAccessoryImpl implements LightSensorAccessory {
|
||||
|
||||
public HomekitLightSensorImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
getServices().add(new LightSensorService(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Double> getCurrentAmbientLightLevel() {
|
||||
final @Nullable DecimalType state = getStateAs(LIGHT_LEVEL, DecimalType.class);
|
||||
return CompletableFuture.completedFuture(state != null ? state.doubleValue() : 0.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeCurrentAmbientLightLevel(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(LIGHT_LEVEL, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeCurrentAmbientLightLevel() {
|
||||
unsubscribe(LIGHT_LEVEL);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.openhab.core.items.GenericItem;
|
||||
import org.openhab.core.items.GroupItem;
|
||||
import org.openhab.core.library.items.DimmerItem;
|
||||
import org.openhab.core.library.items.SwitchItem;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitCharacteristicType;
|
||||
import org.openhab.io.homekit.internal.HomekitCommandType;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
|
||||
import io.github.hapjava.accessories.LightbulbAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.services.impl.LightbulbService;
|
||||
|
||||
/**
|
||||
* Implements Lightbulb using an Item that provides an On/Off state
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
class HomekitLightbulbImpl extends AbstractHomekitAccessoryImpl implements LightbulbAccessory {
|
||||
|
||||
public HomekitLightbulbImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
this.getServices().add(new LightbulbService(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> getLightbulbPowerState() {
|
||||
OnOffType state = getStateAs(HomekitCharacteristicType.ON_STATE, OnOffType.class);
|
||||
return CompletableFuture.completedFuture(state == OnOffType.ON);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setLightbulbPowerState(boolean value) {
|
||||
getCharacteristic(HomekitCharacteristicType.ON_STATE).ifPresent(tItem -> {
|
||||
final OnOffType onOffState = OnOffType.from(value);
|
||||
final GenericItem item = (GenericItem) tItem.getItem();
|
||||
if (item instanceof DimmerItem) {
|
||||
tItem.sendCommandProxy(HomekitCommandType.ON_COMMAND, onOffState);
|
||||
} else if (item instanceof SwitchItem) {
|
||||
((SwitchItem) item).send(onOffState);
|
||||
} else if (item instanceof GroupItem) {
|
||||
((GroupItem) item).send(onOffState);
|
||||
}
|
||||
});
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeLightbulbPowerState(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(HomekitCharacteristicType.ON_STATE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeLightbulbPowerState() {
|
||||
unsubscribe(HomekitCharacteristicType.ON_STATE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.items.GenericItem;
|
||||
import org.openhab.core.library.items.SwitchItem;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitCharacteristicType;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
|
||||
import io.github.hapjava.accessories.LockMechanismAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.characteristics.impl.lock.LockCurrentStateEnum;
|
||||
import io.github.hapjava.characteristics.impl.lock.LockTargetStateEnum;
|
||||
import io.github.hapjava.services.impl.LockMechanismService;
|
||||
|
||||
/**
|
||||
* Implements the support of Lock accessories, mapping them to OpenHAB Switch type
|
||||
*
|
||||
* @author blafois - Initial contribution.
|
||||
*
|
||||
*/
|
||||
public class HomekitLockImpl extends AbstractHomekitAccessoryImpl implements LockMechanismAccessory {
|
||||
|
||||
public HomekitLockImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
getServices().add(new LockMechanismService(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<LockCurrentStateEnum> getLockCurrentState() {
|
||||
final Optional<GenericItem> item = getItem(HomekitCharacteristicType.LOCK_CURRENT_STATE, GenericItem.class);
|
||||
LockCurrentStateEnum lockState = LockCurrentStateEnum.UNKNOWN;
|
||||
if (item.isPresent()) {
|
||||
final State state = item.get().getState();
|
||||
if (state instanceof DecimalType) {
|
||||
lockState = LockCurrentStateEnum.fromCode(((DecimalType) state).intValue());
|
||||
} else if (state instanceof OnOffType) {
|
||||
lockState = state == OnOffType.ON ? LockCurrentStateEnum.SECURED : LockCurrentStateEnum.UNSECURED;
|
||||
}
|
||||
}
|
||||
return CompletableFuture.completedFuture(lockState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<LockTargetStateEnum> getLockTargetState() {
|
||||
final @Nullable OnOffType state = getStateAs(HomekitCharacteristicType.LOCK_TARGET_STATE, OnOffType.class);
|
||||
if (state != null) {
|
||||
return CompletableFuture.completedFuture(
|
||||
state == OnOffType.ON ? LockTargetStateEnum.SECURED : LockTargetStateEnum.UNSECURED);
|
||||
}
|
||||
return CompletableFuture.completedFuture(LockTargetStateEnum.UNSECURED);
|
||||
// Apple HAP specification has only SECURED and UNSECURED values for lock target state.
|
||||
// unknown does not supported for target state.
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setLockTargetState(LockTargetStateEnum state) {
|
||||
getItem(HomekitCharacteristicType.LOCK_TARGET_STATE, SwitchItem.class).ifPresent(item -> {
|
||||
switch (state) {
|
||||
case SECURED:
|
||||
// Close the door
|
||||
if (item instanceof SwitchItem) {
|
||||
item.send(OnOffType.ON);
|
||||
}
|
||||
break;
|
||||
case UNSECURED:
|
||||
// Open the door
|
||||
if (item instanceof SwitchItem) {
|
||||
item.send(OnOffType.OFF);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeLockCurrentState(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(HomekitCharacteristicType.LOCK_CURRENT_STATE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeLockCurrentState() {
|
||||
unsubscribe(HomekitCharacteristicType.LOCK_CURRENT_STATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeLockTargetState(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(HomekitCharacteristicType.LOCK_TARGET_STATE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeLockTargetState() {
|
||||
unsubscribe(HomekitCharacteristicType.LOCK_TARGET_STATE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitCharacteristicType;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
|
||||
import io.github.hapjava.accessories.MotionSensorAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.services.impl.MotionSensorService;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Tim Harper - Initial contribution
|
||||
*/
|
||||
public class HomekitMotionSensorImpl extends AbstractHomekitAccessoryImpl implements MotionSensorAccessory {
|
||||
private final BooleanItemReader motionSensedReader;
|
||||
|
||||
public HomekitMotionSensorImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) throws IncompleteAccessoryException {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
motionSensedReader = createBooleanReader(HomekitCharacteristicType.MOTION_DETECTED_STATE, OnOffType.ON,
|
||||
OpenClosedType.OPEN);
|
||||
getServices().add(new MotionSensorService(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> getMotionDetected() {
|
||||
return CompletableFuture.completedFuture(motionSensedReader.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeMotionDetected(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(HomekitCharacteristicType.MOTION_DETECTED_STATE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeMotionDetected() {
|
||||
unsubscribe(HomekitCharacteristicType.MOTION_DETECTED_STATE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.OCCUPANCY_DETECTED_STATE;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
|
||||
import io.github.hapjava.accessories.OccupancySensorAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.characteristics.impl.occupancysensor.OccupancyDetectedEnum;
|
||||
import io.github.hapjava.services.impl.OccupancySensorService;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Tim Harper - Initial contribution
|
||||
*/
|
||||
public class HomekitOccupancySensorImpl extends AbstractHomekitAccessoryImpl implements OccupancySensorAccessory {
|
||||
private final BooleanItemReader occupancySensedReader;
|
||||
|
||||
public HomekitOccupancySensorImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) throws IncompleteAccessoryException {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
occupancySensedReader = createBooleanReader(OCCUPANCY_DETECTED_STATE, OnOffType.ON, OpenClosedType.OPEN);
|
||||
getServices().add(new OccupancySensorService(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<OccupancyDetectedEnum> getOccupancyDetected() {
|
||||
return CompletableFuture.completedFuture(
|
||||
occupancySensedReader.getValue() ? OccupancyDetectedEnum.DETECTED : OccupancyDetectedEnum.NOT_DETECTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeOccupancyDetected(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(OCCUPANCY_DETECTED_STATE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeOccupancyDetected() {
|
||||
unsubscribe(OCCUPANCY_DETECTED_STATE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitCharacteristicType;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
|
||||
import io.github.hapjava.accessories.OutletAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.services.impl.OutletService;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Eugen Freiter - Initial contribution
|
||||
*/
|
||||
public class HomekitOutletImpl extends AbstractHomekitAccessoryImpl implements OutletAccessory {
|
||||
private final BooleanItemReader inUseReader;
|
||||
private final BooleanItemReader onReader;
|
||||
|
||||
public HomekitOutletImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) throws IncompleteAccessoryException {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
inUseReader = createBooleanReader(HomekitCharacteristicType.INUSE_STATUS, OnOffType.ON, OpenClosedType.OPEN);
|
||||
onReader = createBooleanReader(HomekitCharacteristicType.ON_STATE, OnOffType.ON, OpenClosedType.OPEN);
|
||||
getServices().add(new OutletService(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> getPowerState() {
|
||||
return CompletableFuture.completedFuture(onReader.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> getOutletInUse() {
|
||||
return CompletableFuture.completedFuture(inUseReader.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setPowerState(boolean state) {
|
||||
onReader.setValue(state);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribePowerState(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(HomekitCharacteristicType.ON_STATE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeOutletInUse(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(HomekitCharacteristicType.INUSE_STATUS, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribePowerState() {
|
||||
unsubscribe(HomekitCharacteristicType.ON_STATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeOutletInUse() {
|
||||
unsubscribe(HomekitCharacteristicType.INUSE_STATUS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.SECURITY_SYSTEM_CURRENT_STATE;
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.SECURITY_SYSTEM_TARGET_STATE;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.openhab.core.library.items.StringItem;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitCharacteristicType;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
|
||||
import io.github.hapjava.accessories.SecuritySystemAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.characteristics.impl.securitysystem.CurrentSecuritySystemStateEnum;
|
||||
import io.github.hapjava.characteristics.impl.securitysystem.TargetSecuritySystemStateEnum;
|
||||
import io.github.hapjava.services.impl.SecuritySystemService;
|
||||
|
||||
/**
|
||||
* Implements SecuritySystem as a GroupedAccessory made up of multiple items:
|
||||
* <ul>
|
||||
* <li>CurrentSecuritySystemState: String type</li>
|
||||
* <li>TargetSecuritySystemState: String type</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Cody Cutrer - Initial contribution
|
||||
*/
|
||||
public class HomekitSecuritySystemImpl extends AbstractHomekitAccessoryImpl implements SecuritySystemAccessory {
|
||||
private final Map<CurrentSecuritySystemStateEnum, String> currentStateMapping = new EnumMap<CurrentSecuritySystemStateEnum, String>(
|
||||
CurrentSecuritySystemStateEnum.class) {
|
||||
{
|
||||
put(CurrentSecuritySystemStateEnum.DISARMED, "DISARMED");
|
||||
put(CurrentSecuritySystemStateEnum.AWAY_ARM, "AWAY_ARM");
|
||||
put(CurrentSecuritySystemStateEnum.STAY_ARM, "STAY_ARM");
|
||||
put(CurrentSecuritySystemStateEnum.NIGHT_ARM, "NIGHT_ARM");
|
||||
put(CurrentSecuritySystemStateEnum.TRIGGERED, "TRIGGERED");
|
||||
}
|
||||
};
|
||||
private final Map<TargetSecuritySystemStateEnum, String> targetStateMapping = new EnumMap<TargetSecuritySystemStateEnum, String>(
|
||||
TargetSecuritySystemStateEnum.class) {
|
||||
{
|
||||
put(TargetSecuritySystemStateEnum.DISARM, "DISARM");
|
||||
put(TargetSecuritySystemStateEnum.AWAY_ARM, "AWAY_ARM");
|
||||
put(TargetSecuritySystemStateEnum.STAY_ARM, "STAY_ARM");
|
||||
put(TargetSecuritySystemStateEnum.NIGHT_ARM, "NIGHT_ARM");
|
||||
}
|
||||
};
|
||||
|
||||
public HomekitSecuritySystemImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
updateMapping(SECURITY_SYSTEM_CURRENT_STATE, currentStateMapping);
|
||||
updateMapping(SECURITY_SYSTEM_TARGET_STATE, targetStateMapping);
|
||||
getServices().add(new SecuritySystemService(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<CurrentSecuritySystemStateEnum> getCurrentSecuritySystemState() {
|
||||
return CompletableFuture.completedFuture(getKeyFromMapping(SECURITY_SYSTEM_CURRENT_STATE, currentStateMapping,
|
||||
CurrentSecuritySystemStateEnum.DISARMED));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTargetSecuritySystemState(TargetSecuritySystemStateEnum state) {
|
||||
getItem(HomekitCharacteristicType.SECURITY_SYSTEM_TARGET_STATE, StringItem.class)
|
||||
.ifPresent(item -> item.send(new StringType(targetStateMapping.get(state))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<TargetSecuritySystemStateEnum> getTargetSecuritySystemState() {
|
||||
return CompletableFuture.completedFuture(getKeyFromMapping(SECURITY_SYSTEM_TARGET_STATE, targetStateMapping,
|
||||
TargetSecuritySystemStateEnum.DISARM));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeCurrentSecuritySystemState(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(SECURITY_SYSTEM_CURRENT_STATE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeCurrentSecuritySystemState() {
|
||||
unsubscribe(SECURITY_SYSTEM_CURRENT_STATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeTargetSecuritySystemState(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(SECURITY_SYSTEM_TARGET_STATE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeTargetSecuritySystemState() {
|
||||
unsubscribe(SECURITY_SYSTEM_TARGET_STATE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.SMOKE_DETECTED_STATE;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
|
||||
import io.github.hapjava.accessories.SmokeSensorAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.characteristics.impl.smokesensor.SmokeDetectedStateEnum;
|
||||
import io.github.hapjava.services.impl.SmokeSensorService;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Cody Cutrer - Initial contribution
|
||||
*/
|
||||
public class HomekitSmokeSensorImpl extends AbstractHomekitAccessoryImpl implements SmokeSensorAccessory {
|
||||
private final BooleanItemReader smokeDetectedReader;
|
||||
|
||||
public HomekitSmokeSensorImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) throws IncompleteAccessoryException {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
smokeDetectedReader = createBooleanReader(SMOKE_DETECTED_STATE, OnOffType.ON, OpenClosedType.OPEN);
|
||||
this.getServices().add(new SmokeSensorService(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<SmokeDetectedStateEnum> getSmokeDetectedState() {
|
||||
return CompletableFuture.completedFuture(
|
||||
smokeDetectedReader.getValue() ? SmokeDetectedStateEnum.DETECTED : SmokeDetectedStateEnum.NOT_DETECTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeSmokeDetectedState(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(SMOKE_DETECTED_STATE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeSmokeDetectedState() {
|
||||
unsubscribe(SMOKE_DETECTED_STATE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitCharacteristicType;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
|
||||
import io.github.hapjava.accessories.SpeakerAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.services.impl.SpeakerService;
|
||||
|
||||
/**
|
||||
* Implements Speaker using an Item that provides an On/Off state for Mute.
|
||||
*
|
||||
* @author Eugen Freiter - Initial contribution
|
||||
*/
|
||||
public class HomekitSpeakerImpl extends AbstractHomekitAccessoryImpl implements SpeakerAccessory {
|
||||
private final BooleanItemReader muteReader;
|
||||
|
||||
public HomekitSpeakerImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) throws IncompleteAccessoryException {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
muteReader = createBooleanReader(HomekitCharacteristicType.MUTE, OnOffType.ON, OpenClosedType.OPEN);
|
||||
getServices().add(new SpeakerService(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> isMuted() {
|
||||
return CompletableFuture.completedFuture(muteReader.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setMute(boolean state) {
|
||||
muteReader.setValue(state);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeMuteState(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(HomekitCharacteristicType.MUTE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeMuteState() {
|
||||
unsubscribe(HomekitCharacteristicType.MUTE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.ON_STATE;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
|
||||
import io.github.hapjava.accessories.SwitchAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.services.impl.SwitchService;
|
||||
|
||||
/**
|
||||
* Implements Switch using an Item that provides an On/Off state.
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
public class HomekitSwitchImpl extends AbstractHomekitAccessoryImpl implements SwitchAccessory {
|
||||
private final BooleanItemReader onReader;
|
||||
|
||||
public HomekitSwitchImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) throws IncompleteAccessoryException {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
onReader = createBooleanReader(ON_STATE, OnOffType.ON, OpenClosedType.OPEN);
|
||||
getServices().add(new SwitchService(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> getSwitchState() {
|
||||
return CompletableFuture.completedFuture(onReader.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setSwitchState(boolean state) {
|
||||
onReader.setValue(state);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeSwitchState(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(ON_STATE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeSwitchState() {
|
||||
unsubscribe(ON_STATE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitCharacteristicType;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
|
||||
import io.github.hapjava.accessories.TemperatureSensorAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.characteristics.impl.thermostat.TargetTemperatureCharacteristic;
|
||||
import io.github.hapjava.services.impl.TemperatureSensorService;
|
||||
|
||||
/**
|
||||
* Implements a HomeKit TemperatureSensor using a NumberItem
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
class HomekitTemperatureSensorImpl extends AbstractHomekitAccessoryImpl implements TemperatureSensorAccessory {
|
||||
|
||||
public HomekitTemperatureSensorImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
getServices().add(new TemperatureSensorService(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Double> getCurrentTemperature() {
|
||||
final @Nullable DecimalType state = getStateAs(HomekitCharacteristicType.CURRENT_TEMPERATURE,
|
||||
DecimalType.class);
|
||||
return CompletableFuture.completedFuture(state != null ? convertToCelsius(state.doubleValue()) : 0.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeCurrentTemperature(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(HomekitCharacteristicType.CURRENT_TEMPERATURE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMinCurrentTemperature() {
|
||||
return getAccessoryConfiguration(HomekitCharacteristicType.CURRENT_TEMPERATURE, HomekitTaggedItem.MIN_VALUE,
|
||||
BigDecimal.valueOf(TargetTemperatureCharacteristic.DEFAULT_MIN_VALUE)).doubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMaxCurrentTemperature() {
|
||||
return getAccessoryConfiguration(HomekitCharacteristicType.CURRENT_TEMPERATURE, HomekitTaggedItem.MAX_VALUE,
|
||||
BigDecimal.valueOf(TargetTemperatureCharacteristic.DEFAULT_MAX_VALUE)).doubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMinStepCurrentTemperature() {
|
||||
return getAccessoryConfiguration(HomekitCharacteristicType.CURRENT_TEMPERATURE, HomekitTaggedItem.STEP,
|
||||
BigDecimal.valueOf(TargetTemperatureCharacteristic.DEFAULT_STEP)).doubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeCurrentTemperature() {
|
||||
unsubscribe(HomekitCharacteristicType.CURRENT_TEMPERATURE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.openhab.core.library.items.NumberItem;
|
||||
import org.openhab.core.library.items.StringItem;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitCharacteristicType;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.github.hapjava.accessories.ThermostatAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.characteristics.impl.thermostat.CurrentHeatingCoolingStateEnum;
|
||||
import io.github.hapjava.characteristics.impl.thermostat.TargetHeatingCoolingStateEnum;
|
||||
import io.github.hapjava.characteristics.impl.thermostat.TargetTemperatureCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.thermostat.TemperatureDisplayUnitEnum;
|
||||
import io.github.hapjava.services.impl.ThermostatService;
|
||||
|
||||
/**
|
||||
* Implements Thermostat as a GroupedAccessory made up of multiple items:
|
||||
* <ul>
|
||||
* <li>Current Temperature: Number type</li>
|
||||
* <li>Target Temperature: Number type</li>
|
||||
* <li>Current Heating/Cooling Mode: String type (see HomekitSettings.thermostat*Mode)</li>
|
||||
* <li>Target Heating/Cooling Mode: String type (see HomekitSettings.thermostat*Mode)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Andy Lintner - Initial contribution
|
||||
*/
|
||||
class HomekitThermostatImpl extends AbstractHomekitAccessoryImpl implements ThermostatAccessory {
|
||||
private final Logger logger = LoggerFactory.getLogger(HomekitThermostatImpl.class);
|
||||
|
||||
public HomekitThermostatImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
this.getServices().add(new ThermostatService(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<CurrentHeatingCoolingStateEnum> getCurrentState() {
|
||||
final HomekitSettings settings = getSettings();
|
||||
String stringValue = settings.thermostatCurrentModeOff;
|
||||
final Optional<HomekitTaggedItem> characteristic = getCharacteristic(
|
||||
HomekitCharacteristicType.CURRENT_HEATING_COOLING_STATE);
|
||||
if (characteristic.isPresent()) {
|
||||
stringValue = characteristic.get().getItem().getState().toString();
|
||||
} else {
|
||||
logger.warn("Missing mandatory characteristic {}", HomekitCharacteristicType.CURRENT_HEATING_COOLING_STATE);
|
||||
}
|
||||
|
||||
CurrentHeatingCoolingStateEnum mode;
|
||||
|
||||
if (stringValue.equalsIgnoreCase(settings.thermostatCurrentModeCooling)) {
|
||||
mode = CurrentHeatingCoolingStateEnum.COOL;
|
||||
} else if (stringValue.equalsIgnoreCase(settings.thermostatCurrentModeHeating)) {
|
||||
mode = CurrentHeatingCoolingStateEnum.HEAT;
|
||||
} else if (stringValue.equalsIgnoreCase(settings.thermostatCurrentModeOff)) {
|
||||
mode = CurrentHeatingCoolingStateEnum.OFF;
|
||||
} else if (stringValue.equals("UNDEF") || stringValue.equals("NULL")) {
|
||||
logger.warn("Heating cooling current mode not available. Relaying value of OFF to Homekit");
|
||||
mode = CurrentHeatingCoolingStateEnum.OFF;
|
||||
} else {
|
||||
logger.warn("Unrecognized heatingCoolingCurrentMode: {}. Expected {}, {}, or {} strings in value.",
|
||||
stringValue, settings.thermostatCurrentModeCooling, settings.thermostatCurrentModeHeating,
|
||||
settings.thermostatCurrentModeOff);
|
||||
mode = CurrentHeatingCoolingStateEnum.OFF;
|
||||
}
|
||||
return CompletableFuture.completedFuture(mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Double> getCurrentTemperature() {
|
||||
DecimalType state = getStateAs(HomekitCharacteristicType.CURRENT_TEMPERATURE, DecimalType.class);
|
||||
return CompletableFuture.completedFuture(state != null ? convertToCelsius(state.doubleValue()) : 0.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMinCurrentTemperature() {
|
||||
return getAccessoryConfiguration(HomekitCharacteristicType.CURRENT_TEMPERATURE, HomekitTaggedItem.MIN_VALUE,
|
||||
BigDecimal.valueOf(TargetTemperatureCharacteristic.DEFAULT_MIN_VALUE)).doubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMaxCurrentTemperature() {
|
||||
return getAccessoryConfiguration(HomekitCharacteristicType.CURRENT_TEMPERATURE, HomekitTaggedItem.MAX_VALUE,
|
||||
BigDecimal.valueOf(TargetTemperatureCharacteristic.DEFAULT_MAX_VALUE)).doubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMinStepCurrentTemperature() {
|
||||
return getAccessoryConfiguration(HomekitCharacteristicType.CURRENT_TEMPERATURE, HomekitTaggedItem.STEP,
|
||||
BigDecimal.valueOf(TargetTemperatureCharacteristic.DEFAULT_STEP)).doubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<TargetHeatingCoolingStateEnum> getTargetState() {
|
||||
final HomekitSettings settings = getSettings();
|
||||
String stringValue = settings.thermostatTargetModeOff;
|
||||
|
||||
final Optional<HomekitTaggedItem> characteristic = getCharacteristic(
|
||||
HomekitCharacteristicType.TARGET_HEATING_COOLING_STATE);
|
||||
if (characteristic.isPresent()) {
|
||||
stringValue = characteristic.get().getItem().getState().toString();
|
||||
} else {
|
||||
logger.warn("Missing mandatory characteristic {}", HomekitCharacteristicType.TARGET_HEATING_COOLING_STATE);
|
||||
}
|
||||
TargetHeatingCoolingStateEnum mode;
|
||||
|
||||
if (stringValue.equalsIgnoreCase(settings.thermostatTargetModeCool)) {
|
||||
mode = TargetHeatingCoolingStateEnum.COOL;
|
||||
} else if (stringValue.equalsIgnoreCase(settings.thermostatTargetModeHeat)) {
|
||||
mode = TargetHeatingCoolingStateEnum.HEAT;
|
||||
} else if (stringValue.equalsIgnoreCase(settings.thermostatTargetModeAuto)) {
|
||||
mode = TargetHeatingCoolingStateEnum.AUTO;
|
||||
} else if (stringValue.equalsIgnoreCase(settings.thermostatTargetModeOff)) {
|
||||
mode = TargetHeatingCoolingStateEnum.OFF;
|
||||
} else if (stringValue.equals("UNDEF") || stringValue.equals("NULL")) {
|
||||
logger.warn("Heating cooling target mode not available. Relaying value of OFF to Homekit");
|
||||
mode = TargetHeatingCoolingStateEnum.OFF;
|
||||
} else {
|
||||
logger.warn("Unrecognized heating cooling target mode: {}. Expected {}, {}, {}, or {} strings in value.",
|
||||
stringValue, settings.thermostatTargetModeCool, settings.thermostatTargetModeHeat,
|
||||
settings.thermostatTargetModeAuto, settings.thermostatTargetModeOff);
|
||||
mode = TargetHeatingCoolingStateEnum.OFF;
|
||||
}
|
||||
return CompletableFuture.completedFuture(mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<TemperatureDisplayUnitEnum> getTemperatureDisplayUnit() {
|
||||
return CompletableFuture
|
||||
.completedFuture(getSettings().useFahrenheitTemperature ? TemperatureDisplayUnitEnum.FAHRENHEIT
|
||||
: TemperatureDisplayUnitEnum.CELSIUS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTemperatureDisplayUnit(TemperatureDisplayUnitEnum value) {
|
||||
// TODO: add support for display unit change
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Double> getTargetTemperature() {
|
||||
DecimalType state = getStateAs(HomekitCharacteristicType.TARGET_TEMPERATURE, DecimalType.class);
|
||||
return CompletableFuture.completedFuture(state != null ? convertToCelsius(state.doubleValue()) : 0.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTargetState(TargetHeatingCoolingStateEnum mode) {
|
||||
final HomekitSettings settings = getSettings();
|
||||
String modeString = null;
|
||||
switch (mode) {
|
||||
case AUTO:
|
||||
modeString = settings.thermostatTargetModeAuto;
|
||||
break;
|
||||
|
||||
case COOL:
|
||||
modeString = settings.thermostatTargetModeCool;
|
||||
break;
|
||||
|
||||
case HEAT:
|
||||
modeString = settings.thermostatTargetModeHeat;
|
||||
break;
|
||||
|
||||
case OFF:
|
||||
modeString = settings.thermostatTargetModeOff;
|
||||
break;
|
||||
}
|
||||
final Optional<HomekitTaggedItem> characteristic = getCharacteristic(
|
||||
HomekitCharacteristicType.TARGET_HEATING_COOLING_STATE);
|
||||
if (characteristic.isPresent()) {
|
||||
((StringItem) characteristic.get().getItem()).send(new StringType(modeString));
|
||||
} else {
|
||||
logger.warn("Missing mandatory characteristic {}", HomekitCharacteristicType.TARGET_HEATING_COOLING_STATE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTargetTemperature(Double value) {
|
||||
final Optional<HomekitTaggedItem> characteristic = getCharacteristic(
|
||||
HomekitCharacteristicType.TARGET_TEMPERATURE);
|
||||
if (characteristic.isPresent()) {
|
||||
((NumberItem) characteristic.get().getItem())
|
||||
.send(new DecimalType(BigDecimal.valueOf(convertFromCelsius(value))));
|
||||
} else {
|
||||
logger.warn("Missing mandatory characteristic {}", HomekitCharacteristicType.TARGET_TEMPERATURE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMinTargetTemperature() {
|
||||
return getAccessoryConfiguration(HomekitCharacteristicType.TARGET_TEMPERATURE, HomekitTaggedItem.MIN_VALUE,
|
||||
BigDecimal.valueOf(TargetTemperatureCharacteristic.DEFAULT_MIN_VALUE)).doubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMaxTargetTemperature() {
|
||||
return getAccessoryConfiguration(HomekitCharacteristicType.TARGET_TEMPERATURE, HomekitTaggedItem.MAX_VALUE,
|
||||
BigDecimal.valueOf(TargetTemperatureCharacteristic.DEFAULT_MAX_VALUE)).doubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMinStepTargetTemperature() {
|
||||
return getAccessoryConfiguration(HomekitCharacteristicType.TARGET_TEMPERATURE, HomekitTaggedItem.STEP,
|
||||
BigDecimal.valueOf(TargetTemperatureCharacteristic.DEFAULT_STEP)).doubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeCurrentState(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(HomekitCharacteristicType.CURRENT_HEATING_COOLING_STATE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeCurrentTemperature(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(HomekitCharacteristicType.CURRENT_TEMPERATURE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeTargetState(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(HomekitCharacteristicType.TARGET_HEATING_COOLING_STATE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeTargetTemperature(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(HomekitCharacteristicType.TARGET_TEMPERATURE, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeTemperatureDisplayUnit(HomekitCharacteristicChangeCallback callback) {
|
||||
// TODO: add support for display unit change
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeCurrentState() {
|
||||
unsubscribe(HomekitCharacteristicType.CURRENT_HEATING_COOLING_STATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeCurrentTemperature() {
|
||||
unsubscribe(HomekitCharacteristicType.CURRENT_TEMPERATURE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeTemperatureDisplayUnit() {
|
||||
// TODO: add support for display unit change
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeTargetState() {
|
||||
unsubscribe(HomekitCharacteristicType.TARGET_HEATING_COOLING_STATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeTargetTemperature() {
|
||||
unsubscribe(HomekitCharacteristicType.TARGET_TEMPERATURE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.ACTIVE_STATUS;
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.INUSE_STATUS;
|
||||
import static org.openhab.io.homekit.internal.HomekitCharacteristicType.REMAINING_DURATION;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.items.GenericItem;
|
||||
import org.openhab.core.library.items.SwitchItem;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitCharacteristicType;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import io.github.hapjava.accessories.ValveAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.characteristics.impl.common.ActiveEnum;
|
||||
import io.github.hapjava.characteristics.impl.common.InUseEnum;
|
||||
import io.github.hapjava.characteristics.impl.valve.RemainingDurationCharacteristic;
|
||||
import io.github.hapjava.characteristics.impl.valve.ValveTypeEnum;
|
||||
import io.github.hapjava.services.impl.ValveService;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Tim Harper - Initial contribution
|
||||
* @author Eugen Freiter - timer implementation
|
||||
*/
|
||||
public class HomekitValveImpl extends AbstractHomekitAccessoryImpl implements ValveAccessory {
|
||||
private final Logger logger = LoggerFactory.getLogger(HomekitValveImpl.class);
|
||||
private static final String CONFIG_VALVE_TYPE = "homekitValveType";
|
||||
public static final String CONFIG_DEFAULT_DURATION = "homekitDefaultDuration";
|
||||
private static final String CONFIG_TIMER = "homekitTimer";
|
||||
|
||||
private static final Map<String, ValveTypeEnum> CONFIG_VALVE_TYPE_MAPPING = new HashMap<String, ValveTypeEnum>() {
|
||||
{
|
||||
put("GENERIC", ValveTypeEnum.GENERIC);
|
||||
put("IRRIGATION", ValveTypeEnum.IRRIGATION);
|
||||
put("SHOWER", ValveTypeEnum.SHOWER);
|
||||
put("FAUCET", ValveTypeEnum.WATER_FAUCET);
|
||||
}
|
||||
};
|
||||
private final BooleanItemReader inUseReader;
|
||||
private final BooleanItemReader activeReader;
|
||||
private final ScheduledExecutorService timerService = Executors.newSingleThreadScheduledExecutor();
|
||||
private ScheduledFuture<?> valveTimer;
|
||||
private final boolean homekitTimer;
|
||||
|
||||
public HomekitValveImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) throws IncompleteAccessoryException {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
inUseReader = createBooleanReader(INUSE_STATUS, OnOffType.ON, OpenClosedType.OPEN);
|
||||
activeReader = createBooleanReader(ACTIVE_STATUS, OnOffType.ON, OpenClosedType.OPEN);
|
||||
ValveService service = new ValveService(this);
|
||||
getServices().add(service);
|
||||
final String timerConfig = getAccessoryConfiguration(CONFIG_TIMER, "");
|
||||
homekitTimer = timerConfig.equalsIgnoreCase("yes") || timerConfig.equalsIgnoreCase("true");
|
||||
if (homekitTimer) {
|
||||
addRemainingDurationCharacteristic(taggedItem, updater, service);
|
||||
}
|
||||
}
|
||||
|
||||
private void addRemainingDurationCharacteristic(HomekitTaggedItem taggedItem, HomekitAccessoryUpdater updater,
|
||||
ValveService service) {
|
||||
logger.trace("addRemainingDurationCharacteristic for {}", taggedItem);
|
||||
service.addOptionalCharacteristic(new RemainingDurationCharacteristic(() -> {
|
||||
int remainingTime = 0;
|
||||
ScheduledFuture<?> future = valveTimer;
|
||||
if (future != null && !future.isDone()) {
|
||||
remainingTime = Math.toIntExact(future.getDelay(TimeUnit.SECONDS));
|
||||
}
|
||||
return CompletableFuture.completedFuture(remainingTime);
|
||||
}, HomekitCharacteristicFactory.getSubscriber(taggedItem, REMAINING_DURATION, updater),
|
||||
HomekitCharacteristicFactory.getUnsubscriber(taggedItem, REMAINING_DURATION, updater)));
|
||||
}
|
||||
|
||||
/**
|
||||
* return duration set by home app at corresponding OH items. if ot set, then return the default duration from
|
||||
* configuration.
|
||||
*
|
||||
* @return duraion
|
||||
*/
|
||||
private int getDuration() {
|
||||
int duration = 0;
|
||||
final @Nullable DecimalType durationState = getStateAs(HomekitCharacteristicType.DURATION, DecimalType.class);
|
||||
if (durationState != null) {
|
||||
duration = durationState.intValue();
|
||||
}
|
||||
return duration;
|
||||
}
|
||||
|
||||
private void startTimer() {
|
||||
int duration = getDuration();
|
||||
logger.trace("start timer for duration {}", duration);
|
||||
if (duration > 0) {
|
||||
stopTimer();
|
||||
valveTimer = timerService.schedule(() -> {
|
||||
logger.trace("valve timer is over. switching off the valve");
|
||||
switchOffValve();
|
||||
// let home app refresh the remaining duration, which is 0
|
||||
((GenericItem) getRootAccessory().getItem()).send(RefreshType.REFRESH);
|
||||
}, duration, TimeUnit.SECONDS);
|
||||
logger.trace("started valve timer for {} seconds.", duration);
|
||||
} else {
|
||||
logger.debug("valve timer not started as duration = 0");
|
||||
}
|
||||
}
|
||||
|
||||
private void stopTimer() {
|
||||
ScheduledFuture<?> future = valveTimer;
|
||||
if (future != null && !future.isDone()) {
|
||||
future.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<ActiveEnum> getValveActive() {
|
||||
return CompletableFuture
|
||||
.completedFuture(this.activeReader.getValue() ? ActiveEnum.ACTIVE : ActiveEnum.INACTIVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setValveActive(ActiveEnum state) {
|
||||
getItem(ACTIVE_STATUS, SwitchItem.class).ifPresent(item -> {
|
||||
item.send(OnOffType.from(state == ActiveEnum.ACTIVE));
|
||||
if (homekitTimer) {
|
||||
if ((state == ActiveEnum.ACTIVE)) {
|
||||
startTimer();
|
||||
} else {
|
||||
stopTimer();
|
||||
}
|
||||
// let home app refresh the remaining duration
|
||||
((GenericItem) getRootAccessory().getItem()).send(RefreshType.REFRESH);
|
||||
}
|
||||
});
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
private void switchOffValve() {
|
||||
getItem(ACTIVE_STATUS, SwitchItem.class).ifPresent(item -> item.send(OnOffType.OFF));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeValveActive(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(ACTIVE_STATUS, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeValveActive() {
|
||||
unsubscribe(ACTIVE_STATUS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<InUseEnum> getValveInUse() {
|
||||
return CompletableFuture.completedFuture(inUseReader.getValue() ? InUseEnum.IN_USE : InUseEnum.NOT_IN_USE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeValveInUse(HomekitCharacteristicChangeCallback callback) {
|
||||
subscribe(INUSE_STATUS, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeValveInUse() {
|
||||
unsubscribe(INUSE_STATUS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<ValveTypeEnum> getValveType() {
|
||||
final String valveType = getAccessoryConfiguration(CONFIG_VALVE_TYPE, "GENERIC");
|
||||
ValveTypeEnum type = CONFIG_VALVE_TYPE_MAPPING.get(valveType.toUpperCase());
|
||||
return CompletableFuture.completedFuture(type != null ? type : ValveTypeEnum.GENERIC);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribeValveType(HomekitCharacteristicChangeCallback callback) {
|
||||
// nothing changes here
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribeValveType() {
|
||||
// nothing changes here
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
|
||||
import io.github.hapjava.accessories.WindowCoveringAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.characteristics.impl.windowcovering.PositionStateEnum;
|
||||
import io.github.hapjava.services.impl.WindowCoveringService;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author epike - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HomekitWindowCoveringImpl extends AbstractHomekitPositionAccessoryImpl implements WindowCoveringAccessory {
|
||||
|
||||
public HomekitWindowCoveringImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
getServices().add(new WindowCoveringService(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public CompletableFuture<Integer> getCurrentPosition() {
|
||||
return super.getCurrentPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public CompletableFuture<PositionStateEnum> getPositionState() {
|
||||
return super.getPositionState();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public CompletableFuture<Integer> getTargetPosition() {
|
||||
return super.getTargetPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public CompletableFuture<Void> setTargetPosition(int value) {
|
||||
return super.setTargetPosition(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public void subscribeCurrentPosition(HomekitCharacteristicChangeCallback callback) {
|
||||
super.subscribeCurrentPosition(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public void subscribePositionState(HomekitCharacteristicChangeCallback callback) {
|
||||
super.subscribePositionState(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public void subscribeTargetPosition(HomekitCharacteristicChangeCallback callback) {
|
||||
super.subscribeTargetPosition(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public void unsubscribeCurrentPosition() {
|
||||
super.unsubscribeCurrentPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public void unsubscribePositionState() {
|
||||
super.unsubscribePositionState();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public void unsubscribeTargetPosition() {
|
||||
super.unsubscribeTargetPosition();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.io.homekit.internal.HomekitAccessoryUpdater;
|
||||
import org.openhab.io.homekit.internal.HomekitSettings;
|
||||
import org.openhab.io.homekit.internal.HomekitTaggedItem;
|
||||
|
||||
import io.github.hapjava.accessories.WindowAccessory;
|
||||
import io.github.hapjava.characteristics.HomekitCharacteristicChangeCallback;
|
||||
import io.github.hapjava.characteristics.impl.windowcovering.PositionStateEnum;
|
||||
import io.github.hapjava.services.impl.WindowService;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Eugen Freiter - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HomekitWindowImpl extends AbstractHomekitPositionAccessoryImpl implements WindowAccessory {
|
||||
|
||||
public HomekitWindowImpl(HomekitTaggedItem taggedItem, List<HomekitTaggedItem> mandatoryCharacteristics,
|
||||
HomekitAccessoryUpdater updater, HomekitSettings settings) {
|
||||
super(taggedItem, mandatoryCharacteristics, updater, settings);
|
||||
getServices().add(new WindowService(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public CompletableFuture<Integer> getCurrentPosition() {
|
||||
return super.getCurrentPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public CompletableFuture<PositionStateEnum> getPositionState() {
|
||||
return super.getPositionState();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public CompletableFuture<Integer> getTargetPosition() {
|
||||
return super.getTargetPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public CompletableFuture<Void> setTargetPosition(Integer value) {
|
||||
return super.setTargetPosition(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public void subscribeCurrentPosition(HomekitCharacteristicChangeCallback callback) {
|
||||
super.subscribeCurrentPosition(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public void subscribePositionState(HomekitCharacteristicChangeCallback callback) {
|
||||
super.subscribePositionState(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public void subscribeTargetPosition(HomekitCharacteristicChangeCallback callback) {
|
||||
super.subscribeTargetPosition(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public void unsubscribeCurrentPosition() {
|
||||
super.unsubscribeCurrentPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public void unsubscribePositionState() {
|
||||
super.unsubscribePositionState();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNullByDefault({})
|
||||
public void unsubscribeTargetPosition() {
|
||||
super.unsubscribeTargetPosition();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.io.homekit.internal.accessories;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.io.homekit.internal.HomekitCharacteristicType;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Tim Harper - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class IncompleteAccessoryException extends Exception {
|
||||
private static final long serialVersionUID = 8595808359805444177L;
|
||||
|
||||
public IncompleteAccessoryException(HomekitCharacteristicType missingType) {
|
||||
super(String.format("Missing accessory type %s", missingType.getTag()));
|
||||
}
|
||||
|
||||
public IncompleteAccessoryException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
|
||||
https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="io:homekit">
|
||||
|
||||
<parameter-group name="core">
|
||||
<label>Core Configuration</label>
|
||||
</parameter-group>
|
||||
<parameter-group name="thermostat">
|
||||
<label>Thermostat Settings</label>
|
||||
<description>General thermostat settings</description>
|
||||
</parameter-group>
|
||||
<parameter-group name="thermostatTargetHeatingCooling">
|
||||
<label>Thermostat Target Heating/Cooling Mapping</label>
|
||||
<description>String values used by your thermostat to set different targetHeatingCooling modes</description>
|
||||
</parameter-group>
|
||||
<parameter-group name="thermostatCurrentHeatingCooling">
|
||||
<label>Thermostat Current Heating/Cooling Mapping</label>
|
||||
<description>String values used by your thermostat to set different targetHeatingCooling modes</description>
|
||||
</parameter-group>
|
||||
|
||||
<parameter name="port" type="integer" required="true" groupName="core">
|
||||
<label>Port</label>
|
||||
<description>Defines the port the HomeKit integration listens on.</description>
|
||||
<default>9124</default>
|
||||
</parameter>
|
||||
<parameter name="pin" type="text" pattern="\d{3}-\d{2}-\d{3}" required="true" groupName="core">
|
||||
<label>Pin</label>
|
||||
<description>Defines the pin, used for pairing, in the form ###-##-###.</description>
|
||||
<default>031-45-154</default>
|
||||
</parameter>
|
||||
<parameter name="networkInterface" type="text" required="false" groupName="core">
|
||||
<label>Network Interface</label>
|
||||
<description>Defines the IP address of the network interface to expose the HomeKit integration on.</description>
|
||||
</parameter>
|
||||
<parameter name="startDelay" type="integer" required="true" groupName="core" unit="s">
|
||||
<label>Start Delay</label>
|
||||
<description>HomeKit start delay in case of item configuration differences.</description>
|
||||
<default>30</default>
|
||||
</parameter>
|
||||
<parameter name="useFahrenheitTemperature" type="boolean" required="true" groupName="thermostat">
|
||||
<label>Use Fahrenheit Temperature</label>
|
||||
<description>Defines whether or not to direct HomeKit clients to use fahrenheit temperatures instead of celsius.</description>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
|
||||
<parameter name="thermostatTargetModeCool" type="text" required="true"
|
||||
groupName="thermostatTargetHeatingCooling">
|
||||
<label>Cool Value</label>
|
||||
<description>Word used to set the target heatingCoolingMode to COOL (if a thermostat is defined).</description>
|
||||
<default>CoolOn</default>
|
||||
</parameter>
|
||||
<parameter name="thermostatTargetModeHeat" type="text" required="true"
|
||||
groupName="thermostatTargetHeatingCooling">
|
||||
<label>Heat Value</label>
|
||||
<description>Word used to set the target heatingCoolingMode to HEAT (if a thermostat is defined).</description>
|
||||
<default>HeatOn</default>
|
||||
</parameter>
|
||||
<parameter name="thermostatTargetModeAuto" type="text" required="true"
|
||||
groupName="thermostatTargetHeatingCooling">
|
||||
<label>Auto Value</label>
|
||||
<description>Word used to set the target heatingCoolingMode to AUTO (if a thermostat is defined).</description>
|
||||
<default>Auto</default>
|
||||
</parameter>
|
||||
<parameter name="thermostatTargetModeOff" type="text" required="true"
|
||||
groupName="thermostatTargetHeatingCooling">
|
||||
<label>Off Value</label>
|
||||
<description>Word used to set the target heatingCoolingMode to OFF (if a thermostat is defined).</description>
|
||||
<default>Off</default>
|
||||
</parameter>
|
||||
|
||||
<parameter name="thermostatCurrentModeHeating" type="text" required="true"
|
||||
groupName="thermostatCurrentHeatingCooling">
|
||||
<label>Heating Value</label>
|
||||
<description>Value for setting target heatingCoolingCurrentMode to HEAT (IE: indicating that the heater is currently
|
||||
warming the home).</description>
|
||||
<default>Heating</default>
|
||||
</parameter>
|
||||
<parameter name="thermostatCurrentModeCooling" type="text" required="true"
|
||||
groupName="thermostatCurrentHeatingCooling">
|
||||
<label>Cooling Value</label>
|
||||
<description>Value for setting target heatingCoolingCurrentMode to COOL (IE: indicating that the air condition is
|
||||
currently cooling the home).</description>
|
||||
<default>Cooling</default>
|
||||
</parameter>
|
||||
<parameter name="thermostatCurrentModeOff" type="text" required="true"
|
||||
groupName="thermostatCurrentHeatingCooling">
|
||||
<label>Off Value</label>
|
||||
<description>Value for setting target heatingCoolingCurrentMode to OFF (IE: the hvac is currently idle, because the
|
||||
target temperature has been reached per the mode).</description>
|
||||
<default>Off</default>
|
||||
</parameter>
|
||||
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
||||
Reference in New Issue
Block a user