added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
32
bundles/org.openhab.binding.mihome/.classpath
Normal file
32
bundles/org.openhab.binding.mihome/.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.binding.mihome/.project
Normal file
23
bundles/org.openhab.binding.mihome/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.mihome</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>
|
||||
13
bundles/org.openhab.binding.mihome/NOTICE
Normal file
13
bundles/org.openhab.binding.mihome/NOTICE
Normal file
@@ -0,0 +1,13 @@
|
||||
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
|
||||
575
bundles/org.openhab.binding.mihome/README.md
Normal file
575
bundles/org.openhab.binding.mihome/README.md
Normal file
@@ -0,0 +1,575 @@
|
||||
# Xiaomi Mi Smart Home Binding
|
||||
|
||||
This binding allows your openHAB to communicate with the Xiaomi Smart Home Suite.
|
||||
It consists of devices communicating over a ZigBee network with a ZigBee - WiFi gateway.
|
||||
|
||||
The devices are very affordable and you can get them from your favourite chinese marktes like [AliExpress](https://www.aliexpress.com/) or [GearBest](https://www.gearbest.com).
|
||||
The sensors run on a coincell battery for over a year.
|
||||
|
||||
After setup, you can disconnect the gateway from the internet to keep your sensor information private.
|
||||
|
||||
## Supported devices
|
||||
|
||||
| Device | Picture |
|
||||
| --- | --- |
|
||||
| Gateway v2 (with radio support) or v3 |  |
|
||||
| Mijia Temperature and Humidity Sensor | |
|
||||
| Aqara Temperature, Humidity and Pressure Sensor |  |
|
||||
| Mijia Door/Window Sensor |  |
|
||||
| Aqara Door/Window Sensor |  |
|
||||
| Mijia Human Body Sensor |  |
|
||||
| Aqara Motion Sensor (with light intensity support) |  |
|
||||
| Smart Socket (Zigbee version) |  |
|
||||
| Magic Cube Controller |  |
|
||||
| Aqara Magic Cube Controller |  |
|
||||
| Aqara Vibration Sensor |  |
|
||||
| Mijia Wireless Switch |  |
|
||||
| Aqara Wireless Switch |  |
|
||||
| Aqara Wireless Switch (with acceleration sensor) |  |
|
||||
| Aqara Wall Switch (1 & 2 Button / With or Without Neutral Line) |  |
|
||||
| Aqara Wireless Light Control (1 & 2 Button) |  |
|
||||
| Aqara Curtain Motor |  |
|
||||
| Aqara Water Leak Sensor |  |
|
||||
| Honeywell Gas Detector |  |
|
||||
| Honeywell Smoke Detector |  |
|
||||
| Aqara Fingerprint & Keyless Card & PIN Lock |  |
|
||||
|
||||
## Setup
|
||||
|
||||
* Install the binding
|
||||
* Is your gateway already configured to connect to your WiFi? If not:
|
||||
|
||||
1. Install MiHome app from [Google Play](https://play.google.com/store/apps/details?id=com.xiaomi.smarthome) or [AppStore](https://itunes.apple.com/app/mi-home-xiaomi-for-your-smarthome/id957323480) (your phone may need to be changed to English language first)
|
||||
2. In the app create a Mi Home account and make sure to set your region to Mainland (China) under Settings -> Locale
|
||||
3. If asked, do NOT update your gateway to the latest firmware (note that update window may pop up sequentially). If you update, you may not be able to access the developer mode below.
|
||||
|
||||
* Enable developer mode of your gateway:
|
||||
|
||||
1. Select your Gateway in the MiHome app
|
||||
2. Go to the "..." menu on the top right corner and click "About"
|
||||
3. You now see two options "Smart Home Kit Forum" and "Gameplay Tutorial". Tap 5 times below the "Gameplay Tutorial" in the empty space (not the button itself) until you enable developer mode
|
||||
4. You should now have 2 extra options listed: `wireless communication protocol` and `hub info`, it may appear in Chinese
|
||||
5. Choose `wireless communication protocol`
|
||||
6. Tap the toggle switch to enable WiFi functions. Note down the developer key (aka password), something like: 91bg8zfkf9vd6uw7
|
||||
7. Make sure you hit the OK button (to the right of the cancel button) to save your changes
|
||||
8. Now update the gateway to the latest firmware
|
||||
|
||||
* Enable developer mode of your gateway (legacy app):
|
||||
|
||||
1. Select your Gateway in the MiHome app
|
||||
2. Go to the "..." menu on the top right corner and click "About"
|
||||
3. Tap the version number "Plug-in version : 2.XX.X" at the bottom of the screen repeatedly until you enable developer mode
|
||||
4. You should now have 2 extra options listed: `wireless communication protocol` and `hub info`
|
||||
5. Choose `wireless communication protocol`
|
||||
6. Tap the toggle switch to enable WiFi functions. Note down the developer key (aka password), something like: 91bg8zfkf9vd6uw7
|
||||
7. Make sure you hit the OK button (to the right of the cancel button) to save your changes
|
||||
8. Now update the gateway to the latest firmware
|
||||
|
||||
* In openHAB you should now be able to discover the Xiaomi Gateway
|
||||
* From now on you don't really need the app anymore. Only if you want to update the gateway firmware or if you want to add devices (see below). But adding devices can also be done without the app (see below)
|
||||
* Enter the previously noted developer key in openHAB Paper UI -> Configuration -> Things -> Xiaomi Gateway -> Edit -> Developer Key. Save (This is required if you want to be able to send controls to the devices like the light of the gateway)
|
||||
|
||||
## Connecting devices to the gateway
|
||||
|
||||
There are three ways of connecting supported devices to the gateway:
|
||||
|
||||
* Online - within the MiHome App
|
||||
* Offline - manual
|
||||
|
||||
1. Click 3 times on the Gateway's button
|
||||
2. Gateway will flash in blue and you will hear female voice in Chinese, you have 30 seconds to include your new device
|
||||
3. Place the needle into the sensor and hold it for at least 3 seconds
|
||||
4. You will hear confirmation message in Chinese
|
||||
5. The device appears in openHAB thing Inbox
|
||||
|
||||
* With the binding
|
||||
|
||||
1. After adding the gateway make sure you have entered the right developer key
|
||||
2. In Paper UI, go to your Inbox and trigger a discovery for the binding
|
||||
3. The gateway flashes in blue and you hear a female voice in Chinese, you have 30 seconds to include your new device
|
||||
4. Follow the instructions for your device to pair it to the gateway
|
||||
5. You will hear a confirmation message in Chinese
|
||||
6. The device appears in openHAB thing Inbox
|
||||
|
||||
__Hints:__
|
||||
|
||||
* If you don't want to hear the Chinese voice every time, you can disable it by setting the volume to minimum in the MiHome App (same for the blinking light)
|
||||
|
||||
* The devices don't need an Internet connection to be working after you have set up the developer mode BUT you will not be able to connect to them via App anymore - easiest way is to block their outgoing Internet connection in your router and enable it later, when you want to check for updates etc. This will ensure that your smart home data stays only with you!
|
||||
|
||||
## Removing devices from the gateway
|
||||
|
||||
If you remove a Thing in PapaerUI it will also trigger the gateway to unpair the device.
|
||||
It will only reappear in your Inbox, if you connect it to the gateway again.
|
||||
Just follow the instructions in ["Connecting devices to the gateway"](#connecting-devices-to-the-gateway).
|
||||
|
||||
## Network configuration
|
||||
|
||||
- The binding requires port `9898` to not be used by any other service on the system.
|
||||
- Make sure multicast traffic is correctly routed between the gateway and your openHAB instance
|
||||
|
||||
## Configuration examples
|
||||
|
||||
### xiaomi.things:
|
||||
|
||||
```
|
||||
Bridge mihome:bridge:f0b429XXXXXX "Xiaomi Gateway" [ serialNumber="f0b429XXXXXX", ipAddress="192.168.0.3", port=9898, key="XXXXXXXXXXXXXXXX", pollingInterval=6000 ] {
|
||||
Things:
|
||||
gateway f0b429XXXXXX "Xiaomi Mi Smart Home Gateway" [itemId="f0b429XXXXXX"]
|
||||
sensor_ht 158d0001XXXXXX "Xiaomi Temperature Sensor" [itemId="158d0001XXXXXX"]
|
||||
sensor_weather_v1 158d0001XXXXXX "Xiaomi Aqara Temp, Hum and Press Sensor" [itemId="158d0001XXXXXX"]
|
||||
sensor_motion 158d0001XXXXXX "Xiaomi Motion Sensor" [itemId="158d0001XXXXXX"]
|
||||
sensor_plug 158d0001XXXXXX "Xiaomi Plug" [itemId="158d0001XXXXXX"]
|
||||
sensor_magnet 158d0001XXXXXX "Xiaomi Door Sensor" [itemId="158d0001XXXXXX"]
|
||||
sensor_switch 158d0001XXXXXX "Xiaomi Mi Wireless Switch" [itemId="158d0001XXXXXX"]
|
||||
86sw2 158d0001XXXXXX "Aqara Wireless Wall Switch" [itemId="158d0001XXXXXX"]
|
||||
}
|
||||
```
|
||||
|
||||
### xiaomi.items:
|
||||
|
||||
```
|
||||
// Replace <GwID> with itemId of gateway from Things file
|
||||
// Replace <ID> with itemId of item from Things file
|
||||
// Gateway
|
||||
Switch Gateway_LightSwitch <light> { channel="mihome:gateway:<GwID>:<ID>:brightness" }
|
||||
Dimmer Gateway_Brightness <dimmablelight> { channel="mihome:gateway:<GwID>:<ID>:brightness" }
|
||||
Color Gateway_Color <rgb> { channel="mihome:gateway:<GwID>:<ID>:color" }
|
||||
Dimmer Gateway_ColorTemperature <heating> { channel="mihome:gateway:<GwID>:<ID>:colorTemperature" }
|
||||
Number Gateway_AmbientLight <sun> { channel="mihome:gateway:<GwID>:<ID>:illumination" }
|
||||
Number Gateway_Sound <soundvolume-0> { channel="mihome:gateway:<GwID>:<ID>:sound" }
|
||||
Switch Gateway_SoundSwitch <soundvolume_mute> { channel="mihome:gateway:<GwID>:<ID>:enableSound" }
|
||||
Dimmer Gateway_SoundVolume <soundvolume> { channel="mihome:gateway:<GwID>:<ID>:volume" }
|
||||
|
||||
// Temperature and Humidity Sensor
|
||||
Number:Temperature HT_Temperature <temperature> { channel="mihome:sensor_ht:<GwID>:<ID>:temperature" }
|
||||
Number:Dimensionless HT_Humidity <humidity> { channel="mihome:sensor_ht:<GwID>:<ID>:humidity" }
|
||||
Number HT_Battery <battery> { channel="mihome:sensor_ht:<GwID>:<ID>:batteryLevel" }
|
||||
Switch HT_BatteryLow <energy> { channel="mihome:sensor_ht:<GwID>:<ID>:lowBattery" }
|
||||
|
||||
// Aqara Temperature, Humidity and pressure Sensor
|
||||
Number:Temperature HTP_Temperature <temperature> { channel="mihome:sensor_weather_v1:<GwID>:<ID>:temperature" }
|
||||
Number:Dimensionless HTP_Humidity <humidity> { channel="mihome:sensor_weather_v1:<GwID>:<ID>:humidity" }
|
||||
Number:Pressure HTP_Pressure <pressure> { channel="mihome:sensor_weather_v1:<GwID>:<ID>:pressure" }
|
||||
Number HTP_Battery <battery> { channel="mihome:sensor_weather_v1:<GwID>:<ID>:batteryLevel" }
|
||||
Switch HTP_BatteryLow <energy> { channel="mihome:sensor_weather_v1:<GwID>:<ID>:lowBattery" }
|
||||
|
||||
// Mijia & Aqara Door/Window Sensor
|
||||
Contact WindowSwitch_Status <window> { channel="mihome:sensor_magnet:<GwID>:<ID>:isOpen" }
|
||||
// minimum 30 seconds
|
||||
Number WindowSwitch_AlarmTimer <clock> { channel="mihome:sensor_magnet:<GwID>:<ID>:isOpenAlarmTimer" }
|
||||
DateTime WindowSwitch_LastOpened "[%1$tY-%1$tm-%1$td %1$tH:%1$tM]" <clock-on> { channel="mihome:sensor_magnet:<GwID>:<ID>:lastOpened" }
|
||||
Number WindowSwitch_Battery <battery> { channel="mihome:sensor_magnet:<GwID>:<ID>:batteryLevel" }
|
||||
Switch WindowSwitch_BatteryLow <energy> { channel="mihome:sensor_magnet:<GwID>:<ID>:lowBattery" }
|
||||
|
||||
// Mijia Motion Sensor
|
||||
Switch MotionSensor_MotionStatus <motion> { channel="mihome:sensor_motion:<GwID>:<ID>:motion" }
|
||||
// minimum 5 seconds - remember that the sensor only triggers every minute to save energy
|
||||
Number MotionSensor_MotionTimer <clock> { channel="mihome:sensor_motion:<GwID>:<ID>:motionOffTimer" }
|
||||
DateTime MotionSensor_LastMotion "[%1$tY-%1$tm-%1$td %1$tH:%1$tM]" <clock-on> { channel="mihome:sensor_motion:<GwID>:<ID>:lastMotion" }
|
||||
Number MotionSensor_Battery <battery> { channel="mihome:sensor_motion:<GwID>:<ID>:batteryLevel" }
|
||||
Switch MotionSensor_BatteryLow <energy> { channel="mihome:sensor_motion:<GwID>:<ID>:lowBattery" }
|
||||
|
||||
// Aqara Motion Sensor
|
||||
Switch MotionSensor_MotionStatus <motion> { channel="mihome:sensor_motion_aq2:<GwID>:<ID>:motion" }
|
||||
// minimum 5 seconds - the sensor only triggers once every minute to save energy
|
||||
Number MotionSensor_MotionTimer <clock> { channel="mihome:sensor_motion_aq2:<GwID>:<ID>:motionOffTimer" }
|
||||
DateTime MotionSensor_LastMotion "[%1$tY-%1$tm-%1$td %1$tH:%1$tM]" <clock-on> { channel="mihome:sensor_motion_aq2:<GwID>:<ID>:lastMotion" }
|
||||
Number MotionSensor_Battery <battery> { channel="mihome:sensor_motion_aq2:<GwID>:<ID>:batteryLevel" }
|
||||
Switch MotionSensor_BatteryLow <energy> { channel="mihome:sensor_motion_aq2:<GwID>:<ID>:lowBattery" }
|
||||
Number MotionSensor_Lux "LUX [%.1f]" <sun> { channel="mihome:sensor_motion_aq2:<GwID>:<ID>:illumination" }
|
||||
|
||||
// Smart Socket
|
||||
Switch Plug_Switch <switch> { channel="mihome:sensor_plug:<GwID>:<ID>:power" }
|
||||
Switch Plug_Active <switch> { channel="mihome:sensor_plug:<GwID>:<ID>:inUse" }
|
||||
Number Plug_Power <energy> { channel="mihome:sensor_plug:<GwID>:<ID>:loadPower" }
|
||||
Number Plug_Consumption <line-incline> { channel="mihome:sensor_plug:<GwID>:<ID>:powerConsumed" }
|
||||
|
||||
// Mijia & Aqara Cube Controller - see "xiaomi.rules" for action triggers
|
||||
DateTime Cube_LastAction "[%1$tY-%1$tm-%1$td %1$tH:%1$tM]" <clock-on> { channel="mihome:sensor_cube:<GwID>:<ID>:lastAction" }
|
||||
Number:Angle Cube_RotationAngle { channel="mihome:sensor_cube:<GwID>:<ID>:rotationAngle" }
|
||||
Number:Time Cube_RotationTime { channel="mihome:sensor_cube:<GwID>:<ID>:rotationTime" }
|
||||
Number Cube_Battery <battery> { channel="mihome:sensor_cube:<GwID>:<ID>:batteryLevel" }
|
||||
Switch Cube_BatteryLow <energy> { channel="mihome:sensor_cube:<GwID>:<ID>:lowBattery" }
|
||||
|
||||
// Aqara Smart Motion Sensor - see "xiaomi.rules" for action triggers
|
||||
DateTime Vibration_LastAction "[%1$tY-%1$tm-%1$td %1$tH:%1$tM]" <clock-on> { channel="mihome:sensor_vibration:<GwID>:<ID>:lastAction" }
|
||||
Number Vibration_TiltAngle { channel="mihome:sensor_vibration:<GwID>:<ID>:tiltAngle" }
|
||||
Number Vibration_CoordinateX { channel="mihome:sensor_vibration:<GwID>:<ID>:coordinateX" }
|
||||
Number Vibration_CoordinateY { channel="mihome:sensor_vibration:<GwID>:<ID>:coordinateY" }
|
||||
Number Vibration_CoordinateZ { channel="mihome:sensor_vibration:<GwID>:<ID>:coordinateZ" }
|
||||
Number Vibration_BedActivity { channel="mihome:sensor_vibration:<GwID>:<ID>:bedActivity" }
|
||||
Number Vibration_Battery <battery> { channel="mihome:sensor_vibration:<GwID>:<ID>:batteryLevel" }
|
||||
Switch Vibration_BatteryLow <energy> { channel="mihome:sensor_vibration:<GwID>:<ID>:lowBattery" }
|
||||
|
||||
// Mijia & Aqara Wireless Switch - see "xiaomi.rules" for action triggers
|
||||
Number Switch_Battery <battery> { channel="mihome:sensor_switch:<GwID>:<ID>:batteryLevel" }
|
||||
Switch Switch_BatteryLow <energy> { channel="mihome:sensor_switch:<GwID>:<ID>:lowBattery" }
|
||||
|
||||
// Aqara Wirelss Light Control (1 Button) - see "xiaomi.rules" for action triggers
|
||||
Number AqaraSwitch1_Battery <battery> { channel="mihome:86sw1:<GwID>:<ID>:batteryLevel" }
|
||||
Switch AqaraSwitch1_BatteryLow <energy> { channel="mihome:86sw1:<GwID>:<ID>:lowBattery" }
|
||||
|
||||
// Aqara Wirelss Light Control (2 Button) - see "xiaomi.rules" for action triggers
|
||||
Number AqaraSwitch2_Battery <battery> { channel="mihome:86sw2:<GwID>:<ID>:batteryLevel" }
|
||||
Switch AqaraSwitch2_BatteryLow <energy> { channel="mihome:86sw2:<GwID>:<ID>:lowBattery" }
|
||||
|
||||
// Aqara Wall Switch (1 Button)
|
||||
Switch AqaraWallSwitch <switch> { channel="mihome:ctrl_neutral1:<GwID>:<ID>:ch1" }
|
||||
|
||||
// Aqara Wall Switch (2 Button)
|
||||
Switch AqaraWallSwitch1 <switch> { channel="mihome:ctrl_neutral2:<GwID>:<ID>:ch1" }
|
||||
Switch AqaraWallSwitch2 <switch> { channel="mihome:ctrl_neutral2:<GwID>:<ID>:ch2" }
|
||||
|
||||
// Aqara Wall Switch (1 Button & without neutral line)
|
||||
Switch AqaraWallSwitch <switch> { channel="mihome:ctrl_ln1:<GwID>:<ID>:ch1" }
|
||||
|
||||
// Aqara Wall Switch (2 Button & without neutral line)
|
||||
Switch AqaraWallSwitch1 <switch> { channel="mihome:ctrl_ln2:<GwID>:<ID>:ch1" }
|
||||
Switch AqaraWallSwitch2 <switch> { channel="mihome:ctrl_ln2:<GwID>:<ID>:ch2" }
|
||||
|
||||
// Aqara Curtain Motor
|
||||
Rollershutter CurtainMotorControl <blinds> { channel="curtain:<GwID>:<ID>:curtainControl" }
|
||||
```
|
||||
|
||||
### xiaomi.rules:
|
||||
|
||||
```
|
||||
rule "Mijia & Aqara Wireless Switch"
|
||||
when
|
||||
Channel "mihome:sensor_switch:<GwID>:<ID>:button" triggered
|
||||
then
|
||||
var actionName = receivedEvent.getEvent()
|
||||
switch(actionName) {
|
||||
case "SHORT_PRESSED": {
|
||||
<ACTION>
|
||||
}
|
||||
case "DOUBLE_PRESSED": {
|
||||
<ACTION>
|
||||
}
|
||||
case "LONG_PRESSED": {
|
||||
<ACTION>
|
||||
}
|
||||
case "LONG_RELEASED": {
|
||||
<ACTION>
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
rule "Mijia & Aqara Cube Controller"
|
||||
when
|
||||
Channel 'mihome:sensor_cube:<GwID>:<ID>:action' triggered
|
||||
then
|
||||
var actionName = receivedEvent.getEvent()
|
||||
switch(actionName) {
|
||||
case "MOVE": {
|
||||
<ACTION>
|
||||
}
|
||||
case "ROTATE_RIGHT": {
|
||||
<ACTION>
|
||||
}
|
||||
case "ROTATE_LEFT": {
|
||||
<ACTION>
|
||||
}
|
||||
case "FLIP90": {
|
||||
<ACTION>
|
||||
}
|
||||
case "FLIP180": {
|
||||
<ACTION>
|
||||
}
|
||||
case "TAP_TWICE": {
|
||||
<ACTION>
|
||||
}
|
||||
case "SHAKE_AIR": {
|
||||
<ACTION>
|
||||
}
|
||||
case "FREE_FALL": {
|
||||
<ACTION>
|
||||
}
|
||||
case "ALERT": {
|
||||
<ACTION>
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
rule "Aqara Smart Motion Sensor"
|
||||
when
|
||||
Channel 'mihome:sensor_vibration:<GwID>:<ID>:action' triggered
|
||||
then
|
||||
var actionName = receivedEvent.getEvent()
|
||||
switch(actionName) {
|
||||
case "VIBRATE": {
|
||||
<ACTION>
|
||||
}
|
||||
case "TILT": {
|
||||
<ACTION>
|
||||
}
|
||||
case "FREE_FALL": {
|
||||
<ACTION>
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
rule "Mijia & Aqara Motion Sensor"
|
||||
when
|
||||
Item MotionSensor_MotionStatus changed
|
||||
then
|
||||
if (MotionSensor_MotionStatus.state == ON) {
|
||||
<ACTION>
|
||||
} else {
|
||||
<ACTION>
|
||||
}
|
||||
end
|
||||
|
||||
rule "Mijia & Aqara Door/Window Sensor"
|
||||
when
|
||||
Item WindowSwitch_Status changed
|
||||
then
|
||||
if (WindowSwitch_Status.state == OPEN) {
|
||||
<ACTION>
|
||||
} else {
|
||||
<ACTION>
|
||||
}
|
||||
end
|
||||
|
||||
rule "Mijia & Aqara Door/Window Sensor - Window is open for longer than WindowSwitch_AlarmTimer"
|
||||
when
|
||||
Channel "mihome:sensor_magnet:<GwID>:<ID>:isOpenAlarm" triggered ALARM
|
||||
then
|
||||
<ACTION>
|
||||
end
|
||||
|
||||
rule "Aqara Wirelss Light Control (1 Button)"
|
||||
when
|
||||
Channel "mihome:86sw1:<GwID>:<ID>:ch1" triggered SHORT_PRESSED
|
||||
then
|
||||
<ACTION>
|
||||
end
|
||||
|
||||
rule "Aqara Wirelss Light Control (2 Button)"
|
||||
when
|
||||
Channel "mihome:86sw2:<GwID>:<ID>:ch1" triggered SHORT_PRESSED
|
||||
then
|
||||
<ACTION>
|
||||
end
|
||||
|
||||
rule "Aqara Wirelss Light Control (2 Button)"
|
||||
when
|
||||
Channel "mihome:86sw2:<GwID>:<ID>:ch2" triggered SHORT_PRESSED
|
||||
then
|
||||
<ACTION>
|
||||
end
|
||||
|
||||
rule "Aqara Wirelss Light Control (2 Button)"
|
||||
when
|
||||
Channel "mihome:86sw2:<GwID>:<ID>:dual_ch" triggered SHORT_PRESSED
|
||||
then
|
||||
<ACTION>
|
||||
end
|
||||
|
||||
// This rule is applicable for every battery powered sensor device
|
||||
rule "Xiaomi Motion Sensor Low Battery"
|
||||
when
|
||||
Item MotionSensor_BatteryLow changed to ON
|
||||
then
|
||||
<ACTION>
|
||||
end
|
||||
|
||||
rule "Play quiet knock-knock ringtone with the Xiaomi Gateway"
|
||||
when
|
||||
// Item ExampleSwitch changed to ON
|
||||
then
|
||||
Gateway_SoundVolume.sendCommand(2)
|
||||
Gateway_Sound.sendCommand(11)
|
||||
Thread::sleep(2000) /* wait for 2 seconds */
|
||||
Gateway_Sound.sendCommand(10000)
|
||||
Gateway_SoundVolume.sendCommand(0)
|
||||
end
|
||||
```
|
||||
|
||||
### xiaomi.sitemap:
|
||||
|
||||
```
|
||||
sitemap xiaomi label="Xiaomi" {
|
||||
// Example for selection of predefined sound file - you can also upload your own files with the official MiHome App and play them!
|
||||
Frame {
|
||||
...
|
||||
|
||||
// Selection for Xiaomi Gateway Sounds
|
||||
// 10000 is STOP
|
||||
// >10001 are own sounds you uploaded to the gateway
|
||||
Selection item=Gateway_Sound mappings=[ 0="police car 1",
|
||||
1="police car 2",
|
||||
2="accident",
|
||||
3="countdown",
|
||||
4="ghost",
|
||||
5="sniper rifle",
|
||||
6="battle",
|
||||
7="air raid",
|
||||
8="bark",
|
||||
10="doorbell",
|
||||
11="knock at a door",
|
||||
12="amuse",
|
||||
13="alarm clock",
|
||||
20="mimix",
|
||||
21="enthusuastic",
|
||||
22="guitar classic",
|
||||
23="ice world piano",
|
||||
24="leisure time",
|
||||
25="child hood",
|
||||
26="morning stream liet",
|
||||
27="music box",
|
||||
28="orange",
|
||||
29="thinker"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Handling unsupported devices
|
||||
|
||||
The Xiaomi ecosystem grows at a steady rate.
|
||||
So there is a good chance that in the future even more devices get added to the suite.
|
||||
This section describes, how to get the necessary information to support new device types.
|
||||
While a device is not supported yet, it is still possible to access its information.
|
||||
|
||||
Make sure you have connected your gateway to openHAB and the communication is working.
|
||||
|
||||
### Connect the new device
|
||||
|
||||
- Go through the normal procedure to add a device to the gateway
|
||||
- The device will show up in your inbox as a new unsupported device and its model name
|
||||
- Add the device as a new thing of type "basic device", now you have different channels to receive and send messages from/to the device
|
||||
- raw messages from the device
|
||||
- the data from the four different type of messages (see their details in the next chapter)
|
||||
- parameters you can send to the device
|
||||
|
||||
### Gather information about the new device for future support
|
||||
|
||||
The devices send different types of messages to the gateway.
|
||||
You have to capture as many of them as possible, so that the device is fully supported in the end.
|
||||
|
||||
1. Heartbeat (transmitted usually every 60 minutes)
|
||||
2. Report (device reports new sensor or status values)
|
||||
3. Read ACK (binding refreshes all sensor values after a restart of openHAB)
|
||||
4. Write ACK (device has received a command) __not avaiable for sensor-only devices__
|
||||
|
||||
### Open a new issue or get your hands dirty
|
||||
|
||||
Every little help is welcome, be part of the community!
|
||||
Post an issue in the GitHub repository with as much information as possible about the new device:
|
||||
- brand and link to device description
|
||||
- model name
|
||||
- content of all the different message types
|
||||
|
||||
Or implement the support by youself and submit a pull request.
|
||||
|
||||
### Handle the message contents of a basic device thing with items
|
||||
|
||||
You can access the whole message contents of the basic device thing with String items.
|
||||
That way you can make use of your device, even if it is not supported yet!
|
||||
The following examples are a demonstration, where a basic device thing for the gateway was manually added.
|
||||
|
||||
```
|
||||
String Gateway_Raw { channel="mihome:basic:xxx:lastMessage" }
|
||||
String Gateway_Heartbeat { channel="mihome:basic:xxx:heartbeatMessage" }
|
||||
```
|
||||
|
||||
_Example for a raw message from the gateway: ```{"cmd":"heartbeat","model":"gateway","sid":"xxx","short_id":"0","token":"xxx","data":"{\"ip\":\"192.168.0.124\"}"}```_
|
||||
|
||||
_Example for the same message from the heartbeat channel - only the data is returned: ```{"ip":"192.168.0.124"}```_
|
||||
|
||||
These messages are in JSON format, which also gives you the ability to parse single values.
|
||||
|
||||
_Example for the retrieved IP from the heartbeat message and transformed with JSONPATH transfomration: ```String Gateway_IP {channel="mihome:basic:xxx:heartbeatMessage"[profile="transform:JSONPATH", function="$.ip"]}```_
|
||||
|
||||
The item will get the value `192.168.0.124`.
|
||||
|
||||
### Write commands to a basic device
|
||||
|
||||
You can write commands to devices which support it, usually all battery powered devices are not able to receive commands.
|
||||
The commands have to be issued as attributes of a JSON Object, e.g. instead of writing ```{"attr":"value"}``` you have to write ```"attr":"value"``` or ```"channel_0":"on", "channel_1":"on"``` to the item.
|
||||
|
||||
The following example uses a rule to enable device pairing on the gateway:
|
||||
|
||||
__mihome.items__
|
||||
|
||||
```
|
||||
String Gateway_Write { channel="mihome:basic:xxx:writeMessage" }
|
||||
Switch Gateway_AddDevicesSwitch
|
||||
```
|
||||
|
||||
__mihome.rules__
|
||||
|
||||
```
|
||||
rule "Enable device pairing with gateway as basic device thing"
|
||||
when
|
||||
Item Gateway_AddDevicesSwitch changed to ON
|
||||
then
|
||||
Gateway_Write.sendCommand("\"join_permission\":\"yes\"")
|
||||
end
|
||||
```
|
||||
You can also send multiple command at once:
|
||||
```
|
||||
GatewayWrite.sendCommand("\"rgb\":150000,\"join_permission\":\"yes\"")
|
||||
```
|
||||
|
||||
Make sure to write numbers without quotes and strings with quotes. Also, quotes have to be escaped.
|
||||
|
||||
## Debugging
|
||||
|
||||
If you experience any unexpected behaviour or just want to know what is going on behind the scenes, you can enable debug logging.
|
||||
This makes possible following the communication between the binding and the gateway.
|
||||
|
||||
### Enable debug logging for the binding
|
||||
|
||||
- Login to the [openHAB Console](https://www.openhab.org/docs/administration/console.html)
|
||||
- Enter ```log:set TRACE org.openhab.binding.mihome``` in the console to enable full logs
|
||||
|
||||
_When you are done you can disable the extended logging with ```log:set DEFAULT org.openhab.binding.mihome```_
|
||||
|
||||
- Enter ```log:tail``` in the console or exit the console and start [viewing the logs](https://www.openhab.org/docs/tutorial/logs.html)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
For the binding to function properly it is very important, that your network config allows the machine running openHAB to receive multicast traffic.
|
||||
In case you want to check if the communication between the machine and the gateway is working, you can find some hints here.
|
||||
- Set up the developer communication as described in the Setup section
|
||||
|
||||
### Check if your Linux machine receives multicast traffic
|
||||
|
||||
- Login to the Linux console
|
||||
- make sure you have __netcat__ installed
|
||||
- Enter ```netcat -ukl 9898```
|
||||
- At least every 10 seconds you should see a message coming in from the gateway which looks like
|
||||
```{"cmd":"heartbeat","model":"gateway","sid":"`xxx","short_id":"0","token":"xxx","data":"{\"ip\":\"`xxx\"}"}```
|
||||
|
||||
### Check if your Windows/Mac machine receives multicast traffic
|
||||
|
||||
- Download Wireshark
|
||||
- Start and select the network interface which is connected to the same network as the gateway
|
||||
- Filter for the multicast messages with the expression ```udp.dstport== 9898 && data.text```
|
||||
- At least every 10 seconds you should see a message coming in from the gateway which looks like
|
||||
```{"cmd":"heartbeat","model":"gateway","sid":"`xxx","short_id":"0","token":"xxx","data":"{\"ip\":\"`xxx\"}"}```
|
||||
|
||||
__My gateway shows up in openHAB and I have added all devices, but I don't get any value updates:__
|
||||
- Most likely your machine is not receiving multicast messages
|
||||
- Check your network config:
|
||||
- Routers often block multicast - enable it
|
||||
- Make sure the gateway and the machine are in the same subnet
|
||||
- Try to connect your machine via Ethernet instead of Wifi
|
||||
- Make sure you don't have any firewall rules blocking multicast
|
||||
|
||||
__I have connected my gateway to the network but it doesn't show up in openHAB:__
|
||||
- Make sure to have the developer mode enabled in the MiHome app
|
||||
- Reinstall the binding
|
||||
- Try to update the firmware of the gateway
|
||||
- Make sure you have a supported gateway hardware
|
||||
- Search the openHAB Community forum
|
||||
- Contact Xiaomi support - get your gateway replaced
|
||||
|
||||
__Nothing works, I'm frustrated and have thrown my gateway into the bin. Now I don't know what to do with all the sensors:__
|
||||
Check out the Zigbee2Mqtt project on GitHub.
|
||||
It allows you to use the sensors without the gateway and get their values through MQTT.
|
||||
You will need some hardware to act as a gateway which is not expensive.
|
||||
You can find more information and a list of supported Xiaomi devices in the GitHub repository.
|
||||
17
bundles/org.openhab.binding.mihome/pom.xml
Normal file
17
bundles/org.openhab.binding.mihome/pom.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?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.binding.mihome</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Xiaomi Mi Smart Home Binding</name>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.mihome-${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-binding-mihome" description="Xiaomi Mi Smart Home Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.mihome/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.openhab.core.thing.CommonTriggerEvents;
|
||||
|
||||
/**
|
||||
* Maps the various JSON Strings reported from the devices to Channels
|
||||
*
|
||||
* @author Dieter Schmidt - Initial contribution
|
||||
*/
|
||||
public class ChannelMapper {
|
||||
|
||||
private static final Map<String, String> SYSTEM_BUTTON_MAP = new HashMap<>();
|
||||
static {
|
||||
// Alphabetical order
|
||||
SYSTEM_BUTTON_MAP.put("CLICK", CommonTriggerEvents.SHORT_PRESSED);
|
||||
SYSTEM_BUTTON_MAP.put("BOTH_CLICK", CommonTriggerEvents.SHORT_PRESSED);
|
||||
SYSTEM_BUTTON_MAP.put("DOUBLE_CLICK", CommonTriggerEvents.DOUBLE_PRESSED);
|
||||
SYSTEM_BUTTON_MAP.put("LONG_CLICK_PRESS", CommonTriggerEvents.LONG_PRESSED);
|
||||
SYSTEM_BUTTON_MAP.put("LONG_CLICK", CommonTriggerEvents.LONG_PRESSED);
|
||||
SYSTEM_BUTTON_MAP.put("LONG_BOTH_CLICK", CommonTriggerEvents.LONG_PRESSED);
|
||||
SYSTEM_BUTTON_MAP.put("LONG_CLICK_RELEASE", "LONG_RELEASED");
|
||||
}
|
||||
|
||||
public static String getChannelEvent(String reportedString) {
|
||||
String ret = SYSTEM_BUTTON_MAP.get(reportedString);
|
||||
if (ret != null) {
|
||||
return ret;
|
||||
} else {
|
||||
return reportedString;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal;
|
||||
|
||||
/**
|
||||
* Color utilities for conversions
|
||||
*
|
||||
* @author Patrick Boos - Initial contribution
|
||||
*/
|
||||
public class ColorUtil {
|
||||
/**
|
||||
* Convert color temperature in Kelvins to RGB color for AWT
|
||||
*
|
||||
* @param temperature the temperature
|
||||
* @return ready to use color object
|
||||
*/
|
||||
public static int getRGBFromK(int temperature) {
|
||||
// Used this: https://gist.github.com/paulkaplan/5184275 at the beginning
|
||||
// based on http://stackoverflow.com/questions/7229895/display-temperature-as-a-color-with-c
|
||||
// this answer: http://stackoverflow.com/a/24856307
|
||||
// (so, just interpretation of pseudocode in Java)
|
||||
|
||||
double x = temperature / 1000.0;
|
||||
if (x > 40) {
|
||||
x = 40;
|
||||
}
|
||||
double red;
|
||||
double green;
|
||||
double blue;
|
||||
|
||||
// R
|
||||
if (temperature < 6527) {
|
||||
red = 1;
|
||||
} else {
|
||||
double[] redpoly = { 4.93596077e0, -1.29917429e0, 1.64810386e-01, -1.16449912e-02, 4.86540872e-04,
|
||||
-1.19453511e-05, 1.59255189e-07, -8.89357601e-10 };
|
||||
red = poly(redpoly, x);
|
||||
}
|
||||
// G
|
||||
if (temperature < 850) {
|
||||
green = 0;
|
||||
} else if (temperature <= 6600) {
|
||||
double[] greenpoly = { -4.95931720e-01, 1.08442658e0, -9.17444217e-01, 4.94501179e-01, -1.48487675e-01,
|
||||
2.49910386e-02, -2.21528530e-03, 8.06118266e-05 };
|
||||
green = poly(greenpoly, x);
|
||||
} else {
|
||||
double[] greenpoly = { 3.06119745e0, -6.76337896e-01, 8.28276286e-02, -5.72828699e-03, 2.35931130e-04,
|
||||
-5.73391101e-06, 7.58711054e-08, -4.21266737e-10 };
|
||||
|
||||
green = poly(greenpoly, x);
|
||||
}
|
||||
// B
|
||||
if (temperature < 1900) {
|
||||
blue = 0;
|
||||
} else if (temperature < 6600) {
|
||||
double[] bluepoly = { 4.93997706e-01, -8.59349314e-01, 5.45514949e-01, -1.81694167e-01, 4.16704799e-02,
|
||||
-6.01602324e-03, 4.80731598e-04, -1.61366693e-05 };
|
||||
blue = poly(bluepoly, x);
|
||||
} else {
|
||||
blue = 1;
|
||||
}
|
||||
|
||||
red = clamp(red, 0, 1);
|
||||
blue = clamp(blue, 0, 1);
|
||||
green = clamp(green, 0, 1);
|
||||
return (int) (red * 255 * 256 * 256) + (int) (green * 255 * 256) + (int) (blue * 255);
|
||||
}
|
||||
|
||||
public static double poly(double[] coefficients, double x) {
|
||||
double result = coefficients[0];
|
||||
double xn = x;
|
||||
for (int i = 1; i < coefficients.length; i++) {
|
||||
result += xn * coefficients[i];
|
||||
xn *= x;
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static double clamp(double x, double min, double max) {
|
||||
if (x < min) {
|
||||
return min;
|
||||
}
|
||||
if (x > max) {
|
||||
return max;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Encrypts communication between openhab & xiaomi bridge (required by xiaomi).
|
||||
*
|
||||
* @author Ondřej Pečta - Initial contribution to Xiaomi MiHome Binding for OH 1.x
|
||||
* @author Dieter Schmidt - Refactor logger
|
||||
*/
|
||||
public class EncryptionHelper {
|
||||
|
||||
protected static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
|
||||
|
||||
// AES‐CBC 128 initial vector, taken from protocol description
|
||||
protected static final byte[] IV = parseHexBinary("17996D093D28DDB3BA695A2E6F58562E");
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(EncryptionHelper.class);
|
||||
|
||||
public String encrypt(String text, String key) {
|
||||
return encrypt(text, key, IV);
|
||||
}
|
||||
|
||||
public String encrypt(String text, String key, byte[] iv) {
|
||||
IvParameterSpec vector = new IvParameterSpec(iv);
|
||||
Cipher cipher;
|
||||
try {
|
||||
cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
|
||||
logger.warn("Failed to construct Cipher");
|
||||
return "";
|
||||
}
|
||||
SecretKeySpec keySpec;
|
||||
try {
|
||||
keySpec = new SecretKeySpec(key.getBytes("UTF8"), "AES");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
logger.warn("Failed to construct SecretKeySpec");
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, vector);
|
||||
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
|
||||
logger.warn("Failed to init Cipher");
|
||||
return "";
|
||||
}
|
||||
byte[] encrypted;
|
||||
try {
|
||||
encrypted = cipher.doFinal(text.getBytes());
|
||||
return bytesToHex(encrypted);
|
||||
} catch (IllegalBlockSizeException | BadPaddingException e) {
|
||||
logger.warn("Failed to finally encrypt");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] parseHexBinary(String s) {
|
||||
final int len = s.length();
|
||||
|
||||
// "111" is not a valid hex encoding.
|
||||
if (len % 2 != 0) {
|
||||
throw new IllegalArgumentException("hexBinary needs to be even-length: " + s);
|
||||
}
|
||||
|
||||
byte[] out = new byte[len / 2];
|
||||
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
int h = hexToBin(s.charAt(i));
|
||||
int l = hexToBin(s.charAt(i + 1));
|
||||
if (h == -1 || l == -1) {
|
||||
throw new IllegalArgumentException("contains illegal character for hexBinary: " + s);
|
||||
}
|
||||
|
||||
out[i / 2] = (byte) (h * 16 + l);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
private static int hexToBin(char ch) {
|
||||
if ('0' <= ch && ch <= '9') {
|
||||
return ch - '0';
|
||||
}
|
||||
if ('A' <= ch && ch <= 'F') {
|
||||
return ch - 'A' + 10;
|
||||
}
|
||||
if ('a' <= ch && ch <= 'f') {
|
||||
return ch - 'a' + 10;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private String bytesToHex(byte[] bytes) {
|
||||
char[] hexChars = new char[bytes.length * 2];
|
||||
for (int j = 0; j < bytes.length; j++) {
|
||||
int v = bytes[j] & 0xFF;
|
||||
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
|
||||
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
|
||||
}
|
||||
return new String(hexChars);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal;
|
||||
|
||||
import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* Maps the model (provided from Xiaomi) to thing.
|
||||
*
|
||||
* @author Patrick Boos - Initial contribution
|
||||
* @author Kuba Wolanin - Renamed labels
|
||||
* @author Dieter Schmidt - Refactor
|
||||
* @author Daniel Walters - Add Xiaomi Aqara Temperature, Humidity & Pressure sensor and Xiaomi Aqara Door/Window sensor
|
||||
*/
|
||||
public class ModelMapper {
|
||||
|
||||
private static final Map<String, ThingTypeUID> THING_MAP = new HashMap<>();
|
||||
private static final Map<String, String> LABEL_MAP = new HashMap<>();
|
||||
static {
|
||||
// Alphabetical order
|
||||
THING_MAP.put("curtain", THING_TYPE_ACTOR_CURTAIN);
|
||||
THING_MAP.put("gateway", THING_TYPE_GATEWAY);
|
||||
THING_MAP.put("plug", THING_TYPE_ACTOR_PLUG);
|
||||
THING_MAP.put("ctrl_86plug.aq1", THING_TYPE_ACTOR_PLUG);
|
||||
THING_MAP.put("ctrl_neutral1", THING_TYPE_ACTOR_AQARA1);
|
||||
THING_MAP.put("ctrl_neutral2", THING_TYPE_ACTOR_AQARA2);
|
||||
THING_MAP.put("ctrl_ln1", THING_TYPE_ACTOR_AQARA_ZERO1);
|
||||
THING_MAP.put("ctrl_ln1.aq1", THING_TYPE_ACTOR_AQARA_ZERO1);
|
||||
THING_MAP.put("ctrl_ln2", THING_TYPE_ACTOR_AQARA_ZERO2);
|
||||
THING_MAP.put("ctrl_ln2.aq1", THING_TYPE_ACTOR_AQARA_ZERO2);
|
||||
THING_MAP.put("86sw1", THING_TYPE_SENSOR_AQARA1);
|
||||
THING_MAP.put("86sw2", THING_TYPE_SENSOR_AQARA2);
|
||||
THING_MAP.put("cube", THING_TYPE_SENSOR_CUBE);
|
||||
THING_MAP.put("sensor_cube.aqgl01", THING_TYPE_SENSOR_CUBE);
|
||||
THING_MAP.put("sensor_ht", THING_TYPE_SENSOR_HT);
|
||||
THING_MAP.put("lock.aq1", THING_TYPE_SENSOR_AQARA_LOCK);
|
||||
THING_MAP.put("magnet", THING_TYPE_SENSOR_MAGNET);
|
||||
THING_MAP.put("motion", THING_TYPE_SENSOR_MOTION);
|
||||
THING_MAP.put("natgas", THING_TYPE_SENSOR_GAS);
|
||||
THING_MAP.put("sensor_magnet.aq2", THING_TYPE_SENSOR_AQARA_MAGNET);
|
||||
THING_MAP.put("sensor_motion.aq2", THING_TYPE_SENSOR_AQARA_MOTION);
|
||||
THING_MAP.put("sensor_wleak.aq1", THING_TYPE_SENSOR_WATER);
|
||||
THING_MAP.put("sensor_switch.aq2", THING_TYPE_SENSOR_AQARA_SWITCH);
|
||||
THING_MAP.put("sensor_switch.aq3", THING_TYPE_SENSOR_AQARA_SWITCH);
|
||||
THING_MAP.put("remote.b1acn01", THING_TYPE_SENSOR_AQARA_SWITCH);
|
||||
THING_MAP.put("remote.b186acn01", THING_TYPE_SENSOR_AQARA1);
|
||||
THING_MAP.put("remote.b286acn01", THING_TYPE_SENSOR_AQARA2);
|
||||
THING_MAP.put("smoke", THING_TYPE_SENSOR_SMOKE);
|
||||
THING_MAP.put("switch", THING_TYPE_SENSOR_SWITCH);
|
||||
THING_MAP.put("vibration", THING_TYPE_SENSOR_AQARA_VIBRATION);
|
||||
THING_MAP.put("weather.v1", THING_TYPE_SENSOR_AQARA_WEATHER_V1);
|
||||
|
||||
LABEL_MAP.put("curtain", "Xiaomi Aqara Intelligent Curtain Motor");
|
||||
LABEL_MAP.put("gateway", "Xiaomi Mi Smart Home Gateway");
|
||||
LABEL_MAP.put("plug", "Xiaomi Mi Smart Socket Plug");
|
||||
LABEL_MAP.put("ctrl_86plug.aq1", "Xiaomi Aqara Smart Socket Plug");
|
||||
LABEL_MAP.put("ctrl_neutral1", "Xiaomi Aqara Wall Switch 1 Button");
|
||||
LABEL_MAP.put("ctrl_neutral2", "Xiaomi Aqara Wall Switch 2 Button");
|
||||
LABEL_MAP.put("ctrl_ln1", "Xiaomi \"zero-fire\" 1 Channel Wall Switch");
|
||||
LABEL_MAP.put("ctrl_ln1.aq1", "Xiaomi Aqara \"zero-fire\" 1 Channel Wall Switch");
|
||||
LABEL_MAP.put("ctrl_ln2", "Xiaomi \"zero-fire\" 2 Channel Wall Switch");
|
||||
LABEL_MAP.put("ctrl_ln2.aq1", "Xiaomi \"zero-fire\" 2 Channel Wall Switch");
|
||||
LABEL_MAP.put("86sw1", "Xiaomi Aqara Smart Switch 1 Button");
|
||||
LABEL_MAP.put("86sw2", "Xiaomi Aqara Smart Switch 2 Button");
|
||||
LABEL_MAP.put("cube", "Xiaomi Mi Smart Cube");
|
||||
LABEL_MAP.put("sensor_cube.aqgl01", "Xiaomi Mi Smart Cube");
|
||||
LABEL_MAP.put("sensor_ht", "Xiaomi Mi Temperature & Humidity Sensor");
|
||||
LABEL_MAP.put("lock.aq1", "Xiaomi Aqara Fingerprint Door Lock");
|
||||
LABEL_MAP.put("magnet", "Xiaomi Door/Window Sensor");
|
||||
LABEL_MAP.put("motion", "Xiaomi Mi Motion Sensor");
|
||||
LABEL_MAP.put("natgas", "Xiaomi Mijia Honeywell Gas Alarm Detector");
|
||||
LABEL_MAP.put("sensor_magnet.aq2", "Xiaomi Aqara Door/Window Sensor");
|
||||
LABEL_MAP.put("sensor_motion.aq2", "Xiaomi Aqara Motion Sensor");
|
||||
LABEL_MAP.put("sensor_wleak.aq1", "Xiaomi Aqara Water Leak Sensor");
|
||||
LABEL_MAP.put("sensor_switch.aq2", "Xiaomi Aqara Wireless Switch");
|
||||
LABEL_MAP.put("sensor_switch.aq3", "Xiaomi Aqara Wireless Switch with Accelerometer");
|
||||
LABEL_MAP.put("remote.b1acn01", "Xiaomi Aqara Wireless Switch");
|
||||
LABEL_MAP.put("remote.b186acn01", "Xiaomi Aqara Smart Switch 1 Button");
|
||||
LABEL_MAP.put("remote.b286acn01", "Xiaomi Aqara Smart Switch 2 Button");
|
||||
LABEL_MAP.put("smoke", "Xiaomi Mijia Honeywell Fire Alarm Detector");
|
||||
LABEL_MAP.put("switch", "Xiaomi Mi Wireless Switch");
|
||||
LABEL_MAP.put("vibration", "Xiaomi Aqara Smart Motion Sensor");
|
||||
LABEL_MAP.put("weather.v1", "Xiaomi Aqara Temperature, Humidity & Pressure Sensor");
|
||||
}
|
||||
|
||||
public static ThingTypeUID getThingTypeForModel(String model) {
|
||||
return THING_MAP.get(model);
|
||||
}
|
||||
|
||||
public static String getLabelForModel(String model) {
|
||||
return LABEL_MAP.get(model);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link XiaomiGatewayBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Patrick Boos - Initial contribution
|
||||
* @author Dieter Schmidt - added cube, gateway sound channels, window sensor alarm
|
||||
* @author Daniel Walters - Added Aqara Door/Window sensor and Aqara temperature, humidity and pressure sensor
|
||||
* @author Kuba Wolanin - Added Water Leak sensor
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class XiaomiGatewayBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "mihome";
|
||||
|
||||
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
|
||||
public static final ThingTypeUID THING_TYPE_GATEWAY = new ThingTypeUID(BINDING_ID, "gateway");
|
||||
public static final ThingTypeUID THING_TYPE_BASIC = new ThingTypeUID(BINDING_ID, "basic");
|
||||
// sensors
|
||||
public static final ThingTypeUID THING_TYPE_SENSOR_HT = new ThingTypeUID(BINDING_ID, "sensor_ht");
|
||||
public static final ThingTypeUID THING_TYPE_SENSOR_AQARA_WEATHER_V1 = new ThingTypeUID(BINDING_ID,
|
||||
"sensor_weather_v1");
|
||||
public static final ThingTypeUID THING_TYPE_SENSOR_MOTION = new ThingTypeUID(BINDING_ID, "sensor_motion");
|
||||
public static final ThingTypeUID THING_TYPE_SENSOR_AQARA_MOTION = new ThingTypeUID(BINDING_ID, "sensor_motion_aq2");
|
||||
public static final ThingTypeUID THING_TYPE_SENSOR_SWITCH = new ThingTypeUID(BINDING_ID, "sensor_switch");
|
||||
public static final ThingTypeUID THING_TYPE_SENSOR_AQARA_SWITCH = new ThingTypeUID(BINDING_ID, "sensor_switch_aq2");
|
||||
public static final ThingTypeUID THING_TYPE_SENSOR_MAGNET = new ThingTypeUID(BINDING_ID, "sensor_magnet");
|
||||
public static final ThingTypeUID THING_TYPE_SENSOR_AQARA_MAGNET = new ThingTypeUID(BINDING_ID, "sensor_magnet_aq2");
|
||||
public static final ThingTypeUID THING_TYPE_SENSOR_AQARA_LOCK = new ThingTypeUID(BINDING_ID, "sensor_lock_aq1");
|
||||
public static final ThingTypeUID THING_TYPE_SENSOR_CUBE = new ThingTypeUID(BINDING_ID, "sensor_cube");
|
||||
public static final ThingTypeUID THING_TYPE_SENSOR_AQARA_VIBRATION = new ThingTypeUID(BINDING_ID,
|
||||
"sensor_vibration");
|
||||
public static final ThingTypeUID THING_TYPE_SENSOR_AQARA1 = new ThingTypeUID(BINDING_ID, "86sw1");
|
||||
public static final ThingTypeUID THING_TYPE_SENSOR_AQARA2 = new ThingTypeUID(BINDING_ID, "86sw2");
|
||||
public static final ThingTypeUID THING_TYPE_SENSOR_GAS = new ThingTypeUID(BINDING_ID, "natgas");
|
||||
public static final ThingTypeUID THING_TYPE_SENSOR_SMOKE = new ThingTypeUID(BINDING_ID, "smoke");
|
||||
public static final ThingTypeUID THING_TYPE_SENSOR_WATER = new ThingTypeUID(BINDING_ID, "sensor_wleak_aq1");
|
||||
|
||||
// actors
|
||||
public static final ThingTypeUID THING_TYPE_ACTOR_PLUG = new ThingTypeUID(BINDING_ID, "sensor_plug");
|
||||
public static final ThingTypeUID THING_TYPE_ACTOR_AQARA1 = new ThingTypeUID(BINDING_ID, "ctrl_neutral1");
|
||||
public static final ThingTypeUID THING_TYPE_ACTOR_AQARA2 = new ThingTypeUID(BINDING_ID, "ctrl_neutral2");
|
||||
public static final ThingTypeUID THING_TYPE_ACTOR_AQARA_ZERO1 = new ThingTypeUID(BINDING_ID, "ctrl_ln1");
|
||||
public static final ThingTypeUID THING_TYPE_ACTOR_AQARA_ZERO2 = new ThingTypeUID(BINDING_ID, "ctrl_ln2");
|
||||
public static final ThingTypeUID THING_TYPE_ACTOR_CURTAIN = new ThingTypeUID(BINDING_ID, "curtain");
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_BATTERY_LEVEL = "batteryLevel";
|
||||
public static final String CHANNEL_LOW_BATTERY = "lowBattery";
|
||||
// HT sensor
|
||||
public static final String CHANNEL_TEMPERATURE = "temperature";
|
||||
public static final String CHANNEL_HUMIDITY = "humidity";
|
||||
public static final String CHANNEL_PRESSURE = "pressure";
|
||||
// motion sensor
|
||||
public static final String CHANNEL_MOTION = "motion";
|
||||
public static final String CHANNEL_MOTION_OFF_TIMER = "motionOffTimer";
|
||||
public static final String CHANNEL_LAST_MOTION = "lastMotion";
|
||||
// window sensor
|
||||
public static final String CHANNEL_IS_OPEN = "isOpen";
|
||||
public static final String CHANNEL_LAST_OPENED = "lastOpened";
|
||||
public static final String CHANNEL_OPEN_ALARM = "isOpenAlarm";
|
||||
public static final String CHANNEL_OPEN_ALARM_TIMER = "isOpenAlarmTimer";
|
||||
// plug
|
||||
public static final String CHANNEL_POWER_ON = "power";
|
||||
public static final String CHANNEL_IN_USE = "inUse";
|
||||
public static final String CHANNEL_LOAD_POWER = "loadPower";
|
||||
public static final String CHANNEL_POWER_CONSUMED = "powerConsumed";
|
||||
// switch
|
||||
public static final String CHANNEL_BUTTON = "button";
|
||||
// cube
|
||||
public static final String CHANNEL_ACTION = "action";
|
||||
public static final String CHANNEL_LAST_ACTION = "lastAction";
|
||||
public static final String CHANNEL_CUBE_ROTATION_ANGLE = "rotationAngle";
|
||||
public static final String CHANNEL_CUBE_ROTATION_TIME = "rotationTime";
|
||||
// vibration
|
||||
public static final String CHANNEL_TILT_ANGLE = "tiltAngle";
|
||||
public static final String CHANNEL_ORIENTATION_X = "orientationX";
|
||||
public static final String CHANNEL_ORIENTATION_Y = "orientationY";
|
||||
public static final String CHANNEL_ORIENTATION_Z = "orientationZ";
|
||||
public static final String CHANNEL_BED_ACTIVITY = "bedActivity";
|
||||
// gateway sound
|
||||
public static final String CHANNEL_GATEWAY_SOUND_SWITCH = "enableSound";
|
||||
public static final String CHANNEL_GATEWAY_SOUND = "sound";
|
||||
public static final String CHANNEL_GATEWAY_VOLUME = "volume";
|
||||
// gateway light
|
||||
public static final String CHANNEL_BRIGHTNESS = "brightness";
|
||||
public static final String CHANNEL_ILLUMINATION = "illumination";
|
||||
public static final String CHANNEL_COLOR = "color";
|
||||
public static final String CHANNEL_COLOR_TEMPERATURE = "colorTemperature";
|
||||
// aqara switches
|
||||
public static final String CHANNEL_SWITCH_CH0 = "ch1";
|
||||
public static final String CHANNEL_SWITCH_CH1 = "ch2";
|
||||
public static final String CHANNEL_SWITCH_DUAL_CH = "dual_ch";
|
||||
// curtain
|
||||
public static final String CHANNEL_CURTAIN_CONTROL = "curtainControl";
|
||||
// gas & smoke sensor
|
||||
public static final String CHANNEL_ALARM = "alarm";
|
||||
public static final String CHANNEL_STATUS = "status";
|
||||
// smoke sensor
|
||||
public static final String CHANNEL_DENSITY = "density";
|
||||
// water leak sensor
|
||||
public static final String CHANNEL_LEAK = "leak";
|
||||
// aqara lock
|
||||
public static final String CHANNEL_ID = "id";
|
||||
public static final String CHANNEL_WRONG_ACCESS = "wrongAccess";
|
||||
// Bridge config properties
|
||||
public static final String SERIAL_NUMBER = "serialNumber";
|
||||
public static final String HOST = "ipAddress";
|
||||
public static final String PORT = "port";
|
||||
public static final String TOKEN = "token";
|
||||
|
||||
// Item config properties
|
||||
public static final String ITEM_ID = "itemId";
|
||||
|
||||
// Basic Device channels
|
||||
public static final String CHANNEL_REPORT_MSG = "reportMessage";
|
||||
public static final String CHANNEL_HEARTBEAT_MSG = "heartbeatMessage";
|
||||
public static final String CHANNEL_READ_ACK_MSG = "readAckMessage";
|
||||
public static final String CHANNEL_WRITE_ACK_MSG = "writeAckMessage";
|
||||
public static final String CHANNEL_LAST_MSG = "lastMessage";
|
||||
public static final String CHANNEL_WRITE_MSG = "writeMessage";
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal;
|
||||
|
||||
import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.openhab.binding.mihome.internal.discovery.XiaomiItemDiscoveryService;
|
||||
import org.openhab.binding.mihome.internal.handler.XiaomiActorCurtainHandler;
|
||||
import org.openhab.binding.mihome.internal.handler.XiaomiActorGatewayHandler;
|
||||
import org.openhab.binding.mihome.internal.handler.XiaomiActorPlugHandler;
|
||||
import org.openhab.binding.mihome.internal.handler.XiaomiAqaraActorSwitch1Handler;
|
||||
import org.openhab.binding.mihome.internal.handler.XiaomiAqaraActorSwitch2Handler;
|
||||
import org.openhab.binding.mihome.internal.handler.XiaomiAqaraSensorSwitch1Handler;
|
||||
import org.openhab.binding.mihome.internal.handler.XiaomiAqaraSensorSwitch2Handler;
|
||||
import org.openhab.binding.mihome.internal.handler.XiaomiBridgeHandler;
|
||||
import org.openhab.binding.mihome.internal.handler.XiaomiDeviceBaseHandler;
|
||||
import org.openhab.binding.mihome.internal.handler.XiaomiSensorCubeHandler;
|
||||
import org.openhab.binding.mihome.internal.handler.XiaomiSensorGasHandler;
|
||||
import org.openhab.binding.mihome.internal.handler.XiaomiSensorHtHandler;
|
||||
import org.openhab.binding.mihome.internal.handler.XiaomiSensorLockHandler;
|
||||
import org.openhab.binding.mihome.internal.handler.XiaomiSensorMagnetHandler;
|
||||
import org.openhab.binding.mihome.internal.handler.XiaomiSensorMotionHandler;
|
||||
import org.openhab.binding.mihome.internal.handler.XiaomiSensorSmokeHandler;
|
||||
import org.openhab.binding.mihome.internal.handler.XiaomiSensorSwitchHandler;
|
||||
import org.openhab.binding.mihome.internal.handler.XiaomiSensorVibrationHandler;
|
||||
import org.openhab.binding.mihome.internal.handler.XiaomiSensorWaterHandler;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.framework.ServiceRegistration;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* The {@link XiaomiHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Patrick Boos - Initial contribution
|
||||
* @author Dieter Schmidt - Refactor, add devices
|
||||
* @author Daniel Walters - Added Aqara Door/Window sensor and Aqara temperature, humidity and pressure sensor
|
||||
* @author Kuba Wolanin - Added Water Leak sensor and Aqara motion sensor
|
||||
*/
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.mihome")
|
||||
public class XiaomiHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
|
||||
.unmodifiableSet(Stream.concat(XiaomiBridgeHandler.SUPPORTED_THING_TYPES.stream(),
|
||||
XiaomiDeviceBaseHandler.SUPPORTED_THING_TYPES.stream()).collect(Collectors.toSet()));
|
||||
|
||||
private Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, ThingUID thingUID,
|
||||
ThingUID bridgeUID) {
|
||||
if (XiaomiBridgeHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||
ThingUID newBridgeUID = getBridgeThingUID(thingTypeUID, thingUID, configuration);
|
||||
return super.createThing(thingTypeUID, configuration, newBridgeUID, null);
|
||||
} else if (XiaomiDeviceBaseHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
|
||||
ThingUID newThingUID = getThingUID(thingTypeUID, thingUID, configuration, bridgeUID);
|
||||
return super.createThing(thingTypeUID, configuration, newThingUID, bridgeUID);
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"The thing type " + thingTypeUID + " is not supported by the mihome binding.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
private ThingUID getBridgeThingUID(ThingTypeUID thingTypeUID, ThingUID thingUID, Configuration configuration) {
|
||||
if (thingUID == null) {
|
||||
String serialNumber = (String) configuration.get(SERIAL_NUMBER);
|
||||
return new ThingUID(thingTypeUID, serialNumber);
|
||||
}
|
||||
return thingUID;
|
||||
}
|
||||
|
||||
private ThingUID getThingUID(ThingTypeUID thingTypeUID, ThingUID thingUID, Configuration configuration,
|
||||
ThingUID bridgeUID) {
|
||||
if (thingUID == null) {
|
||||
String itemId = (String) configuration.get(ITEM_ID);
|
||||
return new ThingUID(thingTypeUID, itemId, bridgeUID.getId());
|
||||
}
|
||||
return thingUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (THING_TYPE_BRIDGE.equals(thingTypeUID)) {
|
||||
XiaomiBridgeHandler handler = new XiaomiBridgeHandler((Bridge) thing);
|
||||
registerItemDiscoveryService(handler);
|
||||
return handler;
|
||||
} else if (THING_TYPE_GATEWAY.equals(thingTypeUID)) {
|
||||
return new XiaomiActorGatewayHandler(thing);
|
||||
} else if (THING_TYPE_SENSOR_HT.equals(thingTypeUID)) {
|
||||
return new XiaomiSensorHtHandler(thing);
|
||||
} else if (THING_TYPE_SENSOR_MOTION.equals(thingTypeUID)) {
|
||||
return new XiaomiSensorMotionHandler(thing);
|
||||
} else if (THING_TYPE_SENSOR_SWITCH.equals(thingTypeUID)) {
|
||||
return new XiaomiSensorSwitchHandler(thing);
|
||||
} else if (THING_TYPE_SENSOR_AQARA_SWITCH.equals(thingTypeUID)) {
|
||||
return new XiaomiSensorSwitchHandler(thing);
|
||||
} else if (THING_TYPE_SENSOR_MAGNET.equals(thingTypeUID)) {
|
||||
return new XiaomiSensorMagnetHandler(thing);
|
||||
} else if (THING_TYPE_SENSOR_CUBE.equals(thingTypeUID)) {
|
||||
return new XiaomiSensorCubeHandler(thing);
|
||||
} else if (THING_TYPE_SENSOR_SMOKE.equals(thingTypeUID)) {
|
||||
return new XiaomiSensorSmokeHandler(thing);
|
||||
} else if (THING_TYPE_SENSOR_GAS.equals(thingTypeUID)) {
|
||||
return new XiaomiSensorGasHandler(thing);
|
||||
} else if (THING_TYPE_SENSOR_WATER.equals(thingTypeUID)) {
|
||||
return new XiaomiSensorWaterHandler(thing);
|
||||
} else if (THING_TYPE_SENSOR_AQARA1.equals(thingTypeUID)) {
|
||||
return new XiaomiAqaraSensorSwitch1Handler(thing);
|
||||
} else if (THING_TYPE_SENSOR_AQARA2.equals(thingTypeUID)) {
|
||||
return new XiaomiAqaraSensorSwitch2Handler(thing);
|
||||
} else if (THING_TYPE_ACTOR_AQARA1.equals(thingTypeUID)) {
|
||||
return new XiaomiAqaraActorSwitch1Handler(thing);
|
||||
} else if (THING_TYPE_ACTOR_AQARA2.equals(thingTypeUID)) {
|
||||
return new XiaomiAqaraActorSwitch2Handler(thing);
|
||||
} else if (THING_TYPE_ACTOR_PLUG.equals(thingTypeUID)) {
|
||||
return new XiaomiActorPlugHandler(thing);
|
||||
} else if (THING_TYPE_ACTOR_AQARA_ZERO1.equals(thingTypeUID)) {
|
||||
return new XiaomiAqaraActorSwitch1Handler(thing);
|
||||
} else if (THING_TYPE_ACTOR_AQARA_ZERO2.equals(thingTypeUID)) {
|
||||
return new XiaomiAqaraActorSwitch2Handler(thing);
|
||||
} else if (THING_TYPE_ACTOR_CURTAIN.equals(thingTypeUID)) {
|
||||
return new XiaomiActorCurtainHandler(thing);
|
||||
} else if (THING_TYPE_SENSOR_AQARA_WEATHER_V1.equals(thingTypeUID)) {
|
||||
return new XiaomiSensorHtHandler(thing);
|
||||
} else if (THING_TYPE_SENSOR_AQARA_MAGNET.equals(thingTypeUID)) {
|
||||
return new XiaomiSensorMagnetHandler(thing);
|
||||
} else if (THING_TYPE_SENSOR_AQARA_MOTION.equals(thingTypeUID)) {
|
||||
return new XiaomiSensorMotionHandler(thing);
|
||||
} else if (THING_TYPE_SENSOR_AQARA_VIBRATION.equals(thingTypeUID)) {
|
||||
return new XiaomiSensorVibrationHandler(thing);
|
||||
} else if (THING_TYPE_SENSOR_AQARA_LOCK.equals(thingTypeUID)) {
|
||||
return new XiaomiSensorLockHandler(thing);
|
||||
} else if (THING_TYPE_BASIC.equals(thingTypeUID)) {
|
||||
return new XiaomiDeviceBaseHandler(thing);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void removeHandler(ThingHandler thingHandler) {
|
||||
if (thingHandler instanceof XiaomiBridgeHandler) {
|
||||
ServiceRegistration<?> serviceReg = this.discoveryServiceRegs.remove(thingHandler.getThing().getUID());
|
||||
if (serviceReg != null) {
|
||||
// remove discovery service, if bridge handler is removed
|
||||
XiaomiItemDiscoveryService service = (XiaomiItemDiscoveryService) bundleContext
|
||||
.getService(serviceReg.getReference());
|
||||
serviceReg.unregister();
|
||||
if (service != null) {
|
||||
service.onHandlerRemoved();
|
||||
service.deactivate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void registerItemDiscoveryService(XiaomiBridgeHandler bridgeHandler) {
|
||||
XiaomiItemDiscoveryService discoveryService = new XiaomiItemDiscoveryService(bridgeHandler);
|
||||
this.discoveryServiceRegs.put(bridgeHandler.getThing().getUID(),
|
||||
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Listener for item/sensor updates.
|
||||
*
|
||||
* @author Patrick Boos - Initial contribution
|
||||
*/
|
||||
public interface XiaomiItemUpdateListener {
|
||||
/**
|
||||
* Callback method to notify the listener about a device state update
|
||||
*
|
||||
* @param sid the itemID of the device
|
||||
* @param command the command type of the received message
|
||||
* @param message the received message
|
||||
*
|
||||
* @author Patrick Boos - Initial contribution
|
||||
*/
|
||||
|
||||
void onItemUpdate(String sid, String command, JsonObject message);
|
||||
|
||||
/**
|
||||
* Returns the itemID, to which the listener listens
|
||||
*
|
||||
* @return itemID of the device
|
||||
*/
|
||||
String getItemId();
|
||||
}
|
||||
@@ -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.binding.mihome.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mihome.internal.socket.XiaomiDiscoverySocket;
|
||||
import org.openhab.binding.mihome.internal.socket.XiaomiSocketListener;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Discovery service for the Xiaomi bridge.
|
||||
*
|
||||
* @author Patrick Boos - Initial contribution
|
||||
* @author Kuba Wolanin - logger fixes
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.mihome")
|
||||
public class XiaomiBridgeDiscoveryService extends AbstractDiscoveryService implements XiaomiSocketListener {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);
|
||||
private static final int DISCOVERY_TIMEOUT_SEC = 30;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(XiaomiBridgeDiscoveryService.class);
|
||||
private final XiaomiDiscoverySocket socket = new XiaomiDiscoverySocket("discovery");
|
||||
|
||||
public XiaomiBridgeDiscoveryService() {
|
||||
super(SUPPORTED_THING_TYPES, DISCOVERY_TIMEOUT_SEC, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
socket.initialize();
|
||||
logger.debug("Start scan for bridges");
|
||||
socket.registerListener(this);
|
||||
discoverGateways();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void stopScan() {
|
||||
super.stopScan();
|
||||
logger.debug("Stop scan");
|
||||
removeOlderResults(getTimestampOfLastScan());
|
||||
socket.unregisterListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
super.deactivate();
|
||||
socket.unregisterListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataReceived(JsonObject data) {
|
||||
logger.debug("Received message {}", data);
|
||||
if (data.get("cmd").getAsString().equals("iam")) {
|
||||
getGatewayInfo(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getScanTimeout() {
|
||||
return DISCOVERY_TIMEOUT_SEC;
|
||||
}
|
||||
|
||||
private void discoverGateways() {
|
||||
socket.sendMessage("{\"cmd\":\"whois\"}");
|
||||
}
|
||||
|
||||
private void getGatewayInfo(JsonObject jobject) {
|
||||
Map<String, Object> properties = new HashMap<>(4);
|
||||
String serialNumber = jobject.get("sid").getAsString();
|
||||
String ipAddress = jobject.get("ip").getAsString();
|
||||
int port = jobject.get("port").getAsInt();
|
||||
|
||||
// It is reported that the gateway is sometimes providing the serial number without the leading 0
|
||||
// This is a workaround for a bug in the gateway
|
||||
if (serialNumber.length() == 11) {
|
||||
serialNumber = "0" + serialNumber;
|
||||
}
|
||||
|
||||
properties.put(SERIAL_NUMBER, serialNumber);
|
||||
properties.put(HOST, ipAddress);
|
||||
properties.put(PORT, port);
|
||||
|
||||
logger.debug("Discovered Xiaomi Gateway - sid: {} ip: {} port: {}", serialNumber, ipAddress, port);
|
||||
|
||||
ThingUID thingUID = new ThingUID(THING_TYPE_BRIDGE, serialNumber);
|
||||
|
||||
thingDiscovered(
|
||||
DiscoveryResultBuilder.create(thingUID).withThingType(THING_TYPE_BRIDGE).withProperties(properties)
|
||||
.withLabel("Xiaomi Gateway").withRepresentationProperty(SERIAL_NUMBER).build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.mihome.internal.ModelMapper.*;
|
||||
import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.openhab.binding.mihome.internal.XiaomiItemUpdateListener;
|
||||
import org.openhab.binding.mihome.internal.handler.XiaomiBridgeHandler;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Discovery service for items/sensors.
|
||||
*
|
||||
* @author Patrick Boos - Initial contribution
|
||||
*/
|
||||
public class XiaomiItemDiscoveryService extends AbstractDiscoveryService implements XiaomiItemUpdateListener {
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = new HashSet<>(Arrays.asList(THING_TYPE_GATEWAY,
|
||||
THING_TYPE_SENSOR_HT, THING_TYPE_SENSOR_AQARA_WEATHER_V1, THING_TYPE_SENSOR_MOTION,
|
||||
THING_TYPE_SENSOR_AQARA_MOTION, THING_TYPE_SENSOR_SWITCH, THING_TYPE_SENSOR_AQARA_SWITCH,
|
||||
THING_TYPE_SENSOR_MAGNET, THING_TYPE_SENSOR_AQARA_LOCK, THING_TYPE_SENSOR_AQARA_MAGNET,
|
||||
THING_TYPE_SENSOR_CUBE, THING_TYPE_SENSOR_AQARA_VIBRATION, THING_TYPE_SENSOR_AQARA1,
|
||||
THING_TYPE_SENSOR_AQARA2, THING_TYPE_SENSOR_GAS, THING_TYPE_SENSOR_SMOKE, THING_TYPE_SENSOR_WATER,
|
||||
THING_TYPE_ACTOR_AQARA1, THING_TYPE_ACTOR_AQARA2, THING_TYPE_ACTOR_PLUG, THING_TYPE_ACTOR_AQARA_ZERO1,
|
||||
THING_TYPE_ACTOR_AQARA_ZERO2, THING_TYPE_ACTOR_CURTAIN, THING_TYPE_BASIC));
|
||||
|
||||
private static final int DISCOVERY_TIMEOUT_SEC = 30;
|
||||
private final XiaomiBridgeHandler xiaomiBridgeHandler;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(XiaomiItemDiscoveryService.class);
|
||||
|
||||
public XiaomiItemDiscoveryService(XiaomiBridgeHandler xiaomiBridgeHandler) {
|
||||
super(SUPPORTED_THING_TYPES, DISCOVERY_TIMEOUT_SEC, true);
|
||||
this.xiaomiBridgeHandler = xiaomiBridgeHandler;
|
||||
xiaomiBridgeHandler.registerItemListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startScan() {
|
||||
logger.debug("Start scan for items");
|
||||
xiaomiBridgeHandler.registerItemListener(this); // this will as well get us all items
|
||||
xiaomiBridgeHandler.discoverItems(TimeUnit.SECONDS.toMillis(DISCOVERY_TIMEOUT_SEC));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void stopScan() {
|
||||
super.stopScan();
|
||||
removeOlderResults(getTimestampOfLastScan());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
super.deactivate();
|
||||
xiaomiBridgeHandler.unregisterItemListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getScanTimeout() {
|
||||
return DISCOVERY_TIMEOUT_SEC;
|
||||
}
|
||||
|
||||
public void onHandlerRemoved() {
|
||||
removeOlderResults(new Date().getTime());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemUpdate(String sid, String command, JsonObject data) {
|
||||
if (command.equals("read_ack") || command.equals("report") || command.equals("heartbeat")) {
|
||||
String model = data.get("model").getAsString();
|
||||
|
||||
ThingTypeUID thingType = getThingTypeForModel(model);
|
||||
String modelLabel = getLabelForModel(model);
|
||||
if (thingType == null) {
|
||||
logger.warn("Discovered unsupported device with id \"{}\" -> Creating Basic Device Thing", model);
|
||||
thingType = THING_TYPE_BASIC;
|
||||
modelLabel = String.format("Unsupported Xiaomi MiHome Device \"%s\"", model);
|
||||
}
|
||||
|
||||
Map<String, Object> properties = new HashMap<>(1);
|
||||
properties.put(ITEM_ID, sid);
|
||||
|
||||
ThingUID bridgeUID = xiaomiBridgeHandler.getThing().getUID();
|
||||
ThingUID thingUID = new ThingUID(thingType, bridgeUID, sid);
|
||||
|
||||
logger.debug("Discovered device - sid: {} model: {}", sid, model);
|
||||
thingDiscovered(DiscoveryResultBuilder.create(thingUID).withThingType(thingType).withProperties(properties)
|
||||
.withRepresentationProperty(ITEM_ID).withLabel(modelLabel).withBridge(bridgeUID).build());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getItemId() {
|
||||
// The discovery service is not bound to a specific device
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal.handler;
|
||||
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Abstract base class for controllable devices
|
||||
*
|
||||
* @author Dieter Schmidt - Initial contribution
|
||||
*/
|
||||
public abstract class XiaomiActorBaseHandler extends XiaomiDeviceBaseHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(XiaomiActorBaseHandler.class);
|
||||
|
||||
public XiaomiActorBaseHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
void execute(ChannelUID channelUID, Command command) {
|
||||
logger.debug("The binding does not support this message yet, contact authors if you want it to");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.CHANNEL_CURTAIN_CONTROL;
|
||||
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Manage the Xiaomi Smart Curtain over the API
|
||||
*
|
||||
* @author Kuba Wolanin - initial contribution
|
||||
*/
|
||||
public class XiaomiActorCurtainHandler extends XiaomiActorBaseHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(XiaomiActorCurtainHandler.class);
|
||||
private String lastDirection;
|
||||
|
||||
private static final String STATUS = "status";
|
||||
private static final String OPEN = "open";
|
||||
private static final String CLOSED = "close";
|
||||
private static final String STOP = "stop";
|
||||
private static final String CURTAIN_LEVEL = "curtain_level";
|
||||
private static final String AUTO = "auto";
|
||||
|
||||
public XiaomiActorCurtainHandler(Thing thing) {
|
||||
super(thing);
|
||||
lastDirection = OPEN;
|
||||
}
|
||||
|
||||
@Override
|
||||
void execute(ChannelUID channelUID, Command command) {
|
||||
String status = command.toString().toLowerCase();
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_CURTAIN_CONTROL:
|
||||
if (command instanceof UpDownType) {
|
||||
if (command.equals(UpDownType.UP)) {
|
||||
status = OPEN;
|
||||
} else {
|
||||
status = CLOSED;
|
||||
}
|
||||
} else if (command instanceof StopMoveType) {
|
||||
if (command.equals(StopMoveType.STOP)) {
|
||||
status = STOP;
|
||||
} else {
|
||||
status = lastDirection;
|
||||
}
|
||||
} else if (command instanceof PercentType) {
|
||||
getXiaomiBridgeHandler().writeToDevice(getItemId(), new String[] { STATUS }, new Object[] { AUTO });
|
||||
getXiaomiBridgeHandler().writeToDevice(getItemId(), new String[] { CURTAIN_LEVEL },
|
||||
new Object[] { status });
|
||||
} else {
|
||||
logger.warn("Only UpDown or StopMove commands supported - not the command {}", command);
|
||||
return;
|
||||
}
|
||||
if (OPEN.equals(status) | CLOSED.equals(status)) {
|
||||
if (!status.equals(lastDirection)) {
|
||||
lastDirection = status;
|
||||
}
|
||||
}
|
||||
getXiaomiBridgeHandler().writeToDevice(getItemId(), new String[] { STATUS }, new Object[] { status });
|
||||
break;
|
||||
default:
|
||||
logger.warn("Can't handle command {} on channel {}", command, channelUID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseHeartbeat(JsonObject data) {
|
||||
parseDefault(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseReadAck(JsonObject data) {
|
||||
parseDefault(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseReport(JsonObject data) {
|
||||
parseDefault(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseWriteAck(JsonObject data) {
|
||||
parseDefault(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseDefault(JsonObject data) {
|
||||
if (data.has(CURTAIN_LEVEL)) {
|
||||
int level = data.get(CURTAIN_LEVEL).getAsInt();
|
||||
if (level >= 0 | level <= 100) {
|
||||
updateState(CHANNEL_CURTAIN_CONTROL, new PercentType(level));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.binding.mihome.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.*;
|
||||
|
||||
import org.openhab.binding.mihome.internal.ColorUtil;
|
||||
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.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* @author Patrick Boos - Initial contribution
|
||||
* @author Dieter Schmidt - Refactor & sound
|
||||
*/
|
||||
public class XiaomiActorGatewayHandler extends XiaomiActorBaseHandler {
|
||||
|
||||
private static final int COLOR_TEMPERATURE_MAX = 6500;
|
||||
private static final int COLOR_TEMPERATURE_MIN = 1700;
|
||||
|
||||
private static final int DEFAULT_BRIGTHNESS_PCENT = 100;
|
||||
private static final int DEFAULT_VOLUME_PCENT = 50;
|
||||
private static final int DEFAULT_COLOR = 0xffffff;
|
||||
|
||||
private static final String RGB = "rgb";
|
||||
private static final String ILLUMINATION = "illumination";
|
||||
private static final String MID = "mid";
|
||||
private static final String VOL = "vol";
|
||||
|
||||
private Integer lastBrigthness;
|
||||
private Integer lastVolume;
|
||||
private Integer lastColor;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(XiaomiActorGatewayHandler.class);
|
||||
|
||||
public XiaomiActorGatewayHandler(Thing thing) {
|
||||
super(thing);
|
||||
lastBrigthness = DEFAULT_BRIGTHNESS_PCENT;
|
||||
lastVolume = DEFAULT_VOLUME_PCENT;
|
||||
lastColor = DEFAULT_COLOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
void execute(ChannelUID channelUID, Command command) {
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_BRIGHTNESS:
|
||||
if (command instanceof PercentType) {
|
||||
int newBright = ((PercentType) command).intValue();
|
||||
if (lastBrigthness != newBright) {
|
||||
lastBrigthness = newBright;
|
||||
logger.debug("Set brigthness to {}", lastBrigthness);
|
||||
writeBridgeLightColor(lastColor, lastBrigthness);
|
||||
} else {
|
||||
logger.debug("Do not send this command, value {} already set", newBright);
|
||||
}
|
||||
return;
|
||||
} else if (command instanceof OnOffType) {
|
||||
writeBridgeLightColor(lastColor, command == OnOffType.ON ? lastBrigthness : 0);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CHANNEL_COLOR:
|
||||
if (command instanceof HSBType) {
|
||||
lastColor = ((HSBType) command).getRGB() & 0xffffff;
|
||||
writeBridgeLightColor(lastColor, lastBrigthness);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CHANNEL_COLOR_TEMPERATURE:
|
||||
if (command instanceof PercentType) {
|
||||
PercentType colorTemperature = (PercentType) command;
|
||||
int kelvin = (COLOR_TEMPERATURE_MAX - COLOR_TEMPERATURE_MIN) / 100 * colorTemperature.intValue()
|
||||
+ COLOR_TEMPERATURE_MIN;
|
||||
int color = ColorUtil.getRGBFromK(kelvin);
|
||||
writeBridgeLightColor(color, lastBrigthness);
|
||||
updateState(CHANNEL_COLOR,
|
||||
HSBType.fromRGB((color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CHANNEL_GATEWAY_SOUND:
|
||||
if (command instanceof DecimalType) {
|
||||
writeBridgeRingtone(((DecimalType) command).intValue(), lastVolume);
|
||||
updateState(CHANNEL_GATEWAY_SOUND_SWITCH, OnOffType.ON);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CHANNEL_GATEWAY_SOUND_SWITCH:
|
||||
if (command instanceof OnOffType) {
|
||||
if (((OnOffType) command) == OnOffType.OFF) {
|
||||
stopRingtone();
|
||||
}
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CHANNEL_GATEWAY_VOLUME:
|
||||
if (command instanceof DecimalType) {
|
||||
updateLastVolume((DecimalType) command);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Only gets here, if no condition was met
|
||||
logger.warn("Can't handle command {} on channel {}", command, channelUID);
|
||||
}
|
||||
|
||||
private void updateLastVolume(DecimalType newVolume) {
|
||||
lastVolume = newVolume.intValue();
|
||||
logger.debug("Changed volume to {}", lastVolume);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdate(ChannelUID channelUID, State newState) {
|
||||
logger.debug("Update {} for channel {} received", newState, channelUID);
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_BRIGHTNESS:
|
||||
if (newState instanceof PercentType) {
|
||||
lastBrigthness = ((PercentType) newState).intValue();
|
||||
}
|
||||
break;
|
||||
case CHANNEL_COLOR:
|
||||
if (newState instanceof HSBType) {
|
||||
lastColor = ((HSBType) newState).getRGB();
|
||||
}
|
||||
break;
|
||||
case CHANNEL_GATEWAY_VOLUME:
|
||||
if (newState instanceof DecimalType) {
|
||||
updateLastVolume((DecimalType) newState);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseReport(JsonObject data) {
|
||||
parseDefault(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseHeartbeat(JsonObject data) {
|
||||
parseDefault(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseReadAck(JsonObject data) {
|
||||
parseDefault(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseWriteAck(JsonObject data) {
|
||||
parseDefault(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseDefault(JsonObject data) {
|
||||
if (data.has(RGB)) {
|
||||
long rgb = data.get(RGB).getAsLong();
|
||||
updateState(CHANNEL_BRIGHTNESS, new PercentType((int) (((rgb >> 24) & 0xff))));
|
||||
updateState(CHANNEL_COLOR,
|
||||
HSBType.fromRGB((int) (rgb >> 16) & 0xff, (int) (rgb >> 8) & 0xff, (int) rgb & 0xff));
|
||||
}
|
||||
if (data.has(ILLUMINATION)) {
|
||||
int illu = data.get(ILLUMINATION).getAsInt();
|
||||
updateState(CHANNEL_ILLUMINATION, new DecimalType(illu));
|
||||
}
|
||||
}
|
||||
|
||||
private void writeBridgeLightColor(int color, int brightness) {
|
||||
long brightnessInt = brightness << 24;
|
||||
writeBridgeLightColor((color & 0xffffff) | brightnessInt & 0xff000000);
|
||||
}
|
||||
|
||||
private void writeBridgeLightColor(long color) {
|
||||
getXiaomiBridgeHandler().writeToBridge(new String[] { RGB }, new Object[] { color });
|
||||
}
|
||||
|
||||
/**
|
||||
* Play ringtone on Xiaomi Gateway
|
||||
* 0 - 8, 10 - 13, 20 - 29 -- ringtones that come with the system)
|
||||
* > 10001 -- user-defined ringtones
|
||||
*
|
||||
* @param ringtoneId
|
||||
*/
|
||||
private void writeBridgeRingtone(int ringtoneId, int volume) {
|
||||
getXiaomiBridgeHandler().writeToBridge(new String[] { MID, VOL }, new Object[] { ringtoneId, volume });
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop playing ringtone on Xiaomi Gateway
|
||||
* by setting "mid" parameter to 10000
|
||||
*/
|
||||
private void stopRingtone() {
|
||||
getXiaomiBridgeHandler().writeToBridge(new String[] { MID }, new Object[] { 10000 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.*;
|
||||
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Handles the Xiaomi smart plug device
|
||||
*
|
||||
* @author Patrick Boos - Initial contribution
|
||||
* @author Dieter Schmidt - Refactor
|
||||
*/
|
||||
public class XiaomiActorPlugHandler extends XiaomiActorBaseHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(XiaomiActorPlugHandler.class);
|
||||
|
||||
private static final String STATUS = "status";
|
||||
private static final String IN_USE = "inuse";
|
||||
private static final String LOAD_POWER = "load_power";
|
||||
private static final String ON = "on";
|
||||
private static final String POWER_CONSUMED = "power_consumed";
|
||||
|
||||
public XiaomiActorPlugHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
void execute(ChannelUID channelUID, Command command) {
|
||||
if (CHANNEL_POWER_ON.equals(channelUID.getId())) {
|
||||
String status = command.toString().toLowerCase();
|
||||
getXiaomiBridgeHandler().writeToDevice(getItemId(), new String[] { STATUS }, new Object[] { status });
|
||||
return;
|
||||
}
|
||||
// Only gets here, if no condition was met
|
||||
logger.warn("Can't handle command {} on channel {}", command, channelUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseHeartbeat(JsonObject data) {
|
||||
parseDefault(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseReadAck(JsonObject data) {
|
||||
parseDefault(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseReport(JsonObject data) {
|
||||
getStatusFromData(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseWriteAck(JsonObject data) {
|
||||
parseDefault(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseDefault(JsonObject data) {
|
||||
getStatusFromData(data);
|
||||
if (data.has(IN_USE)) {
|
||||
updateState(CHANNEL_IN_USE, (data.get(IN_USE).getAsInt() == 1) ? OnOffType.ON : OnOffType.OFF);
|
||||
}
|
||||
if (data.has(LOAD_POWER)) {
|
||||
updateState(CHANNEL_LOAD_POWER, new DecimalType(data.get(LOAD_POWER).getAsBigDecimal()));
|
||||
}
|
||||
if (data.has(POWER_CONSUMED)) {
|
||||
updateState(CHANNEL_POWER_CONSUMED,
|
||||
new DecimalType(data.get(POWER_CONSUMED).getAsBigDecimal().scaleByPowerOfTen(-3)));
|
||||
}
|
||||
}
|
||||
|
||||
private void getStatusFromData(JsonObject data) {
|
||||
if (data.has(STATUS)) {
|
||||
boolean isOn = ON.equals(data.get(STATUS).getAsString());
|
||||
updateState(CHANNEL_POWER_ON, isOn ? OnOffType.ON : OnOffType.OFF);
|
||||
if (!isOn) {
|
||||
updateState(CHANNEL_IN_USE, OnOffType.OFF);
|
||||
updateState(CHANNEL_LOAD_POWER, new DecimalType(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.binding.mihome.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.CHANNEL_SWITCH_CH0;
|
||||
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Handles the Xiaomi aqara wall switch with one button
|
||||
*
|
||||
* @author Dieter Schmidt - Initial contribution
|
||||
*/
|
||||
public class XiaomiAqaraActorSwitch1Handler extends XiaomiActorBaseHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(XiaomiAqaraActorSwitch1Handler.class);
|
||||
|
||||
private static final String CHANNEL_0 = "channel_0";
|
||||
private static final String ON = "on";
|
||||
|
||||
public XiaomiAqaraActorSwitch1Handler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
void execute(ChannelUID channelUID, Command command) {
|
||||
if (CHANNEL_SWITCH_CH0.equals(channelUID.getId())) {
|
||||
String status = command.toString().toLowerCase();
|
||||
getXiaomiBridgeHandler().writeToDevice(getItemId(), new String[] { CHANNEL_0 }, new Object[] { status });
|
||||
return;
|
||||
}
|
||||
// Only gets here, if no condition was met
|
||||
logger.error("Can't handle command {} on channel {}", command, channelUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseReport(JsonObject data) {
|
||||
parseDefault(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseHeartbeat(JsonObject data) {
|
||||
parseDefault(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseReadAck(JsonObject data) {
|
||||
parseDefault(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseWriteAck(JsonObject data) {
|
||||
logger.debug("Got write ack message but ignoring it to prevent item state toggling");
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseDefault(JsonObject data) {
|
||||
if (data.has(CHANNEL_0)) {
|
||||
boolean isOn = ON.equals(data.get(CHANNEL_0).getAsString().toLowerCase());
|
||||
updateState(CHANNEL_SWITCH_CH0, isOn ? OnOffType.ON : OnOffType.OFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.*;
|
||||
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Handles the Xiaomi aqara wall switch with two buttons
|
||||
*
|
||||
* @author Dieter Schmidt - Initial contribution
|
||||
*/
|
||||
public class XiaomiAqaraActorSwitch2Handler extends XiaomiActorBaseHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(XiaomiAqaraActorSwitch2Handler.class);
|
||||
|
||||
private static final String CHANNEL_0 = "channel_0";
|
||||
private static final String CHANNEL_1 = "channel_1";
|
||||
private static final String ON = "on";
|
||||
|
||||
public XiaomiAqaraActorSwitch2Handler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
void execute(ChannelUID channelUID, Command command) {
|
||||
String status = command.toString().toLowerCase();
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_SWITCH_CH0:
|
||||
getXiaomiBridgeHandler().writeToDevice(getItemId(), new String[] { CHANNEL_0 },
|
||||
new Object[] { status });
|
||||
return;
|
||||
case CHANNEL_SWITCH_CH1:
|
||||
getXiaomiBridgeHandler().writeToDevice(getItemId(), new String[] { CHANNEL_1 },
|
||||
new Object[] { status });
|
||||
return;
|
||||
}
|
||||
// Only gets here, if no condition was met
|
||||
logger.error("Can't handle command {} on channel {}", command, channelUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseReport(JsonObject data) {
|
||||
parseDefault(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseHeartbeat(JsonObject data) {
|
||||
parseDefault(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseReadAck(JsonObject data) {
|
||||
parseDefault(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseWriteAck(JsonObject data) {
|
||||
logger.debug("Got write ack message but ignoring it to prevent item state toggling");
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseDefault(JsonObject data) {
|
||||
if (data.has(CHANNEL_0)) {
|
||||
boolean isOn = ON.equals(data.get(CHANNEL_0).getAsString().toLowerCase());
|
||||
updateState(CHANNEL_SWITCH_CH0, isOn ? OnOffType.ON : OnOffType.OFF);
|
||||
} else if (data.has(CHANNEL_1)) {
|
||||
boolean isOn = ON.equals(data.get(CHANNEL_1).getAsString().toLowerCase());
|
||||
updateState(CHANNEL_SWITCH_CH1, isOn ? OnOffType.ON : OnOffType.OFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.CHANNEL_SWITCH_CH0;
|
||||
|
||||
import org.openhab.binding.mihome.internal.ChannelMapper;
|
||||
import org.openhab.core.thing.Thing;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Handles the Xiaomi aqara smart switch with one button
|
||||
*
|
||||
* @author Dieter Schmidt - Initial contribution
|
||||
*/
|
||||
public class XiaomiAqaraSensorSwitch1Handler extends XiaomiSensorBaseHandler {
|
||||
|
||||
private static final String CHANNEL_0 = "channel_0";
|
||||
|
||||
public XiaomiAqaraSensorSwitch1Handler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void parseReport(JsonObject data) {
|
||||
if (data.has(CHANNEL_0)) {
|
||||
triggerChannel(CHANNEL_SWITCH_CH0,
|
||||
ChannelMapper.getChannelEvent(data.get(CHANNEL_0).getAsString().toUpperCase()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.*;
|
||||
|
||||
import org.openhab.binding.mihome.internal.ChannelMapper;
|
||||
import org.openhab.core.thing.Thing;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Handles the Xiaomi aqara smart switch with two buttons
|
||||
*
|
||||
* @author Dieter Schmidt - Initial contribution
|
||||
*/
|
||||
public class XiaomiAqaraSensorSwitch2Handler extends XiaomiSensorBaseHandler {
|
||||
|
||||
private static final String CHANNEL_0 = "channel_0";
|
||||
private static final String CHANNEL_1 = "channel_1";
|
||||
private static final String DUAL_CHANNEL = "dual_channel";
|
||||
|
||||
public XiaomiAqaraSensorSwitch2Handler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void parseReport(JsonObject data) {
|
||||
if (data.has(CHANNEL_0)) {
|
||||
triggerChannel(CHANNEL_SWITCH_CH0,
|
||||
ChannelMapper.getChannelEvent(data.get(CHANNEL_0).getAsString().toUpperCase()));
|
||||
}
|
||||
if (data.has(CHANNEL_1)) {
|
||||
triggerChannel(CHANNEL_SWITCH_CH1,
|
||||
ChannelMapper.getChannelEvent(data.get(CHANNEL_1).getAsString().toUpperCase()));
|
||||
}
|
||||
if (data.has(DUAL_CHANNEL)) {
|
||||
triggerChannel(CHANNEL_SWITCH_DUAL_CH,
|
||||
ChannelMapper.getChannelEvent(data.get(DUAL_CHANNEL).getAsString().toUpperCase()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.openhab.binding.mihome.internal.EncryptionHelper;
|
||||
import org.openhab.binding.mihome.internal.XiaomiItemUpdateListener;
|
||||
import org.openhab.binding.mihome.internal.discovery.XiaomiItemDiscoveryService;
|
||||
import org.openhab.binding.mihome.internal.socket.XiaomiBridgeSocket;
|
||||
import org.openhab.binding.mihome.internal.socket.XiaomiSocketListener;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.config.core.status.ConfigStatusMessage;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.ConfigStatusBridgeHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
/**
|
||||
* The {@link XiaomiBridgeHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels for the bridge.
|
||||
*
|
||||
* @author Patrick Boos - Initial contribution
|
||||
* @author Dieter Schmidt - added device update from heartbeat
|
||||
*/
|
||||
public class XiaomiBridgeHandler extends ConfigStatusBridgeHandler implements XiaomiSocketListener {
|
||||
|
||||
private static final long READ_ACK_RETENTION_MILLIS = TimeUnit.HOURS.toMillis(2);
|
||||
private static final long BRIDGE_CONNECTION_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(5);
|
||||
|
||||
private static final String JOIN_PERMISSION = "join_permission";
|
||||
private static final String YES = "yes";
|
||||
private static final String NO = "no";
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);
|
||||
private static final JsonParser PARSER = new JsonParser();
|
||||
private static final EncryptionHelper CRYPTER = new EncryptionHelper();
|
||||
private static Map<String, JsonObject> retentionInbox = new ConcurrentHashMap<>();
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(XiaomiBridgeHandler.class);
|
||||
|
||||
private List<XiaomiItemUpdateListener> itemListeners = new ArrayList<>();
|
||||
private List<XiaomiItemUpdateListener> itemDiscoveryListeners = new ArrayList<>();
|
||||
|
||||
private String gatewayToken;
|
||||
private long lastDiscoveryTime;
|
||||
private Map<String, Long> lastOnlineMap = new ConcurrentHashMap<>();
|
||||
|
||||
private Configuration config;
|
||||
private InetAddress host;
|
||||
private int port;
|
||||
private XiaomiBridgeSocket socket;
|
||||
private Timer connectionTimeout = new Timer();
|
||||
private boolean timerIsRunning = false;
|
||||
|
||||
public XiaomiBridgeHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ConfigStatusMessage> getConfigStatus() {
|
||||
// Currently we have no errors. Since we always use discover, it should always be okay.
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private class TimerAction extends TimerTask {
|
||||
@Override
|
||||
public synchronized void run() {
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
timerIsRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void startTimer() {
|
||||
cancelRunningTimer();
|
||||
connectionTimeout.schedule(new TimerAction(), BRIDGE_CONNECTION_TIMEOUT_MILLIS);
|
||||
timerIsRunning = true;
|
||||
}
|
||||
|
||||
synchronized void cancelRunningTimer() {
|
||||
if (timerIsRunning) {
|
||||
connectionTimeout.cancel();
|
||||
connectionTimeout = new Timer();
|
||||
timerIsRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
try {
|
||||
config = getThing().getConfiguration();
|
||||
host = InetAddress.getByName(config.get(HOST).toString());
|
||||
port = getConfigInteger(config, PORT);
|
||||
} catch (UnknownHostException e) {
|
||||
logger.warn("Bridge IP/PORT config is not set or not valid");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
|
||||
return;
|
||||
}
|
||||
logger.debug("Init socket on Port: {}", port);
|
||||
socket = new XiaomiBridgeSocket(port, getThing().getUID().getId());
|
||||
socket.initialize();
|
||||
socket.registerListener(this);
|
||||
|
||||
scheduler.schedule(() -> {
|
||||
readDeviceList();
|
||||
}, 1, TimeUnit.SECONDS);
|
||||
startTimer();
|
||||
}
|
||||
|
||||
public void readDeviceList() {
|
||||
sendCommandToBridge("get_id_list");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("dispose");
|
||||
socket.unregisterListener(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("Gateway doesn't handle command: {}", command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataReceived(JsonObject message) {
|
||||
logger.trace("Received message {}", message);
|
||||
String sid = message.has("sid") ? message.get("sid").getAsString() : null;
|
||||
String command = message.get("cmd").getAsString();
|
||||
|
||||
updateDeviceStatus(sid);
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
startTimer();
|
||||
switch (command) {
|
||||
case "iam":
|
||||
return;
|
||||
case "heartbeat":
|
||||
if (message.has("token")) {
|
||||
this.gatewayToken = message.get("token").getAsString();
|
||||
}
|
||||
break;
|
||||
case "get_id_list_ack":
|
||||
JsonArray devices = PARSER.parse(message.get("data").getAsString()).getAsJsonArray();
|
||||
for (JsonElement deviceId : devices) {
|
||||
String device = deviceId.getAsString();
|
||||
sendCommandToBridge("read", device);
|
||||
}
|
||||
// as well get gateway status
|
||||
sendCommandToBridge("read", getGatewaySid());
|
||||
return;
|
||||
case "read_ack":
|
||||
logger.debug("Device {} honored read request", sid);
|
||||
defer(sid, message);
|
||||
break;
|
||||
case "write_ack":
|
||||
logger.debug("Device {} honored write request", sid);
|
||||
break;
|
||||
}
|
||||
notifyListeners(command, message);
|
||||
}
|
||||
|
||||
private synchronized void defer(String sid, JsonObject message) {
|
||||
synchronized (retentionInbox) {
|
||||
retentionInbox.remove(sid);
|
||||
retentionInbox.put(sid, message);
|
||||
}
|
||||
scheduler.schedule(new RemoveRetentionRunnable(sid), READ_ACK_RETENTION_MILLIS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private class RemoveRetentionRunnable implements Runnable {
|
||||
private String sid;
|
||||
|
||||
public RemoveRetentionRunnable(String sid) {
|
||||
this.sid = sid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void run() {
|
||||
synchronized (retentionInbox) {
|
||||
retentionInbox.remove(sid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized JsonObject getDeferredMessage(String sid) {
|
||||
synchronized (retentionInbox) {
|
||||
JsonObject ret = retentionInbox.get(sid);
|
||||
if (ret != null) {
|
||||
retentionInbox.remove(sid);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void notifyListeners(String command, JsonObject message) {
|
||||
boolean knownDevice = false;
|
||||
String sid = message.get("sid").getAsString();
|
||||
|
||||
// Not a message to pass to any itemListener
|
||||
if (sid == null) {
|
||||
return;
|
||||
}
|
||||
for (XiaomiItemUpdateListener itemListener : itemListeners) {
|
||||
if (sid.equals(itemListener.getItemId())) {
|
||||
itemListener.onItemUpdate(sid, command, message);
|
||||
knownDevice = true;
|
||||
}
|
||||
}
|
||||
if (!knownDevice) {
|
||||
for (XiaomiItemUpdateListener itemListener : itemDiscoveryListeners) {
|
||||
itemListener.onItemUpdate(sid, command, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean registerItemListener(XiaomiItemUpdateListener listener) {
|
||||
boolean result = false;
|
||||
if (listener == null) {
|
||||
logger.warn("It's not allowed to pass a null XiaomiItemUpdateListener");
|
||||
} else if (listener instanceof XiaomiItemDiscoveryService) {
|
||||
result = !(itemDiscoveryListeners.contains(listener)) ? itemDiscoveryListeners.add(listener) : false;
|
||||
logger.debug("Having {} Item Discovery listeners", itemDiscoveryListeners.size());
|
||||
} else {
|
||||
logger.debug("Adding item listener for device {}", listener.getItemId());
|
||||
result = !(itemListeners.contains(listener)) ? itemListeners.add(listener) : false;
|
||||
logger.debug("Having {} Item listeners", itemListeners.size());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public synchronized boolean unregisterItemListener(XiaomiItemUpdateListener listener) {
|
||||
return itemListeners.remove(listener);
|
||||
}
|
||||
|
||||
private void sendMessageToBridge(String message) {
|
||||
logger.debug("Send to bridge {}: {}", this.getThing().getUID(), message);
|
||||
socket.sendMessage(message, host, port);
|
||||
}
|
||||
|
||||
private void sendCommandToBridge(String cmd) {
|
||||
sendCommandToBridge(cmd, null, null, null);
|
||||
}
|
||||
|
||||
private void sendCommandToBridge(String cmd, String[] keys, Object[] values) {
|
||||
sendCommandToBridge(cmd, null, keys, values);
|
||||
}
|
||||
|
||||
private void sendCommandToBridge(String cmd, String sid) {
|
||||
sendCommandToBridge(cmd, sid, null, null);
|
||||
}
|
||||
|
||||
private void sendCommandToBridge(String cmd, String sid, String[] keys, Object[] values) {
|
||||
StringBuilder message = new StringBuilder("{");
|
||||
message.append("\"cmd\": \"").append(cmd).append("\"");
|
||||
if (sid != null) {
|
||||
message.append(", \"sid\": \"").append(sid).append("\"");
|
||||
}
|
||||
if (keys != null) {
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
message.append(", ").append("\"").append(keys[i]).append("\"").append(": ");
|
||||
|
||||
// write value
|
||||
message.append(toJsonValue(values[i]));
|
||||
}
|
||||
}
|
||||
message.append("}");
|
||||
|
||||
sendMessageToBridge(message.toString());
|
||||
}
|
||||
|
||||
void writeToDevice(String itemId, String[] keys, Object[] values) {
|
||||
sendCommandToBridge("write", new String[] { "sid", "data" },
|
||||
new Object[] { itemId, createDataJsonString(keys, values) });
|
||||
}
|
||||
|
||||
void writeToDevice(String itemId, String command) {
|
||||
sendCommandToBridge("write", new String[] { "sid", "data" },
|
||||
new Object[] { itemId, "{" + command + ", \\\"key\\\": \\\"" + getEncryptedKey() + "\\\"}" });
|
||||
}
|
||||
|
||||
void writeToBridge(String[] keys, Object[] values) {
|
||||
sendCommandToBridge("write", new String[] { "model", "sid", "short_id", "data" },
|
||||
new Object[] { "gateway", getGatewaySid(), "0", createDataJsonString(keys, values) });
|
||||
}
|
||||
|
||||
private String createDataJsonString(String[] keys, Object[] values) {
|
||||
return "{" + createDataString(keys, values) + ", \\\"key\\\": \\\"" + getEncryptedKey() + "\\\"}";
|
||||
}
|
||||
|
||||
private String getGatewaySid() {
|
||||
return (String) getConfig().get(SERIAL_NUMBER);
|
||||
}
|
||||
|
||||
private String getEncryptedKey() {
|
||||
String key = (String) getConfig().get("key");
|
||||
|
||||
if (key == null) {
|
||||
logger.warn("No key set in the gateway settings. Edit it in the configuration.");
|
||||
return "";
|
||||
}
|
||||
if (gatewayToken == null) {
|
||||
logger.warn("No token received from the gateway yet. Unable to encrypt the access key.");
|
||||
return "";
|
||||
}
|
||||
key = CRYPTER.encrypt(gatewayToken, key);
|
||||
return key;
|
||||
}
|
||||
|
||||
private String createDataString(String[] keys, Object[] values) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
if (keys.length != values.length) {
|
||||
return "";
|
||||
}
|
||||
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (i > 0) {
|
||||
builder.append(",");
|
||||
}
|
||||
|
||||
// write key
|
||||
builder.append("\\\"").append(keys[i]).append("\\\"").append(": ");
|
||||
|
||||
// write value
|
||||
builder.append(escapeQuotes(toJsonValue(values[i])));
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String toJsonValue(Object o) {
|
||||
if (o instanceof String) {
|
||||
return "\"" + o + "\"";
|
||||
} else {
|
||||
return o.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private String escapeQuotes(String string) {
|
||||
return string.replaceAll("\"", "\\\\\"");
|
||||
}
|
||||
|
||||
private int getConfigInteger(Configuration config, String key) {
|
||||
Object value = config.get(key);
|
||||
if (value instanceof BigDecimal) {
|
||||
return ((BigDecimal) value).intValue();
|
||||
} else if (value instanceof String) {
|
||||
return Integer.parseInt((String) value);
|
||||
} else {
|
||||
return (Integer) value;
|
||||
}
|
||||
}
|
||||
|
||||
public void discoverItems(long discoveryTimeout) {
|
||||
long lockedFor = discoveryTimeout - (System.currentTimeMillis() - lastDiscoveryTime);
|
||||
if (lockedFor <= 0) {
|
||||
logger.debug("Triggered discovery");
|
||||
readDeviceList();
|
||||
writeToBridge(new String[] { JOIN_PERMISSION }, new Object[] { YES });
|
||||
scheduler.schedule(() -> {
|
||||
writeToBridge(new String[] { JOIN_PERMISSION }, new Object[] { NO });
|
||||
}, discoveryTimeout, TimeUnit.MILLISECONDS);
|
||||
lastDiscoveryTime = System.currentTimeMillis();
|
||||
} else {
|
||||
logger.debug("A discovery has already been triggered, please wait for {}ms", lockedFor);
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasItemActivity(String itemId, long withinLastMillis) {
|
||||
Long lastOnlineTimeMillis = lastOnlineMap.get(itemId);
|
||||
return lastOnlineTimeMillis != null && System.currentTimeMillis() - lastOnlineTimeMillis < withinLastMillis;
|
||||
}
|
||||
|
||||
private void updateDeviceStatus(String sid) {
|
||||
if (sid != null) {
|
||||
lastOnlineMap.put(sid, System.currentTimeMillis());
|
||||
logger.trace("Updated \"last time seen\" for device {}", sid);
|
||||
}
|
||||
}
|
||||
|
||||
public InetAddress getHost() {
|
||||
return host;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.*;
|
||||
import static org.openhab.core.library.unit.MetricPrefix.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.measure.Unit;
|
||||
import javax.measure.quantity.Angle;
|
||||
import javax.measure.quantity.Dimensionless;
|
||||
import javax.measure.quantity.Pressure;
|
||||
import javax.measure.quantity.Temperature;
|
||||
import javax.measure.quantity.Time;
|
||||
|
||||
import org.openhab.binding.mihome.internal.XiaomiItemUpdateListener;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link XiaomiDeviceBaseHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Patrick Boos - Initial contribution
|
||||
* @author Kuba Wolanin - Added voltage and low battery report
|
||||
* @author Dieter Schmidt - Added cube rotation, heartbeat and voltage handling, configurable window and motion delay,
|
||||
* Aqara
|
||||
* switches
|
||||
* @author Daniel Walters - Added Aqara Door/Window sensor and Aqara temperature, humidity and pressure sensor
|
||||
*/
|
||||
public class XiaomiDeviceBaseHandler extends BaseThingHandler implements XiaomiItemUpdateListener {
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = new HashSet<>(Arrays.asList(THING_TYPE_GATEWAY,
|
||||
THING_TYPE_SENSOR_HT, THING_TYPE_SENSOR_AQARA_WEATHER_V1, THING_TYPE_SENSOR_MOTION,
|
||||
THING_TYPE_SENSOR_AQARA_MOTION, THING_TYPE_SENSOR_SWITCH, THING_TYPE_SENSOR_AQARA_SWITCH,
|
||||
THING_TYPE_SENSOR_MAGNET, THING_TYPE_SENSOR_AQARA_LOCK, THING_TYPE_SENSOR_AQARA_MAGNET,
|
||||
THING_TYPE_SENSOR_CUBE, THING_TYPE_SENSOR_AQARA_VIBRATION, THING_TYPE_SENSOR_AQARA1,
|
||||
THING_TYPE_SENSOR_AQARA2, THING_TYPE_SENSOR_GAS, THING_TYPE_SENSOR_SMOKE, THING_TYPE_SENSOR_WATER,
|
||||
THING_TYPE_ACTOR_AQARA1, THING_TYPE_ACTOR_AQARA2, THING_TYPE_ACTOR_PLUG, THING_TYPE_ACTOR_AQARA_ZERO1,
|
||||
THING_TYPE_ACTOR_AQARA_ZERO2, THING_TYPE_ACTOR_CURTAIN, THING_TYPE_BASIC));
|
||||
|
||||
protected static final Unit<Temperature> TEMPERATURE_UNIT = SIUnits.CELSIUS;
|
||||
protected static final Unit<Pressure> PRESSURE_UNIT = KILO(SIUnits.PASCAL);
|
||||
protected static final Unit<Dimensionless> PERCENT_UNIT = SmartHomeUnits.PERCENT;
|
||||
protected static final Unit<Angle> ANGLE_UNIT = SmartHomeUnits.DEGREE_ANGLE;
|
||||
protected static final Unit<Time> TIME_UNIT = MILLI(SmartHomeUnits.SECOND);
|
||||
|
||||
private static final String REMOVE_DEVICE = "remove_device";
|
||||
private static final long ONLINE_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(2);
|
||||
private ScheduledFuture<?> onlineCheckTask;
|
||||
|
||||
private JsonParser parser = new JsonParser();
|
||||
|
||||
private XiaomiBridgeHandler bridgeHandler;
|
||||
|
||||
private String itemId;
|
||||
|
||||
private final void setItemId(String itemId) {
|
||||
this.itemId = itemId;
|
||||
}
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(XiaomiDeviceBaseHandler.class);
|
||||
|
||||
public XiaomiDeviceBaseHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
setItemId((String) getConfig().get(ITEM_ID));
|
||||
onlineCheckTask = scheduler.scheduleWithFixedDelay(this::updateThingStatus, 0, ONLINE_TIMEOUT_MILLIS / 2,
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Handler disposes. Unregistering listener");
|
||||
if (getItemId() != null) {
|
||||
if (bridgeHandler != null) {
|
||||
bridgeHandler.unregisterItemListener(this);
|
||||
bridgeHandler = null;
|
||||
}
|
||||
setItemId(null);
|
||||
}
|
||||
if (!onlineCheckTask.isDone()) {
|
||||
onlineCheckTask.cancel(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRemoval() {
|
||||
getXiaomiBridgeHandler().writeToBridge(new String[] { REMOVE_DEVICE }, new Object[] { itemId });
|
||||
super.handleRemoval();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("Device {} on channel {} received command {}", getItemId(), channelUID, command);
|
||||
if (command instanceof RefreshType) {
|
||||
JsonObject message = getXiaomiBridgeHandler().getDeferredMessage(getItemId());
|
||||
if (message != null) {
|
||||
String cmd = message.get("cmd").getAsString();
|
||||
logger.debug("Update Item {} with retented message", getItemId());
|
||||
onItemUpdate(getItemId(), cmd, message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
execute(channelUID, command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemUpdate(String sid, String command, JsonObject message) {
|
||||
if (getItemId() != null && getItemId().equals(sid)) {
|
||||
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
logger.debug("Item got update: {}", message);
|
||||
try {
|
||||
JsonObject data = parser.parse(message.get("data").getAsString()).getAsJsonObject();
|
||||
parseCommand(command, data);
|
||||
if (THING_TYPE_BASIC.equals(getThing().getThingTypeUID())) {
|
||||
parseDefault(message);
|
||||
}
|
||||
} catch (JsonSyntaxException e) {
|
||||
logger.warn("Unable to parse message as valid JSON: {}", message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getItemId() {
|
||||
return itemId;
|
||||
}
|
||||
|
||||
void parseCommand(String command, JsonObject data) {
|
||||
switch (command) {
|
||||
case "report":
|
||||
parseReport(data);
|
||||
break;
|
||||
case "heartbeat":
|
||||
parseHeartbeat(data);
|
||||
break;
|
||||
case "read_ack":
|
||||
parseReadAck(data);
|
||||
break;
|
||||
case "write_ack":
|
||||
parseWriteAck(data);
|
||||
break;
|
||||
default:
|
||||
logger.debug("Device {} got unknown command {}", getItemId(), command);
|
||||
}
|
||||
}
|
||||
|
||||
void parseReport(JsonObject data) {
|
||||
updateState(CHANNEL_REPORT_MSG, StringType.valueOf(data.toString()));
|
||||
}
|
||||
|
||||
void parseHeartbeat(JsonObject data) {
|
||||
updateState(CHANNEL_HEARTBEAT_MSG, StringType.valueOf(data.toString()));
|
||||
}
|
||||
|
||||
void parseReadAck(JsonObject data) {
|
||||
updateState(CHANNEL_READ_ACK_MSG, StringType.valueOf(data.toString()));
|
||||
}
|
||||
|
||||
void parseWriteAck(JsonObject data) {
|
||||
updateState(CHANNEL_WRITE_ACK_MSG, StringType.valueOf(data.toString()));
|
||||
}
|
||||
|
||||
void parseDefault(JsonObject data) {
|
||||
updateState(CHANNEL_LAST_MSG, StringType.valueOf(data.toString()));
|
||||
}
|
||||
|
||||
void execute(ChannelUID channelUID, Command command) {
|
||||
if (CHANNEL_WRITE_MSG.equals(channelUID.getId())) {
|
||||
if (command instanceof StringType) {
|
||||
getXiaomiBridgeHandler().writeToDevice(itemId, ((StringType) command).toFullString());
|
||||
} else {
|
||||
logger.debug("Command \"{}\" has to be of StringType", command);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Received command on read-only channel, thus ignoring it.");
|
||||
}
|
||||
}
|
||||
|
||||
private void updateThingStatus() {
|
||||
if (getItemId() != null) {
|
||||
// note: this call implicitly registers our handler as a listener on the bridge, if it's not already
|
||||
if (getXiaomiBridgeHandler() != null) {
|
||||
Bridge bridge = getBridge();
|
||||
ThingStatus bridgeStatus = (bridge == null) ? null : bridge.getStatus();
|
||||
if (bridgeStatus == ThingStatus.ONLINE) {
|
||||
ThingStatus itemStatus = getThing().getStatus();
|
||||
boolean hasItemActivity = getXiaomiBridgeHandler().hasItemActivity(getItemId(),
|
||||
ONLINE_TIMEOUT_MILLIS);
|
||||
ThingStatus newStatus = hasItemActivity ? ThingStatus.ONLINE : ThingStatus.OFFLINE;
|
||||
|
||||
if (!newStatus.equals(itemStatus)) {
|
||||
updateStatus(newStatus);
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
synchronized XiaomiBridgeHandler getXiaomiBridgeHandler() {
|
||||
if (this.bridgeHandler == null) {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
return null;
|
||||
}
|
||||
ThingHandler handler = bridge.getHandler();
|
||||
if (handler instanceof XiaomiBridgeHandler) {
|
||||
this.bridgeHandler = (XiaomiBridgeHandler) handler;
|
||||
this.bridgeHandler.registerItemListener(this);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return this.bridgeHandler;
|
||||
}
|
||||
}
|
||||
@@ -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.binding.mihome.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Thing;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Abstract base class for Xiaomi sensor devices, which provide an alarm status message
|
||||
*
|
||||
* @author Dieter Schmidt - Initial contribution
|
||||
*/
|
||||
public abstract class XiaomiSensorBaseAlarmHandler extends XiaomiSensorBaseHandler {
|
||||
|
||||
private static final Map<Integer, String> ALARM_STATUS_MAP = new HashMap<>();
|
||||
private static final String ALARM = "alarm";
|
||||
private static final String UNKNOWN = "unknown";
|
||||
|
||||
public XiaomiSensorBaseAlarmHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseReport(JsonObject data) {
|
||||
if (data.has(ALARM)) {
|
||||
int alarm = data.get(ALARM).getAsInt();
|
||||
// alarm channel only receives the "real alarm"
|
||||
if (alarm == 1) {
|
||||
updateState(CHANNEL_ALARM, OnOffType.ON);
|
||||
} else {
|
||||
updateState(CHANNEL_ALARM, OnOffType.OFF);
|
||||
}
|
||||
// status shows faults
|
||||
String status = ALARM_STATUS_MAP.get(alarm);
|
||||
if (status != null) {
|
||||
updateState(CHANNEL_STATUS, StringType.valueOf(status));
|
||||
} else {
|
||||
updateState(CHANNEL_STATUS, StringType.valueOf(UNKNOWN));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.*;
|
||||
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Abstract base class for battery powered Xiaomi sensor devices
|
||||
*
|
||||
* @author Dieter Schmidt - Initial contribution
|
||||
*/
|
||||
public abstract class XiaomiSensorBaseHandler extends XiaomiDeviceBaseHandler {
|
||||
|
||||
private static final int VOLTAGE_MAX_MILLIVOLTS = 3100;
|
||||
private static final int VOLTAGE_MIN_MILLIVOLTS = 2700;
|
||||
private static final int BATT_LEVEL_LOW = 20;
|
||||
private static final int BATT_LEVEL_LOW_HYS = 5;
|
||||
|
||||
private static final String STATUS = "status";
|
||||
private static final String VOLTAGE = "voltage";
|
||||
|
||||
private boolean isBatteryLow = false;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(XiaomiSensorBaseHandler.class);
|
||||
|
||||
public XiaomiSensorBaseHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseHeartbeat(JsonObject data) {
|
||||
parseDefault(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseReadAck(JsonObject data) {
|
||||
parseDefault(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseDefault(JsonObject data) {
|
||||
if (data.get(VOLTAGE) != null) {
|
||||
Integer voltage = data.get(VOLTAGE).getAsInt();
|
||||
calculateBatteryLevelFromVoltage(voltage);
|
||||
}
|
||||
if (data.get(STATUS) != null) {
|
||||
logger.trace(
|
||||
"Got status {} - Apart from \"report\" all other status updates for sensors seem not right (Firmware 1.4.1.145)",
|
||||
data.get(STATUS));
|
||||
}
|
||||
}
|
||||
|
||||
void calculateBatteryLevelFromVoltage(Integer voltage) {
|
||||
int limVoltage = voltage;
|
||||
limVoltage = Math.min(VOLTAGE_MAX_MILLIVOLTS, limVoltage);
|
||||
limVoltage = Math.max(VOLTAGE_MIN_MILLIVOLTS, limVoltage);
|
||||
Integer battLevel = (int) ((float) (limVoltage - VOLTAGE_MIN_MILLIVOLTS)
|
||||
/ (float) (VOLTAGE_MAX_MILLIVOLTS - VOLTAGE_MIN_MILLIVOLTS) * 100);
|
||||
updateState(CHANNEL_BATTERY_LEVEL, new DecimalType(battLevel));
|
||||
int lowThreshold = isBatteryLow ? BATT_LEVEL_LOW + BATT_LEVEL_LOW_HYS : BATT_LEVEL_LOW;
|
||||
if (battLevel <= lowThreshold) {
|
||||
updateState(CHANNEL_LOW_BATTERY, OnOffType.ON);
|
||||
isBatteryLow = true;
|
||||
} else {
|
||||
updateState(CHANNEL_LOW_BATTERY, OnOffType.OFF);
|
||||
isBatteryLow = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void execute(ChannelUID channelUID, Command command) {
|
||||
logger.warn("Cannot execute command - Sensors by definition only have read only channels");
|
||||
}
|
||||
}
|
||||
@@ -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.binding.mihome.internal.handler;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link XiaomiSensorBaseHandlerWithTimer} is an abstract class for sensor devices
|
||||
* which use a timer to update a certain channel. The user can configure the timer via an item
|
||||
* value.
|
||||
*
|
||||
* @author Dieter Schmidt - Initial contribution
|
||||
*
|
||||
*/
|
||||
public abstract class XiaomiSensorBaseHandlerWithTimer extends XiaomiSensorBaseHandler {
|
||||
|
||||
private int defaultTimer;
|
||||
private int minTimer;
|
||||
private Integer timerSetpoint;
|
||||
private final String setpointChannel;
|
||||
private boolean timerIsRunning;
|
||||
private Timer trigger = new Timer();
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(XiaomiSensorBaseHandlerWithTimer.class);
|
||||
|
||||
public XiaomiSensorBaseHandlerWithTimer(Thing thing, int defaultTimer, int minTimer, String setpointChannel) {
|
||||
super(thing);
|
||||
this.defaultTimer = defaultTimer;
|
||||
this.minTimer = minTimer;
|
||||
this.timerSetpoint = defaultTimer;
|
||||
this.setpointChannel = setpointChannel;
|
||||
}
|
||||
|
||||
class TimerAction extends TimerTask {
|
||||
@Override
|
||||
public synchronized void run() {
|
||||
onTimer();
|
||||
timerIsRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void startTimer() {
|
||||
cancelRunningTimer();
|
||||
logger.debug("Setting timer to {}s", timerSetpoint);
|
||||
trigger.schedule(new TimerAction(), timerSetpoint * 1000);
|
||||
timerIsRunning = true;
|
||||
}
|
||||
|
||||
synchronized void cancelRunningTimer() {
|
||||
if (timerIsRunning) {
|
||||
trigger.cancel();
|
||||
logger.debug("Cancelled running timer");
|
||||
trigger = new Timer();
|
||||
timerIsRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
abstract void onTimer();
|
||||
|
||||
void setTimerFromDecimalType(DecimalType value) {
|
||||
try {
|
||||
int newValue = value.intValue();
|
||||
timerSetpoint = newValue < minTimer ? minTimer : newValue;
|
||||
if (timerSetpoint == minTimer) {
|
||||
updateState(setpointChannel, new DecimalType(timerSetpoint));
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
logger.debug("Cannot parse the value {} to an Integer", value);
|
||||
timerSetpoint = defaultTimer;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdate(ChannelUID channelUID, State newState) {
|
||||
if (setpointChannel.equals(channelUID.getId())) {
|
||||
if (newState instanceof DecimalType) {
|
||||
logger.debug("Received update for timer setpoint channel: {}", newState);
|
||||
timerSetpoint = ((DecimalType) newState).intValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.*;
|
||||
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Handles the Xiaomi magic controller cube
|
||||
*
|
||||
* @author Patrick Boos - Initial contribution
|
||||
* @author Dieter Schmidt - Refactor
|
||||
*/
|
||||
public class XiaomiSensorCubeHandler extends XiaomiSensorBaseHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(XiaomiSensorCubeHandler.class);
|
||||
private static final String STATUS = "status";
|
||||
private static final String ROTATE = "rotate";
|
||||
|
||||
public XiaomiSensorCubeHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseReport(JsonObject data) {
|
||||
if (data.has(STATUS)) {
|
||||
triggerChannel(CHANNEL_ACTION, data.get(STATUS).getAsString().toUpperCase());
|
||||
updateState(CHANNEL_LAST_ACTION, new DateTimeType());
|
||||
} else if (data.has(ROTATE)) {
|
||||
Integer rot = 0;
|
||||
Integer time = 0;
|
||||
try {
|
||||
rot = Integer.parseInt((data.get(ROTATE).getAsString().split(",")[0]));
|
||||
// convert from percent to angle degrees
|
||||
rot = (int) (rot * 3.6);
|
||||
} catch (NumberFormatException e) {
|
||||
logger.error("Could not parse rotation angle", e);
|
||||
}
|
||||
try {
|
||||
time = Integer.parseInt((data.get(ROTATE).getAsString().split(",")[1]));
|
||||
} catch (NumberFormatException e) {
|
||||
logger.error("Could not parse rotation time", e);
|
||||
}
|
||||
updateState(CHANNEL_CUBE_ROTATION_ANGLE, new QuantityType<>(rot, ANGLE_UNIT));
|
||||
updateState(CHANNEL_CUBE_ROTATION_TIME, new QuantityType<>(time, TIME_UNIT));
|
||||
triggerChannel(CHANNEL_ACTION, rot < 0 ? "ROTATE_LEFT" : "ROTATE_RIGHT");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal.handler;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.openhab.core.thing.Thing;
|
||||
|
||||
/**
|
||||
* Handles the Xiaomi gas sensor device
|
||||
*
|
||||
* @author Dieter Schmidt - Initial contribution
|
||||
*/
|
||||
public class XiaomiSensorGasHandler extends XiaomiSensorBaseAlarmHandler {
|
||||
|
||||
private static final Map<Integer, String> ALARM_STATUS_MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
ALARM_STATUS_MAP.put(0, "OK");
|
||||
ALARM_STATUS_MAP.put(1, "Gas alarm");
|
||||
ALARM_STATUS_MAP.put(2, "Analog alarm");
|
||||
ALARM_STATUS_MAP.put(64, "Sensitivity fault alarm");
|
||||
ALARM_STATUS_MAP.put(32768, "I2C communication failure");
|
||||
}
|
||||
|
||||
public XiaomiSensorGasHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.*;
|
||||
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.thing.Thing;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Handles the Xiaomi temperature & humidity sensor
|
||||
*
|
||||
* @author Patrick Boos - Initial contribution
|
||||
* @author Daniel Walters - Add pressure support
|
||||
*/
|
||||
public class XiaomiSensorHtHandler extends XiaomiSensorBaseHandler {
|
||||
|
||||
private static final String HUMIDITY = "humidity";
|
||||
private static final String TEMPERATURE = "temperature";
|
||||
private static final String VOLTAGE = "voltage";
|
||||
private static final String PRESSURE = "pressure";
|
||||
|
||||
public XiaomiSensorHtHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseReport(JsonObject data) {
|
||||
parseDefault(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseHeartbeat(JsonObject data) {
|
||||
parseDefault(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseReadAck(JsonObject data) {
|
||||
parseDefault(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseDefault(JsonObject data) {
|
||||
if (data.has(HUMIDITY)) {
|
||||
float humidity = data.get(HUMIDITY).getAsFloat() / 100;
|
||||
updateState(CHANNEL_HUMIDITY, new QuantityType<>(humidity, PERCENT_UNIT));
|
||||
}
|
||||
if (data.has(TEMPERATURE)) {
|
||||
float temperature = data.get(TEMPERATURE).getAsFloat() / 100;
|
||||
updateState(CHANNEL_TEMPERATURE, new QuantityType<>(temperature, TEMPERATURE_UNIT));
|
||||
}
|
||||
if (data.has(VOLTAGE)) {
|
||||
Integer voltage = data.get(VOLTAGE).getAsInt();
|
||||
calculateBatteryLevelFromVoltage(voltage);
|
||||
}
|
||||
if (data.has(PRESSURE)) {
|
||||
float pressure = (float) (data.get(PRESSURE).getAsFloat() / 1000.0);
|
||||
updateState(CHANNEL_PRESSURE, new QuantityType<>(pressure, PRESSURE_UNIT));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.*;
|
||||
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Thing;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Handles the Xiaomi Aqara Fingerprint Lock
|
||||
*
|
||||
* @author Dieter Schmidt - Initial contribution
|
||||
*/
|
||||
public class XiaomiSensorLockHandler extends XiaomiSensorBaseHandler {
|
||||
|
||||
private static final String FING_VERIFIED = "fing_verified";
|
||||
private static final String PSW_VERIFIED = "psw_verified";
|
||||
private static final String CARD_VERIFIED = "card_verified";
|
||||
private static final String VERIFIED_WRONG = "verified_wrong";
|
||||
private static final String FINGER = "Finger";
|
||||
private static final String PASSWORD = "Password";
|
||||
private static final String CARD = "Card";
|
||||
private static final String WRONG_ACCESS = "Wrong Access";
|
||||
private static final String ALARM = "ALARM";
|
||||
private static final String STATUS = "status";
|
||||
|
||||
public XiaomiSensorLockHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseDefault(JsonObject data) {
|
||||
if (data.has(FING_VERIFIED)) {
|
||||
onOpen();
|
||||
updateState(CHANNEL_STATUS, new StringType(FINGER));
|
||||
updateState(CHANNEL_ID, new DecimalType(data.get(FING_VERIFIED).getAsInt()));
|
||||
} else if (data.has(PSW_VERIFIED)) {
|
||||
onOpen();
|
||||
updateState(CHANNEL_STATUS, new StringType(PASSWORD));
|
||||
updateState(CHANNEL_ID, new DecimalType(data.get(PSW_VERIFIED).getAsInt()));
|
||||
} else if (data.has(CARD_VERIFIED)) {
|
||||
onOpen();
|
||||
updateState(CHANNEL_STATUS, new StringType(CARD));
|
||||
updateState(CHANNEL_ID, new DecimalType(data.get(CARD_VERIFIED).getAsInt()));
|
||||
} else if (data.has(VERIFIED_WRONG)) {
|
||||
updateState(CHANNEL_STATUS, new StringType(WRONG_ACCESS));
|
||||
updateState(CHANNEL_ID, new DecimalType(data.get(VERIFIED_WRONG).getAsInt()));
|
||||
triggerChannel(CHANNEL_WRONG_ACCESS, ALARM);
|
||||
} else if (data.has(STATUS)) {
|
||||
updateState(CHANNEL_STATUS, new StringType(data.get(STATUS).getAsString().toUpperCase()));
|
||||
}
|
||||
super.parseDefault(data);
|
||||
}
|
||||
|
||||
private void onOpen() {
|
||||
triggerChannel(CHANNEL_IS_OPEN, ALARM);
|
||||
updateState(CHANNEL_LAST_OPENED, new DateTimeType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.*;
|
||||
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Handles the Xiaomi smart door/window sensor
|
||||
*
|
||||
* @author Patrick Boos - Initial contribution
|
||||
* @author Dieter Schmidt - Refactor
|
||||
* @author Daniel Walters - Add voltage parsing
|
||||
*/
|
||||
public class XiaomiSensorMagnetHandler extends XiaomiSensorBaseHandlerWithTimer {
|
||||
|
||||
private static final int DEFAULT_TIMER = 300;
|
||||
private static final int MIN_TIMER = 30;
|
||||
private static final String OPEN = "open";
|
||||
private static final String CLOSED = "close";
|
||||
private static final String STATUS = "status";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(XiaomiSensorMagnetHandler.class);
|
||||
|
||||
public XiaomiSensorMagnetHandler(Thing thing) {
|
||||
super(thing, DEFAULT_TIMER, MIN_TIMER, CHANNEL_OPEN_ALARM_TIMER);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseReport(JsonObject data) {
|
||||
if (data.has(STATUS)) {
|
||||
String sensorStatus = data.get(STATUS).getAsString();
|
||||
synchronized (this) {
|
||||
if (OPEN.equals(sensorStatus)) {
|
||||
updateState(CHANNEL_LAST_OPENED, new DateTimeType());
|
||||
startTimer();
|
||||
} else {
|
||||
cancelRunningTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
parseDefault(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseDefault(JsonObject data) {
|
||||
if (data.has(STATUS)) {
|
||||
String sensorStatus = data.get(STATUS).getAsString();
|
||||
if (OPEN.equals(sensorStatus)) {
|
||||
updateState(CHANNEL_IS_OPEN, OpenClosedType.OPEN);
|
||||
} else if (CLOSED.equals(sensorStatus)) {
|
||||
updateState(CHANNEL_IS_OPEN, OpenClosedType.CLOSED);
|
||||
} else {
|
||||
updateState(CHANNEL_IS_OPEN, UnDefType.UNDEF);
|
||||
}
|
||||
}
|
||||
super.parseDefault(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
void execute(ChannelUID channelUID, Command command) {
|
||||
if (CHANNEL_OPEN_ALARM_TIMER.equals(channelUID.getId())) {
|
||||
if (command != null && command instanceof DecimalType) {
|
||||
setTimerFromDecimalType((DecimalType) command);
|
||||
return;
|
||||
}
|
||||
// Only gets here, if no condition was met
|
||||
logger.error("Can't handle command {} on channel {}", command, channelUID);
|
||||
} else {
|
||||
logger.error("Channel {} does not exist", channelUID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void onTimer() {
|
||||
triggerChannel(CHANNEL_OPEN_ALARM, "ALARM");
|
||||
}
|
||||
}
|
||||
@@ -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.binding.mihome.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.*;
|
||||
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Handles the Xiaomi human body motion sensor
|
||||
*
|
||||
* @author Patrick Boos - Initial contribution
|
||||
* @author Dieter Schmidt - Refactor
|
||||
*/
|
||||
public class XiaomiSensorMotionHandler extends XiaomiSensorBaseHandlerWithTimer {
|
||||
|
||||
private static final int DEFAULT_TIMER = 120;
|
||||
private static final int MIN_TIMER = 5;
|
||||
private static final String STATUS = "status";
|
||||
private static final String MOTION = "motion";
|
||||
private static final String LUX = "lux";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(XiaomiSensorMotionHandler.class);
|
||||
|
||||
public XiaomiSensorMotionHandler(Thing thing) {
|
||||
super(thing, DEFAULT_TIMER, MIN_TIMER, CHANNEL_MOTION_OFF_TIMER);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseReport(JsonObject data) {
|
||||
boolean hasMotion = data.has(STATUS) && MOTION.equals(data.get(STATUS).getAsString());
|
||||
|
||||
if (data.has(LUX)) {
|
||||
int illu = data.get(LUX).getAsInt();
|
||||
updateState(CHANNEL_ILLUMINATION, new DecimalType(illu));
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
if (hasMotion) {
|
||||
updateState(CHANNEL_MOTION, OnOffType.ON);
|
||||
updateState(CHANNEL_LAST_MOTION, new DateTimeType());
|
||||
startTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void execute(ChannelUID channelUID, Command command) {
|
||||
if (CHANNEL_MOTION_OFF_TIMER.equals(channelUID.getId())) {
|
||||
if (command != null && command instanceof DecimalType) {
|
||||
setTimerFromDecimalType((DecimalType) command);
|
||||
return;
|
||||
}
|
||||
// Only gets here, if no condition was met
|
||||
logger.error("Can't handle command {} on channel {}", command, channelUID);
|
||||
} else {
|
||||
logger.error("Channel {} does not exist", channelUID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void onTimer() {
|
||||
updateState(CHANNEL_MOTION, OnOffType.OFF);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.CHANNEL_DENSITY;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.thing.Thing;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Handles the Xiaomi smoke sensor
|
||||
*
|
||||
* @author Dieter Schmidt - Initial contribution
|
||||
*/
|
||||
public class XiaomiSensorSmokeHandler extends XiaomiSensorBaseAlarmHandler {
|
||||
|
||||
private static final Map<Integer, String> ALARM_STATUS_MAP = new HashMap<>();
|
||||
|
||||
private static final String DENSITY = "density";
|
||||
|
||||
static {
|
||||
ALARM_STATUS_MAP.put(0, "OK");
|
||||
ALARM_STATUS_MAP.put(1, "Fire alarm");
|
||||
ALARM_STATUS_MAP.put(2, "Analog alarm");
|
||||
ALARM_STATUS_MAP.put(8, "Battery fault alarm");
|
||||
ALARM_STATUS_MAP.put(64, "Sensitivity fault alarm");
|
||||
ALARM_STATUS_MAP.put(32768, "I2C communication failure");
|
||||
}
|
||||
|
||||
public XiaomiSensorSmokeHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseReport(JsonObject data) {
|
||||
super.parseReport(data);
|
||||
if (data.has(DENSITY)) {
|
||||
Integer density = data.get(DENSITY).getAsInt();
|
||||
updateState(CHANNEL_DENSITY, new DecimalType(density));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.CHANNEL_BUTTON;
|
||||
|
||||
import org.openhab.binding.mihome.internal.ChannelMapper;
|
||||
import org.openhab.core.thing.Thing;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Handles the Xiaomi smart switch device
|
||||
*
|
||||
* @author Patrick Boos - Initial contribution
|
||||
*/
|
||||
public class XiaomiSensorSwitchHandler extends XiaomiSensorBaseHandler {
|
||||
|
||||
private static final String STATUS = "status";
|
||||
|
||||
public XiaomiSensorSwitchHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseReport(JsonObject data) {
|
||||
if (data.has(STATUS)) {
|
||||
triggerChannel(CHANNEL_BUTTON, ChannelMapper.getChannelEvent(data.get(STATUS).getAsString().toUpperCase()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.*;
|
||||
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Handles the Xiaomi Aqara Smart Motion Vibration Sensor
|
||||
*
|
||||
* @author Dieter Schmidt - Initial contribution
|
||||
*/
|
||||
public class XiaomiSensorVibrationHandler extends XiaomiSensorBaseHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(XiaomiSensorVibrationHandler.class);
|
||||
private static final String STATUS = "status";
|
||||
private static final String TILT_ANGLE = "final_tilt_angle";
|
||||
private static final String ORIENTATIONS = "coordination";
|
||||
private static final String BED_ACTIVITY = "bed_activity";
|
||||
|
||||
public XiaomiSensorVibrationHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
void parseReport(JsonObject data) {
|
||||
if (data.has(STATUS)) {
|
||||
triggerChannel(CHANNEL_ACTION, data.get(STATUS).getAsString().toUpperCase());
|
||||
updateState(CHANNEL_LAST_ACTION, new DateTimeType());
|
||||
} else if (data.has(TILT_ANGLE)) {
|
||||
updateState(CHANNEL_TILT_ANGLE, new DecimalType(Integer.parseInt((data.get(TILT_ANGLE).getAsString()))));
|
||||
} else if (data.has(ORIENTATIONS)) {
|
||||
Integer x = 0;
|
||||
Integer y = 0;
|
||||
Integer z = 0;
|
||||
try {
|
||||
x = Integer.parseInt((data.get(ORIENTATIONS).getAsString().split(",")[0]));
|
||||
y = Integer.parseInt((data.get(ORIENTATIONS).getAsString().split(",")[1]));
|
||||
z = Integer.parseInt((data.get(ORIENTATIONS).getAsString().split(",")[2]));
|
||||
} catch (NumberFormatException e) {
|
||||
logger.error("Could not parse coordinates", e);
|
||||
}
|
||||
updateState(CHANNEL_ORIENTATION_X, new DecimalType(x));
|
||||
updateState(CHANNEL_ORIENTATION_Y, new DecimalType(y));
|
||||
updateState(CHANNEL_ORIENTATION_Z, new DecimalType(z));
|
||||
} else if (data.has(BED_ACTIVITY)) {
|
||||
updateState(CHANNEL_BED_ACTIVITY,
|
||||
new DecimalType(Integer.parseInt((data.get(BED_ACTIVITY).getAsString()))));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mihome.internal.XiaomiGatewayBindingConstants.CHANNEL_LEAK;
|
||||
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Thing;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Handles the Xiaomi water leak sensor
|
||||
*
|
||||
* @author Kuba Wolanin - Initial contribution
|
||||
*/
|
||||
public class XiaomiSensorWaterHandler extends XiaomiSensorBaseHandler {
|
||||
|
||||
private static final String STATUS = "status";
|
||||
private static final String LEAK = "leak";
|
||||
|
||||
@Override
|
||||
void parseReport(JsonObject data) {
|
||||
boolean leak = data.has(STATUS) && LEAK.equals(data.get(STATUS).getAsString());
|
||||
|
||||
if (leak) {
|
||||
updateState(CHANNEL_LEAK, OnOffType.ON);
|
||||
} else {
|
||||
updateState(CHANNEL_LEAK, OnOffType.OFF);
|
||||
}
|
||||
}
|
||||
|
||||
public XiaomiSensorWaterHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
}
|
||||
@@ -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.binding.mihome.internal.socket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.MulticastSocket;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Takes care of the multicast communication with the bridge.
|
||||
*
|
||||
* @author Dieter Schmidt - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class XiaomiBridgeSocket extends XiaomiSocket {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(XiaomiBridgeSocket.class);
|
||||
|
||||
public XiaomiBridgeSocket(int port, String owner) {
|
||||
super(port, owner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the {@link XiaomiBridgeSocket}.
|
||||
*
|
||||
* Connects the socket to the specific multicast address and port.
|
||||
*/
|
||||
@Override
|
||||
protected synchronized void setupSocket() {
|
||||
MulticastSocket socket = (MulticastSocket) getSocket();
|
||||
if (socket != null) {
|
||||
logger.debug("Socket already setup");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
logger.debug("Setup socket");
|
||||
socket = new MulticastSocket(getPort());
|
||||
setSocket(socket); // must bind receive side
|
||||
socket.joinGroup(InetAddress.getByName(MCAST_ADDR));
|
||||
logger.debug("Initialized socket to {}:{} on {}:{}", socket.getRemoteSocketAddress(), socket.getPort(),
|
||||
socket.getLocalAddress(), socket.getLocalPort());
|
||||
} catch (IOException e) {
|
||||
logger.error("Setup socket error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal.socket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Takes care of the discovery communication with the MiHome gateway
|
||||
*
|
||||
* @author Dieter Schmidt - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class XiaomiDiscoverySocket extends XiaomiSocket {
|
||||
|
||||
private static final int MCAST_PORT = 4321;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(XiaomiDiscoverySocket.class);
|
||||
|
||||
public XiaomiDiscoverySocket(String owner) {
|
||||
super(owner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the {@link XiaomiDiscoverySocket}.
|
||||
*
|
||||
* Connects the socket to the specific multicast address and port.
|
||||
*/
|
||||
@Override
|
||||
protected void setupSocket() {
|
||||
synchronized (XiaomiDiscoverySocket.class) {
|
||||
try {
|
||||
logger.debug("Setup discovery socket");
|
||||
DatagramSocket socket = new DatagramSocket(0);
|
||||
setSocket(socket);
|
||||
logger.debug("Initialized socket to {}:{} on {}:{}", socket.getInetAddress(), socket.getPort(),
|
||||
socket.getLocalAddress(), socket.getLocalPort());
|
||||
} catch (IOException e) {
|
||||
logger.error("Setup socket error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message through the {@link XiaomiDiscoverySocket}
|
||||
* to the MiHome multicast address 224.0.0.50 and port 4321
|
||||
*
|
||||
* @param message - Message to be sent
|
||||
*/
|
||||
public void sendMessage(String message) {
|
||||
try {
|
||||
sendMessage(message, InetAddress.getByName(MCAST_ADDR), MCAST_PORT);
|
||||
} catch (UnknownHostException e) {
|
||||
logger.error("Sending error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
/**
|
||||
* 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.binding.mihome.internal.socket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mihome.internal.discovery.XiaomiBridgeDiscoveryService;
|
||||
import org.openhab.binding.mihome.internal.handler.XiaomiBridgeHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
/**
|
||||
* Takes care of the communication with MiHome devices.
|
||||
*
|
||||
*
|
||||
* @author Patrick Boos - Initial contribution
|
||||
* @author Dieter Schmidt - JavaDoc, refactored, reviewed
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class XiaomiSocket {
|
||||
|
||||
static final String MCAST_ADDR = "224.0.0.50";
|
||||
|
||||
private static final int BUFFER_LENGTH = 1024;
|
||||
private static final JsonParser PARSER = new JsonParser();
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(XiaomiSocket.class);
|
||||
|
||||
private final DatagramPacket datagramPacket = new DatagramPacket(new byte[BUFFER_LENGTH], BUFFER_LENGTH);
|
||||
private final Set<XiaomiSocketListener> listeners = new CopyOnWriteArraySet<>();
|
||||
|
||||
private final int port;
|
||||
private @Nullable DatagramSocket socket;
|
||||
private final Thread socketReceiveThread = new Thread(this::receiveData);
|
||||
|
||||
/**
|
||||
* Sets up an {@link XiaomiSocket} with the MiHome multicast address and a random port
|
||||
*
|
||||
* @param owner identifies the socket owner
|
||||
*/
|
||||
public XiaomiSocket(String owner) {
|
||||
this(0, owner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up an {@link XiaomiSocket} with the MiHome multicast address and a specific port
|
||||
*
|
||||
* @param port the socket will be bound to this port
|
||||
* @param owner identifies the socket owner
|
||||
*/
|
||||
public XiaomiSocket(int port, String owner) {
|
||||
this.port = port;
|
||||
socketReceiveThread.setName("XiaomiSocketReceiveThread(" + port + ", " + owner + ")");
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
setupSocket();
|
||||
if (!socketReceiveThread.isAlive()) {
|
||||
logger.trace("Starting receiver thread {}", socketReceiveThread);
|
||||
socketReceiveThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void setupSocket();
|
||||
|
||||
/**
|
||||
* Interrupts the {@link ReceiverThread} and closes the {@link XiaomiSocket}.
|
||||
*/
|
||||
private void closeSocket() {
|
||||
synchronized (XiaomiSocket.class) {
|
||||
logger.debug("Interrupting receiver thread {}", socketReceiveThread);
|
||||
socketReceiveThread.interrupt();
|
||||
|
||||
DatagramSocket socket = this.socket;
|
||||
if (socket != null) {
|
||||
logger.debug("Closing socket {}", socket);
|
||||
socket.close();
|
||||
this.socket = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@link XiaomiSocketListener} to be called back, when data is received.
|
||||
* If no {@link XiaomiSocket} exists, when the method is called, it is being set up.
|
||||
*
|
||||
* @param listener {@link XiaomiSocketListener} to be called back
|
||||
*/
|
||||
public synchronized void registerListener(XiaomiSocketListener listener) {
|
||||
if (listeners.add(listener)) {
|
||||
logger.trace("Added socket listener {}", listener);
|
||||
}
|
||||
|
||||
DatagramSocket socket = this.socket;
|
||||
if (socket == null) {
|
||||
initialize();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a {@link XiaomiSocketListener}. If there are no listeners left,
|
||||
* the {@link XiaomiSocket} is being closed.
|
||||
*
|
||||
* @param listener {@link XiaomiSocketListener} to be unregistered
|
||||
*/
|
||||
public synchronized void unregisterListener(XiaomiSocketListener listener) {
|
||||
if (listeners.remove(listener)) {
|
||||
logger.trace("Removed socket listener {}", listener);
|
||||
}
|
||||
|
||||
if (listeners.isEmpty()) {
|
||||
closeSocket();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message through the {@link XiaomiSocket} to a specific address and port
|
||||
*
|
||||
* @param message the message to be sent
|
||||
* @param address the message destination address
|
||||
* @param port the message destination port
|
||||
*/
|
||||
public void sendMessage(String message, InetAddress address, int port) {
|
||||
DatagramSocket socket = this.socket;
|
||||
if (socket == null) {
|
||||
logger.error("Error while sending message (socket is null)");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] sendData = message.getBytes(StandardCharsets.UTF_8);
|
||||
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, address, port);
|
||||
logger.trace("Sending message: {} to {}:{}", message, address, port);
|
||||
socket.send(sendPacket);
|
||||
} catch (IOException e) {
|
||||
logger.error("Error while sending message", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the port number of this {@link XiaomiSocket}
|
||||
*/
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
protected @Nullable DatagramSocket getSocket() {
|
||||
return socket;
|
||||
}
|
||||
|
||||
protected void setSocket(DatagramSocket socket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is the main method of the receiver thread for the {@link XiaomiBridgeSocket}.
|
||||
* If the socket has data, it parses the data to a json object and calls all
|
||||
* {@link XiaomiSocketListener} and passes the data to them.
|
||||
*/
|
||||
private void receiveData() {
|
||||
DatagramSocket socket = this.socket;
|
||||
if (socket == null) {
|
||||
logger.error("Failed to receive data (socket is null)");
|
||||
return;
|
||||
}
|
||||
|
||||
Thread currentThread = Thread.currentThread();
|
||||
int localPort = socket.getLocalPort();
|
||||
|
||||
try {
|
||||
while (!currentThread.isInterrupted()) {
|
||||
logger.trace("Thread {} waiting for data on port {}", currentThread.getName(), localPort);
|
||||
socket.receive(datagramPacket);
|
||||
InetAddress address = datagramPacket.getAddress();
|
||||
logger.debug("Received Datagram from {}:{} on port {}", address.getHostAddress(),
|
||||
datagramPacket.getPort(), localPort);
|
||||
String sentence = new String(datagramPacket.getData(), 0, datagramPacket.getLength());
|
||||
JsonObject message = PARSER.parse(sentence).getAsJsonObject();
|
||||
notifyListeners(message, address);
|
||||
logger.trace("Data received and notified {} listeners", listeners.size());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (!currentThread.isInterrupted()) {
|
||||
logger.error("Error while receiving", e);
|
||||
} else {
|
||||
logger.trace("Receiver thread was interrupted");
|
||||
}
|
||||
}
|
||||
logger.debug("Receiver thread ended");
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies all {@link XiaomiSocketListener} on the parent {@link XiaomiSocket}. First checks for any matching
|
||||
* {@link XiaomiBridgeHandler}, before passing to any {@link XiaomiBridgeDiscoveryService}.
|
||||
*
|
||||
* @param message the data message as {@link JsonObject}
|
||||
* @param address the address from which the message was received
|
||||
*/
|
||||
private void notifyListeners(JsonObject message, InetAddress address) {
|
||||
for (XiaomiSocketListener listener : listeners) {
|
||||
if (listener instanceof XiaomiBridgeHandler) {
|
||||
if (((XiaomiBridgeHandler) listener).getHost().equals(address)) {
|
||||
listener.onDataReceived(message);
|
||||
}
|
||||
} else if (listener instanceof XiaomiBridgeDiscoveryService) {
|
||||
listener.onDataReceived(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.binding.mihome.internal.socket;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Interface for a listener on the {@link XiaomiSocket}.
|
||||
* When it is registered on the socket, it gets called back each time, the {@link XiaomiSocket} receives data.
|
||||
*
|
||||
* @author Patrick Boos - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface XiaomiSocketListener {
|
||||
/**
|
||||
* Callback method for the {@link XiaomiSocketListener}
|
||||
*
|
||||
* @param message - The received message in JSON format
|
||||
*/
|
||||
void onDataReceived(JsonObject message);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="mihome" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>Xiaomi Mi Smart Home Binding</name>
|
||||
<description>Connects to Xiaomi Smart Gateway v2 and allows to communicate with Xiaomi Mi Smart Home suite (also called
|
||||
Xiaomi Aqara). The Gateway connects with a range of Xiaomi Zigbee sensors such as Wireless Switches, Wall Plugs or
|
||||
Temperature Sensors.</description>
|
||||
<author>Patrick Boos</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,45 @@
|
||||
<?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="thing-type:mihome:device">
|
||||
<parameter name="itemId" type="text">
|
||||
<label>MiHome Device ID</label>
|
||||
<description>The identifier of this MiHome device</description>
|
||||
<required>true</required>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
<config-description uri="bridge-type:mihome:bridge">
|
||||
<parameter name="serialNumber" type="text" readOnly="true">
|
||||
<label>Serial Number</label>
|
||||
<description>Serial number of this Xiaomi bridge</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="ipAddress" type="text">
|
||||
<context>network-address</context>
|
||||
<label>Network Address</label>
|
||||
<description>Network address of this Xiaomi bridge</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
|
||||
<parameter name="port" type="integer">
|
||||
<context>network-address</context>
|
||||
<label>Port</label>
|
||||
<description>Port of the MiHome communication channel</description>
|
||||
<required>true</required>
|
||||
<default>9898</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="key" type="text">
|
||||
<label>Developer Key</label>
|
||||
<description>Developer key extracted from Xiaomi's app</description>
|
||||
<required>false</required>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="86sw1">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Xiaomi Aqara 1 Channel Smart Light Control</label>
|
||||
<description>Battery powered Aqara Switch with 1 Channel</description>
|
||||
<channels>
|
||||
<channel id="ch1" typeId="system.button">
|
||||
<label>Button</label>
|
||||
<description>The pushbutton on the switch</description>
|
||||
</channel>
|
||||
<channel id="batteryLevel" typeId="system.battery-level"/>
|
||||
<channel id="lowBattery" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:mihome:device"/>
|
||||
</thing-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="86sw2">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Xiaomi Aqara 2 Channel Smart Light Control</label>
|
||||
<description>Battery powered Aqara Switch with 2 channels</description>
|
||||
<channels>
|
||||
<channel id="ch1" typeId="system.button">
|
||||
<label>First Button</label>
|
||||
<description>The first pushbutton on the switch</description>
|
||||
</channel>
|
||||
<channel id="ch2" typeId="system.button">
|
||||
<label>Second Button</label>
|
||||
<description>The second pushbutton on the switch</description>
|
||||
</channel>
|
||||
<channel id="dual_ch" typeId="system.button">
|
||||
<label>Both Buttons Pressed</label>
|
||||
<description>Fires when both buttons are pressed simultaneously</description>
|
||||
</channel>
|
||||
<channel id="batteryLevel" typeId="system.battery-level"/>
|
||||
<channel id="lowBattery" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:mihome:device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="ctrl_ln1">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Xiaomi "zero-fire" 1 Channel Wall Switch</label>
|
||||
<channels>
|
||||
<channel id="ch1" typeId="sw-btn">
|
||||
<description>The pushbutton on the switch.</description>
|
||||
</channel>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:mihome:device"/>
|
||||
</thing-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="ctrl_ln2">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Xiaomi "zero-fire" 2 Channel Wall Switch</label>
|
||||
<channels>
|
||||
<channel id="ch1" typeId="sw-btn">
|
||||
<label>Button 1</label>
|
||||
<description>The first pushbutton on the switch.</description>
|
||||
</channel>
|
||||
<channel id="ch2" typeId="sw-btn">
|
||||
<label>Button 2</label>
|
||||
<description>The second pushbutton on the switch.</description>
|
||||
</channel>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:mihome:device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="ctrl_neutral1">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Xiaomi Aqara 1 Channel Wall Switch</label>
|
||||
<description>Mains powered Aqara Switch with 1 controllable Channel</description>
|
||||
<channels>
|
||||
<channel id="ch1" typeId="sw-btn">
|
||||
<description>The pushbutton on the switch.</description>
|
||||
</channel>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:mihome:device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="ctrl_neutral2">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Xiaomi Aqara 2 Channel Wall Switch</label>
|
||||
<description>Mains powered Aqara Switch with 2 controllable Channels</description>
|
||||
<channels>
|
||||
<channel id="ch1" typeId="sw-btn">
|
||||
<label>Button 1</label>
|
||||
<description>The first pushbutton on the switch.</description>
|
||||
</channel>
|
||||
<channel id="ch2" typeId="sw-btn">
|
||||
<label>Button 2</label>
|
||||
<description>The second pushbutton on the switch.</description>
|
||||
</channel>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:mihome:device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="basic">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Basic Xiaomi MiHome Device</label>
|
||||
<description>This device is not supported by the binding but you can still add it to your openHAB configuration. All
|
||||
messages from the device are available as channels providing values to String Items. Please open an issue on the
|
||||
openhab-addons2 GitHub page to get this device added to the binding in the future. Meanwhile you can parse the
|
||||
messages with rules and/or JSONPATH transformations.</description>
|
||||
<channels>
|
||||
<channel id="reportMessage" typeId="rawMessage">
|
||||
<label>Last Report Message</label>
|
||||
<description>Report state or sensor values</description>
|
||||
</channel>
|
||||
<channel id="heartbeatMessage" typeId="rawMessage">
|
||||
<label>Last Heartbeat Message</label>
|
||||
<description>Alive signal (up to every 60 minutes)</description>
|
||||
</channel>
|
||||
<channel id="readAckMessage" typeId="rawMessage">
|
||||
<label>Last Read Acknowledgement Message</label>
|
||||
<description>Answer to an active read request</description>
|
||||
</channel>
|
||||
<channel id="writeAckMessage" typeId="rawMessage">
|
||||
<label>Last Write Acknowledgement Message</label>
|
||||
<description>Answer to a command</description>
|
||||
</channel>
|
||||
<channel id="lastMessage" typeId="rawMessage">
|
||||
<label>Last Raw Message</label>
|
||||
<description>Last raw message from the device</description>
|
||||
</channel>
|
||||
<channel id="writeMessage" typeId="rawMessage">
|
||||
<label>Last Command Parameters</label>
|
||||
<description>Channel to write command parameters - Example: \"join_permission\":\"yes\"</description>
|
||||
</channel>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:mihome:device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<!-- Xiaomi Mi Smart Home Bridge -->
|
||||
<bridge-type id="bridge">
|
||||
<label>Xiaomi Mi Smart Home Bridge</label>
|
||||
<description>Multifunctional Gateway - a bridge for the sensors</description>
|
||||
|
||||
<properties>
|
||||
<property name="vendor">Xiaomi</property>
|
||||
</properties>
|
||||
<config-description-ref uri="bridge-type:mihome:bridge"/>
|
||||
</bridge-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,257 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<channel-type id="sw-btn">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Button Switch</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="curtainControl">
|
||||
<item-type>Rollershutter</item-type>
|
||||
<label>Curtain</label>
|
||||
<category>Blinds</category>
|
||||
<state max="100" min="0" step="1" pattern="%d %%" readOnly="false"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rawMessage">
|
||||
<item-type>String</item-type>
|
||||
<label>Raw Message</label>
|
||||
<description>Channel for raw messages. No parsing is done on these messages.</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="brightness">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Brightness</label>
|
||||
<description>The brightness channel allows to control the brightness of a light. It is also possible to switch the
|
||||
light on and off.</description>
|
||||
<category>DimmableLight</category>
|
||||
<tags>
|
||||
<tag>Lighting</tag>
|
||||
</tags>
|
||||
<state max="100" min="0" step="1" pattern="%d %%" readOnly="false"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="illumination">
|
||||
<item-type>Number</item-type>
|
||||
<label>Illumination</label>
|
||||
<description>
|
||||
This channel shows the brightness of the environment of the device.
|
||||
</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="color">
|
||||
<item-type>Color</item-type>
|
||||
<label>Color</label>
|
||||
<description>Control the color of light.</description>
|
||||
<category>ColorLight</category>
|
||||
<tags>
|
||||
<tag>Lighting</tag>
|
||||
</tags>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="colorTemperature">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Color Temperature</label>
|
||||
<description>Allows to control the color temperature of light.</description>
|
||||
<tags>
|
||||
<tag>ColorTemperature</tag>
|
||||
</tags>
|
||||
<state pattern="%d K"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="sound">
|
||||
<item-type>Number</item-type>
|
||||
<label>Sound Selector</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="volume">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Sound Volume</label>
|
||||
<description>This channel controls the volume of the gateway for playing sounds</description>
|
||||
<category>SoundVolume</category>
|
||||
<state max="100" min="0" step="1" pattern="%d %%" readOnly="false"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="enableSound">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Switch to Turn Sound Off When Playing</label>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rotationAngle">
|
||||
<item-type>Number:Angle</item-type>
|
||||
<label>Cube Rotation Angle</label>
|
||||
<state readOnly="true" pattern="%d %unit%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="rotationTime">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Cube Rotation Time</label>
|
||||
<state readOnly="false" pattern="%d %unit%"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="gas_alarm">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Gas Detected</label>
|
||||
<category>Gas</category>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="leak">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Leak Detected</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="status">
|
||||
<item-type>String</item-type>
|
||||
<label>Status</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="temperature">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Temperature</label>
|
||||
<category>Temperature</category>
|
||||
<state pattern="%.1f %unit%" readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="humidity">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Humidity</label>
|
||||
<category>Humidity</category>
|
||||
<state pattern="%.1f %unit%" readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="pressure">
|
||||
<item-type>Number:Pressure</item-type>
|
||||
<label>Pressure</label>
|
||||
<category>Pressure</category>
|
||||
<state pattern="%.1f %unit%" readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="isOpen">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Open Status</label>
|
||||
<category>Contact</category>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="alarm">
|
||||
<kind>trigger</kind>
|
||||
<label>Alarm Trigger</label>
|
||||
<description>Triggers ALARM event</description>
|
||||
<event>
|
||||
<options>
|
||||
<option value="ALARM">alarm</option>
|
||||
</options>
|
||||
</event>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="isOpenAlarmTimer">
|
||||
<item-type>Number</item-type>
|
||||
<label>Alarm Timer</label>
|
||||
<description>Time in seconds, after which ALARM event is triggered, when open (Default 300 sec, Min 30 sec)</description>
|
||||
<state readOnly="false" min="30" pattern="%d s" step="1"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="lastOpened">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Time Opened (Date/Time)</label>
|
||||
<description>Date/time when last opened</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="motion">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Motion Status</label>
|
||||
<category>Motion</category>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="motionOffTimer">
|
||||
<item-type>Number</item-type>
|
||||
<label>Motion Off Timer</label>
|
||||
<description>Time in seconds, after which the Motion Switch is set to "OFF" (Default 120 sec, Min 5 sec)</description>
|
||||
<category>Motion</category>
|
||||
<state readOnly="false" min="5" pattern="%d s" step="1"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="lastMotion">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Activity (Date/Time)</label>
|
||||
<description>Date/time when last motion was detected</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="lastAction">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Activity (Date/Time)</label>
|
||||
<description>Date/time when last action was detected</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="power">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Power</label>
|
||||
<category>PowerOutlet</category>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="inUse">
|
||||
<item-type>Switch</item-type>
|
||||
<label>In Use</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="loadPower">
|
||||
<item-type>Number</item-type>
|
||||
<label>Provided Power</label>
|
||||
<state pattern="%.1f W" readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="powerConsumed">
|
||||
<item-type>Number</item-type>
|
||||
<label>Power Consumed</label>
|
||||
<state pattern="%.3f kWh" readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="density">
|
||||
<item-type>Number</item-type>
|
||||
<label>Particle Density</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="smoke_alarm">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Smoke Detected</label>
|
||||
<category>Smoke</category>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="button">
|
||||
<kind>trigger</kind>
|
||||
<label>Button Event</label>
|
||||
<description>Fires when the button is pressed, double pressed or pressed for a long time (press/release)</description>
|
||||
<event>
|
||||
<options>
|
||||
<option value="SHORT_PRESSED">click</option>
|
||||
<option value="DOUBLE_PRESSED">double click</option>
|
||||
<option value="LONG_PRESSED">long click press</option>
|
||||
<option value="LONG_RELEASED">long click release</option>
|
||||
</options>
|
||||
</event>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="idNumber">
|
||||
<item-type>Number</item-type>
|
||||
<label>ID</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="curtain">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Xiaomi Aqara Intelligent Curtain Motor</label>
|
||||
<description>Smart curtain controller for drawing back and forth fabric curtains. It has manual and remote operation
|
||||
modes.</description>
|
||||
<channels>
|
||||
<channel id="curtainControl" typeId="curtainControl"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:mihome:device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="gateway">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Xiaomi Mi Smart Home Gateway</label>
|
||||
<description>Multifunctional Gateway with a built in light, ambient light sensor and capability to play sounds.</description>
|
||||
<channels>
|
||||
<channel id="brightness" typeId="brightness"/>
|
||||
<channel id="illumination" typeId="illumination"/>
|
||||
<channel id="color" typeId="color"/>
|
||||
<channel id="colorTemperature" typeId="colorTemperature"/>
|
||||
<channel typeId="enableSound" id="enableSound"/>
|
||||
<channel typeId="sound" id="sound"/>
|
||||
<channel typeId="volume" id="volume"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:mihome:device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="sensor_cube">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Xiaomi Mi Smart Cube</label>
|
||||
<description>Multifunctional controller equipped with an accelerometer and a gyroscope. Triggers the following
|
||||
actions: move, rotate right, rotate left, flip 90, flip 180, tap twice, shake air, free fall, alert.</description>
|
||||
<channels>
|
||||
<channel id="action" typeId="cubeAction"></channel>
|
||||
<channel id="lastAction" typeId="lastAction"></channel>
|
||||
<channel id="rotationAngle" typeId="rotationAngle"></channel>
|
||||
<channel id="rotationTime" typeId="rotationTime"></channel>
|
||||
<channel id="batteryLevel" typeId="system.battery-level"/>
|
||||
<channel id="lowBattery" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:mihome:device"/>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="cubeAction">
|
||||
<kind>trigger</kind>
|
||||
<label>Cube Event</label>
|
||||
<event>
|
||||
<options>
|
||||
<option value="MOVE">move</option>
|
||||
<option value="ROTATE_RIGHT">rotate</option>
|
||||
<option value="ROTATE_LEFT">rotate</option>
|
||||
<option value="FLIP90">flip 90</option>
|
||||
<option value="FLIP180">flip 180</option>
|
||||
<option value="TAP_TWICE">tap twice</option>
|
||||
<option value="SHAKE_AIR">shake air</option>
|
||||
<option value="FREE_FALL">free fall</option>
|
||||
<option value="ALERT">alert</option>
|
||||
</options>
|
||||
</event>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="natgas">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Xiaomi Gas Sensor</label>
|
||||
<channels>
|
||||
<channel id="alarm" typeId="gas_alarm"></channel>
|
||||
<channel id="status" typeId="status"></channel>
|
||||
<channel id="batteryLevel" typeId="system.battery-level"/>
|
||||
<channel id="lowBattery" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:mihome:device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="sensor_ht">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Xiaomi Mi Temperature & Humidity Sensor</label>
|
||||
<description>Reports temperature and humidity. Operating temperature: −20°C to 60°C. Operating humidity: 0 to 100%.
|
||||
Sensor reports the temperature when there's a difference of around 0.5°C. If there is no significant temperature
|
||||
change then the sensor reports temperature once every 50 minutes.
|
||||
</description>
|
||||
<channels>
|
||||
<channel id="temperature" typeId="temperature"/>
|
||||
<channel id="humidity" typeId="humidity"/>
|
||||
<channel id="batteryLevel" typeId="system.battery-level"/>
|
||||
<channel id="lowBattery" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:mihome:device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="sensor_lock_aq1">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Aqara Fingerprint Door Lock</label>
|
||||
<description>Live fingerprint unlock, password unlock, proximity card unlock, key unlock.</description>
|
||||
<channels>
|
||||
<channel id="status" typeId="status"></channel>
|
||||
<channel id="id" typeId="idNumber"></channel>
|
||||
<channel id="isOpen" typeId="alarm">
|
||||
<label>Open Alarm</label>
|
||||
</channel>
|
||||
<channel id="wrongAccess" typeId="alarm">
|
||||
<label>Wrong Access Alarm</label>
|
||||
</channel>
|
||||
<channel id="lastOpened" typeId="lastOpened"></channel>
|
||||
<channel id="batteryLevel" typeId="system.battery-level"/>
|
||||
<channel id="lowBattery" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:mihome:device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="sensor_magnet">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Xiaomi Door/Window Sensor</label>
|
||||
<description>Contact sensor mounted on doors or windows. Detects states: open and closed.</description>
|
||||
<channels>
|
||||
<channel id="isOpen" typeId="isOpen"/>
|
||||
<channel id="lastOpened" typeId="lastOpened"/>
|
||||
<channel id="isOpenAlarmTimer" typeId="isOpenAlarmTimer"/>
|
||||
<channel id="isOpenAlarm" typeId="alarm"/>
|
||||
<channel id="batteryLevel" typeId="system.battery-level"/>
|
||||
<channel id="lowBattery" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:mihome:device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="sensor_magnet_aq2">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Xiaomi Aqara Door/Window Sensor</label>
|
||||
<description>Contact sensor mounted on doors or windows. Detects states: open and closed.</description>
|
||||
<channels>
|
||||
<channel id="isOpen" typeId="isOpen"/>
|
||||
<channel id="lastOpened" typeId="lastOpened"/>
|
||||
<channel id="isOpenAlarmTimer" typeId="isOpenAlarmTimer"/>
|
||||
<channel id="isOpenAlarm" typeId="alarm"/>
|
||||
<channel id="batteryLevel" typeId="system.battery-level"/>
|
||||
<channel id="lowBattery" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:mihome:device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="sensor_motion">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Xiaomi Mi Motion Sensor</label>
|
||||
<description>Sensor that detects movement. Also called Occupancy Sensor or Human Body Sensor. After it detects motion,
|
||||
it goes to sleep for 1 minute.</description>
|
||||
<channels>
|
||||
<channel id="motion" typeId="motion"/>
|
||||
<channel id="motionOffTimer" typeId="motionOffTimer"/>
|
||||
<channel id="lastMotion" typeId="lastMotion"/>
|
||||
<channel id="batteryLevel" typeId="system.battery-level"/>
|
||||
<channel id="lowBattery" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:mihome:device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="sensor_motion_aq2">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Xiaomi Aqara Motion Sensor</label>
|
||||
<description>Sensor that detects movement. Also called Occupancy Sensor or Human Body Sensor. After it detects motion,
|
||||
it goes to sleep for 1 minute.</description>
|
||||
<channels>
|
||||
<channel id="motion" typeId="motion"/>
|
||||
<channel id="illumination" typeId="illumination"/>
|
||||
<channel id="motionOffTimer" typeId="motionOffTimer"/>
|
||||
<channel id="lastMotion" typeId="lastMotion"/>
|
||||
<channel id="batteryLevel" typeId="system.battery-level"/>
|
||||
<channel id="lowBattery" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:mihome:device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="sensor_plug">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Xiaomi Mi Smart Socket Plug</label>
|
||||
<description>Allows you to enable or disable the socket, read voltage, current and power consumption.</description>
|
||||
<channels>
|
||||
<channel id="power" typeId="power"/>
|
||||
<channel id="inUse" typeId="inUse"/>
|
||||
<channel id="loadPower" typeId="loadPower"/>
|
||||
<channel id="powerConsumed" typeId="powerConsumed"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:mihome:device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="smoke">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Xiaomi Smoke Sensor</label>
|
||||
<channels>
|
||||
<channel id="density" typeId="density"></channel>
|
||||
<channel id="alarm" typeId="smoke_alarm"></channel>
|
||||
<channel id="status" typeId="status"></channel>
|
||||
<channel id="batteryLevel" typeId="system.battery-level"/>
|
||||
<channel id="lowBattery" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:mihome:device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="sensor_switch">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Xiaomi Mi Wireless Switch</label>
|
||||
<description>Round-shaped mini wireless switch that allows to trigger four types of event: single click, double click,
|
||||
long click press and long click release.</description>
|
||||
<channels>
|
||||
<channel id="button" typeId="button"/>
|
||||
<channel id="batteryLevel" typeId="system.battery-level"/>
|
||||
<channel id="lowBattery" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:mihome:device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="sensor_switch_aq2">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Xiaomi Aqara Wireless Switch</label>
|
||||
<description>Square-shaped mini wireless switch that allows to trigger four types of event: single click, double
|
||||
click, long click press and long click release.</description>
|
||||
<channels>
|
||||
<channel id="button" typeId="button"/>
|
||||
<channel id="batteryLevel" typeId="system.battery-level"/>
|
||||
<channel id="lowBattery" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:mihome:device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="sensor_vibration">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Aqara Vibration Sensor</label>
|
||||
<description>Incorporates high-precision six-axis acceleration and gyroscopes, used for collecting external vibration
|
||||
and motion data. Used to monitor the door and windows with status, important items alarmed, and can also monitor
|
||||
user's bed activity, to help determine the quality of sleep. Installation: Click the reset button on the sensor in
|
||||
the selected sensor installation location.</description>
|
||||
<channels>
|
||||
<channel id="action" typeId="vibrationAction"></channel>
|
||||
<channel id="lastAction" typeId="lastAction"></channel>
|
||||
<channel id="tiltAngle" typeId="tiltAngle"></channel>
|
||||
<channel id="orientationX" typeId="orientationX"></channel>
|
||||
<channel id="orientationY" typeId="orientationY"></channel>
|
||||
<channel id="orientationZ" typeId="orientationZ"></channel>
|
||||
<channel id="bedActivity" typeId="bedActivity"></channel>
|
||||
<channel id="batteryLevel" typeId="system.battery-level"/>
|
||||
<channel id="lowBattery" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:mihome:device"/>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="vibrationAction">
|
||||
<kind>trigger</kind>
|
||||
<label>Vibration Sensor Action</label>
|
||||
<event>
|
||||
<options>
|
||||
<option value="VIBRATION">vibration</option>
|
||||
<option value="TILT">tilt</option>
|
||||
<option value="FREE_FALL">free fall</option>
|
||||
</options>
|
||||
</event>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="tiltAngle">
|
||||
<item-type>Number</item-type>
|
||||
<label>Tilt Angle</label>
|
||||
<state step="1" pattern="%d deg" readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="orientationX">
|
||||
<item-type>Number</item-type>
|
||||
<label>X Orientation of the Device</label>
|
||||
<state step="1" pattern="%d" readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="orientationY">
|
||||
<item-type>Number</item-type>
|
||||
<label>Y Orientation of the Device</label>
|
||||
<state step="1" pattern="%d" readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="orientationZ">
|
||||
<item-type>Number</item-type>
|
||||
<label>Z Orientation of the Device</label>
|
||||
<state step="1" pattern="%d" readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="bedActivity">
|
||||
<item-type>Number</item-type>
|
||||
<label>Bed Activity Index</label>
|
||||
<state step="1" pattern="%d" readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="sensor_weather_v1">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Xiaomi Aqara Temperature, Humidity & Pressure Sensor</label>
|
||||
<description>Reports temperature and humidity. Operating temperature: −20°C to 60°C. Operating humidity: 0 to 100%.
|
||||
Sensor reports the temperature when there's a difference of around 0.5°C. If there is no significant temperature
|
||||
change then the sensor reports temperature once every 50 minutes.
|
||||
</description>
|
||||
<channels>
|
||||
<channel id="temperature" typeId="temperature"/>
|
||||
<channel id="humidity" typeId="humidity"/>
|
||||
<channel id="pressure" typeId="pressure"/>
|
||||
<channel id="batteryLevel" typeId="system.battery-level"/>
|
||||
<channel id="lowBattery" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:mihome:device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mihome"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="sensor_wleak_aq1">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Xiaomi Water Leak Sensor</label>
|
||||
<channels>
|
||||
<channel id="leak" typeId="leak"></channel>
|
||||
<channel id="batteryLevel" typeId="system.battery-level"/>
|
||||
<channel id="lowBattery" typeId="system.low-battery"/>
|
||||
</channels>
|
||||
<config-description-ref uri="thing-type:mihome:device"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user