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.wemo</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,273 @@
# Belkin Wemo Binding
This binding integrates the [Belkin WeMo Family](https://www.belkin.com/us/Products/c/home-automation/).
The integration happens either through the WeMo-Link bridge, which acts as an IP gateway to the ZigBee devices or through WiFi connection to standalone devices.
## Supported Things
The WeMo Binding supports the Socket, Insight, Lightswitch, Motion, Dimmer, Coffemaker and Maker devices, as well as the WeMo-Link bridge with WeMo LED bulbs.
The Binding also supports the Crock-Pot Smart Slow Cooker, Mr. Coffee Smart Coffemaker as well as the Holmes Smart Air Purifier, Holmes Smart Humidifier and Holmes Smart Heater.
## Discovery
The WeMo devices are discovered through UPnP discovery service in the network. Devices will show up in the inbox and can be easily added as Things.
## Binding Configuration
The binding does not need any configuration.
## Thing Configuration
For manual Thing configuration, one needs to know the UUID of a certain WeMo device.
In the thing file, this looks e.g. like
```
wemo:socket:Switch1 [udn="Socket-1_0-221242K11xxxxx"]
```
For a WeMo Link bridge and paired LED Lights, please use the following Thing definition
```
Bridge wemo:bridge:Bridge-1_0-231445B01006A0 [udn="Bridge-1_0-231445B010xxxx"] {
MZ100 94103EA2B278xxxx [ deviceID="94103EA2B278xxxx" ]
MZ100 94103EA2B278xxxx [ deviceID="94103EA2B278xxxx" ]
}
```
## Channels
Devices support some of the following channels:
| Channel Type | Item Type | Description | Available on Thing |
|---------------------|-----------|---------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| motionDetection | Switch | On if motion is detected, off otherwise. (Motion Sensor only) | Motion |
| lastMotionDetected | DateTime | Date and Time when the last motion was detected. (Motion Sensor only) | Motion |
| state | Switch | This channel controls the actual binary State of a Device or represents Motion Detection. | All but Dimmer, Crockpot, Airpurifier and Humidifier |
| lastChangedAt | DateTime | Date and Time the device was last turned on or of. | Insight |
| lastOnFor | Number | Time in seconds an Insight device was last turned on for. | Insight |
| onToday | Number | Time in seconds an Insight device has been switched on today. | Insight |
| onTotal | Number | Time in seconds an Insight device has been switched on totally. | Insight |
| timespan | Number | Time in seconds over which onTotal applies. Typically 2 weeks except first used. | Insight |
| averagePower | Number | Average power consumption in Watts. | Insight |
| currentPower | Number | Current power consumption of an Insight device. 0 if switched off. | Insight |
| energyToday | Number | Energy in Wh used today. | Insight |
| energyTotal | Number | Energy in Wh used in total. | Insight |
| standbyLimit | Number | Minimum energy draw in W to register device as switched on (default 8W, configurable via WeMo App). | Insight |
| onStandBy | Switch | Read-only indication of whether or not the device plugged in to the insight switch is drawing more than the standby limit. | Insight |
| relay | Switch | Switches the integrated relay contact close/open | Maker |
| sensor | Switch | Shows the state of the integrated sensor | Maker |
| coffeeMode | String | Operation mode of a WeMo Coffee Maker | CoffeeMaker |
| modeTime | Number | Current amount of time, in minutes, that the Coffee Maker has been in the current mode | CoffeeMaker |
| timeRemaining | Number | Remaining brewing time of a WeMo Coffee Maker | CoffeeMaker |
| waterLevelReached | Switch | Indicates if the WeMo Coffee Maker needs to be refilled | CoffeeMaker |
| cleanAdvise | Switch | Indicates if a WeMo Coffee Maker needs to be cleaned | CoffeeMaker |
| filterAdvise | Switch | Indicates if a WeMo Coffee Maker needs to have the filter changed | CoffeeMaker |
| brewed | DateTime | Date/time the coffee maker last completed brewing coffee | CoffeeMaker |
| lastCleaned | DateTime | Date/time the coffee maker last completed cleaning | CoffeeMaker |
| brightness | Number | Brightness of a WeMo LED od Dimmwer. | LED, DimmerSwitch |
| faderCountDownTime | Number | Dimmer fading duration time in minutes | DimmerSwitch |
| faderEnabled | Switch | Switch the fader ON/OFF | DimmerSwitch |
| timerStart | Switch | Switch the fading timer ON/OFF | DimmerSwitch |
| nightMode | Switch | Switch the nightMode ON/OFF | DimmerSwitch |
| startTime | DateTime | Time when the nightMode starts | DimmerSwitch |
| endTime | DateTime | Time when the nightMode ends | DimmerSwitch |
| nightModeBrightness | Number | Brightness used in nightMode | DimmerSwitch |
| cookMode | String | Shows the operation mode of a WeMo Crockpot (OFF, WARM, LOW, HIGH | Crockpot |
| warmCookTime | Number | Shows the timer settings for warm cooking mode | Crockpot |
| lowCookTime | Number | Shows the timer settings for low cooking mode | Crockpot |
| highCookTime | Number | Shows the timer settings for high cooking mode | Crockpot |
| cookedTime | Number | Shows the elapsed cooking time | Crockpot |
| purifierMode | String | Runmode of Air Purifier (OFF, LOW, MED, HIGH, AUTO) | Air Purifier |
| airQuality | String | Air quality (POOR, MODERATE, GOOD) | Air Purifier |
| ionizer | Switch | Indicates whether the ionizer is switched ON or OFF | Air Purifier |
| filterLife | Number | Indicates the remaining filter lifetime in Percent | Air Purifier, Humidifier |
| expiredFilterTime | Number | Indicates whether the filter lifetime has expired or not | Air Purifier, Humidifier |
| filterPresent | Switch | Indicates the presence of an air filter | Air Purifier |
| humidifierMode | String | Runmode of Humidifier (OFF, MIN, LOW, MED, HIGH, MAX) | Humidifier |
| desiredHumidity | Number | Shows desired humidity in Percent | Humidifier |
| currentHumidity | Number | Shows current humidity in Percent | Humidifier |
| heaterMode | String | Runmode of Heater (OFF, FROSTPROTECT, HIGH, LOW, ECO) | Heater |
| currentTemp | Number | Shows current temperature | Heater |
| targetTemp | Number | Shows target temperature | Heater |
| autoOffTime | DateTime | Time when the heater switches off | Heater |
| heatingRemaining | Number | Shows the remaining heating time | Heater |
## Full Example
demo.things:
```
wemo:socket:Switch1 "DemoSwitch" @ "Office" [udn="Socket-1_0-221242K11xxxxx"]
wemo:motion:Sensor1 "MotionSensor" @ "Entrance" [udn="Sensor-1_0-221337L11xxxxx"]
Bridge wemo:bridge:Bridge-1_0-231445B010xxxx [udn="Bridge-1_0-231445B010xxxx"] {
MZ100 94103EA2B278xxxx "DemoLight1" @ "Living" [ deviceID="94103EA2B278xxxx" ]
MZ100 94103EA2B278xxxx "DemoLoght2" @ "Living" [ deviceID="94103EA2B278xxxx" ]
}
```
demo.items:
```
// Switch
Switch DemoSwitch { channel="wemo:socket:Switch1:state" }
// Lightswitch
Switch LightSwitch { channel="wemo:lightswitch:Lightswitch1:state" }
// Motion
Switch MotionSensor { channel="wemo:Motion:Sensor1:motionDetection" }
DateTime MotionDetected { channel="wemo:Motion:Sensor1:lastMotionDetected" }
// Insight
Switch InsightSwitch { channel="wemo:insight:Insight-1_0-xxxxxxxxxxxxxx:state" }
Number InsightPower { channel="wemo:insight:Insight-1_0-xxxxxxxxxxxxxx:currentPower" }
Number InsightLastOn { channel="wemo:insight:Insight-1_0-xxxxxxxxxxxxxx:lastOnFor" }
Number InsightToday { channel="wemo:insight:Insight-1_0-xxxxxxxxxxxxxx:onToday" }
Number InsightTotal { channel="wemo:insight:Insight-1_0-xxxxxxxxxxxxxx:onTotal" }
// LED Bulbs
Switch LED1 { channel="wemo:MZ100:Bridge-1_0-231445B010xxxx:94103EA2B278xxxx:brightness" }
Dimmer dLED1 { channel="wemo:MZ100:Bridge-1_0-231445B010xxxx:94103EA2B278xxxx:brightness" }
Switch LED2 { channel="wemo:MZ100:Bridge-1_0-231445B010xxxx:94103EA2B278xxxx:brightness" }
Dimmer dLED2 { channel="wemo:MZ100:Bridge-1_0-231445B010xxxx:94103EA2B278xxxx:brightness" }
// DimmerSwitch
Switch DimmerSwitch { channel="wemo:dimmer:Dimmer-1_0-231445B010xxxx:brightness" }
Dimmer dDimmerSwitch { channel="wemo:dimmer:Dimmer-1_0-231445B010xxxx:brightness" }
Number DimmerSwitchFaderTime { channel="wemo:dimmer:Dimmer-1_0-231445B010xxxx:faderCountDownTime" }
Switch DimmerSwitchFaderOn { channel="wemo:dimmer:Dimmer-1_0-231445B010xxxx:faderEnabled" }
Switch DimmerSwitchTimer { channel="wemo:dimmer:Dimmer-1_0-231445B010xxxx:timerStart" }
Switch DimmerNightMode { channel="wemo:dimmer:Dimmer-1_0-231445B010xxxx:nightMode" }
Dimmer NightModeBrightness { channel="wemo:dimmer:Dimmer-1_0-231445B010xxxx:nightModeBrightness" }
DateTime NightModeStart { channel="wemo:dimmer:Dimmer-1_0-231445B010xxxx:startTime" }
DateTime NightModeEnd { channel="wemo:dimmer:Dimmer-1_0-231445B010xxxx:endTime" }
// CoffeMaker
Switch CoffeSwitch { channel="wemo:coffee:Coffee-1_0-231445B010xxxx:state" }
String CoffeMode { channel="wemo:coffee:Coffee-1_0-231445B010xxxx:coffeeMode" }
Number CoffeModeTime { channel="wemo:coffee:Coffee-1_0-231445B010xxxx:modeTime" }
Number CoffeModeRemaining { channel="wemo:coffee:Coffee-1_0-231445B010xxxx:timeRemaining" }
Switch CoffeWater { channel="wemo:coffee:Coffee-1_0-231445B010xxxx:waterLevelReached" }
Switch CoffeCleanAdvicse { channel="wemo:coffee:Coffee-1_0-231445B010xxxx:cleanAdvise" }
Switch CoffeFilterAdvicse { channel="wemo:coffee:Coffee-1_0-231445B010xxxx:filterAdvise" }
DateTime CoffeLastCleaned { channel="wemo:coffee:Coffee-1_0-231445B010xxxx:lastCleaned" }
// Crockpot
String crockpotMode { channel="wemo:crockpot:Crockpot-1_0-231445B010xxxx:cookMode" }
Number warmCookTime { channel="wemo:crockpot:Crockpot-1_0-231445B010xxxx:warmCookTime" }
Number lowCookTime { channel="wemo:crockpot:Crockpot-1_0-231445B010xxxx:lowCookTime" }
Number highCookTime { channel="wemo:crockpot:Crockpot-1_0-231445B010xxxx:highCookTime" }
Number cookedTime { channel="wemo:crockpot:Crockpot-1_0-231445B010xxxx:cookedTime" }
// Air Purifier
String airMode { channel="wemo:purifier:AirPurifier-1_0-231445B010xxxx:airMode" }
String airQuality { channel="wemo:purifier:AirPurifier-1_0-231445B010xxxx:airQuality" }
Switch ionizer { channel="wemo:purifier:AirPurifier-1_0-231445B010xxxx:ionizer" }
Number filterLife { channel="wemo:purifier:AirPurifier-1_0-231445B010xxxx:filterLife" }
Switch filterExpired { channel="wemo:purifier:AirPurifier-1_0-231445B010xxxx:filterExpired" }
Switch filterPresent { channel="wemo:purifier:AirPurifier-1_0-231445B010xxxx:filterPresent" }
// Humidifier
String humidifierMode { channel="wemo:humidifier:Humidifier-1_0-231445B010xxxx:humidifierMode" }
Number desiredHumidity { channel="wemo:humidifier:Humidifier-1_0-231445B010xxxx:desiredHumidity" }
Number currentHumidity { channel="wemo:humidifier:Humidifier-1_0-231445B010xxxx:currentHumidity" }
Number filterLife { channel="wemo:humidifier:Humidifier-1_0-231445B010xxxx:filterLife" }
Switch filterExpired { channel="wemo:humidifier:Humidifier-1_0-231445B010xxxx:filterExpired" }
String waterLevel { channel="wemo:humidifier:Humidifier-1_0-231445B010xxxx:waterLevel" }
// Heater
String heaterMode { channel="wemo:heater:HeaterB-1_0-231445B010xxxx:heaterMode" }
Number currentTemp { channel="wemo:heater:HeaterB-1_0-231445B010xxxx:currentTemp" }
Number targetTemp { channel="wemo:heater:HeaterB-1_0-231445B010xxxx:targetTemp" }
DateTime autoOffTime { channel="wemo:heater:HeaterB-1_0-231445B010xxxx:autoOffTime" }
String heaterRemaining { channel="wemo:heater:HeaterB-1_0-231445B010xxxx:heaterRemaining" }
```
demo.sitemap:
```
sitemap demo label="Main Menu"
{
Frame {
// Switch
Switch item=DemoSwitch
// LightSwitch
Switch item=LightSwitch
// Motion
Switch item=MotionSensor
Text item=MotionSensorLastChanged icon="clock"
// Insight
Switch item=InsightSwitch
Number item=InsightPower
Number item=InsightLastOn
Number item=InsightToday
Number item=InsightTotal
// LED Bulb
Switch item=LED1
Slider item=dLED1
Switch item=LED2
Slider item=dLED2
//DimmerSwitch
Switch item=DimmerSwitch
Slider item=dDimmerSwitch
Number item=DimmerSwitchFaderTime
Switch item=DimmerSwitchFaderOn
Switch item=DimmerSwitchTimer
Switch item=DimmerNightMode
Slider item=NightModeBrightness
Text item=NightModeStart
Text item=NightModeEnd
// CoffeMaker
Switch item=CoffeSwitch
Text item=CoffeMode
Number item=CoffeModeTime
Number item=CoffeModeRemaining
Switch item=CoffeWater
Switch item=CoffeCleanAdvicse
Switch item=CoffeFilterAdvicse
DateTime item=CoffeLastCleaned
// CrockPot
Switch item=crockpotMode label="Cooking Mode" mappings=[OFF="OFF", WARM="Warm", LOW="Low", HIGH="High"]
Number item=warmCookTime
Number item=lowCookTime
Number item=highCookTime
Number item=cookedTime
// Air Purifier
Switch item=airMode label="Cooking Mode" mappings=[OFF="OFF", LOW="Low", MED="Med", HIGH="High", AUTO="Auto"]
Text item=airQuality
Switch item=ionizer
Number item=filterLive
Switch item=filterExpired
Switch item=filterPresent
// Humidifier
Switch item=humidifierMode label="Cooking Mode" mappings=[OFF="OFF", MIN="Min", LOW="Low", MED="Med", HIGH="High", MAX="Max"]
Number item=desiredHumidity
Number item=currentHumidity
Number item=filterLive
Switch item=filterExpired
// Heater
Switch item=heaterMode label="Heater Mode" mappings=[OFF="OFF", FROSTPROTECT="FrostProtect", HIGH="High", LOW="Low", ECO="Eco"]
Number item=currentTemp
Setpoint item=targetTemp
Text item=autoOffTime
Number item=heaterRemaining
}
}
```

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.wemo</artifactId>
<name>openHAB Add-ons :: Bundles :: Wemo Binding</name>
</project>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.wemo-${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-wemo" description="Wemo Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-transport-upnp</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.wemo/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,131 @@
/**
* 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.wemo.internal;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link WemoBinding} class defines common constants, which are
* used across the whole binding.
*
* @author Hans-Jörg Merk - Initial contribution
* @author Mihir Patil - Added standby switch
*/
public class WemoBindingConstants {
public static final String BINDING_ID = "wemo";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_SOCKET = new ThingTypeUID(BINDING_ID, "socket");
public static final ThingTypeUID THING_TYPE_INSIGHT = new ThingTypeUID(BINDING_ID, "insight");
public static final ThingTypeUID THING_TYPE_LIGHTSWITCH = new ThingTypeUID(BINDING_ID, "lightswitch");
public static final ThingTypeUID THING_TYPE_MOTION = new ThingTypeUID(BINDING_ID, "motion");
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
public static final ThingTypeUID THING_TYPE_MZ100 = new ThingTypeUID(BINDING_ID, "MZ100");
public static final ThingTypeUID THING_TYPE_MAKER = new ThingTypeUID(BINDING_ID, "Maker");
public static final ThingTypeUID THING_TYPE_COFFEE = new ThingTypeUID(BINDING_ID, "CoffeeMaker");
public static final ThingTypeUID THING_TYPE_DIMMER = new ThingTypeUID(BINDING_ID, "dimmer");
public static final ThingTypeUID THING_TYPE_CROCKPOT = new ThingTypeUID(BINDING_ID, "Crockpot");
public static final ThingTypeUID THING_TYPE_PURIFIER = new ThingTypeUID(BINDING_ID, "Purifier");
public static final ThingTypeUID THING_TYPE_HUMIDIFIER = new ThingTypeUID(BINDING_ID, "Humidifier");
public static final ThingTypeUID THING_TYPE_HEATER = new ThingTypeUID(BINDING_ID, "Heater");
// List of all Channel ids
public static final String CHANNEL_STATE = "state";
public static final String CHANNEL_MOTIONDETECTION = "motionDetection";
public static final String CHANNEL_LASTMOTIONDETECTED = "lastMotionDetected";
public static final String CHANNEL_LASTCHANGEDAT = "lastChangedAt";
public static final String CHANNEL_LASTONFOR = "lastOnFor";
public static final String CHANNEL_ONTODAY = "onToday";
public static final String CHANNEL_ONTOTAL = "onTotal";
public static final String CHANNEL_TIMESPAN = "timespan";
public static final String CHANNEL_AVERAGEPOWER = "averagePower";
public static final String CHANNEL_CURRENTPOWER = "currentPower";
public static final String CHANNEL_ENERGYTODAY = "energyToday";
public static final String CHANNEL_ENERGYTOTAL = "energyTotal";
public static final String CHANNEL_STANDBYLIMIT = "standByLimit";
public static final String CHANNEL_BRIGHTNESS = "brightness";
public static final String CHANNEL_RELAY = "relay";
public static final String CHANNEL_SENSOR = "sensor";
public static final String CHANNEL_ONSTANDBY = "onStandBy";
public static final String CHANNEL_COFFEEMODE = "coffeeMode";
public static final String CHANNEL_MODETIME = "modeTime";
public static final String CHANNEL_TIMEREMAINING = "timeRemaining";
public static final String CHANNEL_WATERLEVELREACHED = "waterLevelReached";
public static final String CHANNEL_CLEANADVISE = "cleanAdvise";
public static final String CHANNEL_FILTERADVISE = "filterAdvise";
public static final String CHANNEL_BREWED = "brewed";
public static final String CHANNEL_LASTCLEANED = "lastCleaned";
public static final String CHANNEL_FADERENABLED = "faderEnabled";
public static final String CHANNEL_TIMERSTART = "timerStart";
public static final String CHANNEL_FADERCOUNTDOWNTIME = "faderCountDownTime";
public static final String CHANNEL_NIGHTMODE = "nightMode";
public static final String CHANNEL_STARTTIME = "startTime";
public static final String CHANNEL_ENDTIME = "endTime";
public static final String CHANNEL_NIGHTMODEBRIGHTNESS = "nightModeBrightness";
public static final String CHANNEL_COOKMODE = "cookMode";
public static final String CHANNEL_LOWCOOKTIME = "lowCookTime";
public static final String CHANNEL_WARMCOOKTIME = "warmCooktime";
public static final String CHANNEL_HIGHCOOKTIME = "highCooktime";
public static final String CHANNEL_COOKEDTIME = "cookedtime";
public static final String CHANNEL_PURIFIERMODE = "purifierMode";
public static final String CHANNEL_AIRQUALITY = "airQuality";
public static final String CHANNEL_IONIZER = "ionizer";
public static final String CHANNEL_FILTERLIFE = "filterLife";
public static final String CHANNEL_EXPIREDFILTERTIME = "expiredFilterTime";
public static final String CHANNEL_FILTERPRESENT = "filterPresent";
public static final String CHANNEL_HUMIDIFIERMODE = "humidifierMode";
public static final String CHANNEL_CURRENTHUMIDITY = "currentHumidity";
public static final String CHANNEL_DESIREDHUMIDITY = "desiredHumidity";
public static final String CHANNEL_WATERLEVEL = "waterLEvel";
public static final String CHANNEL_HEATERMODE = "heaterMode";
public static final String CHANNEL_CURRENTTEMP = "currentTemperature";
public static final String CHANNEL_TARGETTEMP = "targetTemperature";
public static final String CHANNEL_AUTOOFFTIME = "autoOffTime";
public static final String CHANNEL_HEATINGREMAINING = "heatingRemaining";
// List of thing configuration properties
public static final String UDN = "udn";
public static final String DEVICE_ID = "deviceID";
public static final String POLLINGINTERVALL = "pollingInterval";
public static final int SUBSCRIPTION_DURATION = 600;
public static final int LINK_DISCOVERY_SERVICE_INITIAL_DELAY = 5;
public static final String HTTP_CALL_CONTENT_HEADER = "text/xml; charset=utf-8";
public static final Set<ThingTypeUID> SUPPORTED_BRIDGE_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);
public static final Set<ThingTypeUID> SUPPORTED_LIGHT_THING_TYPES = Collections.singleton(THING_TYPE_MZ100);
public static final Set<ThingTypeUID> SUPPORTED_DEVICE_THING_TYPES = Collections
.unmodifiableSet(Stream.of(THING_TYPE_SOCKET, THING_TYPE_INSIGHT, THING_TYPE_LIGHTSWITCH, THING_TYPE_MOTION)
.collect(Collectors.toSet()));
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections
.unmodifiableSet(Stream
.of(THING_TYPE_SOCKET, THING_TYPE_INSIGHT, THING_TYPE_LIGHTSWITCH, THING_TYPE_MOTION,
THING_TYPE_BRIDGE, THING_TYPE_MZ100, THING_TYPE_MAKER, THING_TYPE_COFFEE, THING_TYPE_DIMMER,
THING_TYPE_CROCKPOT, THING_TYPE_PURIFIER, THING_TYPE_HUMIDIFIER, THING_TYPE_HEATER)
.collect(Collectors.toSet()));
}

View File

@@ -0,0 +1,153 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.wemo.internal;
import static org.openhab.binding.wemo.internal.WemoBindingConstants.UDN;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import org.openhab.binding.wemo.internal.discovery.WemoLinkDiscoveryService;
import org.openhab.binding.wemo.internal.handler.WemoBridgeHandler;
import org.openhab.binding.wemo.internal.handler.WemoCoffeeHandler;
import org.openhab.binding.wemo.internal.handler.WemoCrockpotHandler;
import org.openhab.binding.wemo.internal.handler.WemoDimmerHandler;
import org.openhab.binding.wemo.internal.handler.WemoHandler;
import org.openhab.binding.wemo.internal.handler.WemoHolmesHandler;
import org.openhab.binding.wemo.internal.handler.WemoLightHandler;
import org.openhab.binding.wemo.internal.handler.WemoMakerHandler;
import org.openhab.binding.wemo.internal.http.WemoHttpCall;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.io.transport.upnp.UpnpIOService;
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;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link WemoHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Hans-Jörg Merk - Initial contribution
* @author Kai Kreuzer - some refactoring for performance and simplification
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.wemo")
public class WemoHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(WemoHandlerFactory.class);
private UpnpIOService upnpIOService;
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = WemoBindingConstants.SUPPORTED_THING_TYPES;
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES.contains(thingTypeUID);
}
private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
@SuppressWarnings({ "null", "unused" })
@Override
protected ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID != null) {
logger.debug("Trying to create a handler for ThingType '{}", thingTypeUID);
WemoHttpCall wemoHttpcaller = new WemoHttpCall();
if (thingTypeUID.equals(WemoBindingConstants.THING_TYPE_BRIDGE)) {
logger.debug("Creating a WemoBridgeHandler for thing '{}' with UDN '{}'", thing.getUID(),
thing.getConfiguration().get(UDN));
WemoBridgeHandler handler = new WemoBridgeHandler((Bridge) thing);
registerDeviceDiscoveryService(handler, wemoHttpcaller);
return handler;
} else if (thingTypeUID.equals(WemoBindingConstants.THING_TYPE_MAKER)) {
logger.debug("Creating a WemoMakerHandler for thing '{}' with UDN '{}'", thing.getUID(),
thing.getConfiguration().get(UDN));
return new WemoMakerHandler(thing, upnpIOService, wemoHttpcaller);
} else if (WemoBindingConstants.SUPPORTED_DEVICE_THING_TYPES.contains(thing.getThingTypeUID())) {
logger.debug("Creating a WemoHandler for thing '{}' with UDN '{}'", thing.getUID(),
thing.getConfiguration().get(UDN));
return new WemoHandler(thing, upnpIOService, wemoHttpcaller);
} else if (thingTypeUID.equals(WemoBindingConstants.THING_TYPE_COFFEE)) {
logger.debug("Creating a WemoCoffeeHandler for thing '{}' with UDN '{}'", thing.getUID(),
thing.getConfiguration().get(UDN));
return new WemoCoffeeHandler(thing, upnpIOService, wemoHttpcaller);
} else if (thingTypeUID.equals(WemoBindingConstants.THING_TYPE_DIMMER)) {
logger.debug("Creating a WemoDimmerHandler for thing '{}' with UDN '{}'", thing.getUID(),
thing.getConfiguration().get("udn"));
return new WemoDimmerHandler(thing, upnpIOService, wemoHttpcaller);
} else if (thingTypeUID.equals(WemoBindingConstants.THING_TYPE_CROCKPOT)) {
logger.debug("Creating a WemoCockpotHandler for thing '{}' with UDN '{}'", thing.getUID(),
thing.getConfiguration().get("udn"));
return new WemoCrockpotHandler(thing, upnpIOService, wemoHttpcaller);
} else if (thingTypeUID.equals(WemoBindingConstants.THING_TYPE_PURIFIER)) {
logger.debug("Creating a WemoHolmesHandler for thing '{}' with UDN '{}'", thing.getUID(),
thing.getConfiguration().get("udn"));
return new WemoHolmesHandler(thing, upnpIOService, wemoHttpcaller);
} else if (thingTypeUID.equals(WemoBindingConstants.THING_TYPE_HUMIDIFIER)) {
logger.debug("Creating a WemoHolmesHandler for thing '{}' with UDN '{}'", thing.getUID(),
thing.getConfiguration().get("udn"));
return new WemoHolmesHandler(thing, upnpIOService, wemoHttpcaller);
} else if (thingTypeUID.equals(WemoBindingConstants.THING_TYPE_HEATER)) {
logger.debug("Creating a WemoHolmesHandler for thing '{}' with UDN '{}'", thing.getUID(),
thing.getConfiguration().get("udn"));
return new WemoHolmesHandler(thing, upnpIOService, wemoHttpcaller);
} else if (thingTypeUID.equals(WemoBindingConstants.THING_TYPE_MZ100)) {
return new WemoLightHandler(thing, upnpIOService, wemoHttpcaller);
} else {
logger.warn("ThingHandler not found for {}", thingTypeUID);
return null;
}
}
return null;
}
@Reference
protected void setUpnpIOService(UpnpIOService upnpIOService) {
this.upnpIOService = upnpIOService;
}
protected void unsetUpnpIOService(UpnpIOService upnpIOService) {
this.upnpIOService = null;
}
@Override
protected synchronized void removeHandler(ThingHandler thingHandler) {
if (thingHandler instanceof WemoBridgeHandler) {
ServiceRegistration<?> serviceReg = this.discoveryServiceRegs.remove(thingHandler.getThing().getUID());
if (serviceReg != null) {
serviceReg.unregister();
}
}
}
private synchronized void registerDeviceDiscoveryService(WemoBridgeHandler wemoBridgeHandler,
WemoHttpCall wemoHttpCaller) {
WemoLinkDiscoveryService discoveryService = new WemoLinkDiscoveryService(wemoBridgeHandler, upnpIOService,
wemoHttpCaller);
this.discoveryServiceRegs.put(wemoBridgeHandler.getThing().getUID(),
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
}
}

