added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View 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>

View 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>

View 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

View 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 | ![](https://ae01.alicdn.com/kf/HTB1gF76ciqAXuNjy1Xdq6yYcVXa4/Original-Xiaomi-Mi-Gateway-2-Smart-Home-Kit-Multi-functional-Gateway-Work-with-Mi-Door-Sensor.jpg_300x300.jpg) |
| Mijia Temperature and Humidity Sensor | ![](https://ae01.alicdn.com/kf/HTB1ksk_MXXXXXcWXVXXq6xXFXXXz/Original-Xiaomi-Mi-Smart-Temperature-and-Humidity-Sensor-Put-the-baby-Home-office-Work-With-Android.jpg_300x300.jpg)|
| Aqara Temperature, Humidity and Pressure Sensor | ![](https://ae01.alicdn.com/kf/HTB1fD1URVXXXXaDXFXXq6xXFXXXU/Neue-Original-Xiaomi-Aqara-Intelligente-Luftdruck-Temperatur-Luftfeuchtigkeit-Sensor-Arbeit-Mit-Android-IOS-APP-Fasten-schiff.jpg_300x300.jpg) |
| Mijia Door/Window Sensor | ![](https://ae01.alicdn.com/kf/HTB1WQb3SpXXXXcLXpXXq6xXFXXXz/100-Original-Intelligent-Mini-Mijia-Xiaomi-MI-Door-Window-Sensor-for-Xiaomi-Smart-Home-Suite-Devices.jpg_300x300.jpg) |
| Aqara Door/Window Sensor | ![](https://ae01.alicdn.com/kf/HTB1C2f7RVXXXXbNXpXXq6xXFXXX9/Auf-lager-Original-xiaomi-aqara-Smart-T-ren-und-Fenster-Sensor-Mijia-Smart-home-kit-Zigbee.jpg_300x300.jpg) |
| Mijia Human Body Sensor | ![](https://ae01.alicdn.com/kf/HTB1mvasRXXXXXaZXVXXq6xXFXXXY/XIAOMI-Mi-mijia-Infrared-Motion-Sensor-Smart-Human-Body-Sensor-for-Home-Safety-Smart-Remote-Control.jpg_300x300.jpg) |
| Aqara Motion Sensor (with light intensity support) | ![](https://ae01.alicdn.com/kf/HTB1LaENRFXXXXXNapXXq6xXFXXXZ/Xiaomi-Aqara-Body-Sensor-Light-Intensity-Sensors-ZigBee-wifi-Wireless-Connection-Work-for-xiaomi-smart-home.jpg_300x300.jpg) |
| Smart Socket (Zigbee version) | ![](https://ae01.alicdn.com/kf/HTB17Fy2QXXXXXajaXXXq6xXFXXXQ/Original-Xiaomi-Smart-Socket-Plug-Mi-Zigbee-WiFi-APP-Wireless-Control-Switches-EU-US-AU-Timer.jpg_300x300.jpg) |
| Magic Cube Controller | ![](https://ae01.alicdn.com/kf/HTB1IFoebPuhSKJjSspmq6AQDpXam/Xiaomi-Mi-Magic-Cube-Controller-Zigbee-Version-Controlled-by-Six-Actions-For-Smart-Home-Device-work.jpg_300x300.jpg) |
| Aqara Magic Cube Controller | ![](https://ae01.alicdn.com/kf/HTB1ih7YsL9TBuNjy1zbq6xpepXal/Original-Xiaomi-Aqara-Cube-Magic-Cube-Smart-Home-Controller-Zigbee-Version-6-Gestures-Operation-Mijia-Smart.jpg_300x300.jpg) |
| Aqara Vibration Sensor | ![](https://ae01.alicdn.com/kf/HTB1mjNTKXuWBuNjSszbq6AS7FXaS/Xiaomi-Aqara-Zigbee-Shock-Sensor-Mijia-Aqara-Smart-Motion-Sensor-Vibration-Detection-Alarm-Monitor-for-MiHome.jpg_300x300.jpg) |
| Mijia Wireless Switch | ![](https://ae01.alicdn.com/kf/HTB1qoEAPVXXXXXdaVXXq6xXFXXXr/Original-Xiaomi-Smart-Wireless-Switch-App-Remote-Control-Smart-Home-Intelligent-Device-Accessories-For-Xiaomi-Smart.jpg_300x300.jpg) |
| Aqara Wireless Switch | ![](https://ae01.alicdn.com/kf/HTB17DMORVXXXXbdXFXXq6xXFXXXe/Xiaomi-Mijia-AQara-Smart-Multifunktionale-Intelligente-Drahtlose-Schalter-Schl-ssel-Kreiselkompa-Errichtet-Funktion-Arbeit-Mit-Android.jpg_300x300.jpg) |
| Aqara Wireless Switch (with acceleration sensor) | ![](https://ae01.alicdn.com/kf/HTB1YGiNaNsIL1JjSZFqq6AeCpXaX/Original-xiaomi-Mijia-aqara-wireless-key-Upgraded-with-acceleration-sensor-magic-Mi-cube-sensor-work-with.jpg_300x300.jpg) |
| Aqara Wall Switch (1 & 2 Button / With or Without Neutral Line) | ![](https://ae01.alicdn.com/kf/HTB1VGfGXL9TBuNjy1zbq6xpepXam/Original-Xiaomi-Aqara-Smart-Light-Control-Fire-Wire-Zero-Line-Double-Single-Key-ZiGBee-Wall-Switch.jpg_300x300.jpg) |
| Aqara Wireless Light Control (1 & 2 Button) | ![](https://ae01.alicdn.com/kf/HTB19u.tPVXXXXbbXVXXq6xXFXXXH/Original-Xiaomi-Aqara-Smart-Switch-Light-Control-ZiGBee-Wireless-Key-Wall-Switch-By-Smarphone-Mi-Home.jpg_300x300.jpg) |
| Aqara Curtain Motor | ![](https://ae01.alicdn.com/kf/HTB1jaMXQVXXXXXBXVXXq6xXFXXXF/Original-xiaomi-Aqara-Curtain-motor-Zigbee-wifi-Remote-Control-work-for-Xiaomi-Smart-home-kit-Mi.jpg_300x300.jpg) |
| Aqara Water Leak Sensor | ![](https://ae01.alicdn.com/kf/HTB1zWulSVXXXXaVXXXXq6xXFXXXW/2018-Newest-Xiaomi-Mijia-Aqara-Water-Immersing-Sensor-Flood-Water-Leak-Detector-for-Home-Remote-Alarm.jpg_300x300.jpg) |
| Honeywell Gas Detector | ![](https://ae01.alicdn.com/kf/HTB1F_ffQpXXXXaxXpXXq6xXFXXXS/Xiaomi-Mijia-Honeywell-Smart-Gas-Alarm-CH4-berwachung-Decke-Wand-Montiert-Einfach-Installieren-Typ-Mihome-APP.jpg_300x300.jpg) |
| Honeywell Smoke Detector | ![](https://ae01.alicdn.com/kf/HTB12DGKQpXXXXaeaXXXq6xXFXXXK/Xiaomi-Mijia-Honeywell-Smart-Fire-Alarm-Detector-Progressive-Sound-Photoelectric-Smoke-Sensor-Remote-Linkage-Mihome-APP.jpg_300x300.jpg) |
| Aqara Fingerprint & Keyless Card & PIN Lock | ![](https://ae01.alicdn.com/kf/HTB1lsuqjjuhSKJjSspaq6xFgFXaD/Original-xiaomi-Mijia-aqara-Smart-door-lock-Digital-Touch-Screen-Keyless-Fingerprint-Password-work-to-mi.jpg_300x300.jpg) |
## 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.

View 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>

View File

@@ -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>

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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();
// AESCBC 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);
}
}

View File

@@ -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);
}
}

View File

@@ -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";
}

View File

@@ -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<>()));
}
}

View File

@@ -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();
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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");
}
}

View File

@@ -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));
}
}
}
}

View File

@@ -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 });
}
}

View File

@@ -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));
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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()));
}
}
}

View File

@@ -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()));
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}
}
}

View File

@@ -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");
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}
}

View File

@@ -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());
}
}

View File

@@ -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");
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}
}

View File

@@ -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()));
}
}
}

View File

@@ -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()))));
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 &amp; 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 &amp; 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>

View File

@@ -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>