View File

@@ -0,0 +1,162 @@
/**
* 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.wemo.internal.discovery;
import static org.openhab.binding.wemo.internal.WemoBindingConstants.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.Nullable;
import org.jupnp.model.meta.RemoteDevice;
import org.openhab.binding.wemo.internal.WemoBindingConstants;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant;
import org.openhab.core.config.discovery.upnp.internal.UpnpDiscoveryService;
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;
/**
* The {@link WemoDiscoveryParticipant} is responsible for discovering new and
* removed Wemo devices. It uses the central {@link UpnpDiscoveryService}.
*
* @author Hans-Jörg Merk - Initial contribution
* @author Kai Kreuzer - some refactoring for performance and simplification
*
*/
@Component(service = UpnpDiscoveryParticipant.class, immediate = true)
public class WemoDiscoveryParticipant implements UpnpDiscoveryParticipant {
private Logger logger = LoggerFactory.getLogger(WemoDiscoveryParticipant.class);
@Override
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
return WemoBindingConstants.SUPPORTED_THING_TYPES;
}
@Override
public DiscoveryResult createResult(RemoteDevice device) {
ThingUID uid = getThingUID(device);
if (uid != null) {
Map<String, Object> properties = new HashMap<>(2);
String label = "WeMo Device";
try {
label = device.getDetails().getFriendlyName();
} catch (Exception e) {
// ignore and use default label
}
properties.put(UDN, device.getIdentity().getUdn().getIdentifierString());
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel(label)
.withRepresentationProperty(UDN).build();
logger.debug("Created a DiscoveryResult for device '{}' with UDN '{}'",
device.getDetails().getFriendlyName(), device.getIdentity().getUdn().getIdentifierString());
return result;
} else {
return null;
}
}
@Override
public ThingUID getThingUID(@Nullable RemoteDevice device) {
if (device != null) {
if (device.getDetails().getManufacturerDetails().getManufacturer() != null) {
if (device.getDetails().getManufacturerDetails().getManufacturer().toUpperCase().contains("BELKIN")) {
if (device.getDetails().getModelDetails().getModelName() != null) {
if (device.getDetails().getModelDetails().getModelName().toLowerCase().startsWith("socket")) {
logger.debug("Discovered a WeMo Socket thing with UDN '{}'",
device.getIdentity().getUdn().getIdentifierString());
return new ThingUID(THING_TYPE_SOCKET, device.getIdentity().getUdn().getIdentifierString());
}
if (device.getDetails().getModelDetails().getModelName().toLowerCase().startsWith("insight")) {
logger.debug("Discovered a WeMo Insight thing with UDN '{}'",
device.getIdentity().getUdn().getIdentifierString());
return new ThingUID(THING_TYPE_INSIGHT,
device.getIdentity().getUdn().getIdentifierString());
}
if (device.getDetails().getModelDetails().getModelName().toLowerCase()
.startsWith("lightswitch")) {
logger.debug("Discovered a WeMo Lightswitch thing with UDN '{}'",
device.getIdentity().getUdn().getIdentifierString());
return new ThingUID(THING_TYPE_LIGHTSWITCH,
device.getIdentity().getUdn().getIdentifierString());
}
if (device.getDetails().getModelDetails().getModelName().toLowerCase().startsWith("motion")) {
logger.debug("Discovered a WeMo Motion thing with UDN '{}'",
device.getIdentity().getUdn().getIdentifierString());
return new ThingUID(THING_TYPE_MOTION, device.getIdentity().getUdn().getIdentifierString());
}
if (device.getDetails().getModelDetails().getModelName().toLowerCase().startsWith("sensor")) {
logger.debug("Discovered a WeMo Motion thing with UDN '{}'",
device.getIdentity().getUdn().getIdentifierString());
return new ThingUID(THING_TYPE_MOTION, device.getIdentity().getUdn().getIdentifierString());
}
if (device.getDetails().getModelDetails().getModelName().toLowerCase().startsWith("bridge")) {
logger.debug("Discovered a WeMo Bridge thing with UDN '{}'",
device.getIdentity().getUdn().getIdentifierString());
return new ThingUID(THING_TYPE_BRIDGE, device.getIdentity().getUdn().getIdentifierString());
}
if (device.getDetails().getModelDetails().getModelName().toLowerCase().startsWith("maker")) {
logger.debug("Discovered a WeMo Maker thing with UDN '{}'",
device.getIdentity().getUdn().getIdentifierString());
return new ThingUID(THING_TYPE_MAKER, device.getIdentity().getUdn().getIdentifierString());
}
if (device.getDetails().getModelDetails().getModelName().toLowerCase().startsWith("coffee")) {
logger.debug("Discovered a WeMo Coffe Maker thing with UDN '{}'",
device.getIdentity().getUdn().getIdentifierString());
return new ThingUID(THING_TYPE_COFFEE, device.getIdentity().getUdn().getIdentifierString());
}
if (device.getDetails().getModelDetails().getModelName().toLowerCase().startsWith("dimmer")) {
logger.debug("Discovered a WeMo Dimmer Switch thing with UDN '{}'",
device.getIdentity().getUdn().getIdentifierString());
return new ThingUID(THING_TYPE_DIMMER, device.getIdentity().getUdn().getIdentifierString());
}
if (device.getDetails().getModelDetails().getModelName().toLowerCase().startsWith("crockpot")) {
logger.debug("Discovered a WeMo enabled Crockpot thing with UDN '{}'",
device.getIdentity().getUdn().getIdentifierString());
return new ThingUID(THING_TYPE_CROCKPOT,
device.getIdentity().getUdn().getIdentifierString());
}
if (device.getDetails().getModelDetails().getModelName().toLowerCase()
.startsWith("airpurifier")) {
logger.debug("Discovered a WeMo enabled Holmes Air Purifier thing with UDN '{}'",
device.getIdentity().getUdn().getIdentifierString());
return new ThingUID(THING_TYPE_PURIFIER,
device.getIdentity().getUdn().getIdentifierString());
}
if (device.getDetails().getModelDetails().getModelName().toLowerCase()
.startsWith("humidifier")) {
logger.debug("Discovered a WeMo enabled Holmes Humidifier thing with UDN '{}'",
device.getIdentity().getUdn().getIdentifierString());
return new ThingUID(THING_TYPE_HUMIDIFIER,
device.getIdentity().getUdn().getIdentifierString());
}
if (device.getDetails().getModelDetails().getModelName().toLowerCase().startsWith("heater")) {
logger.debug("Discovered a WeMo enabled Heater thing with UDN '{}'",
device.getIdentity().getUdn().getIdentifierString());
return new ThingUID(THING_TYPE_HEATER, device.getIdentity().getUdn().getIdentifierString());
}
}
}
}
}
return null;
}
}

View File

@@ -0,0 +1,77 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.wemo.internal.discovery;
import org.jupnp.UpnpService;
import org.jupnp.model.message.header.RootDeviceHeader;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryService;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link WemoDiscoveryService} is a {@link DiscoveryService} implementation, which can find WeMo UPnP devices in
* the network.
*
* @author Hans-Jörg Merk - Initial contribution
*
*/
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.wemo")
public class WemoDiscoveryService extends AbstractDiscoveryService {
private Logger logger = LoggerFactory.getLogger(WemoDiscoveryService.class);
public WemoDiscoveryService() {
super(5);
}
private UpnpService upnpService;
@Reference
protected void setUpnpService(UpnpService upnpService) {
this.upnpService = upnpService;
}
protected void unsetUpnpService(UpnpService upnpService) {
this.upnpService = null;
}
public void activate() {
logger.debug("Starting WeMo UPnP discovery...");
startScan();
}
@Override
public void deactivate() {
logger.debug("Stopping WeMo UPnP discovery...");
stopScan();
}
@Override
protected void startScan() {
logger.debug("Starting UPnP RootDevice search...");
if (upnpService != null) {
upnpService.getControlPoint().search(new RootDeviceHeader());
} else {
logger.debug("upnpService not set");
}
}
@Override
protected synchronized void stopScan() {
removeOlderResults(getTimestampOfLastScan());
super.stopScan();
}
}

View File

@@ -0,0 +1,291 @@
/**
* 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.wemo.internal.discovery;
import static org.openhab.binding.wemo.internal.WemoBindingConstants.*;
import java.io.StringReader;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.wemo.internal.handler.WemoBridgeHandler;
import org.openhab.binding.wemo.internal.http.WemoHttpCall;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.io.transport.upnp.UpnpIOParticipant;
import org.openhab.core.io.transport.upnp.UpnpIOService;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
/**
* The {@link WemoLinkDiscoveryService} is responsible for discovering new and
* removed WeMo devices connected to the WeMo Link Bridge.
*
* @author Hans-Jörg Merk - Initial contribution
*
*/
public class WemoLinkDiscoveryService extends AbstractDiscoveryService implements UpnpIOParticipant {
private final Logger logger = LoggerFactory.getLogger(WemoLinkDiscoveryService.class);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_MZ100);
public static final String NORMALIZE_ID_REGEX = "[^a-zA-Z0-9_]";
/**
* Maximum time to search for devices in seconds.
*/
private static final int SEARCH_TIME = 20;
/**
* Scan interval for scanning job in seconds.
*/
private static final int SCAN_INTERVAL = 120;
/**
* The handler for WeMo Link bridge
*/
private final WemoBridgeHandler wemoBridgeHandler;
/**
* Job which will do the background scanning
*/
private final WemoLinkScan scanningRunnable;
/**
* Schedule for scanning
*/
private ScheduledFuture<?> scanningJob;
/**
* The Upnp service
*/
private UpnpIOService service;
private final WemoHttpCall wemoHttpCaller;
public WemoLinkDiscoveryService(WemoBridgeHandler wemoBridgeHandler, UpnpIOService upnpIOService,
WemoHttpCall wemoHttpCaller) {
super(SEARCH_TIME);
this.wemoBridgeHandler = wemoBridgeHandler;
this.wemoHttpCaller = wemoHttpCaller;
if (upnpIOService != null) {
this.service = upnpIOService;
} else {
logger.debug("upnpIOService not set.");
}
this.scanningRunnable = new WemoLinkScan();
if (wemoBridgeHandler == null) {
logger.warn("no bridge handler for scan given");
}
this.activate(null);
}
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
return SUPPORTED_THING_TYPES;
}
@Override
public void startScan() {
logger.trace("Starting WeMoEndDevice discovery on WeMo Link {}", wemoBridgeHandler.getThing().getUID());
try {
String devUDN = "uuid:" + wemoBridgeHandler.getThing().getConfiguration().get(UDN).toString();
logger.trace("devUDN = '{}'", devUDN);
String soapHeader = "\"urn:Belkin:service:bridge:1#GetEndDevices\"";
String content = "<?xml version=\"1.0\"?>"
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
+ "<s:Body>" + "<u:GetEndDevices xmlns:u=\"urn:Belkin:service:bridge:1\">" + "<DevUDN>" + devUDN
+ "</DevUDN><ReqListType>PAIRED_LIST</ReqListType>" + "</u:GetEndDevices>" + "</s:Body>"
+ "</s:Envelope>";
URL descriptorURL = service.getDescriptorURL(this);
if (descriptorURL != null) {
String deviceURL = StringUtils.substringBefore(descriptorURL.toString(), "/setup.xml");
String wemoURL = deviceURL + "/upnp/control/bridge1";
String endDeviceRequest = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (endDeviceRequest != null) {
logger.trace("endDeviceRequest answered '{}'", endDeviceRequest);
try {
String stringParser = StringUtils.substringBetween(endDeviceRequest, "<DeviceLists>",
"</DeviceLists>");
stringParser = StringEscapeUtils.unescapeXml(stringParser);
// check if there are already paired devices with WeMo Link
if ("0".equals(stringParser)) {
logger.debug("There are no devices connected with WeMo Link. Exit discovery");
return;
}
// Build parser for received <DeviceList>
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
InputSource is = new InputSource();
is.setCharacterStream(new StringReader(stringParser));
Document doc = db.parse(is);
NodeList nodes = doc.getElementsByTagName("DeviceInfo");
// iterate the devices
for (int i = 0; i < nodes.getLength(); i++) {
Element element = (Element) nodes.item(i);
NodeList deviceIndex = element.getElementsByTagName("DeviceIndex");
Element line = (Element) deviceIndex.item(0);
logger.trace("DeviceIndex: {}", getCharacterDataFromElement(line));
NodeList deviceID = element.getElementsByTagName("DeviceID");
line = (Element) deviceID.item(0);
String endDeviceID = getCharacterDataFromElement(line);
logger.trace("DeviceID: {}", endDeviceID);
NodeList friendlyName = element.getElementsByTagName("FriendlyName");
line = (Element) friendlyName.item(0);
String endDeviceName = getCharacterDataFromElement(line);
logger.trace("FriendlyName: {}", endDeviceName);
NodeList vendor = element.getElementsByTagName("Manufacturer");
line = (Element) vendor.item(0);
String endDeviceVendor = getCharacterDataFromElement(line);
logger.trace("Manufacturer: {}", endDeviceVendor);
NodeList model = element.getElementsByTagName("ModelCode");
line = (Element) model.item(0);
String endDeviceModelID = getCharacterDataFromElement(line);
endDeviceModelID = endDeviceModelID.replaceAll(NORMALIZE_ID_REGEX, "_");
logger.trace("ModelCode: {}", endDeviceModelID);
if (SUPPORTED_THING_TYPES.contains(new ThingTypeUID(BINDING_ID, endDeviceModelID))) {
logger.debug("Discovered a WeMo LED Light thing with ID '{}'", endDeviceID);
ThingUID bridgeUID = wemoBridgeHandler.getThing().getUID();
ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, endDeviceModelID);
if (thingTypeUID.equals(THING_TYPE_MZ100)) {
String thingLightId = endDeviceID;
ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID, thingLightId);
Map<String, Object> properties = new HashMap<>(1);
properties.put(DEVICE_ID, endDeviceID);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
.withProperties(properties)
.withBridge(wemoBridgeHandler.getThing().getUID()).withLabel(endDeviceName)
.build();
thingDiscovered(discoveryResult);
}
} else {
logger.debug("Discovered an unsupported device :");
logger.debug("DeviceIndex : {}", getCharacterDataFromElement(line));
logger.debug("DeviceID : {}", endDeviceID);
logger.debug("FriendlyName: {}", endDeviceName);
logger.debug("Manufacturer: {}", endDeviceVendor);
logger.debug("ModelCode : {}", endDeviceModelID);
}
}
} catch (Exception e) {
logger.error("Failed to parse endDevices for bridge '{}'",
wemoBridgeHandler.getThing().getUID(), e);
}
}
}
} catch (Exception e) {
logger.error("Failed to get endDevices for bridge '{}'", wemoBridgeHandler.getThing().getUID(), e);
}
}
@Override
protected void startBackgroundDiscovery() {
logger.trace("Start WeMo device background discovery");
if (scanningJob == null || scanningJob.isCancelled()) {
this.scanningJob = scheduler.scheduleWithFixedDelay(this.scanningRunnable,
LINK_DISCOVERY_SERVICE_INITIAL_DELAY, SCAN_INTERVAL, TimeUnit.SECONDS);
} else {
logger.trace("scanningJob active");
}
}
@Override
protected void stopBackgroundDiscovery() {
logger.debug("Stop WeMo device background discovery");
if (scanningJob != null && !scanningJob.isCancelled()) {
scanningJob.cancel(true);
scanningJob = null;
}
}
@Override
public String getUDN() {
return (String) this.wemoBridgeHandler.getThing().getConfiguration().get(UDN);
}
@Override
public void onServiceSubscribed(String service, boolean succeeded) {
}
@Override
public void onValueReceived(String variable, String value, String service) {
}
@Override
public void onStatusChanged(boolean status) {
}
public static String getCharacterDataFromElement(Element e) {
Node child = e.getFirstChild();
if (child instanceof CharacterData) {
CharacterData cd = (CharacterData) child;
return cd.getData();
}
return "?";
}
public class WemoLinkScan implements Runnable {
@Override
public void run() {
startScan();
}
}
}

View File

@@ -0,0 +1,35 @@
/**
* 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.wemo.internal.handler;
import org.openhab.binding.wemo.internal.http.WemoHttpCall;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.binding.BaseThingHandler;
/**
*
* @author Stefan Triller
*
*/
public abstract class AbstractWemoHandler extends BaseThingHandler {
public AbstractWemoHandler(Thing thing) {
super(thing);
}
protected WemoHttpCall wemoHttpCaller;
public void setWemoHttpCaller(WemoHttpCall wemoHttpCaller) {
this.wemoHttpCaller = wemoHttpCaller;
}
}

View File

@@ -0,0 +1,67 @@
/**
* 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.wemo.internal.handler;
import static org.openhab.binding.wemo.internal.WemoBindingConstants.*;
import java.util.Collections;
import java.util.Set;
import org.openhab.core.config.core.Configuration;
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.BaseBridgeHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link WemoBridgeHandler} is the handler for a wemo bridge and connects it to
* the framework.
*
* @author Hans-Jörg Merk - Initial contribution
*/
public class WemoBridgeHandler extends BaseBridgeHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);
private final Logger logger = LoggerFactory.getLogger(WemoBridgeHandler.class);
public WemoBridgeHandler(Bridge bridge) {
super(bridge);
logger.debug("Creating a WemoBridgeHandler for thing '{}'", getThing().getUID());
}
@Override
public void initialize() {
logger.debug("Initializing WemoBridgeHandler");
Configuration configuration = getConfig();
if (configuration.get(UDN) != null) {
logger.trace("Initializing WemoBridgeHandler for UDN '{}'", configuration.get(UDN));
updateStatus(ThingStatus.ONLINE);
} else {
logger.debug("Cannot initalize WemoBridgeHandler. UDN not set.");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// Not needed, all commands are handled in the {@link WemoLightHandler}
}
}

View File

@@ -0,0 +1,471 @@
/**
* 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.wemo.internal.handler;
import static org.openhab.binding.wemo.internal.WemoBindingConstants.*;
import java.io.StringReader;
import java.math.BigDecimal;
import java.net.URL;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.wemo.internal.http.WemoHttpCall;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.io.transport.upnp.UpnpIOParticipant;
import org.openhab.core.io.transport.upnp.UpnpIOService;
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.library.types.StringType;
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.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
/**
* The {@link WemoCoffeeHandler} is responsible for handling commands, which are
* sent to one of the channels and to update their states.
*
* @author Hans-Jörg Merk - Initial contribution
* @author Erdoan Hadzhiyusein - Adapted the class to work with the new DateTimeType
*/
public class WemoCoffeeHandler extends AbstractWemoHandler implements UpnpIOParticipant {
private final Logger logger = LoggerFactory.getLogger(WemoCoffeeHandler.class);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_COFFEE);
private Map<String, Boolean> subscriptionState = new HashMap<>();
protected static final int SUBSCRIPTION_DURATION = 600;
private UpnpIOService service;
/**
* The default refresh interval in Seconds.
*/
private final int REFRESH_INTERVAL = 60;
private ScheduledFuture<?> refreshJob;
private final Runnable refreshRunnable = new Runnable() {
@Override
public void run() {
try {
if (!isUpnpDeviceRegistered()) {
logger.debug("WeMo UPnP device {} not yet registered", getUDN());
}
updateWemoState();
onSubscription();
} catch (Exception e) {
logger.debug("Exception during poll", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
};
public WemoCoffeeHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpcaller) {
super(thing);
this.wemoHttpCaller = wemoHttpcaller;
logger.debug("Creating a WemoCoffeeHandler V0.4 for thing '{}'", getThing().getUID());
if (upnpIOService != null) {
this.service = upnpIOService;
} else {
logger.debug("upnpIOService not set.");
}
}
@Override
public void initialize() {
Configuration configuration = getConfig();
if (configuration.get("udn") != null) {
logger.debug("Initializing WemoCoffeeHandler for UDN '{}'", configuration.get("udn"));
onSubscription();
onUpdate();
updateStatus(ThingStatus.ONLINE);
} else {
logger.debug("Cannot initalize WemoCoffeeHandler. UDN not set.");
}
}
@Override
public void dispose() {
logger.debug("WeMoCoffeeHandler disposed.");
removeSubscription();
if (refreshJob != null && !refreshJob.isCancelled()) {
refreshJob.cancel(true);
refreshJob = null;
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.trace("Command '{}' received for channel '{}'", command, channelUID);
if (command instanceof RefreshType) {
try {
updateWemoState();
} catch (Exception e) {
logger.debug("Exception during poll", e);
}
} else if (channelUID.getId().equals(CHANNEL_STATE)) {
if (command instanceof OnOffType) {
if (command.equals(OnOffType.ON)) {
try {
String soapHeader = "\"urn:Belkin:service:deviceevent:1#SetAttributes\"";
String content = "<?xml version=\"1.0\"?>"
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
+ "<s:Body>" + "<u:SetAttributes xmlns:u=\"urn:Belkin:service:deviceevent:1\">"
+ "<attributeList>&lt;attribute&gt;&lt;name&gt;Brewed&lt;/name&gt;&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;"
+ "&lt;attribute&gt;&lt;name&gt;LastCleaned&lt;/name&gt;&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;&lt;attribute&gt;"
+ "&lt;name&gt;ModeTime&lt;/name&gt;&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;&lt;attribute&gt;&lt;name&gt;Brewing&lt;/name&gt;"
+ "&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;&lt;attribute&gt;&lt;name&gt;TimeRemaining&lt;/name&gt;&lt;value&gt;NULL&lt;/value&gt;"
+ "&lt;/attribute&gt;&lt;attribute&gt;&lt;name&gt;WaterLevelReached&lt;/name&gt;&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;&lt;"
+ "attribute&gt;&lt;name&gt;Mode&lt;/name&gt;&lt;value&gt;4&lt;/value&gt;&lt;/attribute&gt;&lt;attribute&gt;&lt;name&gt;CleanAdvise&lt;/name&gt;"
+ "&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;&lt;attribute&gt;&lt;name&gt;FilterAdvise&lt;/name&gt;&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;"
+ "&lt;attribute&gt;&lt;name&gt;Cleaning&lt;/name&gt;&lt;value&gt;NULL&lt;/value&gt;&lt;/attribute&gt;</attributeList>"
+ "</u:SetAttributes>" + "</s:Body>" + "</s:Envelope>";
String wemoURL = getWemoURL("deviceevent");
if (wemoURL != null) {
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null) {
updateState(CHANNEL_STATE, OnOffType.ON);
State newMode = new StringType("Brewing");
updateState(CHANNEL_COFFEEMODE, newMode);
}
}
} catch (Exception e) {
logger.error("Failed to send command '{}' for device '{}': {}", command, getThing().getUID(),
e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
}
// if command.equals(OnOffType.OFF) we do nothing because WeMo Coffee Maker cannot be switched off
// remotely
updateStatus(ThingStatus.ONLINE);
}
}
}
@Override
public void onServiceSubscribed(String service, boolean succeeded) {
logger.debug("WeMo {}: Subscription to service {} {}", getUDN(), service, succeeded ? "succeeded" : "failed");
subscriptionState.put(service, succeeded);
}
@Override
public void onValueReceived(String variable, String value, String service) {
// We can subscribe to GENA events, but there is no usefull response right now.
}
private synchronized void onSubscription() {
if (service.isRegistered(this)) {
logger.debug("Checking WeMo GENA subscription for '{}'", this);
String subscription = "deviceevent1";
if ((subscriptionState.get(subscription) == null) || !subscriptionState.get(subscription).booleanValue()) {
logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(), subscription);
service.addSubscription(this, subscription, SUBSCRIPTION_DURATION);
subscriptionState.put(subscription, true);
}
} else {
logger.debug("Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE",
this);
}
}
private synchronized void removeSubscription() {
logger.debug("Removing WeMo GENA subscription for '{}'", this);
if (service.isRegistered(this)) {
String subscription = "deviceevent1";
if ((subscriptionState.get(subscription) != null) && subscriptionState.get(subscription).booleanValue()) {
logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
service.removeSubscription(this, subscription);
}
subscriptionState = new HashMap<>();
service.unregisterParticipant(this);
}
}
private synchronized void onUpdate() {
if (refreshJob == null || refreshJob.isCancelled()) {
Configuration config = getThing().getConfiguration();
int refreshInterval = REFRESH_INTERVAL;
Object refreshConfig = config.get("pollingInterval");
if (refreshConfig != null) {
refreshInterval = ((BigDecimal) refreshConfig).intValue();
logger.debug("Setting WemoCoffeeHandler refreshInterval to '{}' seconds", refreshInterval);
}
refreshJob = scheduler.scheduleWithFixedDelay(refreshRunnable, 0, refreshInterval, TimeUnit.SECONDS);
}
}
private boolean isUpnpDeviceRegistered() {
return service.isRegistered(this);
}
@Override
public String getUDN() {
return (String) this.getThing().getConfiguration().get(UDN);
}
/**
* The {@link updateWemoState} polls the actual state of a WeMo CoffeeMaker.
*/
protected void updateWemoState() {
String action = "GetAttributes";
String actionService = "deviceevent";
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
String content = "<?xml version=\"1.0\"?>"
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
+ "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:" + actionService + ":1\">" + "</u:"
+ action + ">" + "</s:Body>" + "</s:Envelope>";
try {
String wemoURL = getWemoURL(actionService);
if (wemoURL != null) {
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null) {
try {
String stringParser = StringUtils.substringBetween(wemoCallResponse, "<attributeList>",
"</attributeList>");
// Due to Belkins bad response formatting, we need to run this twice.
stringParser = StringEscapeUtils.unescapeXml(stringParser);
stringParser = StringEscapeUtils.unescapeXml(stringParser);
logger.trace("CoffeeMaker response '{}' for device '{}' received", stringParser,
getThing().getUID());
stringParser = "<data>" + stringParser + "</data>";
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
InputSource is = new InputSource();
is.setCharacterStream(new StringReader(stringParser));
Document doc = db.parse(is);
NodeList nodes = doc.getElementsByTagName("attribute");
// iterate the attributes
for (int i = 0; i < nodes.getLength(); i++) {
Element element = (Element) nodes.item(i);
NodeList deviceIndex = element.getElementsByTagName("name");
Element line = (Element) deviceIndex.item(0);
String attributeName = getCharacterDataFromElement(line);
logger.trace("attributeName: {}", attributeName);
NodeList deviceID = element.getElementsByTagName("value");
line = (Element) deviceID.item(0);
String attributeValue = getCharacterDataFromElement(line);
logger.trace("attributeValue: {}", attributeValue);
switch (attributeName) {
case "Mode":
State newMode = new StringType("Brewing");
switch (attributeValue) {
case "0":
updateState(CHANNEL_STATE, OnOffType.ON);
newMode = new StringType("Refill");
updateState(CHANNEL_COFFEEMODE, newMode);
break;
case "1":
updateState(CHANNEL_STATE, OnOffType.OFF);
newMode = new StringType("PlaceCarafe");
updateState(CHANNEL_COFFEEMODE, newMode);
break;
case "2":
updateState(CHANNEL_STATE, OnOffType.OFF);
newMode = new StringType("RefillWater");
updateState(CHANNEL_COFFEEMODE, newMode);
break;
case "3":
updateState(CHANNEL_STATE, OnOffType.OFF);
newMode = new StringType("Ready");
updateState(CHANNEL_COFFEEMODE, newMode);
break;
case "4":
updateState(CHANNEL_STATE, OnOffType.ON);
newMode = new StringType("Brewing");
updateState(CHANNEL_COFFEEMODE, newMode);
break;
case "5":
updateState(CHANNEL_STATE, OnOffType.OFF);
newMode = new StringType("Brewed");
updateState(CHANNEL_COFFEEMODE, newMode);
break;
case "6":
updateState(CHANNEL_STATE, OnOffType.OFF);
newMode = new StringType("CleaningBrewing");
updateState(CHANNEL_COFFEEMODE, newMode);
break;
case "7":
updateState(CHANNEL_STATE, OnOffType.OFF);
newMode = new StringType("CleaningSoaking");
updateState(CHANNEL_COFFEEMODE, newMode);
break;
case "8":
updateState(CHANNEL_STATE, OnOffType.OFF);
newMode = new StringType("BrewFailCarafeRemoved");
updateState(CHANNEL_COFFEEMODE, newMode);
break;
}
break;
case "ModeTime":
if (attributeValue != null) {
State newAttributeValue = new DecimalType(attributeValue);
updateState(CHANNEL_MODETIME, newAttributeValue);
}
break;
case "TimeRemaining":
if (attributeValue != null) {
State newAttributeValue = new DecimalType(attributeValue);
updateState(CHANNEL_TIMEREMAINING, newAttributeValue);
}
break;
case "WaterLevelReached":
if (attributeValue != null) {
State newAttributeValue = new DecimalType(attributeValue);
updateState(CHANNEL_WATERLEVELREACHED, newAttributeValue);
}
break;
case "CleanAdvise":
if (attributeValue != null) {
State newAttributeValue = attributeValue.equals("0") ? OnOffType.OFF
: OnOffType.ON;
updateState(CHANNEL_CLEANADVISE, newAttributeValue);
}
break;
case "FilterAdvise":
if (attributeValue != null) {
State newAttributeValue = attributeValue.equals("0") ? OnOffType.OFF
: OnOffType.ON;
updateState(CHANNEL_FILTERADVISE, newAttributeValue);
}
break;
case "Brewed":
if (attributeValue != null) {
State newAttributeValue = getDateTimeState(attributeValue);
if (newAttributeValue != null) {
updateState(CHANNEL_BREWED, newAttributeValue);
}
}
break;
case "LastCleaned":
if (attributeValue != null) {
State newAttributeValue = getDateTimeState(attributeValue);
if (newAttributeValue != null) {
updateState(CHANNEL_LASTCLEANED, newAttributeValue);
}
}
break;
}
}
} catch (Exception e) {
logger.error("Failed to parse attributeList for WeMo CoffeMaker '{}'", this.getThing().getUID(),
e);
}
}
}
} catch (Exception e) {
logger.error("Failed to get attributes for device '{}'", getThing().getUID(), e);
}
}
@SuppressWarnings("null")
public State getDateTimeState(String attributeValue) {
if (attributeValue != null) {
long value = 0;
try {
value = Long.parseLong(attributeValue) * 1000; // convert s to ms
} catch (NumberFormatException e) {
logger.error("Unable to parse attributeValue '{}' for device '{}'; expected long", attributeValue,
getThing().getUID());
return null;
}
ZonedDateTime zoned = ZonedDateTime.ofInstant(Instant.ofEpochMilli(value),
TimeZone.getDefault().toZoneId());
State dateTimeState = new DateTimeType(zoned);
if (dateTimeState != null) {
logger.trace("New attribute brewed '{}' received", dateTimeState);
return dateTimeState;
}
}
return null;
}
public String getWemoURL(String actionService) {
URL descriptorURL = service.getDescriptorURL(this);
String wemoURL = null;
if (descriptorURL != null) {
String deviceURL = StringUtils.substringBefore(descriptorURL.toString(), "/setup.xml");
wemoURL = deviceURL + "/upnp/control/" + actionService + "1";
return wemoURL;
}
return null;
}
public static String getCharacterDataFromElement(Element e) {
Node child = e.getFirstChild();
if (child instanceof CharacterData) {
CharacterData cd = (CharacterData) child;
return cd.getData();
}
return "?";
}
@Override
public void onStatusChanged(boolean status) {
}
}

View File

@@ -0,0 +1,306 @@
/**
* 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.wemo.internal.handler;
import static org.openhab.binding.wemo.internal.WemoBindingConstants.*;
import java.math.BigDecimal;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.wemo.internal.http.WemoHttpCall;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.io.transport.upnp.UpnpIOParticipant;
import org.openhab.core.io.transport.upnp.UpnpIOService;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.StringType;
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.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link WemoCrockpotHandler} is responsible for handling commands, which are
* sent to one of the channels and to update their states.
*
* @author Hans-Jörg Merk - Initial contribution;
*/
public class WemoCrockpotHandler extends AbstractWemoHandler implements UpnpIOParticipant {
private final Logger logger = LoggerFactory.getLogger(WemoCrockpotHandler.class);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_CROCKPOT);
/**
* The default refresh interval in Seconds.
*/
private static final int DEFAULT_REFRESH_INTERVAL_SECONDS = 120;
private final Map<String, Boolean> subscriptionState = new HashMap<>();
private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
private UpnpIOService service;
private ScheduledFuture<?> refreshJob;
private final Runnable refreshRunnable = () -> {
updateWemoState();
if (!isUpnpDeviceRegistered()) {
logger.debug("WeMo UPnP device {} not yet registered", getUDN());
} else {
onSubscription();
}
};
public WemoCrockpotHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemohttpCaller) {
super(thing);
this.wemoHttpCaller = wemohttpCaller;
logger.debug("Creating a WemoCrockpotHandler for thing '{}'", getThing().getUID());
if (upnpIOService != null) {
this.service = upnpIOService;
} else {
logger.debug("upnpIOService not set.");
}
}
@Override
public void initialize() {
Configuration configuration = getConfig();
if (configuration.get("udn") != null) {
logger.debug("Initializing WemoCrockpotHandler for UDN '{}'", configuration.get("udn"));
service.registerParticipant(this);
onSubscription();
onUpdate();
updateStatus(ThingStatus.ONLINE);
} else {
logger.debug("Cannot initalize WemoCrockpotHandler. UDN not set.");
}
}
@Override
public void dispose() {
logger.debug("WeMoCrockpotHandler disposed.");
removeSubscription();
if (refreshJob != null && !refreshJob.isCancelled()) {
refreshJob.cancel(true);
refreshJob = null;
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.trace("Command '{}' received for channel '{}'", command, channelUID);
String mode = "0";
String time = null;
if (command instanceof RefreshType) {
updateWemoState();
} else if (CHANNEL_COOKMODE.equals(channelUID.getId())) {
String commandString = command.toString();
switch (commandString) {
case "OFF":
mode = "0";
time = "0";
break;
case "WARM":
mode = "50";
break;
case "LOW":
mode = "51";
break;
case "HIGH":
mode = "52";
break;
}
try {
String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
String content = "<?xml version=\"1.0\"?>"
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
+ "<s:Body>" + "<u:SetCrockpotState xmlns:u=\"urn:Belkin:service:basicevent:1\">" + "<mode>"
+ mode + "</mode>" + "<time>" + time + "</time>" + "</u:SetCrockpotState>" + "</s:Body>"
+ "</s:Envelope>";
String wemoURL = getWemoURL("basicevent");
if (wemoURL != null) {
wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
}
} catch (RuntimeException e) {
logger.debug("Failed to send command '{}' for device '{}':", command, getThing().getUID(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
updateStatus(ThingStatus.ONLINE);
}
}
@Override
public void onServiceSubscribed(String service, boolean succeeded) {
logger.debug("WeMo {}: Subscription to service {} {}", getUDN(), service, succeeded ? "succeeded" : "failed");
subscriptionState.put(service, succeeded);
}
@Override
public void onValueReceived(String variable, String value, String service) {
logger.debug("Received pair '{}':'{}' (service '{}') for thing '{}'", variable, value, service,
this.getThing().getUID());
updateStatus(ThingStatus.ONLINE);
this.stateMap.put(variable, value);
}
private synchronized void onSubscription() {
if (service.isRegistered(this)) {
logger.debug("Checking WeMo GENA subscription for '{}'", this);
String subscription = "basicevent1";
if ((subscriptionState.get(subscription) == null) || !subscriptionState.get(subscription).booleanValue()) {
logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(), subscription);
service.addSubscription(this, subscription, SUBSCRIPTION_DURATION);
subscriptionState.put(subscription, true);
}
} else {
logger.debug("Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE",
this);
}
}
private synchronized void removeSubscription() {
logger.debug("Removing WeMo GENA subscription for '{}'", this);
if (service.isRegistered(this)) {
String subscription = "basicevent1";
if ((subscriptionState.get(subscription) != null) && subscriptionState.get(subscription).booleanValue()) {
logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
service.removeSubscription(this, subscription);
}
subscriptionState.remove(subscription);
service.unregisterParticipant(this);
}
}
private synchronized void onUpdate() {
if (refreshJob == null || refreshJob.isCancelled()) {
Configuration config = getThing().getConfiguration();
int refreshInterval = DEFAULT_REFRESH_INTERVAL_SECONDS;
Object refreshConfig = config.get("refresh");
refreshInterval = refreshConfig == null ? DEFAULT_REFRESH_INTERVAL_SECONDS
: ((BigDecimal) refreshConfig).intValue();
refreshJob = scheduler.scheduleWithFixedDelay(refreshRunnable, 0, refreshInterval, TimeUnit.SECONDS);
}
}
private boolean isUpnpDeviceRegistered() {
return service.isRegistered(this);
}
@Override
public String getUDN() {
return (String) this.getThing().getConfiguration().get(UDN);
}
/**
* The {@link updateWemoState} polls the actual state of a WeMo device and
* calls {@link onValueReceived} to update the statemap and channels..
*
*/
protected void updateWemoState() {
String action = "GetCrockpotState";
String actionService = "basicevent";
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
String content = "<?xml version=\"1.0\"?>"
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
+ "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:" + actionService + ":1\">" + "</u:"
+ action + ">" + "</s:Body>" + "</s:Envelope>";
try {
String wemoURL = getWemoURL(actionService);
if (wemoURL != null) {
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null) {
logger.trace("State response '{}' for device '{}' received", wemoCallResponse, getThing().getUID());
String mode = StringUtils.substringBetween(wemoCallResponse, "<mode>", "</mode>");
String time = StringUtils.substringBetween(wemoCallResponse, "<time>", "</time>");
String coockedTime = StringUtils.substringBetween(wemoCallResponse, "<coockedTime>",
"</coockedTime>");
if (mode != null && time != null && coockedTime != null) {
State newMode = new StringType(mode);
State newCoockedTime = DecimalType.valueOf(coockedTime);
switch (mode) {
case "0":
newMode = new StringType("OFF");
break;
case "50":
newMode = new StringType("WARM");
State warmTime = DecimalType.valueOf(time);
updateState(CHANNEL_WARMCOOKTIME, warmTime);
break;
case "51":
newMode = new StringType("LOW");
State lowTime = DecimalType.valueOf(time);
updateState(CHANNEL_LOWCOOKTIME, lowTime);
break;
case "52":
newMode = new StringType("HIGH");
State highTime = DecimalType.valueOf(time);
updateState(CHANNEL_HIGHCOOKTIME, highTime);
break;
}
updateState(CHANNEL_COOKMODE, newMode);
updateState(CHANNEL_COOKEDTIME, newCoockedTime);
}
}
}
} catch (RuntimeException e) {
logger.debug("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
updateStatus(ThingStatus.ONLINE);
}
public String getWemoURL(String actionService) {
URL descriptorURL = service.getDescriptorURL(this);
String wemoURL = null;
if (descriptorURL != null) {
String deviceURL = StringUtils.substringBefore(descriptorURL.toString(), "/setup.xml");
wemoURL = deviceURL + "/upnp/control/" + actionService + "1";
return wemoURL;
}
return null;
}
@Override
public void onStatusChanged(boolean status) {
}
}

View File

@@ -0,0 +1,613 @@
/**
* 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.wemo.internal.handler;
import static org.openhab.binding.wemo.internal.WemoBindingConstants.*;
import java.math.BigDecimal;
import java.net.URL;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.wemo.internal.http.WemoHttpCall;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.io.transport.upnp.UpnpIOParticipant;
import org.openhab.core.io.transport.upnp.UpnpIOService;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.IncreaseDecreaseType;
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.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link WemoDimmerHandler} is responsible for handling commands, which are
* sent to one of the channels and to update their states.
*
* @author Hans-Jörg Merk - Initial contribution
*/
public class WemoDimmerHandler extends AbstractWemoHandler implements UpnpIOParticipant {
private final Logger logger = LoggerFactory.getLogger(WemoDimmerHandler.class);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_DIMMER);
private Map<String, Boolean> subscriptionState = new HashMap<>();
private Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
protected static final int SUBSCRIPTION_DURATION = 600;
private UpnpIOService service;
private int currentBrightness;
private int currentNightModeBrightness;
private String currentNightModeState = null;
/**
* Set dimming stepsize to 5%
*/
private static final int DIM_STEPSIZE = 5;
/**
* The default refresh interval in Seconds.
*/
private int DEFAULT_REFRESH_INTERVAL = 60;
private ScheduledFuture<?> refreshJob;
private Runnable refreshRunnable = new Runnable() {
@Override
public void run() {
try {
if (!isUpnpDeviceRegistered()) {
logger.debug("WeMo UPnP device {} not yet registered", getUDN());
}
updateWemoState();
onSubscription();
} catch (Exception e) {
logger.debug("Exception during poll : {}", e.getMessage(), e);
}
}
};
public WemoDimmerHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemohttpCaller) {
super(thing);
this.wemoHttpCaller = wemohttpCaller;
logger.debug("Creating a WemoDimmerHandler for thing '{}'", getThing().getUID());
if (upnpIOService != null) {
this.service = upnpIOService;
} else {
logger.debug("upnpIOService not set.");
}
}
@Override
public void initialize() {
Configuration configuration = getConfig();
if (configuration.get("udn") != null) {
logger.debug("Initializing WemoDimmerHandler for UDN '{}'", configuration.get("udn"));
service.registerParticipant(this);
onSubscription();
onUpdate();
} else {
logger.debug("Cannot initalize WemoDimmerHandler. UDN not set.");
}
}
@Override
public void dispose() {
logger.debug("WeMoDimmerHandler disposed.");
removeSubscription();
if (refreshJob != null && !refreshJob.isCancelled()) {
refreshJob.cancel(true);
refreshJob = null;
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.trace("Command '{}' received for channel '{}'", command, channelUID);
if (command instanceof RefreshType) {
try {
updateWemoState();
} catch (Exception e) {
logger.debug("Exception during poll", e);
}
} else {
String action = "SetBinaryState";
String argument = "BinaryState";
String value = "0";
String timeStamp = null;
switch (channelUID.getId()) {
case CHANNEL_BRIGHTNESS:
if (command instanceof OnOffType) {
value = command.equals(OnOffType.OFF) ? "0" : "1";
setBinaryState(action, argument, value);
if (command.equals(OnOffType.OFF)) {
State brightnessState = new PercentType("0");
updateState(CHANNEL_BRIGHTNESS, brightnessState);
updateState(CHANNEL_TIMERSTART, OnOffType.OFF);
} else {
State brightnessState = new PercentType(currentBrightness);
updateState(CHANNEL_BRIGHTNESS, brightnessState);
}
} else if (command instanceof PercentType) {
int newBrightness = ((PercentType) command).intValue();
value = String.valueOf(newBrightness);
currentBrightness = newBrightness;
argument = "brightness";
if (value.equals("0")) {
value = "1";
argument = "brightness";
setBinaryState(action, argument, "1");
value = "0";
argument = "BinaryState";
setBinaryState(action, argument, "0");
} else if (this.stateMap.get("BinaryState").equals("0")) {
argument = "BinaryState";
setBinaryState(action, argument, "1");
}
argument = "brightness";
setBinaryState(action, argument, value);
} else if (command instanceof IncreaseDecreaseType) {
int newBrightness;
switch (command.toString()) {
case "INCREASE":
newBrightness = currentBrightness + DIM_STEPSIZE;
if (newBrightness > 100) {
newBrightness = 100;
}
value = String.valueOf(newBrightness);
currentBrightness = newBrightness;
break;
case "DECREASE":
newBrightness = currentBrightness - DIM_STEPSIZE;
if (newBrightness < 0) {
newBrightness = 0;
}
value = String.valueOf(newBrightness);
currentBrightness = newBrightness;
break;
}
argument = "brightness";
if (value.equals("0")) {
value = "1";
argument = "brightness";
setBinaryState(action, argument, "1");
value = "0";
argument = "BinaryState";
setBinaryState(action, argument, "0");
} else if (this.stateMap.get("BinaryState").equals("0")) {
argument = "BinaryState";
setBinaryState(action, argument, "1");
}
argument = "brightness";
setBinaryState(action, argument, value);
}
break;
case CHANNEL_FADERCOUNTDOWNTIME:
argument = "Fader";
if (command instanceof DecimalType) {
int commandValue = Integer.valueOf(String.valueOf(command));
commandValue = commandValue * 60;
String commandString = String.valueOf(commandValue);
value = "<BinaryState></BinaryState>" + "<Duration></Duration>" + "<EndAction></EndAction>"
+ "<brightness></brightness>" + "<fader>" + commandString + ":-1:1:0:0</fader>"
+ "<UDN></UDN>";
setBinaryState(action, argument, value);
}
break;
case CHANNEL_FADERENABLED:
argument = "Fader";
if (command.equals(OnOffType.ON)) {
value = "<BinaryState></BinaryState>" + "<Duration></Duration>" + "<EndAction></EndAction>"
+ "<brightness></brightness>" + "<fader>600:-1:1:0:0</fader>" + "<UDN></UDN>";
} else if (command.equals(OnOffType.OFF)) {
value = "<BinaryState></BinaryState>" + "<Duration></Duration>" + "<EndAction></EndAction>"
+ "<brightness></brightness>" + "<fader>600:-1:0:0:0</fader>" + "<UDN></UDN>";
}
setBinaryState(action, argument, value);
break;
case CHANNEL_TIMERSTART:
argument = "Fader";
long ts = System.currentTimeMillis() / 1000;
timeStamp = String.valueOf(ts);
logger.info("timestamp '{}' created", timeStamp);
String faderSeconds = null;
String faderEnabled = null;
String[] splitFader = this.stateMap.get("fader").split(":");
if (splitFader[0] != null) {
faderSeconds = splitFader[0];
}
if (splitFader[0] != null) {
faderEnabled = splitFader[2];
}
if (faderSeconds != null && faderEnabled != null) {
if (command.equals(OnOffType.ON)) {
value = "<BinaryState></BinaryState>" + "<Duration></Duration>" + "<EndAction></EndAction>"
+ "<brightness></brightness>" + "<fader>" + faderSeconds + ":" + timeStamp + ":"
+ faderEnabled + ":0:0</fader>" + "<UDN></UDN>";
updateState(CHANNEL_STATE, OnOffType.ON);
} else if (command.equals(OnOffType.OFF)) {
value = "<BinaryState></BinaryState>" + "<Duration></Duration>" + "<EndAction></EndAction>"
+ "<brightness></brightness>" + "<fader>" + faderSeconds + ":-1:" + faderEnabled
+ ":0:0</fader>" + "<UDN></UDN>";
}
}
setBinaryState(action, argument, value);
break;
case CHANNEL_NIGHTMODE:
action = "ConfigureNightMode";
argument = "NightModeConfiguration";
String nightModeBrightness = String.valueOf(currentNightModeBrightness);
if (command.equals(OnOffType.ON)) {
value = "&lt;startTime&gt;0&lt;/startTime&gt; \\n&lt;nightMode&gt;1&lt;/nightMode&gt; \\n&lt;endTime&gt;23400&lt;/endTime&gt; \\n&lt;nightModeBrightness&gt;"
+ nightModeBrightness + "&lt;/nightModeBrightness&gt; \\n";
} else if (command.equals(OnOffType.OFF)) {
value = "&lt;startTime&gt;0&lt;/startTime&gt; \\n&lt;nightMode&gt;0&lt;/nightMode&gt; \\n&lt;endTime&gt;23400&lt;/endTime&gt; \\n&lt;nightModeBrightness&gt;"
+ nightModeBrightness + "&lt;/nightModeBrightness&gt; \\n";
}
setBinaryState(action, argument, value);
break;
case CHANNEL_NIGHTMODEBRIGHTNESS:
action = "ConfigureNightMode";
argument = "NightModeConfiguration";
if (command instanceof PercentType) {
int newBrightness = ((PercentType) command).intValue();
String newNightModeBrightness = String.valueOf(newBrightness);
value = "&lt;startTime&gt;0&lt;/startTime&gt; \\n&lt;nightMode&gt;" + currentNightModeState
+ "&lt;/nightMode&gt; \\n&lt;endTime&gt;23400&lt;/endTime&gt; \\n&lt;nightModeBrightness&gt;"
+ newNightModeBrightness + "&lt;/nightModeBrightness&gt; \\n";
currentNightModeBrightness = newBrightness;
} else if (command instanceof IncreaseDecreaseType) {
int newBrightness;
String newNightModeBrightness = null;
switch (command.toString()) {
case "INCREASE":
newBrightness = currentNightModeBrightness + DIM_STEPSIZE;
if (newBrightness > 100) {
newBrightness = 100;
}
newNightModeBrightness = String.valueOf(newBrightness);
currentBrightness = newBrightness;
break;
case "DECREASE":
newBrightness = currentNightModeBrightness - DIM_STEPSIZE;
if (newBrightness < 0) {
newBrightness = 0;
}
newNightModeBrightness = String.valueOf(newBrightness);
currentNightModeBrightness = newBrightness;
break;
}
value = "&lt;startTime&gt;0&lt;/startTime&gt; \\n&lt;nightMode&gt;" + currentNightModeState
+ "&lt;/nightMode&gt; \\n&lt;endTime&gt;23400&lt;/endTime&gt; \\n&lt;nightModeBrightness&gt;"
+ newNightModeBrightness + "&lt;/nightModeBrightness&gt; \\n";
}
setBinaryState(action, argument, value);
break;
}
}
}
@Override
public void onServiceSubscribed(String service, boolean succeeded) {
logger.debug("WeMo {}: Subscription to service {} {}", getUDN(), service, succeeded ? "succeeded" : "failed");
subscriptionState.put(service, succeeded);
}
@Override
public void onValueReceived(String variable, String value, String service) {
logger.debug("Received pair '{}':'{}' (service '{}') for thing '{}'",
new Object[] { variable, value, service, this.getThing().getUID() });
updateStatus(ThingStatus.ONLINE);
this.stateMap.put(variable, value);
switch (variable) {
case "BinaryState":
State state = value.equals("0") ? OnOffType.OFF : OnOffType.ON;
logger.debug("State '{}' for device '{}' received", state, getThing().getUID());
updateState(CHANNEL_BRIGHTNESS, state);
if (state.equals(OnOffType.OFF)) {
updateState(CHANNEL_TIMERSTART, OnOffType.OFF);
}
break;
case "brightness":
logger.debug("brightness '{}' for device '{}' received", value, getThing().getUID());
int newBrightnessValue = Integer.valueOf(value);
State newBrightnessState = new PercentType(newBrightnessValue);
if (this.stateMap.get("BinaryState").equals("1")) {
updateState(CHANNEL_BRIGHTNESS, newBrightnessState);
}
currentBrightness = newBrightnessValue;
break;
case "fader":
logger.debug("fader '{}' for device '{}' received", value, getThing().getUID());
String[] splitFader = value.split(":");
if (splitFader[0] != null) {
int faderSeconds = Integer.valueOf(splitFader[0]);
State faderMinutes = new DecimalType(faderSeconds / 60);
logger.debug("faderTime '{} minutes' for device '{}' received", faderMinutes, getThing().getUID());
updateState(CHANNEL_FADERCOUNTDOWNTIME, faderMinutes);
}
if (splitFader[1] != null) {
State isTimerRunning = splitFader[1].equals("-1") ? OnOffType.OFF : OnOffType.ON;
logger.debug("isTimerRunning '{}' for device '{}' received", isTimerRunning, getThing().getUID());
updateState(CHANNEL_TIMERSTART, isTimerRunning);
if (isTimerRunning.equals(OnOffType.ON)) {
updateState(CHANNEL_STATE, OnOffType.ON);
}
}
if (splitFader[2] != null) {
State isFaderEnabled = splitFader[1].equals("0") ? OnOffType.OFF : OnOffType.ON;
logger.debug("isFaderEnabled '{}' for device '{}' received", isFaderEnabled, getThing().getUID());
updateState(CHANNEL_FADERENABLED, isFaderEnabled);
}
break;
case "nightMode":
State nightModeState = value.equals("0") ? OnOffType.OFF : OnOffType.ON;
currentNightModeState = value;
logger.debug("nightModeState '{}' for device '{}' received", nightModeState, getThing().getUID());
updateState(CHANNEL_NIGHTMODE, nightModeState);
break;
case "startTime":
State startTimeState = getDateTimeState(value);
logger.debug("startTimeState '{}' for device '{}' received", startTimeState, getThing().getUID());
updateState(CHANNEL_STARTTIME, startTimeState);
break;
case "endTime":
State endTimeState = getDateTimeState(value);
logger.debug("endTimeState '{}' for device '{}' received", endTimeState, getThing().getUID());
updateState(CHANNEL_ENDTIME, endTimeState);
break;
case "nightModeBrightness":
int nightModeBrightnessValue = Integer.valueOf(value);
currentNightModeBrightness = nightModeBrightnessValue;
State nightModeBrightnessState = new PercentType(nightModeBrightnessValue);
logger.debug("nightModeBrightnessState '{}' for device '{}' received", nightModeBrightnessState,
getThing().getUID());
updateState(CHANNEL_NIGHTMODEBRIGHTNESS, nightModeBrightnessState);
break;
}
}
private synchronized void onSubscription() {
if (service.isRegistered(this)) {
logger.debug("Checking WeMo GENA subscription for '{}'", this);
String subscription = "basicevent1";
if ((subscriptionState.get(subscription) == null) || !subscriptionState.get(subscription).booleanValue()) {
logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(), subscription);
service.addSubscription(this, subscription, SUBSCRIPTION_DURATION);
subscriptionState.put(subscription, true);
}
} else {
logger.debug("Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE",
this);
}
}
private synchronized void removeSubscription() {
logger.debug("Removing WeMo GENA subscription for '{}'", this);
if (service.isRegistered(this)) {
String subscription = "basicevent1";
if ((subscriptionState.get(subscription) != null) && subscriptionState.get(subscription).booleanValue()) {
logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
service.removeSubscription(this, subscription);
}
subscriptionState = new HashMap<>();
service.unregisterParticipant(this);
}
}
private synchronized void onUpdate() {
if (refreshJob == null || refreshJob.isCancelled()) {
Configuration config = getThing().getConfiguration();
int refreshInterval = DEFAULT_REFRESH_INTERVAL;
Object refreshConfig = config.get("refresh");
if (refreshConfig != null) {
refreshInterval = ((BigDecimal) refreshConfig).intValue();
}
refreshJob = scheduler.scheduleWithFixedDelay(refreshRunnable, 10, refreshInterval, TimeUnit.SECONDS);
}
}
private boolean isUpnpDeviceRegistered() {
return service.isRegistered(this);
}
@Override
public String getUDN() {
return (String) this.getThing().getConfiguration().get(UDN);
}
/**
* The {@link updateWemoState} polls the actual state of a WeMo device and
* calls {@link onValueReceived} to update the statemap and channels..
*
*/
protected void updateWemoState() {
String action = "GetBinaryState";
String variable = null;
String actionService = "basicevent";
String value = null;
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
String content = "<?xml version=\"1.0\"?>"
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
+ "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:" + actionService + ":1\">" + "</u:"
+ action + ">" + "</s:Body>" + "</s:Envelope>";
try {
String wemoURL = getWemoURL(actionService);
if (wemoURL != null) {
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null) {
logger.trace("State response '{}' for device '{}' received", wemoCallResponse, getThing().getUID());
value = StringUtils.substringBetween(wemoCallResponse, "<BinaryState>", "</BinaryState>");
if (value != null) {
variable = "BinaryState";
logger.trace("New state '{}' for device '{}' received", value, getThing().getUID());
this.onValueReceived(variable, value, actionService + "1");
}
value = StringUtils.substringBetween(wemoCallResponse, "<brightness>", "</brightness>");
if (value != null) {
variable = "brightness";
logger.trace("New brightness '{}' for device '{}' received", value, getThing().getUID());
this.onValueReceived(variable, value, actionService + "1");
}
value = StringUtils.substringBetween(wemoCallResponse, "<fader>", "</fader>");
if (value != null) {
variable = "fader";
logger.trace("New fader value '{}' for device '{}' received", value, getThing().getUID());
this.onValueReceived(variable, value, actionService + "1");
}
}
}
} catch (Exception e) {
logger.debug("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
updateStatus(ThingStatus.ONLINE);
action = "GetNightModeConfiguration";
variable = null;
value = null;
soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
content = "<?xml version=\"1.0\"?>"
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
+ "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:" + actionService + ":1\">" + "</u:"
+ action + ">" + "</s:Body>" + "</s:Envelope>";
try {
String wemoURL = getWemoURL(actionService);
if (wemoURL != null) {
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null) {
logger.trace("GetNightModeConfiguration response '{}' for device '{}' received", wemoCallResponse,
getThing().getUID());
value = StringUtils.substringBetween(wemoCallResponse, "<startTime>", "</startTime>");
if (value != null) {
variable = "startTime";
logger.trace("New startTime '{}' for device '{}' received", value, getThing().getUID());
this.onValueReceived(variable, value, actionService + "1");
}
value = StringUtils.substringBetween(wemoCallResponse, "<endTime>", "</endTime>");
if (value != null) {
variable = "endTime";
logger.trace("New endTime '{}' for device '{}' received", value, getThing().getUID());
this.onValueReceived(variable, value, actionService + "1");
}
value = StringUtils.substringBetween(wemoCallResponse, "<nightMode>", "</nightMode>");
if (value != null) {
variable = "nightMode";
logger.trace("New nightMode state '{}' for device '{}' received", value, getThing().getUID());
this.onValueReceived(variable, value, actionService + "1");
}
value = StringUtils.substringBetween(wemoCallResponse, "<nightModeBrightness>",
"</nightModeBrightness>");
if (value != null) {
variable = "nightModeBrightness";
logger.trace("New nightModeBrightness '{}' for device '{}' received", value,
getThing().getUID());
this.onValueReceived(variable, value, actionService + "1");
}
}
}
} catch (Exception e) {
logger.debug("Failed to get actual NightMode state for device '{}': {}", getThing().getUID(),
e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
updateStatus(ThingStatus.ONLINE);
}
public String getWemoURL(String actionService) {
URL descriptorURL = service.getDescriptorURL(this);
String wemoURL = null;
if (descriptorURL != null) {
String deviceURL = StringUtils.substringBefore(descriptorURL.toString(), "/setup.xml");
wemoURL = deviceURL + "/upnp/control/" + actionService + "1";
return wemoURL;
}
return null;
}
@SuppressWarnings("null")
public State getDateTimeState(String attributeValue) {
if (attributeValue != null) {
long value = 0;
try {
value = Long.parseLong(attributeValue) * 1000; // convert s to ms
} catch (NumberFormatException e) {
logger.warn("Unable to parse attributeValue '{}' for device '{}'; expected long", attributeValue,
getThing().getUID());
return null;
}
ZonedDateTime zoned = ZonedDateTime.ofInstant(Instant.ofEpochMilli(value),
TimeZone.getDefault().toZoneId());
State dateTimeState = new DateTimeType(zoned);
if (dateTimeState != null) {
logger.trace("New attribute '{}' received", dateTimeState);
return dateTimeState;
}
}
return null;
}
public void setBinaryState(String action, String argument, String value) {
try {
String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
String content = "<?xml version=\"1.0\"?>"
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
+ "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:basicevent:1\">" + "<" + argument
+ ">" + value + "</" + argument + ">" + "</u:" + action + ">" + "</s:Body>" + "</s:Envelope>";
String wemoURL = getWemoURL("basicevent");
if (wemoURL != null) {
logger.trace("About to send content to Dimmer {}", content);
wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
}
} catch (Exception e) {
logger.debug("Failed to set binaryState '{}' for device '{}': {}", value, getThing().getUID(),
e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
public void setTimerStart(String action, String argument, String value) {
try {
String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
String content = "<?xml version=\"1.0\"?>"
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
+ "<s:Body>" + "<u:SetBinaryState xmlns:u=\"urn:Belkin:service:basicevent:1\">" + value
+ "</u:SetBinaryState>" + "</s:Body>" + "</s:Envelope>";
String wemoURL = getWemoURL("basicevent");
if (wemoURL != null) {
logger.trace("About to send content to Dimmer {}", content);
wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
}
} catch (Exception e) {
logger.debug("Failed to set binaryState '{}' for device '{}': {}", value, getThing().getUID(),
e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
@Override
public void onStatusChanged(boolean status) {
}
}

View File

@@ -0,0 +1,497 @@
/**
* 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.wemo.internal.handler;
import static org.openhab.binding.wemo.internal.WemoBindingConstants.*;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URL;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.wemo.internal.http.WemoHttpCall;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.io.net.http.HttpUtil;
import org.openhab.core.io.transport.upnp.UpnpIOParticipant;
import org.openhab.core.io.transport.upnp.UpnpIOService;
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.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link WemoHandler} is responsible for handling commands, which are
* sent to one of the channels and to update their states.
*
* @author Hans-Jörg Merk - Initial contribution; Added support for WeMo Insight energy measurement
* @author Kai Kreuzer - some refactoring for performance and simplification
* @author Stefan Bußweiler - Added new thing status handling
* @author Erdoan Hadzhiyusein - Adapted the class to work with the new DateTimeType
* @author Mihir Patil - Added standby switch
*/
public class WemoHandler extends AbstractWemoHandler implements UpnpIOParticipant {
private final Logger logger = LoggerFactory.getLogger(WemoHandler.class);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Stream
.of(THING_TYPE_SOCKET, THING_TYPE_INSIGHT, THING_TYPE_LIGHTSWITCH, THING_TYPE_MOTION)
.collect(Collectors.toSet());
private Map<String, Boolean> subscriptionState = new HashMap<>();
private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
// protected static final int SUBSCRIPTION_DURATION = WemoBindingConstants.SUBSCRIPTION_DURATION;
private UpnpIOService service;
/**
* The default refresh interval in Seconds.
*/
private final int DEFAULT_REFRESH_INTERVAL = 120;
private ScheduledFuture<?> refreshJob;
private final Runnable refreshRunnable = new Runnable() {
@Override
public void run() {
try {
if (!isUpnpDeviceRegistered()) {
logger.debug("WeMo UPnP device {} not yet registered", getUDN());
}
updateWemoState();
onSubscription();
} catch (Exception e) {
logger.debug("Exception during poll", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
};
public WemoHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemohttpCaller) {
super(thing);
this.wemoHttpCaller = wemohttpCaller;
logger.debug("Creating a WemoHandler for thing '{}'", getThing().getUID());
if (upnpIOService != null) {
this.service = upnpIOService;
} else {
logger.debug("upnpIOService not set.");
}
}
@Override
public void initialize() {
Configuration configuration = getConfig();
if (configuration.get("udn") != null) {
logger.debug("Initializing WemoHandler for UDN '{}'", configuration.get("udn"));
service.registerParticipant(this);
onSubscription();
onUpdate();
updateStatus(ThingStatus.ONLINE);
} else {
logger.debug("Cannot initalize WemoHandler. UDN not set.");
}
}
@Override
public void dispose() {
logger.debug("WeMoHandler disposed.");
removeSubscription();
if (refreshJob != null && !refreshJob.isCancelled()) {
refreshJob.cancel(true);
refreshJob = null;
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.trace("Command '{}' received for channel '{}'", command, channelUID);
if (command instanceof RefreshType) {
try {
updateWemoState();
} catch (Exception e) {
logger.debug("Exception during poll", e);
}
} else if (channelUID.getId().equals(CHANNEL_STATE)) {
if (command instanceof OnOffType) {
try {
String binaryState = null;
if (command.equals(OnOffType.ON)) {
binaryState = "1";
} else if (command.equals(OnOffType.OFF)) {
binaryState = "0";
}
String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
String content = "<?xml version=\"1.0\"?>"
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
+ "<s:Body>" + "<u:SetBinaryState xmlns:u=\"urn:Belkin:service:basicevent:1\">"
+ "<BinaryState>" + binaryState + "</BinaryState>" + "</u:SetBinaryState>" + "</s:Body>"
+ "</s:Envelope>";
String wemoURL = getWemoURL("basicevent");
if (wemoURL != null) {
wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
}
} catch (Exception e) {
logger.error("Failed to send command '{}' for device '{}': {}", command, getThing().getUID(),
e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
updateStatus(ThingStatus.ONLINE);
}
}
}
@Override
public void onServiceSubscribed(String service, boolean succeeded) {
logger.debug("WeMo {}: Subscription to service {} {}", getUDN(), service, succeeded ? "succeeded" : "failed");
subscriptionState.put(service, succeeded);
}
@SuppressWarnings("null")
@Override
public void onValueReceived(String variable, String value, String service) {
logger.debug("Received pair '{}':'{}' (service '{}') for thing '{}'",
new Object[] { variable, value, service, this.getThing().getUID() });
updateStatus(ThingStatus.ONLINE);
this.stateMap.put(variable, value);
if (getThing().getThingTypeUID().getId().equals("insight")) {
String insightParams = stateMap.get("InsightParams");
if (insightParams != null) {
String[] splitInsightParams = insightParams.split("\\|");
if (splitInsightParams[0] != null) {
OnOffType binaryState = null;
binaryState = splitInsightParams[0].equals("0") ? OnOffType.OFF : OnOffType.ON;
if (binaryState != null) {
logger.trace("New InsightParam binaryState '{}' for device '{}' received", binaryState,
getThing().getUID());
updateState(CHANNEL_STATE, binaryState);
}
}
long lastChangedAt = 0;
try {
lastChangedAt = Long.parseLong(splitInsightParams[1]) * 1000; // convert s to ms
} catch (NumberFormatException e) {
logger.error("Unable to parse lastChangedAt value '{}' for device '{}'; expected long",
splitInsightParams[1], getThing().getUID());
}
ZonedDateTime zoned = ZonedDateTime.ofInstant(Instant.ofEpochMilli(lastChangedAt),
TimeZone.getDefault().toZoneId());
State lastChangedAtState = new DateTimeType(zoned);
if (lastChangedAt != 0) {
logger.trace("New InsightParam lastChangedAt '{}' for device '{}' received", lastChangedAtState,
getThing().getUID());
updateState(CHANNEL_LASTCHANGEDAT, lastChangedAtState);
}
State lastOnFor = DecimalType.valueOf(splitInsightParams[2]);
if (lastOnFor != null) {
logger.trace("New InsightParam lastOnFor '{}' for device '{}' received", lastOnFor,
getThing().getUID());
updateState(CHANNEL_LASTONFOR, lastOnFor);
}
State onToday = DecimalType.valueOf(splitInsightParams[3]);
if (onToday != null) {
logger.trace("New InsightParam onToday '{}' for device '{}' received", onToday,
getThing().getUID());
updateState(CHANNEL_ONTODAY, onToday);
}
State onTotal = DecimalType.valueOf(splitInsightParams[4]);
if (onTotal != null) {
logger.trace("New InsightParam onTotal '{}' for device '{}' received", onTotal,
getThing().getUID());
updateState(CHANNEL_ONTOTAL, onTotal);
}
State timespan = DecimalType.valueOf(splitInsightParams[5]);
if (timespan != null) {
logger.trace("New InsightParam timespan '{}' for device '{}' received", timespan,
getThing().getUID());
updateState(CHANNEL_TIMESPAN, timespan);
}
State averagePower = DecimalType.valueOf(splitInsightParams[6]); // natively given in W
if (averagePower != null) {
logger.trace("New InsightParam averagePower '{}' for device '{}' received", averagePower,
getThing().getUID());
updateState(CHANNEL_AVERAGEPOWER, averagePower);
}
BigDecimal currentMW = new BigDecimal(splitInsightParams[7]);
State currentPower = new DecimalType(currentMW.divide(new BigDecimal(1000), RoundingMode.HALF_UP)); // recalculate
// mW to W
if (currentPower != null) {
logger.trace("New InsightParam currentPower '{}' for device '{}' received", currentPower,
getThing().getUID());
updateState(CHANNEL_CURRENTPOWER, currentPower);
}
BigDecimal energyTodayMWMin = new BigDecimal(splitInsightParams[8]);
// recalculate mW-mins to Wh
State energyToday = new DecimalType(
energyTodayMWMin.divide(new BigDecimal(60000), RoundingMode.HALF_UP));
if (energyToday != null) {
logger.trace("New InsightParam energyToday '{}' for device '{}' received", energyToday,
getThing().getUID());
updateState(CHANNEL_ENERGYTODAY, energyToday);
}
BigDecimal energyTotalMWMin = new BigDecimal(splitInsightParams[9]);
// recalculate mW-mins to Wh
State energyTotal = new DecimalType(
energyTotalMWMin.divide(new BigDecimal(60000), RoundingMode.HALF_UP));
if (energyTotal != null) {
logger.trace("New InsightParam energyTotal '{}' for device '{}' received", energyTotal,
getThing().getUID());
updateState(CHANNEL_ENERGYTOTAL, energyTotal);
}
BigDecimal standByLimitMW = new BigDecimal(splitInsightParams[10]);
State standByLimit = new DecimalType(standByLimitMW.divide(new BigDecimal(1000), RoundingMode.HALF_UP)); // recalculate
// mW to W
if (standByLimit != null) {
logger.trace("New InsightParam standByLimit '{}' for device '{}' received", standByLimit,
getThing().getUID());
updateState(CHANNEL_STANDBYLIMIT, standByLimit);
}
if (currentMW.divide(new BigDecimal(1000), RoundingMode.HALF_UP).intValue() > standByLimitMW
.divide(new BigDecimal(1000), RoundingMode.HALF_UP).intValue()) {
updateState(CHANNEL_ONSTANDBY, OnOffType.OFF);
} else {
updateState(CHANNEL_ONSTANDBY, OnOffType.ON);
}
}
} else {
State state = stateMap.get("BinaryState").equals("0") ? OnOffType.OFF : OnOffType.ON;
logger.debug("State '{}' for device '{}' received", state, getThing().getUID());
if (state != null) {
if (getThing().getThingTypeUID().getId().equals("motion")) {
updateState(CHANNEL_MOTIONDETECTION, state);
if (state.equals(OnOffType.ON)) {
State lastMotionDetected = new DateTimeType();
updateState(CHANNEL_LASTMOTIONDETECTED, lastMotionDetected);
}
} else {
updateState(CHANNEL_STATE, state);
}
}
}
}
private synchronized void onSubscription() {
if (service.isRegistered(this)) {
logger.debug("Checking WeMo GENA subscription for '{}'", this);
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
String subscription = "basicevent1";
if ((subscriptionState.get(subscription) == null) || !subscriptionState.get(subscription).booleanValue()) {
logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(), subscription);
service.addSubscription(this, subscription, SUBSCRIPTION_DURATION);
subscriptionState.put(subscription, true);
}
if (thingTypeUID.equals(THING_TYPE_INSIGHT)) {
subscription = "insight1";
if ((subscriptionState.get(subscription) == null)
|| !subscriptionState.get(subscription).booleanValue()) {
logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(),
subscription);
service.addSubscription(this, subscription, SUBSCRIPTION_DURATION);
subscriptionState.put(subscription, true);
}
}
} else {
logger.debug("Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE",
this);
}
}
private synchronized void removeSubscription() {
logger.debug("Removing WeMo GENA subscription for '{}'", this);
if (service.isRegistered(this)) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
String subscription = "basicevent1";
if ((subscriptionState.get(subscription) != null) && subscriptionState.get(subscription).booleanValue()) {
logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
service.removeSubscription(this, subscription);
}
if (thingTypeUID.equals(THING_TYPE_INSIGHT)) {
subscription = "insight1";
if ((subscriptionState.get(subscription) != null)
&& subscriptionState.get(subscription).booleanValue()) {
logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
service.removeSubscription(this, subscription);
}
}
subscriptionState = new HashMap<>();
service.unregisterParticipant(this);
}
}
private synchronized void onUpdate() {
if (refreshJob == null || refreshJob.isCancelled()) {
Configuration config = getThing().getConfiguration();
int refreshInterval = DEFAULT_REFRESH_INTERVAL;
Object refreshConfig = config.get("refresh");
if (refreshConfig != null) {
refreshInterval = ((BigDecimal) refreshConfig).intValue();
}
refreshJob = scheduler.scheduleWithFixedDelay(refreshRunnable, 0, refreshInterval, TimeUnit.SECONDS);
}
}
private boolean isUpnpDeviceRegistered() {
return service.isRegistered(this);
}
@Override
public String getUDN() {
return (String) this.getThing().getConfiguration().get(UDN);
}
/**
* The {@link updateWemoState} polls the actual state of a WeMo device and
* calls {@link onValueReceived} to update the statemap and channels..
*
*/
protected void updateWemoState() {
String action = "GetBinaryState";
String variable = "BinaryState";
String actionService = "basicevent";
String value = null;
if (getThing().getThingTypeUID().getId().equals("insight")) {
action = "GetInsightParams";
variable = "InsightParams";
actionService = "insight";
}
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
String content = "<?xml version=\"1.0\"?>"
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
+ "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:" + actionService + ":1\">" + "</u:"
+ action + ">" + "</s:Body>" + "</s:Envelope>";
try {
String wemoURL = getWemoURL(actionService);
if (wemoURL != null) {
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null) {
logger.trace("State response '{}' for device '{}' received", wemoCallResponse, getThing().getUID());
if (variable.equals("InsightParams")) {
value = StringUtils.substringBetween(wemoCallResponse, "<InsightParams>", "</InsightParams>");
} else {
value = StringUtils.substringBetween(wemoCallResponse, "<BinaryState>", "</BinaryState>");
}
if (value != null) {
logger.trace("New state '{}' for device '{}' received", value, getThing().getUID());
this.onValueReceived(variable, value, actionService + "1");
}
}
}
} catch (Exception e) {
logger.error("Failed to get actual state for device '{}': {}", getThing().getUID(), e.getMessage());
}
}
public String getWemoURL(String actionService) {
URL descriptorURL = service.getDescriptorURL(this);
int portCheckStart = 49151;
int portCheckStop = 49157;
String wemoURL = null;
String host = null;
String port = null;
if (descriptorURL != null) {
host = StringUtils.substringBetween(descriptorURL.toString(), "://", ":");
for (int i = portCheckStart; i < portCheckStop; i++) {
try {
boolean portFound = servicePing(host, i);
if (portFound) {
logger.trace("WeMo device {} responded at Port {}", getUDN(), i);
port = String.valueOf(i);
break;
}
} catch (Exception e) {
}
}
wemoURL = "http://" + host + ":" + port + "/upnp/control/" + actionService + "1";
logger.trace("WeMo url {}", wemoURL);
return wemoURL;
}
return wemoURL;
}
public boolean servicePing(String host, int port) {
logger.trace("Ping WeMo device at '{}:{}'", host, port);
try {
HttpUtil.executeUrl("GET", "http://" + host + ":" + port, 250);
} catch (IOException e) {
return false;
}
return true;
}
@Override
public void onStatusChanged(boolean status) {
}
}

View File

@@ -0,0 +1,576 @@
/**
* 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.wemo.internal.handler;
import static org.openhab.binding.wemo.internal.WemoBindingConstants.*;
import java.io.IOException;
import java.io.StringReader;
import java.math.BigDecimal;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.wemo.internal.http.WemoHttpCall;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.io.transport.upnp.UpnpIOParticipant;
import org.openhab.core.io.transport.upnp.UpnpIOService;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StringType;
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.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* The {@link WemoHolmesHandler} is responsible for handling commands, which are
* sent to one of the channels and to update their states.
*
* @author Hans-Jörg Merk - Initial contribution;
*/
public class WemoHolmesHandler extends AbstractWemoHandler implements UpnpIOParticipant {
private final Logger logger = LoggerFactory.getLogger(WemoHolmesHandler.class);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_PURIFIER);
/**
* The default refresh interval in Seconds.
*/
private static final int DEFAULT_REFRESH_INTERVAL_SECONDS = 120;
private static final int FILTER_LIFE_DAYS = 330;
private static final int FILTER_LIFE_MINS = FILTER_LIFE_DAYS * 24 * 60;
private final Map<String, Boolean> subscriptionState = new HashMap<>();
private final Map<String, String> stateMap = Collections.synchronizedMap(new HashMap<>());
private UpnpIOService service;
private ScheduledFuture<?> refreshJob;
private final Runnable refreshRunnable = () -> {
if (!isUpnpDeviceRegistered()) {
logger.debug("WeMo UPnP device {} not yet registered", getUDN());
} else {
updateWemoState();
onSubscription();
}
};
public WemoHolmesHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemohttpCaller) {
super(thing);
this.wemoHttpCaller = wemohttpCaller;
logger.debug("Creating a WemoHolmesHandler for thing '{}'", getThing().getUID());
if (upnpIOService != null) {
this.service = upnpIOService;
} else {
logger.debug("upnpIOService not set.");
}
}
@Override
public void initialize() {
Configuration configuration = getConfig();
if (configuration.get("udn") != null) {
logger.debug("Initializing WemoHolmesHandler for UDN '{}'", configuration.get("udn"));
service.registerParticipant(this);
onSubscription();
onUpdate();
updateStatus(ThingStatus.ONLINE);
} else {
logger.debug("Cannot initalize WemoHolmesHandler. UDN not set.");
}
}
@Override
public void dispose() {
logger.debug("WemoHolmesHandler disposed.");
removeSubscription();
if (refreshJob != null && !refreshJob.isCancelled()) {
refreshJob.cancel(true);
refreshJob = null;
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.trace("Command '{}' received for channel '{}'", command, channelUID);
String attribute = null;
String value = null;
if (command instanceof RefreshType) {
updateWemoState();
} else if (CHANNEL_PURIFIERMODE.equals(channelUID.getId())) {
attribute = "Mode";
String commandString = command.toString();
switch (commandString) {
case "OFF":
value = "0";
break;
case "LOW":
value = "1";
break;
case "MED":
value = "2";
break;
case "HIGH":
value = "3";
break;
case "AUTO":
value = "4";
break;
}
} else if (CHANNEL_IONIZER.equals(channelUID.getId())) {
attribute = "Ionizer";
if (OnOffType.ON.equals(command)) {
value = "1";
} else if (OnOffType.OFF.equals(command)) {
value = "0";
}
} else if (CHANNEL_HUMIDIFIERMODE.equals(channelUID.getId())) {
attribute = "FanMode";
String commandString = command.toString();
switch (commandString) {
case "OFF":
value = "0";
break;
case "MIN":
value = "1";
break;
case "LOW":
value = "2";
break;
case "MED":
value = "3";
break;
case "HIGH":
value = "4";
break;
case "MAX":
value = "5";
break;
}
} else if (CHANNEL_DESIREDHUMIDITY.equals(channelUID.getId())) {
attribute = "DesiredHumidity";
String commandString = command.toString();
switch (commandString) {
case "45":
value = "0";
break;
case "50":
value = "1";
break;
case "55":
value = "2";
break;
case "60":
value = "3";
break;
case "100":
value = "4";
break;
}
} else if (CHANNEL_HEATERMODE.equals(channelUID.getId())) {
attribute = "Mode";
String commandString = command.toString();
switch (commandString) {
case "OFF":
value = "0";
break;
case "FROSTPROTECT":
value = "1";
break;
case "HIGH":
value = "2";
break;
case "LOW":
value = "3";
break;
case "ECO":
value = "4";
break;
}
} else if (CHANNEL_TARGETTEMP.equals(channelUID.getId())) {
attribute = "SetTemperature";
value = command.toString();
}
try {
String soapHeader = "\"urn:Belkin:service:deviceevent:1#SetAttributes\"";
String content = "<?xml version=\"1.0\"?>"
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
+ "<s:Body>" + "<u:SetAttributes xmlns:u=\"urn:Belkin:service:deviceevent:1\">"
+ "<attributeList>&lt;attribute&gt;&lt;name&gt;" + attribute + "&lt;/name&gt;&lt;value&gt;" + value
+ "&lt;/value&gt;&lt;/attribute&gt;</attributeList>" + "</u:SetAttributes>" + "</s:Body>"
+ "</s:Envelope>";
String wemoURL = getWemoURL("deviceevent");
if (wemoURL != null) {
wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
}
} catch (RuntimeException e) {
logger.debug("Failed to send command '{}' for device '{}':", command, getThing().getUID(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
updateStatus(ThingStatus.ONLINE);
}
@Override
public void onServiceSubscribed(String service, boolean succeeded) {
logger.debug("WeMo {}: Subscription to service {} {}", getUDN(), service, succeeded ? "succeeded" : "failed");
subscriptionState.put(service, succeeded);
}
@Override
public void onValueReceived(String variable, String value, String service) {
logger.debug("Received pair '{}':'{}' (service '{}') for thing '{}'", variable, value, service,
this.getThing().getUID());
updateStatus(ThingStatus.ONLINE);
this.stateMap.put(variable, value);
}
private synchronized void onSubscription() {
if (service.isRegistered(this)) {
logger.debug("Checking WeMo GENA subscription for '{}'", this);
String subscription = "basicevent1";
if ((subscriptionState.get(subscription) == null) || !subscriptionState.get(subscription).booleanValue()) {
logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(), subscription);
service.addSubscription(this, subscription, SUBSCRIPTION_DURATION);
subscriptionState.put(subscription, true);
}
} else {
logger.debug("Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE",
this);
}
}
private synchronized void removeSubscription() {
logger.debug("Removing WeMo GENA subscription for '{}'", this);
if (service.isRegistered(this)) {
String subscription = "basicevent1";
if ((subscriptionState.get(subscription) != null) && subscriptionState.get(subscription).booleanValue()) {
logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), subscription);
service.removeSubscription(this, subscription);
}
subscriptionState.remove(subscription);
service.unregisterParticipant(this);
}
}
private synchronized void onUpdate() {
if (refreshJob == null || refreshJob.isCancelled()) {
Configuration config = getThing().getConfiguration();
int refreshInterval = DEFAULT_REFRESH_INTERVAL_SECONDS;
Object refreshConfig = config.get("refresh");
refreshInterval = refreshConfig == null ? DEFAULT_REFRESH_INTERVAL_SECONDS
: ((BigDecimal) refreshConfig).intValue();
refreshJob = scheduler.scheduleWithFixedDelay(refreshRunnable, 0, refreshInterval, TimeUnit.SECONDS);
}
}
private boolean isUpnpDeviceRegistered() {
return service.isRegistered(this);
}
@Override
public String getUDN() {
return (String) this.getThing().getConfiguration().get(UDN);
}
/**
* The {@link updateWemoState} polls the actual state of a WeMo device and
* calls {@link onValueReceived} to update the statemap and channels..
*
*/
protected void updateWemoState() {
String action = "GetAttributes";
String actionService = "deviceevent";
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
String content = "<?xml version=\"1.0\"?>"
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
+ "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:" + actionService + ":1\">" + "</u:"
+ action + ">" + "</s:Body>" + "</s:Envelope>";
try {
String wemoURL = getWemoURL(actionService);
if (wemoURL != null) {
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null) {
logger.trace("State response '{}' for device '{}' received", wemoCallResponse, getThing().getUID());
String stringParser = StringUtils.substringBetween(wemoCallResponse, "<attributeList>",
"</attributeList>");
// Due to Belkins bad response formatting, we need to run this twice.
stringParser = StringEscapeUtils.unescapeXml(stringParser);
stringParser = StringEscapeUtils.unescapeXml(stringParser);
logger.trace("AirPurifier response '{}' for device '{}' received", stringParser,
getThing().getUID());
stringParser = "<data>" + stringParser + "</data>";
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
InputSource is = new InputSource();
is.setCharacterStream(new StringReader(stringParser));
Document doc = db.parse(is);
NodeList nodes = doc.getElementsByTagName("attribute");
// iterate the attributes
for (int i = 0; i < nodes.getLength(); i++) {
Element element = (Element) nodes.item(i);
NodeList deviceIndex = element.getElementsByTagName("name");
Element line = (Element) deviceIndex.item(0);
String attributeName = getCharacterDataFromElement(line);
logger.trace("attributeName: {}", attributeName);
NodeList deviceID = element.getElementsByTagName("value");
line = (Element) deviceID.item(0);
String attributeValue = getCharacterDataFromElement(line);
logger.trace("attributeValue: {}", attributeValue);
State newMode = new StringType();
switch (attributeName) {
case "Mode":
if ("purifier".equals(getThing().getThingTypeUID().getId())) {
switch (attributeValue) {
case "0":
newMode = new StringType("OFF");
break;
case "1":
newMode = new StringType("LOW");
break;
case "2":
newMode = new StringType("MED");
break;
case "3":
newMode = new StringType("HIGH");
break;
case "4":
newMode = new StringType("AUTO");
break;
}
updateState(CHANNEL_PURIFIERMODE, newMode);
} else {
switch (attributeValue) {
case "0":
newMode = new StringType("OFF");
break;
case "1":
newMode = new StringType("FROSTPROTECT");
break;
case "2":
newMode = new StringType("HIGH");
break;
case "3":
newMode = new StringType("LOW");
break;
case "4":
newMode = new StringType("ECO");
break;
}
updateState(CHANNEL_HEATERMODE, newMode);
}
break;
case "Ionizer":
switch (attributeValue) {
case "0":
newMode = OnOffType.OFF;
break;
case "1":
newMode = OnOffType.ON;
break;
}
updateState(CHANNEL_IONIZER, newMode);
break;
case "AirQuality":
switch (attributeValue) {
case "0":
newMode = new StringType("POOR");
break;
case "1":
newMode = new StringType("MODERATE");
break;
case "2":
newMode = new StringType("GOOD");
break;
}
updateState(CHANNEL_AIRQUALITY, newMode);
break;
case "FilterLife":
int filterLife = Integer.valueOf(attributeValue);
if ("purifier".equals(getThing().getThingTypeUID().getId())) {
filterLife = Math.round((filterLife / FILTER_LIFE_MINS) * 100);
} else {
filterLife = Math.round((filterLife / 60480) * 100);
}
updateState(CHANNEL_FILTERLIFE, new PercentType(String.valueOf(filterLife)));
break;
case "ExpiredFilterTime":
switch (attributeValue) {
case "0":
newMode = OnOffType.OFF;
break;
case "1":
newMode = OnOffType.ON;
break;
}
updateState(CHANNEL_EXPIREDFILTERTIME, newMode);
break;
case "FilterPresent":
switch (attributeValue) {
case "0":
newMode = OnOffType.OFF;
break;
case "1":
newMode = OnOffType.ON;
break;
}
updateState(CHANNEL_FILTERPRESENT, newMode);
break;
case "FANMode":
switch (attributeValue) {
case "0":
newMode = new StringType("OFF");
break;
case "1":
newMode = new StringType("LOW");
break;
case "2":
newMode = new StringType("MED");
break;
case "3":
newMode = new StringType("HIGH");
break;
case "4":
newMode = new StringType("AUTO");
break;
}
updateState(CHANNEL_PURIFIERMODE, newMode);
break;
case "DesiredHumidity":
switch (attributeValue) {
case "0":
newMode = new PercentType("45");
break;
case "1":
newMode = new PercentType("50");
break;
case "2":
newMode = new PercentType("55");
break;
case "3":
newMode = new PercentType("60");
break;
case "4":
newMode = new PercentType("100");
break;
}
updateState(CHANNEL_DESIREDHUMIDITY, newMode);
break;
case "CurrentHumidity":
newMode = new StringType(attributeValue);
updateState(CHANNEL_CURRENTHUMIDITY, newMode);
break;
case "Temperature":
newMode = new StringType(attributeValue);
updateState(CHANNEL_CURRENTTEMP, newMode);
break;
case "SetTemperature":
newMode = new StringType(attributeValue);
updateState(CHANNEL_TARGETTEMP, newMode);
break;
case "AutoOffTime":
newMode = new StringType(attributeValue);
updateState(CHANNEL_AUTOOFFTIME, newMode);
break;
case "TimeRemaining":
newMode = new StringType(attributeValue);
updateState(CHANNEL_HEATINGREMAINING, newMode);
break;
}
}
}
}
} catch (RuntimeException | ParserConfigurationException | SAXException | IOException e) {
logger.debug("Failed to get actual state for device '{}':", getThing().getUID(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
updateStatus(ThingStatus.ONLINE);
}
public String getWemoURL(String actionService) {
URL descriptorURL = service.getDescriptorURL(this);
String wemoURL = null;
if (descriptorURL != null) {
String deviceURL = StringUtils.substringBefore(descriptorURL.toString(), "/setup.xml");
wemoURL = deviceURL + "/upnp/control/" + actionService + "1";
return wemoURL;
}
return null;
}
public static String getCharacterDataFromElement(Element e) {
Node child = e.getFirstChild();
if (child instanceof CharacterData) {
CharacterData cd = (CharacterData) child;
return cd.getData();
}
return "?";
}
@Override
public void onStatusChanged(boolean status) {
}
}

View File

@@ -0,0 +1,433 @@
/**
* 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.wemo.internal.handler;
import static org.openhab.binding.wemo.internal.WemoBindingConstants.*;
import java.math.BigDecimal;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.wemo.internal.http.WemoHttpCall;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.io.transport.upnp.UpnpIOParticipant;
import org.openhab.core.io.transport.upnp.UpnpIOService;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
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.ThingStatusInfo;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link WemoLightHandler} is the handler for a WeMo light, responsible for handling commands and state updates for the
* different channels of a WeMo light.
*
* @author Hans-Jörg Merk - Initial contribution
*/
public class WemoLightHandler extends AbstractWemoHandler implements UpnpIOParticipant {
private final Logger logger = LoggerFactory.getLogger(WemoLightHandler.class);
private Map<String, Boolean> subscriptionState = new HashMap<>();
private UpnpIOService service;
private WemoBridgeHandler wemoBridgeHandler;
private String wemoLightID;
private int currentBrightness;
/**
* Set dimming stepsize to 5%
*/
private static final int DIM_STEPSIZE = 5;
protected static final String SUBSCRIPTION = "bridge1";
protected static final int SUBSCRIPTION_DURATION = 600;
/**
* The default refresh interval in Seconds.
*/
private final int DEFAULT_REFRESH_INTERVAL = 60;
/**
* The default refresh initial delay in Seconds.
*/
private static final int DEFAULT_REFRESH_INITIAL_DELAY = 15;
private ScheduledFuture<?> refreshJob;
private final Runnable refreshRunnable = new Runnable() {
@Override
public void run() {
try {
if (!isUpnpDeviceRegistered()) {
logger.debug("WeMo UPnP device {} not yet registered", getUDN());
}
getDeviceState();
onSubscription();
} catch (Exception e) {
logger.debug("Exception during poll", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
};
public WemoLightHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpcaller) {
super(thing);
this.wemoHttpCaller = wemoHttpcaller;
if (upnpIOService != null) {
logger.debug("UPnPIOService '{}'", upnpIOService);
this.service = upnpIOService;
} else {
logger.debug("upnpIOService not set.");
}
}
@Override
public void initialize() {
// initialize() is only called if the required parameter 'deviceID' is available
wemoLightID = (String) getConfig().get(DEVICE_ID);
if (getBridge() != null) {
logger.debug("Initializing WemoLightHandler for LightID '{}'", wemoLightID);
if (getBridge().getStatus() == ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
onSubscription();
onUpdate();
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.BRIDGE_OFFLINE);
}
} else {
updateStatus(ThingStatus.OFFLINE);
}
}
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
if (bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) {
updateStatus(ThingStatus.ONLINE);
onSubscription();
onUpdate();
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.BRIDGE_OFFLINE);
if (refreshJob != null && !refreshJob.isCancelled()) {
refreshJob.cancel(true);
refreshJob = null;
}
}
}
@Override
public void dispose() {
logger.debug("WeMoLightHandler disposed.");
removeSubscription();
if (refreshJob != null && !refreshJob.isCancelled()) {
refreshJob.cancel(true);
refreshJob = null;
}
}
private synchronized WemoBridgeHandler getWemoBridgeHandler() {
if (this.wemoBridgeHandler == null) {
Bridge bridge = getBridge();
if (bridge == null) {
logger.error("Required bridge not defined for device {}.", wemoLightID);
return null;
}
ThingHandler handler = bridge.getHandler();
if (handler instanceof WemoBridgeHandler) {
this.wemoBridgeHandler = (WemoBridgeHandler) handler;
} else {
logger.debug("No available bridge handler found for {} bridge {} .", wemoLightID, bridge.getUID());
return null;
}
}
return this.wemoBridgeHandler;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
try {
getDeviceState();
} catch (Exception e) {
logger.debug("Exception during poll", e);
}
} else {
Configuration configuration = getConfig();
configuration.get(DEVICE_ID);
WemoBridgeHandler wemoBridge = getWemoBridgeHandler();
if (wemoBridge == null) {
logger.debug("wemoBridgeHandler not found, cannot handle command");
return;
}
String devUDN = "uuid:" + wemoBridge.getThing().getConfiguration().get(UDN).toString();
logger.trace("WeMo Bridge to send command to : {}", devUDN);
String value = null;
String capability = null;
switch (channelUID.getId()) {
case CHANNEL_BRIGHTNESS:
capability = "10008";
if (command instanceof PercentType) {
int newBrightness = ((PercentType) command).intValue();
logger.trace("wemoLight received Value {}", newBrightness);
int value1 = Math.round(newBrightness * 255 / 100);
value = value1 + ":0";
currentBrightness = newBrightness;
} else if (command instanceof OnOffType) {
switch (command.toString()) {
case "ON":
value = "255:0";
break;
case "OFF":
value = "0:0";
break;
}
} else if (command instanceof IncreaseDecreaseType) {
int newBrightness;
switch (command.toString()) {
case "INCREASE":
currentBrightness = currentBrightness + DIM_STEPSIZE;
newBrightness = Math.round(currentBrightness * 255 / 100);
if (newBrightness > 255) {
newBrightness = 255;
}
value = newBrightness + ":0";
break;
case "DECREASE":
currentBrightness = currentBrightness - DIM_STEPSIZE;
newBrightness = Math.round(currentBrightness * 255 / 100);
if (newBrightness < 0) {
newBrightness = 0;
}
value = newBrightness + ":0";
break;
}
}
break;
case CHANNEL_STATE:
capability = "10006";
switch (command.toString()) {
case "ON":
value = "1";
break;
case "OFF":
value = "0";
break;
}
break;
}
try {
String soapHeader = "\"urn:Belkin:service:bridge:1#SetDeviceStatus\"";
String content = "<?xml version=\"1.0\"?>"
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
+ "<s:Body>" + "<u:SetDeviceStatus xmlns:u=\"urn:Belkin:service:bridge:1\">"
+ "<DeviceStatusList>"
+ "&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&lt;DeviceStatus&gt;&lt;DeviceID&gt;"
+ wemoLightID
+ "&lt;/DeviceID&gt;&lt;IsGroupAction&gt;NO&lt;/IsGroupAction&gt;&lt;CapabilityID&gt;"
+ capability + "&lt;/CapabilityID&gt;&lt;CapabilityValue&gt;" + value
+ "&lt;/CapabilityValue&gt;&lt;/DeviceStatus&gt;" + "</DeviceStatusList>"
+ "</u:SetDeviceStatus>" + "</s:Body>" + "</s:Envelope>";
String wemoURL = getWemoURL();
if (wemoURL != null && capability != null && value != null) {
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null) {
if (capability.equals("10008")) {
OnOffType binaryState = null;
binaryState = value.equals("0") ? OnOffType.OFF : OnOffType.ON;
updateState(CHANNEL_STATE, binaryState);
}
}
}
} catch (Exception e) {
throw new IllegalStateException("Could not send command to WeMo Bridge", e);
}
}
}
@Override
public String getUDN() {
WemoBridgeHandler wemoBridge = getWemoBridgeHandler();
if (wemoBridge == null) {
logger.debug("wemoBridgeHandler not found");
return null;
}
return (String) wemoBridge.getThing().getConfiguration().get(UDN);
}
/**
* The {@link getDeviceState} is used for polling the actual state of a WeMo Light and updating the according
* channel states.
*/
public void getDeviceState() {
logger.debug("Request actual state for LightID '{}'", wemoLightID);
try {
String soapHeader = "\"urn:Belkin:service:bridge:1#GetDeviceStatus\"";
String content = "<?xml version=\"1.0\"?>"
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
+ "<s:Body>" + "<u:GetDeviceStatus xmlns:u=\"urn:Belkin:service:bridge:1\">" + "<DeviceIDs>"
+ wemoLightID + "</DeviceIDs>" + "</u:GetDeviceStatus>" + "</s:Body>" + "</s:Envelope>";
String wemoURL = getWemoURL();
if (wemoURL != null) {
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null) {
wemoCallResponse = StringEscapeUtils.unescapeXml(wemoCallResponse);
String response = StringUtils.substringBetween(wemoCallResponse, "<CapabilityValue>",
"</CapabilityValue>");
logger.trace("wemoNewLightState = {}", response);
String[] splitResponse = response.split(",");
if (splitResponse[0] != null) {
OnOffType binaryState = null;
binaryState = splitResponse[0].equals("0") ? OnOffType.OFF : OnOffType.ON;
updateState(CHANNEL_STATE, binaryState);
}
if (splitResponse[1] != null) {
String splitBrightness[] = splitResponse[1].split(":");
if (splitBrightness[0] != null) {
int newBrightnessValue = Integer.valueOf(splitBrightness[0]);
int newBrightness = Math.round(newBrightnessValue * 100 / 255);
logger.trace("newBrightness = {}", newBrightness);
State newBrightnessState = new PercentType(newBrightness);
updateState(CHANNEL_BRIGHTNESS, newBrightnessState);
currentBrightness = newBrightness;
}
}
}
}
} catch (Exception e) {
throw new IllegalStateException("Could not retrieve new Wemo light state", e);
}
}
@Override
public void onServiceSubscribed(String service, boolean succeeded) {
}
@Override
public void onValueReceived(String variable, String value, String service) {
logger.trace("Received pair '{}':'{}' (service '{}') for thing '{}'",
new Object[] { variable, value, service, this.getThing().getUID() });
String capabilityId = StringUtils.substringBetween(value, "<CapabilityId>", "</CapabilityId>");
String newValue = StringUtils.substringBetween(value, "<Value>", "</Value>");
switch (capabilityId) {
case "10006":
OnOffType binaryState = null;
binaryState = newValue.equals("0") ? OnOffType.OFF : OnOffType.ON;
updateState(CHANNEL_STATE, binaryState);
break;
case "10008":
String splitValue[] = newValue.split(":");
if (splitValue[0] != null) {
int newBrightnessValue = Integer.valueOf(splitValue[0]);
int newBrightness = Math.round(newBrightnessValue * 100 / 255);
State newBrightnessState = new PercentType(newBrightness);
updateState(CHANNEL_BRIGHTNESS, newBrightnessState);
currentBrightness = newBrightness;
}
break;
}
}
@Override
public void onStatusChanged(boolean status) {
}
private synchronized void onSubscription() {
if (service.isRegistered(this)) {
logger.debug("Checking WeMo GENA subscription for '{}'", this);
if ((subscriptionState.get(SUBSCRIPTION) == null) || !subscriptionState.get(SUBSCRIPTION).booleanValue()) {
logger.debug("Setting up GENA subscription {}: Subscribing to service {}...", getUDN(), SUBSCRIPTION);
service.addSubscription(this, SUBSCRIPTION, SUBSCRIPTION_DURATION);
subscriptionState.put(SUBSCRIPTION, true);
}
} else {
logger.debug("Setting up WeMo GENA subscription for '{}' FAILED - service.isRegistered(this) is FALSE",
this);
}
}
private synchronized void removeSubscription() {
if (service.isRegistered(this)) {
logger.debug("Removing WeMo GENA subscription for '{}'", this);
if ((subscriptionState.get(SUBSCRIPTION) != null) && subscriptionState.get(SUBSCRIPTION).booleanValue()) {
logger.debug("WeMo {}: Unsubscribing from service {}...", getUDN(), SUBSCRIPTION);
service.removeSubscription(this, SUBSCRIPTION);
}
subscriptionState = new HashMap<>();
service.unregisterParticipant(this);
}
}
private synchronized void onUpdate() {
if (refreshJob == null || refreshJob.isCancelled()) {
Configuration config = getThing().getConfiguration();
int refreshInterval = DEFAULT_REFRESH_INTERVAL;
Object refreshConfig = config.get("refresh");
if (refreshConfig != null) {
refreshInterval = ((BigDecimal) refreshConfig).intValue();
}
logger.trace("Start polling job for LightID '{}'", wemoLightID);
refreshJob = scheduler.scheduleWithFixedDelay(refreshRunnable, DEFAULT_REFRESH_INITIAL_DELAY,
refreshInterval, TimeUnit.SECONDS);
}
}
private boolean isUpnpDeviceRegistered() {
return service.isRegistered(this);
}
public String getWemoURL() {
URL descriptorURL = service.getDescriptorURL(this);
String wemoURL = null;
if (descriptorURL != null) {
String deviceURL = StringUtils.substringBefore(descriptorURL.toString(), "/setup.xml");
wemoURL = deviceURL + "/upnp/control/bridge1";
return wemoURL;
}
return null;
}
}

View File

@@ -0,0 +1,306 @@
/**
* 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.wemo.internal.handler;
import static org.openhab.binding.wemo.internal.WemoBindingConstants.*;
import java.io.StringReader;
import java.math.BigDecimal;
import java.net.URL;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.wemo.internal.http.WemoHttpCall;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.io.transport.upnp.UpnpIOParticipant;
import org.openhab.core.io.transport.upnp.UpnpIOService;
import org.openhab.core.library.types.OnOffType;
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.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
/**
* The {@link WemoMakerHandler} is responsible for handling commands, which are
* sent to one of the channels and to update their states.
*
* @author Hans-Jörg Merk - Initial contribution
*/
public class WemoMakerHandler extends AbstractWemoHandler implements UpnpIOParticipant {
private final Logger logger = LoggerFactory.getLogger(WemoMakerHandler.class);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_MAKER);
private UpnpIOService service;
/**
* The default refresh interval in Seconds.
*/
private final int DEFAULT_REFRESH_INTERVAL = 15;
private ScheduledFuture<?> refreshJob;
private final Runnable refreshRunnable = new Runnable() {
@Override
public void run() {
try {
updateWemoState();
} catch (Exception e) {
logger.debug("Exception during poll", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
};
public WemoMakerHandler(Thing thing, UpnpIOService upnpIOService, WemoHttpCall wemoHttpcaller) {
super(thing);
this.wemoHttpCaller = wemoHttpcaller;
logger.debug("Creating a WemoMakerHandler for thing '{}'", getThing().getUID());
if (upnpIOService != null) {
this.service = upnpIOService;
} else {
logger.debug("upnpIOService not set.");
}
}
@Override
public void initialize() {
Configuration configuration = getConfig();
if (configuration.get("udn") != null) {
logger.debug("Initializing WemoMakerHandler for UDN '{}'", configuration.get("udn"));
onUpdate();
updateStatus(ThingStatus.ONLINE);
} else {
logger.debug("Cannot initalize WemoMakerHandler. UDN not set.");
}
}
@Override
public void dispose() {
logger.debug("WeMoMakerHandler disposed.");
if (refreshJob != null && !refreshJob.isCancelled()) {
refreshJob.cancel(true);
refreshJob = null;
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.trace("Command '{}' received for channel '{}'", command, channelUID);
if (command instanceof RefreshType) {
try {
updateWemoState();
} catch (Exception e) {
logger.debug("Exception during poll", e);
}
} else if (channelUID.getId().equals(CHANNEL_RELAY)) {
if (command instanceof OnOffType) {
try {
String binaryState = null;
if (command.equals(OnOffType.ON)) {
binaryState = "1";
} else if (command.equals(OnOffType.OFF)) {
binaryState = "0";
}
String soapHeader = "\"urn:Belkin:service:basicevent:1#SetBinaryState\"";
String content = "<?xml version=\"1.0\"?>"
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
+ "<s:Body>" + "<u:SetBinaryState xmlns:u=\"urn:Belkin:service:basicevent:1\">"
+ "<BinaryState>" + binaryState + "</BinaryState>" + "</u:SetBinaryState>" + "</s:Body>"
+ "</s:Envelope>";
String wemoURL = getWemoURL("basicevent");
if (wemoURL != null) {
@SuppressWarnings("unused")
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
}
} catch (Exception e) {
logger.error("Failed to send command '{}' for device '{}' ", command, getThing().getUID(), e);
}
}
}
}
@SuppressWarnings("unused")
private synchronized void onSubscription() {
}
@SuppressWarnings("unused")
private synchronized void removeSubscription() {
}
private synchronized void onUpdate() {
if (service.isRegistered(this)) {
if (refreshJob == null || refreshJob.isCancelled()) {
Configuration config = getThing().getConfiguration();
int refreshInterval = DEFAULT_REFRESH_INTERVAL;
Object refreshConfig = config.get("refresh");
if (refreshConfig != null) {
refreshInterval = ((BigDecimal) refreshConfig).intValue();
}
refreshJob = scheduler.scheduleWithFixedDelay(refreshRunnable, 0, refreshInterval, TimeUnit.SECONDS);
}
}
}
@Override
public String getUDN() {
return (String) this.getThing().getConfiguration().get(UDN);
}
/**
* The {@link updateWemoState} polls the actual state of a WeMo Maker.
*/
@SuppressWarnings("null")
protected void updateWemoState() {
String action = "GetAttributes";
String actionService = "deviceevent";
String soapHeader = "\"urn:Belkin:service:" + actionService + ":1#" + action + "\"";
String content = "<?xml version=\"1.0\"?>"
+ "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
+ "<s:Body>" + "<u:" + action + " xmlns:u=\"urn:Belkin:service:" + actionService + ":1\">" + "</u:"
+ action + ">" + "</s:Body>" + "</s:Envelope>";
try {
String wemoURL = getWemoURL(actionService);
if (wemoURL != null) {
String wemoCallResponse = wemoHttpCaller.executeCall(wemoURL, soapHeader, content);
if (wemoCallResponse != null) {
try {
String stringParser = StringUtils.substringBetween(wemoCallResponse, "<attributeList>",
"</attributeList>");
// Due to Belkins bad response formatting, we need to run this twice.
stringParser = StringEscapeUtils.unescapeXml(stringParser);
stringParser = StringEscapeUtils.unescapeXml(stringParser);
logger.trace("Maker response '{}' for device '{}' received", stringParser, getThing().getUID());
stringParser = "<data>" + stringParser + "</data>";
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
InputSource is = new InputSource();
is.setCharacterStream(new StringReader(stringParser));
Document doc = db.parse(is);
NodeList nodes = doc.getElementsByTagName("attribute");
// iterate the attributes
for (int i = 0; i < nodes.getLength(); i++) {
Element element = (Element) nodes.item(i);
NodeList deviceIndex = element.getElementsByTagName("name");
Element line = (Element) deviceIndex.item(0);
String attributeName = getCharacterDataFromElement(line);
logger.trace("attributeName: {}", attributeName);
NodeList deviceID = element.getElementsByTagName("value");
line = (Element) deviceID.item(0);
String attributeValue = getCharacterDataFromElement(line);
logger.trace("attributeValue: {}", attributeValue);
switch (attributeName) {
case "Switch":
State relayState = attributeValue.equals("0") ? OnOffType.OFF : OnOffType.ON;
if (relayState != null) {
logger.debug("New relayState '{}' for device '{}' received", relayState,
getThing().getUID());
updateState(CHANNEL_RELAY, relayState);
}
break;
case "Sensor":
State sensorState = attributeValue.equals("1") ? OnOffType.OFF : OnOffType.ON;
if (sensorState != null) {
logger.debug("New sensorState '{}' for device '{}' received", sensorState,
getThing().getUID());
updateState(CHANNEL_SENSOR, sensorState);
}
break;
}
}
} catch (Exception e) {
logger.error("Failed to parse attributeList for WeMo Maker '{}'", this.getThing().getUID(), e);
}
}
}
} catch (Exception e) {
logger.error("Failed to get attributes for device '{}'", getThing().getUID(), e);
}
}
public String getWemoURL(String actionService) {
URL descriptorURL = service.getDescriptorURL(this);
String wemoURL = null;
if (descriptorURL != null) {
String deviceURL = StringUtils.substringBefore(descriptorURL.toString(), "/setup.xml");
wemoURL = deviceURL + "/upnp/control/" + actionService + "1";
return wemoURL;
}
return null;
}
public static String getCharacterDataFromElement(Element e) {
Node child = e.getFirstChild();
if (child instanceof CharacterData) {
CharacterData cd = (CharacterData) child;
return cd.getData();
}
return "?";
}
@Override
public void onStatusChanged(boolean status) {
}
@Override
public void onServiceSubscribed(String service, boolean succeeded) {
}
@Override
public void onValueReceived(String variable, String value, String service) {
}
}

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.wemo.internal.http;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
import org.openhab.binding.wemo.internal.WemoBindingConstants;
import org.openhab.core.io.net.http.HttpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link WemoHttpCall} is responsible for calling a WeMo device to send commands or retrieve status updates.
*
* @author Hans-Jörg Merk - Initial contribution
*/
public class WemoHttpCall {
private final Logger logger = LoggerFactory.getLogger(WemoHttpCall.class);
public String executeCall(String wemoURL, String soapHeader, String content) {
try {
Properties wemoHeaders = new Properties();
wemoHeaders.setProperty("CONTENT-TYPE", WemoBindingConstants.HTTP_CALL_CONTENT_HEADER);
wemoHeaders.put("SOAPACTION", soapHeader);
InputStream wemoContent = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
String wemoCallResponse = HttpUtil.executeUrl("POST", wemoURL, wemoHeaders, wemoContent, null, 2000);
return wemoCallResponse;
} catch (IOException e) {
// throw new IllegalStateException("Could not call WeMo", e);
logger.debug("Could not make HTTP call to WeMo");
return null;
}
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="wemo" 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>WeMo Binding</name>
<description>The WeMo binding integrates Belkin WeMo devices.</description>
<author>Hans-Jörg Merk</author>
</binding:binding>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="wemo"
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">
<!-- Wemo Link -->
<bridge-type id="bridge">
<label>WeMo Link</label>
<description>The Wemo Link represents the Belkin WeMo Link bridge.</description>
<properties>
<property name="vendor">Belkin</property>
</properties>
<config-description>
<parameter name="udn" type="text">
<label>Unique Device Name</label>
<description>The UDN identifies the WeMo Link Device</description>
<required>true</required>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,430 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="wemo"
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="socket">
<label>WeMo Switch</label>
<description>This is a standard WeMo Switch</description>
<channels>
<channel id="state" typeId="state"/>
</channels>
<config-description>
<parameter name="udn" type="text">
<label>Unique Device Name</label>
<description>The UDN identifies the WeMo Device</description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
<thing-type id="insight">
<label>WeMo Insight Switch</label>
<description>This is a WeMo Insight Switch with energy measurement</description>
<channels>
<channel id="state" typeId="state"/>
<channel id="lastChangedAt" typeId="lastChangedAt"/>
<channel id="lastOnFor" typeId="lastOnFor"/>
<channel id="onToday" typeId="onToday"/>
<channel id="onTotal" typeId="onTotal"/>
<channel id="timespan" typeId="timespan"/>
<channel id="averagePower" typeId="averagePower"/>
<channel id="currentPower" typeId="currentPower"/>
<channel id="energyToday" typeId="energyToday"/>
<channel id="energyTotal" typeId="energyTotal"/>
<channel id="standByLimit" typeId="standByLimit"/>
<channel id="onStandBy" typeId="onStandBy"/>
</channels>
<config-description>
<parameter name="udn" type="text">
<label>Unique Device Name</label>
<description>The UDN identifies the WeMo Device</description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
<thing-type id="lightswitch">
<label>WeMo Light Switch</label>
<description>This is a WeMo LightSwitch</description>
<channels>
<channel id="state" typeId="state"/>
</channels>
<config-description>
<parameter name="udn" type="text">
<label>Unique Device Name</label>
<description>The UDN identifies the WeMo Device</description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
<thing-type id="motion">
<label>WeMo Motion</label>
<description>This is a WeMo MotionSensor</description>
<channels>
<channel id="motionDetection" typeId="motionDetection"/>
<channel id="lastMotionDetected" typeId="lastMotionDetected"/>
</channels>
<config-description>
<parameter name="udn" type="text">
<label>Unique Device Name</label>
<description>The UDN identifies the WeMo Device</description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
<!-- Standard WeMo Bulb with E27 socket -->
<thing-type id="MZ100">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>WeMo LED Light</label>
<description>This is a standard WeMo light bulb with E27 socket </description>
<channels>
<channel id="state" typeId="state"/>
<channel id="brightness" typeId="brightness"/>
</channels>
<properties>
<property name="vendor">MRVL</property>
<property name="modelId">MZ100</property>
</properties>
<config-description>
<parameter name="deviceID" type="text">
<label>Device ID</label>
<description>The device ID identifies one certain WeMo light.</description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
<thing-type id="Maker">
<label>WeMo Maker</label>
<description>This is a WeMo Maker</description>
<channels>
<channel id="relay" typeId="relay"/>
<channel id="sensor" typeId="sensor"/>
</channels>
<config-description>
<parameter name="udn" type="text">
<label>Unique Device Name</label>
<description>The UDN identifies the WeMo Device</description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
<thing-type id="CoffeeMaker">
<label>Mr. Coffee WeMo Enabled Coffeemaker</label>
<description>This is a WeMo enabled coffee maker</description>
<channels>
<channel id="state" typeId="state"/>
<channel id="coffeeMode" typeId="coffeeMode"/>
<channel id="modeTime" typeId="modeTime"/>
<channel id="timeRemaining" typeId="timeRemaining"/>
<channel id="waterLevelReached" typeId="waterLevelReached"/>
<channel id="cleanAdvise" typeId="cleanAdvise"/>
<channel id="filterAdvise" typeId="filterAdvise"/>
<channel id="brewed" typeId="brewed"/>
<channel id="lastCleaned" typeId="lastCleaned"/>
</channels>
<config-description>
<parameter name="udn" type="text">
<label>Unique Device Name</label>
<description>The UDN identifies the WeMo Device</description>
<required>true</required>
</parameter>
<parameter name="pollingInterval" type="integer" required="false" min="15" max="180">
<label>Polling Interval</label>
<description>Interval polling the WeMo Coffee Maker.
</description>
<default>60</default>
</parameter>
</config-description>
</thing-type>
<thing-type id="dimmer">
<label>WeMo DimmerSwitch</label>
<description>This is a WeMo DimmerSwitch</description>
<channels>
<channel id="brightness" typeId="brightness"/>
<channel id="faderCountDownTime" typeId="faderCountDownTime"/>
<channel id="faderEnabled" typeId="faderEnabled"/>
<channel id="timerStart" typeId="timerStart"/>
<channel id="nightMode" typeId="nightMode"/>
<channel id="startTime" typeId="startTime"/>
<channel id="endTime" typeId="endTime"/>
<channel id="nightModeBrightness" typeId="nightModeBrightness"/>
</channels>
<config-description>
<parameter name="udn" type="text">
<label>Unique Device Name</label>
<description>The UDN identifies the WeMo Device</description>
<required>true</required>
</parameter>
</config-description>
</thing-type>
<channel-type id="state">
<item-type>Switch</item-type>
<label>Switch</label>
<description>Turns the power on or off</description>
<category>Switch</category>
</channel-type>
<channel-type id="motionDetection">
<item-type>Switch</item-type>
<label>Motion Status</label>
<description>Indicates whether motion is detected or not</description>
<category>Switch</category>
<state readOnly="true"></state>
</channel-type>
<channel-type id="lastMotionDetected" advanced="true">
<item-type>DateTime</item-type>
<label>Last Activity (Date/Time)</label>
<description>Date/time when last motion was detected</description>
</channel-type>
<channel-type id="lastChangedAt" advanced="true">
<item-type>DateTime</item-type>
<label>Last Activity (Date/Time)</label>
<description>Date/time when the state last changed</description>
</channel-type>
<channel-type id="lastOnFor" advanced="true">
<item-type>Number</item-type>
<label>Last Activity (s)</label>
<description>Duration the device has been switched on for</description>
<state pattern="%.1f s"/>
</channel-type>
<channel-type id="onToday" advanced="true">
<item-type>Number</item-type>
<label>Today's Activity (s)</label>
<description>How long has the device been switched on today</description>
<state pattern="%.1f s"/>
</channel-type>
<channel-type id="onTotal" advanced="true">
<item-type>Number</item-type>
<label>Total Activity (s)</label>
<description>How long has the device been switched on totally</description>
<state pattern="%.1f s"/>
</channel-type>
<channel-type id="timespan" advanced="true">
<item-type>Number</item-type>
<label>Usage Timespan (s)</label>
<description>Time used to measure average usage</description>
<state pattern="%.1f s"/>
</channel-type>
<channel-type id="averagePower" advanced="true">
<item-type>Number</item-type>
<label>Average Power</label>
<description>The average power consumption</description>
<category>Energy</category>
<state pattern="%.1f W"/>
</channel-type>
<channel-type id="currentPower">
<item-type>Number</item-type>
<label>Power</label>
<description>The current power consumption</description>
<category>Energy</category>
<state pattern="%.1f W"/>
</channel-type>
<channel-type id="energyToday" advanced="true">
<item-type>Number</item-type>
<label>Energy Today</label>
<description>Todays power consumption</description>
<category>Energy</category>
<state pattern="%.1f Wh"/>
</channel-type>
<channel-type id="energyTotal" advanced="true">
<item-type>Number</item-type>
<label>Energy Total</label>
<description>Total power consumption</description>
<category>Energy</category>
<state pattern="%.1f Wh"/>
</channel-type>
<channel-type id="standByLimit" advanced="true">
<item-type>Number</item-type>
<label>StandBy Limit</label>
<description>Total power consumption</description>
<category>Energy</category>
<state pattern="%.1f W"/>
</channel-type>
<channel-type id="onStandBy" advanced="true">
<item-type>Switch</item-type>
<label>On Standby</label>
<description>Appliance on standby</description>
<category>Energy</category>
<state readOnly="true"/>
</channel-type>
<!-- Brightness Channel -->
<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>
</channel-type>
<channel-type id="relay">
<item-type>Switch</item-type>
<label>Relay</label>
<description>Switches the integrated relay contact close/open</description>
<category>Switch</category>
</channel-type>
<channel-type id="sensor">
<item-type>Switch</item-type>
<label>Sensor</label>
<description>Shows the state of the integrated sensor</description>
<category>Switch</category>
</channel-type>
<channel-type id="coffeeMode">
<item-type>String</item-type>
<label>Mode</label>
<description>Shows the operation mode of a WeMo Coffee Maker</description>
<state readOnly="true">
<options>
<option value="Refill">Not Ready: Refill Water and Replace Carafe</option>
<option value="PlaceCarafe">Not Ready: Replace Carafe</option>
<option value="RefillWater">Not Ready: Refill Water</option>
<option value="Ready">Ready</option>
<option value="Brewing">Brewing</option>
<option value="Brewed">Brewing Finished</option>
<option value="CleaningBrewing">Cleaning Coffee Maker</option>
<option value="CleaningSoaking">Cleaning Filter</option>
<option value="BrewFailCarafeRemoved">Brewing Failed: Carafe Removed</option>
</options>
</state>
</channel-type>
<channel-type id="modeTime">
<item-type>Number</item-type>
<label>ModeTime</label>
<description>Shows the current amount of time, in minutes, that the Coffee Maker has been in the current mode</description>
</channel-type>
<channel-type id="timeRemaining">
<item-type>Number</item-type>
<label>TimeRemaining</label>
<description>Shows the remaining brewing time of a WeMo Coffee Maker</description>
</channel-type>
<channel-type id="waterLevelReached" advanced="true">
<item-type>Switch</item-type>
<label>WaterLevelReached</label>
<description>Indicates if the WeMo Coffee Maker needs to be refilled</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="cleanAdvise" advanced="true">
<item-type>Switch</item-type>
<label>CleanAdvise</label>
<description>Indicates if a WeMo Coffee Maker needs to be cleaned</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="filterAdvise" advanced="true">
<item-type>Switch</item-type>
<label>FilterAdvise</label>
<description>Indicates if a WeMo Coffee Maker needs to have the filter changed</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="brewed" advanced="true">
<item-type>DateTime</item-type>
<label>Brewed</label>
<description>Date/time the coffee maker last completed brewing coffee</description>
</channel-type>
<channel-type id="lastCleaned" advanced="true">
<item-type>DateTime</item-type>
<label>LastCleaned</label>
<description>Date/time the coffee maker last completed cleaning</description>
</channel-type>
<channel-type id="faderCountDownTime" advanced="true">
<item-type>Number</item-type>
<label>Fader CountDown Time</label>
<description>The fading duration time in minutes</description>
<state min="1" max="30" step="1.0" pattern="%d min" readOnly="false"/>
</channel-type>
<channel-type id="faderEnabled" advanced="true">
<item-type>Switch</item-type>
<label>Fader OFF/ON</label>
<description>Allows to switch the fader ON/OFF</description>
</channel-type>
<channel-type id="timerStart" advanced="true">
<item-type>Switch</item-type>
<label>Timer OFF/ON</label>
<description>Allows to switch the timer ON/OFF</description>
</channel-type>
<channel-type id="nightMode" advanced="true">
<item-type>Switch</item-type>
<label>NightMode OFF/ON</label>
<description>Allows to switch the nightMode ON/OFF</description>
</channel-type>
<channel-type id="startTime" advanced="true">
<item-type>DateTime</item-type>
<label>NightMode Start Time</label>
<description>Time when the Night Mode starts</description>
<state pattern="%1$tR" readOnly="true"/>
</channel-type>
<channel-type id="endTime" advanced="true">
<item-type>DateTime</item-type>
<label>NightMode End Time</label>
<description>Time when the Night Mode ends</description>
<state pattern="%1$tR" readOnly="true"/>
</channel-type>
<channel-type id="nightModeBrightness" advanced="true">
<item-type>Dimmer</item-type>
<label>Night Mode Brightness</label>
<description>Allows setting the brightness of Night Mode</description>
</channel-type>
</thing:thing-descriptions>