[revogi] Initial contribution - Resubmitted for OH3 (#8534)
* Rename binding and resubmit to OH3 Signed-off-by: Andreas Bräu <ab@andi95.de>
This commit is contained in:
parent
c49eeb2528
commit
1c93eb9737
|
@ -200,6 +200,7 @@
|
||||||
/bundles/org.openhab.binding.pushbullet/ @hakan42
|
/bundles/org.openhab.binding.pushbullet/ @hakan42
|
||||||
/bundles/org.openhab.binding.radiothermostat/ @mlobstein
|
/bundles/org.openhab.binding.radiothermostat/ @mlobstein
|
||||||
/bundles/org.openhab.binding.regoheatpump/ @crnjan
|
/bundles/org.openhab.binding.regoheatpump/ @crnjan
|
||||||
|
/bundles/org.openhab.binding.revogi/ @andibraeu
|
||||||
/bundles/org.openhab.binding.remoteopenhab/ @lolodomo
|
/bundles/org.openhab.binding.remoteopenhab/ @lolodomo
|
||||||
/bundles/org.openhab.binding.rfxcom/ @martinvw @paulianttila
|
/bundles/org.openhab.binding.rfxcom/ @martinvw @paulianttila
|
||||||
/bundles/org.openhab.binding.rme/ @kgoderis
|
/bundles/org.openhab.binding.rme/ @kgoderis
|
||||||
|
|
|
@ -991,6 +991,11 @@
|
||||||
<artifactId>org.openhab.binding.regoheatpump</artifactId>
|
<artifactId>org.openhab.binding.regoheatpump</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.binding.revogi</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openhab.addons.bundles</groupId>
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
<artifactId>org.openhab.binding.remoteopenhab</artifactId>
|
<artifactId>org.openhab.binding.remoteopenhab</artifactId>
|
||||||
|
|
|
@ -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
|
|
@ -0,0 +1,78 @@
|
||||||
|
# Revogi Binding
|
||||||
|
|
||||||
|
This binding is written to control Revogi devices.
|
||||||
|
The first thing implemented is the [Revogi Smart Power Strip](https://www.revogi.com/smart-power/smart-power-strip-eu/#section6).
|
||||||
|
The device has 6 power plugs that can be switched independently, or all together.
|
||||||
|
It also provides information like power consumption and electric current for each plug.
|
||||||
|
|
||||||
|
It was hard to find out how to control it without internet access, but there's a way to use UDP packets.
|
||||||
|
See the following [support document](https://github.com/andibraeu/revogismartstripcontrol/blob/master/doc/LAN%20UDP%20Control.pdf) for details. This was the only document the Revogi support provided.
|
||||||
|
|
||||||
|
## Supported Things
|
||||||
|
|
||||||
|
Currently only the model `SOW019` is supported.
|
||||||
|
|
||||||
|
## Discovery
|
||||||
|
|
||||||
|
If your smart strip is within your network (broadcast domain), discovery can work.
|
||||||
|
The discovery service will send udp packets to the broadcast address and waits for a feedback.
|
||||||
|
|
||||||
|
It is required to integrate your power strip into your network first, maybe with the official app.
|
||||||
|
|
||||||
|
## Thing Configuration
|
||||||
|
|
||||||
|
You need to know the serial number. Usually you can find it on the back.
|
||||||
|
The serial number will also be discovered.
|
||||||
|
The IP address of the device is also necessary, this address should be set static.
|
||||||
|
There's a fallback to broadcast status and switch requests.
|
||||||
|
That may be unreliable if you have more than one smart plug in your network.
|
||||||
|
They all react on UDP packets.
|
||||||
|
|
||||||
|
## Channels
|
||||||
|
|
||||||
|
| channel | type | description |
|
||||||
|
|--------------------|------------------------|-------------------------------------------|
|
||||||
|
| overallPlug#switch | Switch | Switches all plugs |
|
||||||
|
| plug1#switch | Switch | Switch plug 1 |
|
||||||
|
| plug1#watt | Number:Power | Contains currently used power of plug 1 |
|
||||||
|
| plug1#amp | Number:ElectricCurrent | Contains currently used current of plug 1 |
|
||||||
|
| plug2#switch | Switch | Switch plug 2 |
|
||||||
|
| plug2#watt | Number:Power | Contains currently used power of plug 2 |
|
||||||
|
| plug2#amp | Number:ElectricCurrent | Contains currently used current of plug 2 |
|
||||||
|
| plug3#switch | Switch | Switch plug 3 |
|
||||||
|
| plug3#watt | Number:Power | Contains currently used power of plug 3 |
|
||||||
|
| plug3#amp | Number:ElectricCurrent | Contains currently used current of plug 3 |
|
||||||
|
| plug4#switch | Switch | Switch plug 4 |
|
||||||
|
| plug4#watt | Number:Power | Contains currently used power of plug 4 |
|
||||||
|
| plug4#amp | Number:ElectricCurrent | Contains currently used current of plug 4 |
|
||||||
|
| plug5#switch | Switch | Switch plug 5 |
|
||||||
|
| plug5#watt | Number:Power | Contains currently used power of plug 5 |
|
||||||
|
| plug5#amp | Number:ElectricCurrent | Contains currently used current of plug 5 |
|
||||||
|
| plug6#switch | Switch | Switch plug 6 |
|
||||||
|
| plug6#watt | Number:Power | Contains currently used power of plug 6 |
|
||||||
|
| plug6#amp | Number:ElectricCurrent | Contains currently used current of plug 6 |
|
||||||
|
|
||||||
|
## Full Example
|
||||||
|
|
||||||
|
Example Thing configuration:
|
||||||
|
|
||||||
|
```
|
||||||
|
Thing revogi:smartstrip:<serialNumber> "<Name>" @ "<Location>" [serialNumber="<serialNumnber>", ipAddress=<ipaddress>, pollIntervall=45]
|
||||||
|
```
|
||||||
|
|
||||||
|
Example Items configuration:
|
||||||
|
|
||||||
|
```
|
||||||
|
Group revogi (LivingRoom)
|
||||||
|
|
||||||
|
Group plug1 (revogi)
|
||||||
|
Group plug2 (revogi)
|
||||||
|
|
||||||
|
Switch All_Plugs "Steckdosen komplett" <switch> (revogi) {channel="revogi:smartstrip:<serialNumnber>:overallPlug#switch"}
|
||||||
|
|
||||||
|
Switch Plug_1 "Steckdose 1" <switch> (plug1) {channel="revogi:smartstrip:<serialNumnber>:plug1#switch"}
|
||||||
|
Number Plug_1_Watt "Steckdose 1 Leistung" <chart> (plug1) {channel="revogi:smartstrip:<serialNumnber>:plug1#watt"}
|
||||||
|
Number Plug_1_Amp "Steckdose 1 Strom" <chart> (plug1) {channel="revogi:smartstrip:<serialNumnber>:plug1#amp"}
|
||||||
|
|
||||||
|
...
|
||||||
|
```
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
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.revogi</artifactId>
|
||||||
|
|
||||||
|
<name>openHAB Add-ons :: Bundles :: Revogi Binding</name>
|
||||||
|
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<features name="org.openhab.binding.revogi-${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-revogi" description="Revogi Binding" version="${project.version}">
|
||||||
|
<feature>openhab-runtime-base</feature>
|
||||||
|
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.revogi/${project.version}</bundle>
|
||||||
|
</feature>
|
||||||
|
</features>
|
|
@ -0,0 +1,42 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.revogi.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link RevogiSmartStripControlBindingConstants} class defines common constants, which are
|
||||||
|
* used across the whole binding.
|
||||||
|
*
|
||||||
|
* @author Andi Bräu - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class RevogiSmartStripControlBindingConstants {
|
||||||
|
|
||||||
|
private static final String BINDING_ID = "revogi";
|
||||||
|
|
||||||
|
// List of all Thing Type UIDs
|
||||||
|
public static final ThingTypeUID SMART_STRIP_THING_TYPE = new ThingTypeUID(BINDING_ID, "smartstrip");
|
||||||
|
|
||||||
|
// List of all Channel ids
|
||||||
|
public static final String PLUG_1_SWITCH = "plug1#switch";
|
||||||
|
public static final String PLUG_2_SWITCH = "plug2#switch";
|
||||||
|
public static final String PLUG_3_SWITCH = "plug3#switch";
|
||||||
|
public static final String PLUG_4_SWITCH = "plug4#switch";
|
||||||
|
public static final String PLUG_5_SWITCH = "plug5#switch";
|
||||||
|
public static final String PLUG_6_SWITCH = "plug6#switch";
|
||||||
|
public static final String ALL_PLUGS = "overallPlug#switch";
|
||||||
|
|
||||||
|
public static final String SERIAL_NUMBER = "serialNumber";
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/**
|
||||||
|
* 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.revogi.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link RevogiSmartStripControlConfiguration} class contains fields mapping thing configuration parameters.
|
||||||
|
*
|
||||||
|
* @author Andi Bräu - Initial contribution
|
||||||
|
*/
|
||||||
|
|
||||||
|
@NonNullByDefault
|
||||||
|
public class RevogiSmartStripControlConfiguration {
|
||||||
|
|
||||||
|
public String serialNumber = "Serial Number";
|
||||||
|
|
||||||
|
public int pollInterval = 60;
|
||||||
|
|
||||||
|
public String ipAddress = "127.0.0.1";
|
||||||
|
}
|
|
@ -0,0 +1,166 @@
|
||||||
|
/**
|
||||||
|
* 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.revogi.internal;
|
||||||
|
|
||||||
|
import static org.openhab.core.library.unit.MetricPrefix.MILLI;
|
||||||
|
import static org.openhab.core.library.unit.SmartHomeUnits.AMPERE;
|
||||||
|
import static org.openhab.core.library.unit.SmartHomeUnits.WATT;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.revogi.internal.api.StatusDTO;
|
||||||
|
import org.openhab.binding.revogi.internal.api.StatusService;
|
||||||
|
import org.openhab.binding.revogi.internal.api.SwitchService;
|
||||||
|
import org.openhab.binding.revogi.internal.udp.DatagramSocketWrapper;
|
||||||
|
import org.openhab.binding.revogi.internal.udp.UdpSenderService;
|
||||||
|
import org.openhab.core.library.types.OnOffType;
|
||||||
|
import org.openhab.core.library.types.QuantityType;
|
||||||
|
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.binding.BaseThingHandler;
|
||||||
|
import org.openhab.core.types.Command;
|
||||||
|
import org.openhab.core.types.RefreshType;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link RevogiSmartStripControlHandler} is responsible for handling commands, which are
|
||||||
|
* sent to one of the channels.
|
||||||
|
*
|
||||||
|
* @author Andi Bräu - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class RevogiSmartStripControlHandler extends BaseThingHandler {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(RevogiSmartStripControlHandler.class);
|
||||||
|
private final StatusService statusService;
|
||||||
|
private final SwitchService switchService;
|
||||||
|
private @Nullable ScheduledFuture<?> pollingJob;
|
||||||
|
|
||||||
|
private RevogiSmartStripControlConfiguration config;
|
||||||
|
|
||||||
|
public RevogiSmartStripControlHandler(Thing thing) {
|
||||||
|
super(thing);
|
||||||
|
config = getConfigAs(RevogiSmartStripControlConfiguration.class);
|
||||||
|
UdpSenderService udpSenderService = new UdpSenderService(new DatagramSocketWrapper(), scheduler);
|
||||||
|
this.statusService = new StatusService(udpSenderService);
|
||||||
|
this.switchService = new SwitchService(udpSenderService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
|
switch (channelUID.getId()) {
|
||||||
|
case RevogiSmartStripControlBindingConstants.PLUG_1_SWITCH:
|
||||||
|
switchPlug(command, 1);
|
||||||
|
break;
|
||||||
|
case RevogiSmartStripControlBindingConstants.PLUG_2_SWITCH:
|
||||||
|
switchPlug(command, 2);
|
||||||
|
break;
|
||||||
|
case RevogiSmartStripControlBindingConstants.PLUG_3_SWITCH:
|
||||||
|
switchPlug(command, 3);
|
||||||
|
break;
|
||||||
|
case RevogiSmartStripControlBindingConstants.PLUG_4_SWITCH:
|
||||||
|
switchPlug(command, 4);
|
||||||
|
break;
|
||||||
|
case RevogiSmartStripControlBindingConstants.PLUG_5_SWITCH:
|
||||||
|
switchPlug(command, 5);
|
||||||
|
break;
|
||||||
|
case RevogiSmartStripControlBindingConstants.PLUG_6_SWITCH:
|
||||||
|
switchPlug(command, 6);
|
||||||
|
break;
|
||||||
|
case RevogiSmartStripControlBindingConstants.ALL_PLUGS:
|
||||||
|
switchPlug(command, 0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger.debug("Something went wrong, we've got a message for {}", channelUID.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void switchPlug(Command command, int port) {
|
||||||
|
RevogiSmartStripControlConfiguration localConfig = this.config;
|
||||||
|
if (command instanceof OnOffType) {
|
||||||
|
int state = convertOnOffTypeToState(command);
|
||||||
|
switchService.switchPort(localConfig.serialNumber, localConfig.ipAddress, port, state);
|
||||||
|
}
|
||||||
|
if (command instanceof RefreshType) {
|
||||||
|
updateStripInformation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int convertOnOffTypeToState(Command command) {
|
||||||
|
if (command == OnOffType.ON) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
config = getConfigAs(RevogiSmartStripControlConfiguration.class);
|
||||||
|
updateStatus(ThingStatus.UNKNOWN);
|
||||||
|
|
||||||
|
pollingJob = scheduler.scheduleWithFixedDelay(this::updateStripInformation, 0, config.pollInterval,
|
||||||
|
TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
ScheduledFuture<?> localPollingJob = this.pollingJob;
|
||||||
|
if (localPollingJob != null) {
|
||||||
|
localPollingJob.cancel(true);
|
||||||
|
this.pollingJob = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStripInformation() {
|
||||||
|
CompletableFuture<StatusDTO> futureStatus = statusService.queryStatus(config.serialNumber, config.ipAddress);
|
||||||
|
futureStatus.thenAccept(this::updatePlugStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePlugStatus(StatusDTO status) {
|
||||||
|
if (status.isOnline()) {
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
handleAllPlugsInformation(status);
|
||||||
|
handleSinglePlugInformation(status);
|
||||||
|
} else {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE,
|
||||||
|
"Retrieved status code: " + status.getResponseCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSinglePlugInformation(StatusDTO status) {
|
||||||
|
for (int i = 0; i < status.getSwitchValue().size(); i++) {
|
||||||
|
int plugNumber = i + 1;
|
||||||
|
updateState("plug" + plugNumber + "#switch", OnOffType.from(status.getSwitchValue().get(i).toString()));
|
||||||
|
updateState("plug" + plugNumber + "#watt", new QuantityType<>(status.getWatt().get(i), MILLI(WATT)));
|
||||||
|
updateState("plug" + plugNumber + "#amp", new QuantityType<>(status.getAmp().get(i), MILLI(AMPERE)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleAllPlugsInformation(StatusDTO status) {
|
||||||
|
long onCount = status.getSwitchValue().stream().filter(statusValue -> statusValue == 1).count();
|
||||||
|
if (onCount == 6) {
|
||||||
|
updateState(RevogiSmartStripControlBindingConstants.ALL_PLUGS, OnOffType.ON);
|
||||||
|
} else {
|
||||||
|
updateState(RevogiSmartStripControlBindingConstants.ALL_PLUGS, OnOffType.OFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
/**
|
||||||
|
* 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.revogi.internal;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.core.thing.Thing;
|
||||||
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
|
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandler;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||||
|
import org.osgi.service.component.annotations.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link RevogiSmartStripControlHandlerFactory} is responsible for creating things and thing
|
||||||
|
* handlers.
|
||||||
|
*
|
||||||
|
* @author Andi Bräu - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
@Component(configurationPid = "binding.revogi", service = ThingHandlerFactory.class)
|
||||||
|
public class RevogiSmartStripControlHandlerFactory extends BaseThingHandlerFactory {
|
||||||
|
|
||||||
|
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
|
||||||
|
.singleton(RevogiSmartStripControlBindingConstants.SMART_STRIP_THING_TYPE);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||||
|
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||||
|
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||||
|
|
||||||
|
if (RevogiSmartStripControlBindingConstants.SMART_STRIP_THING_TYPE.equals(thingTypeUID)) {
|
||||||
|
return new RevogiSmartStripControlHandler(thing);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
/**
|
||||||
|
* 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.revogi.internal;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.revogi.internal.api.DiscoveryRawResponseDTO;
|
||||||
|
import org.openhab.binding.revogi.internal.api.DiscoveryResponseDTO;
|
||||||
|
import org.openhab.binding.revogi.internal.api.RevogiDiscoveryService;
|
||||||
|
import org.openhab.binding.revogi.internal.udp.DatagramSocketWrapper;
|
||||||
|
import org.openhab.binding.revogi.internal.udp.UdpSenderService;
|
||||||
|
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.config.discovery.DiscoveryService;
|
||||||
|
import org.openhab.core.thing.Thing;
|
||||||
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
|
import org.openhab.core.thing.ThingUID;
|
||||||
|
import org.osgi.service.component.annotations.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link RevogiSmartStripDiscoveryService} helps to discover new smart strips
|
||||||
|
*
|
||||||
|
* @author Andi Bräu - Initial contribution
|
||||||
|
*/
|
||||||
|
@Component(service = DiscoveryService.class, configurationPid = "discovery.revogi")
|
||||||
|
@NonNullByDefault
|
||||||
|
public class RevogiSmartStripDiscoveryService extends AbstractDiscoveryService {
|
||||||
|
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections
|
||||||
|
.singleton(RevogiSmartStripControlBindingConstants.SMART_STRIP_THING_TYPE);
|
||||||
|
|
||||||
|
private final RevogiDiscoveryService revogiDiscoveryService;
|
||||||
|
|
||||||
|
private static final int SEARCH_TIMEOUT_SEC = 10;
|
||||||
|
|
||||||
|
public RevogiSmartStripDiscoveryService() {
|
||||||
|
super(SUPPORTED_THING_TYPES, SEARCH_TIMEOUT_SEC);
|
||||||
|
revogiDiscoveryService = new RevogiDiscoveryService(
|
||||||
|
new UdpSenderService(new DatagramSocketWrapper(), scheduler));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<ThingTypeUID> getSupportedThingTypes() {
|
||||||
|
return SUPPORTED_THING_TYPES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void startScan() {
|
||||||
|
CompletableFuture<List<DiscoveryRawResponseDTO>> discoveryResponses = revogiDiscoveryService
|
||||||
|
.discoverSmartStrips();
|
||||||
|
discoveryResponses.thenAccept(this::applyDiscoveryResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyDiscoveryResults(final List<DiscoveryRawResponseDTO> discoveryRawResponses) {
|
||||||
|
discoveryRawResponses.forEach(response -> {
|
||||||
|
ThingUID thingUID = getThingUID(response.getData());
|
||||||
|
if (thingUID != null) {
|
||||||
|
Map<String, Object> properties = new HashMap<>();
|
||||||
|
properties.put(Thing.PROPERTY_MODEL_ID, response.getData().getRegId());
|
||||||
|
properties.put(Thing.PROPERTY_MAC_ADDRESS, response.getData().getMacAddress());
|
||||||
|
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, response.getData().getVersion());
|
||||||
|
properties.put(Thing.PROPERTY_SERIAL_NUMBER, response.getData().getSerialNumber());
|
||||||
|
properties.put("ipAddress", response.getIpAddress());
|
||||||
|
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
|
||||||
|
.withThingType(RevogiSmartStripControlBindingConstants.SMART_STRIP_THING_TYPE)
|
||||||
|
.withProperties(properties).withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER).build();
|
||||||
|
thingDiscovered(discoveryResult);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable ThingUID getThingUID(DiscoveryResponseDTO response) {
|
||||||
|
if (getSupportedThingTypes().contains(RevogiSmartStripControlBindingConstants.SMART_STRIP_THING_TYPE)) {
|
||||||
|
return new ThingUID(RevogiSmartStripControlBindingConstants.SMART_STRIP_THING_TYPE,
|
||||||
|
response.getSerialNumber());
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
/**
|
||||||
|
* 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.revogi.internal.api;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Andi Bräu - Initial contribution
|
||||||
|
*/
|
||||||
|
public class DiscoveryRawResponseDTO {
|
||||||
|
|
||||||
|
private final int response;
|
||||||
|
private final DiscoveryResponseDTO data;
|
||||||
|
private String ipAddress;
|
||||||
|
|
||||||
|
public DiscoveryRawResponseDTO(int response, DiscoveryResponseDTO data) {
|
||||||
|
this.response = response;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getResponse() {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscoveryResponseDTO getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIpAddress() {
|
||||||
|
return ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIpAddress(String ipAddress) {
|
||||||
|
this.ipAddress = ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o)
|
||||||
|
return true;
|
||||||
|
if (o == null || getClass() != o.getClass())
|
||||||
|
return false;
|
||||||
|
DiscoveryRawResponseDTO that = (DiscoveryRawResponseDTO) o;
|
||||||
|
return response == that.response && data.equals(that.data) && Objects.equals(ipAddress, that.ipAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(response, data, ipAddress);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
/**
|
||||||
|
* 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.revogi.internal.api;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Andi Bräu - Initial contribution
|
||||||
|
*/
|
||||||
|
public class DiscoveryResponseDTO {
|
||||||
|
@SerializedName("sn")
|
||||||
|
private final String serialNumber;
|
||||||
|
@SerializedName("regid")
|
||||||
|
private final String regId;
|
||||||
|
private final String sak;
|
||||||
|
private final String name;
|
||||||
|
@SerializedName("mac")
|
||||||
|
private final String macAddress;
|
||||||
|
@SerializedName("ver")
|
||||||
|
private final String version;
|
||||||
|
|
||||||
|
public DiscoveryResponseDTO(String serialNumber, String regId, String sak, String name, String macAddress,
|
||||||
|
String version) {
|
||||||
|
this.serialNumber = serialNumber;
|
||||||
|
this.regId = regId;
|
||||||
|
this.sak = sak;
|
||||||
|
this.name = name;
|
||||||
|
this.macAddress = macAddress;
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DiscoveryResponseDTO() {
|
||||||
|
serialNumber = "";
|
||||||
|
regId = "";
|
||||||
|
sak = "";
|
||||||
|
name = "";
|
||||||
|
macAddress = "";
|
||||||
|
version = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSerialNumber() {
|
||||||
|
return serialNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRegId() {
|
||||||
|
return regId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSak() {
|
||||||
|
return sak;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMacAddress() {
|
||||||
|
return macAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o)
|
||||||
|
return true;
|
||||||
|
if (o == null || getClass() != o.getClass())
|
||||||
|
return false;
|
||||||
|
DiscoveryResponseDTO that = (DiscoveryResponseDTO) o;
|
||||||
|
return serialNumber.equals(that.serialNumber) && regId.equals(that.regId) && sak.equals(that.sak)
|
||||||
|
&& name.equals(that.name) && macAddress.equals(that.macAddress) && version.equals(that.version);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(serialNumber, regId, sak, name, macAddress, version);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.revogi.internal.api;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.binding.revogi.internal.udp.UdpResponseDTO;
|
||||||
|
import org.openhab.binding.revogi.internal.udp.UdpSenderService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link RevogiDiscoveryService} helps to discover smart strips within your network
|
||||||
|
*
|
||||||
|
* @author Andi Bräu - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class RevogiDiscoveryService {
|
||||||
|
private static final String UDP_DISCOVERY_QUERY = "00sw=all,,,;";
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(RevogiDiscoveryService.class);
|
||||||
|
|
||||||
|
private final Gson gson = new GsonBuilder().create();
|
||||||
|
private final UdpSenderService udpSenderService;
|
||||||
|
|
||||||
|
public RevogiDiscoveryService(UdpSenderService udpSenderService) {
|
||||||
|
this.udpSenderService = udpSenderService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<List<DiscoveryRawResponseDTO>> discoverSmartStrips() {
|
||||||
|
CompletableFuture<List<UdpResponseDTO>> responses = udpSenderService.broadcastUdpDatagram(UDP_DISCOVERY_QUERY);
|
||||||
|
return responses.thenApply(futureList -> futureList.stream().filter(response -> !response.getAnswer().isEmpty())
|
||||||
|
.map(this::deserializeString).filter(discoveryRawResponse -> discoveryRawResponse.getResponse() == 0)
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private DiscoveryRawResponseDTO deserializeString(UdpResponseDTO response) {
|
||||||
|
try {
|
||||||
|
DiscoveryRawResponseDTO discoveryRawResponse = gson.fromJson(response.getAnswer(),
|
||||||
|
DiscoveryRawResponseDTO.class);
|
||||||
|
discoveryRawResponse.setIpAddress(response.getIpAddress());
|
||||||
|
return discoveryRawResponse;
|
||||||
|
} catch (JsonSyntaxException e) {
|
||||||
|
logger.warn("Could not parse string \"{}\" to DiscoveryRawResponse", response, e);
|
||||||
|
return new DiscoveryRawResponseDTO(503, new DiscoveryResponseDTO());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
/**
|
||||||
|
* 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.revogi.internal.api;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link StatusDTO} is the internal data model used to control Revogi's SmartStrip
|
||||||
|
*
|
||||||
|
* @author Andi Bräu - Initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class StatusDTO {
|
||||||
|
private final boolean online;
|
||||||
|
private final int responseCode;
|
||||||
|
@SerializedName("switch")
|
||||||
|
private final List<Integer> switchValue;
|
||||||
|
private final List<Integer> watt;
|
||||||
|
private final List<Integer> amp;
|
||||||
|
|
||||||
|
public StatusDTO() {
|
||||||
|
online = false;
|
||||||
|
responseCode = 0;
|
||||||
|
switchValue = null;
|
||||||
|
watt = null;
|
||||||
|
amp = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatusDTO(boolean online, int responseCode, List<Integer> switchValue, List<Integer> watt,
|
||||||
|
List<Integer> amp) {
|
||||||
|
this.online = online;
|
||||||
|
this.responseCode = responseCode;
|
||||||
|
this.switchValue = switchValue;
|
||||||
|
this.watt = watt;
|
||||||
|
this.amp = amp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOnline() {
|
||||||
|
return online;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getResponseCode() {
|
||||||
|
return responseCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Integer> getSwitchValue() {
|
||||||
|
return switchValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Integer> getWatt() {
|
||||||
|
return watt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Integer> getAmp() {
|
||||||
|
return amp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
StatusDTO status = (StatusDTO) o;
|
||||||
|
return online == status.online && responseCode == status.responseCode
|
||||||
|
&& Objects.equals(switchValue, status.switchValue) && Objects.equals(watt, status.watt)
|
||||||
|
&& Objects.equals(amp, status.amp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(online, responseCode, switchValue, watt, amp);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.revogi.internal.api;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class {@link StatusRawDTO} represents the raw data received from Revogi's SmartStrip
|
||||||
|
*
|
||||||
|
* @author Andi Bräu - Initial contribution
|
||||||
|
*/
|
||||||
|
public class StatusRawDTO {
|
||||||
|
private final int response;
|
||||||
|
private final int code;
|
||||||
|
private final StatusDTO data;
|
||||||
|
|
||||||
|
public StatusRawDTO(int response, int code, StatusDTO data) {
|
||||||
|
this.response = response;
|
||||||
|
this.code = code;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getResponse() {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatusDTO getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.revogi.internal.api;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.openhab.binding.revogi.internal.udp.UdpResponseDTO;
|
||||||
|
import org.openhab.binding.revogi.internal.udp.UdpSenderService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link StatusService} contains methods to get a status of a Revogi SmartStrip
|
||||||
|
*
|
||||||
|
* @author Andi Bräu - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class StatusService {
|
||||||
|
|
||||||
|
private static final String UDP_DISCOVERY_QUERY = "V3{\"sn\":\"%s\", \"cmd\": 90}";
|
||||||
|
public static final String VERSION_STRING = "V3";
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(StatusService.class);
|
||||||
|
|
||||||
|
private final Gson gson = new GsonBuilder().create();
|
||||||
|
private final UdpSenderService udpSenderService;
|
||||||
|
|
||||||
|
public StatusService(UdpSenderService udpSenderService) {
|
||||||
|
this.udpSenderService = udpSenderService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<StatusDTO> queryStatus(String serialNumber, String ipAddress) {
|
||||||
|
CompletableFuture<List<UdpResponseDTO>> responses;
|
||||||
|
if (ipAddress.trim().isEmpty()) {
|
||||||
|
responses = udpSenderService.broadcastUdpDatagram(String.format(UDP_DISCOVERY_QUERY, serialNumber));
|
||||||
|
} else {
|
||||||
|
responses = udpSenderService.sendMessage(String.format(UDP_DISCOVERY_QUERY, serialNumber), ipAddress);
|
||||||
|
}
|
||||||
|
return responses.thenApply(this::getStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private StatusDTO getStatus(final List<UdpResponseDTO> singleResponse) {
|
||||||
|
return singleResponse.stream()
|
||||||
|
.filter(response -> !response.getAnswer().isEmpty() && response.getAnswer().contains(VERSION_STRING))
|
||||||
|
.map(response -> deserializeString(response.getAnswer()))
|
||||||
|
.filter(statusRaw -> statusRaw.getCode() == 200 && statusRaw.getResponse() == 90)
|
||||||
|
.map(statusRaw -> new StatusDTO(true, statusRaw.getCode(), statusRaw.getData().getSwitchValue(),
|
||||||
|
statusRaw.getData().getWatt(), statusRaw.getData().getAmp()))
|
||||||
|
.findFirst().orElse(new StatusDTO(false, 503, null, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
private StatusRawDTO deserializeString(String response) {
|
||||||
|
String extractedJsonResponse = response.substring(response.lastIndexOf(VERSION_STRING) + 2);
|
||||||
|
try {
|
||||||
|
return gson.fromJson(extractedJsonResponse, StatusRawDTO.class);
|
||||||
|
} catch (JsonSyntaxException e) {
|
||||||
|
logger.warn("Could not parse string \"{}\" to StatusRaw", response, e);
|
||||||
|
return new StatusRawDTO(503, 0, new StatusDTO(false, 503, null, null, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
/**
|
||||||
|
* 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.revogi.internal.api;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class {@link SwitchResponseDTO} describes the response when you switch a plug
|
||||||
|
*
|
||||||
|
* @author Andi Bräu - Initial contribution
|
||||||
|
*/
|
||||||
|
public class SwitchResponseDTO {
|
||||||
|
private final int response;
|
||||||
|
private final int code;
|
||||||
|
|
||||||
|
public SwitchResponseDTO(int response, int code) {
|
||||||
|
this.response = response;
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getResponse() {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o)
|
||||||
|
return true;
|
||||||
|
if (o == null || getClass() != o.getClass())
|
||||||
|
return false;
|
||||||
|
SwitchResponseDTO that = (SwitchResponseDTO) o;
|
||||||
|
return response == that.response && code == that.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(response, code);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
/**
|
||||||
|
* 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.revogi.internal.api;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.openhab.binding.revogi.internal.udp.UdpResponseDTO;
|
||||||
|
import org.openhab.binding.revogi.internal.udp.UdpSenderService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link SwitchService} enables the binding to actually switch plugs on and of
|
||||||
|
*
|
||||||
|
* @author Andi Bräu - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class SwitchService {
|
||||||
|
|
||||||
|
private static final String UDP_DISCOVERY_QUERY = "V3{\"sn\":\"%s\", \"cmd\": 20, \"port\": %d, \"state\": %d}";
|
||||||
|
private static final String VERSION_STRING = "V3";
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(SwitchService.class);
|
||||||
|
|
||||||
|
private final Gson gson = new GsonBuilder().create();
|
||||||
|
private final UdpSenderService udpSenderService;
|
||||||
|
|
||||||
|
public SwitchService(UdpSenderService udpSenderService) {
|
||||||
|
this.udpSenderService = udpSenderService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<SwitchResponseDTO> switchPort(String serialNumber, String ipAddress, int port, int state) {
|
||||||
|
if (state < 0 || state > 1) {
|
||||||
|
throw new IllegalArgumentException("state has to be 0 or 1");
|
||||||
|
}
|
||||||
|
if (port < 0) {
|
||||||
|
throw new IllegalArgumentException("Given port doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture<List<UdpResponseDTO>> responses;
|
||||||
|
if (ipAddress.trim().isEmpty()) {
|
||||||
|
responses = udpSenderService
|
||||||
|
.broadcastUdpDatagram(String.format(UDP_DISCOVERY_QUERY, serialNumber, port, state));
|
||||||
|
} else {
|
||||||
|
responses = udpSenderService.sendMessage(String.format(UDP_DISCOVERY_QUERY, serialNumber, port, state),
|
||||||
|
ipAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses.thenApply(this::getSwitchResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private SwitchResponseDTO getSwitchResponse(final List<UdpResponseDTO> singleResponse) {
|
||||||
|
return singleResponse.stream().filter(response -> !response.getAnswer().isEmpty())
|
||||||
|
.map(response -> deserializeString(response.getAnswer()))
|
||||||
|
.filter(switchResponse -> switchResponse.getCode() == 200 && switchResponse.getResponse() == 20)
|
||||||
|
.findFirst().orElse(new SwitchResponseDTO(0, 503));
|
||||||
|
}
|
||||||
|
|
||||||
|
private SwitchResponseDTO deserializeString(String response) {
|
||||||
|
String extractedJsonResponse = response.substring(response.lastIndexOf(VERSION_STRING) + 2);
|
||||||
|
try {
|
||||||
|
return gson.fromJson(extractedJsonResponse, SwitchResponseDTO.class);
|
||||||
|
} catch (JsonSyntaxException e) {
|
||||||
|
logger.warn("Could not parse string \"{}\" to SwitchResponse", response);
|
||||||
|
return new SwitchResponseDTO(0, 503);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.revogi.internal.udp;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.DatagramPacket;
|
||||||
|
import java.net.DatagramSocket;
|
||||||
|
import java.net.SocketException;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link DatagramSocketWrapper} wraps Java's DatagramSocket for better testing
|
||||||
|
* UdpSenderService
|
||||||
|
*
|
||||||
|
* @author Andi Bräu - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class DatagramSocketWrapper {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private DatagramSocket datagramSocket;
|
||||||
|
|
||||||
|
public void initSocket() throws SocketException {
|
||||||
|
closeSocket();
|
||||||
|
DatagramSocket localDatagramSocket = new DatagramSocket();
|
||||||
|
localDatagramSocket.setBroadcast(true);
|
||||||
|
localDatagramSocket.setSoTimeout(3);
|
||||||
|
datagramSocket = localDatagramSocket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void closeSocket() {
|
||||||
|
DatagramSocket localDatagramSocket = this.datagramSocket;
|
||||||
|
if (localDatagramSocket != null && !localDatagramSocket.isClosed()) {
|
||||||
|
localDatagramSocket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendPacket(DatagramPacket datagramPacket) throws IOException {
|
||||||
|
DatagramSocket localDatagramSocket = this.datagramSocket;
|
||||||
|
if (localDatagramSocket != null && !localDatagramSocket.isClosed()) {
|
||||||
|
localDatagramSocket.send(datagramPacket);
|
||||||
|
} else {
|
||||||
|
throw new SocketException("Datagram Socket closed or not initialized");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void receiveAnswer(DatagramPacket datagramPacket) throws IOException {
|
||||||
|
DatagramSocket localDatagramSocket = this.datagramSocket;
|
||||||
|
if (localDatagramSocket != null && !localDatagramSocket.isClosed()) {
|
||||||
|
localDatagramSocket.receive(datagramPacket);
|
||||||
|
} else {
|
||||||
|
throw new SocketException("Datagram Socket closed or not initialized");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
/**
|
||||||
|
* 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.revogi.internal.udp;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class {@link UdpResponseDTO} represents udp reponse we expect
|
||||||
|
*
|
||||||
|
* @author Andi Bräu - Initial contribution
|
||||||
|
*/
|
||||||
|
public class UdpResponseDTO {
|
||||||
|
private final String answer;
|
||||||
|
private final String ipAddress;
|
||||||
|
|
||||||
|
public UdpResponseDTO(String answer, String ipAddress) {
|
||||||
|
this.answer = answer;
|
||||||
|
this.ipAddress = ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAnswer() {
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIpAddress() {
|
||||||
|
return ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o)
|
||||||
|
return true;
|
||||||
|
if (o == null || getClass() != o.getClass())
|
||||||
|
return false;
|
||||||
|
UdpResponseDTO that = (UdpResponseDTO) o;
|
||||||
|
return answer.equals(that.answer) && ipAddress.equals(that.ipAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(answer, ipAddress);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
/**
|
||||||
|
* 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.revogi.internal.udp;
|
||||||
|
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.DatagramPacket;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.common.ThreadPoolManager;
|
||||||
|
import org.openhab.core.net.NetUtil;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link UdpSenderService} is responsible for sending and receiving udp packets
|
||||||
|
*
|
||||||
|
* @author Andi Bräu - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class UdpSenderService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limit timeout waiting time, as we have to deal with UDP
|
||||||
|
*
|
||||||
|
* How it works: for every loop, we'll wait a bit longer, so the timeout counter is multiplied with the timeout base
|
||||||
|
* value. Let max timeout count be 2 and timeout base value 800, then we'll have a maximum of loops of 3, waiting
|
||||||
|
* 800ms in the 1st loop, 1600ms in the 2nd loop and 2400ms in the third loop.
|
||||||
|
*/
|
||||||
|
private static final int MAX_TIMEOUT_COUNT = 2;
|
||||||
|
private static final long TIMEOUT_BASE_VALUE_MS = 800L;
|
||||||
|
private static final int REVOGI_PORT = 8888;
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(UdpSenderService.class);
|
||||||
|
private final DatagramSocketWrapper datagramSocketWrapper;
|
||||||
|
private final ScheduledExecutorService scheduler;
|
||||||
|
private final long timeoutBaseValue;
|
||||||
|
|
||||||
|
public UdpSenderService(DatagramSocketWrapper datagramSocketWrapper, ScheduledExecutorService scheduler) {
|
||||||
|
this.timeoutBaseValue = TIMEOUT_BASE_VALUE_MS;
|
||||||
|
this.datagramSocketWrapper = datagramSocketWrapper;
|
||||||
|
this.scheduler = scheduler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UdpSenderService(DatagramSocketWrapper datagramSocketWrapper, long timeout) {
|
||||||
|
this.timeoutBaseValue = timeout;
|
||||||
|
this.datagramSocketWrapper = datagramSocketWrapper;
|
||||||
|
this.scheduler = ThreadPoolManager.getScheduledPool("test pool");
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<List<UdpResponseDTO>> broadcastUdpDatagram(String content) {
|
||||||
|
List<String> allBroadcastAddresses = NetUtil.getAllBroadcastAddresses();
|
||||||
|
CompletableFuture<List<UdpResponseDTO>> future = new CompletableFuture<>();
|
||||||
|
scheduler.submit(() -> future.complete(allBroadcastAddresses.stream().map(address -> {
|
||||||
|
try {
|
||||||
|
return sendMessage(content, InetAddress.getByName(address));
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
logger.warn("Could not find host with IP {}", address);
|
||||||
|
return new ArrayList<UdpResponseDTO>();
|
||||||
|
}
|
||||||
|
}).flatMap(Collection::stream).distinct().collect(toList())));
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<List<UdpResponseDTO>> sendMessage(String content, String ipAddress) {
|
||||||
|
try {
|
||||||
|
CompletableFuture<List<UdpResponseDTO>> future = new CompletableFuture<>();
|
||||||
|
InetAddress inetAddress = InetAddress.getByName(ipAddress);
|
||||||
|
scheduler.submit(() -> future.complete(sendMessage(content, inetAddress)));
|
||||||
|
return future;
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
logger.warn("Could not find host with IP {}", ipAddress);
|
||||||
|
return CompletableFuture.completedFuture(Collections.emptyList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<UdpResponseDTO> sendMessage(String content, InetAddress inetAddress) {
|
||||||
|
logger.debug("Using address {}", inetAddress);
|
||||||
|
byte[] buf = content.getBytes(Charset.defaultCharset());
|
||||||
|
DatagramPacket packet = new DatagramPacket(buf, buf.length, inetAddress, REVOGI_PORT);
|
||||||
|
List<UdpResponseDTO> responses = Collections.emptyList();
|
||||||
|
try {
|
||||||
|
datagramSocketWrapper.initSocket();
|
||||||
|
datagramSocketWrapper.sendPacket(packet);
|
||||||
|
responses = getUdpResponses();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Error sending message or reading anwser {}", e.getMessage());
|
||||||
|
} finally {
|
||||||
|
datagramSocketWrapper.closeSocket();
|
||||||
|
}
|
||||||
|
return responses;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<UdpResponseDTO> getUdpResponses() {
|
||||||
|
int timeoutCounter = 0;
|
||||||
|
List<UdpResponseDTO> list = new ArrayList<>();
|
||||||
|
while (timeoutCounter < MAX_TIMEOUT_COUNT && !Thread.interrupted()) {
|
||||||
|
byte[] receivedBuf = new byte[512];
|
||||||
|
DatagramPacket answer = new DatagramPacket(receivedBuf, receivedBuf.length);
|
||||||
|
try {
|
||||||
|
datagramSocketWrapper.receiveAnswer(answer);
|
||||||
|
} catch (SocketTimeoutException | SocketException e) {
|
||||||
|
timeoutCounter++;
|
||||||
|
try {
|
||||||
|
TimeUnit.MILLISECONDS.sleep(timeoutCounter * timeoutBaseValue);
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
logger.debug("Interrupted sleep");
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Error sending message or reading anwser {}", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (answer.getAddress() != null && answer.getLength() > 0) {
|
||||||
|
list.add(new UdpResponseDTO(new String(answer.getData(), 0, answer.getLength()),
|
||||||
|
answer.getAddress().getHostAddress()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<binding:binding id="revogi" 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>Revogi Binding</name>
|
||||||
|
<description>This is the binding for Revogi devices. Revogi is a vendor of several smart home devices like light bulbs,
|
||||||
|
power strips and sensors.</description>
|
||||||
|
<author>Andi Bräu</author>
|
||||||
|
|
||||||
|
</binding:binding>
|
|
@ -0,0 +1,37 @@
|
||||||
|
# binding
|
||||||
|
binding.revogi.name = Revogi Smart Strip Binding
|
||||||
|
binding.revogi.description = Mit diesem Binding kann man die Steckdosen im Smart Strip steuern und Verbrauchsinformationen erhalten
|
||||||
|
|
||||||
|
# thing types
|
||||||
|
thing-type.revogi.smartstrip.label = SmartStrip
|
||||||
|
thing-type.revogi.smartstrip.description = Ein Binding, um Revogis Smart Strip zu steuern
|
||||||
|
|
||||||
|
# thing type config description
|
||||||
|
thing-type.config.revogi.smartstrip.serialNumber.label = Seriennummer
|
||||||
|
thing-type.config.revogi.smartstrip.serialNumber.description = Die Seriennummer des Smart Strips
|
||||||
|
thing-type.config.revogi.smartstrip.pollInterval.label = Aktualisierungsintervall
|
||||||
|
thing-type.config.revogi.smartstrip.pollInterval.description = Intervall, in dem der Status aktualisiert wird
|
||||||
|
thing-type.config.revogi.smartstrip.ipAddress.label = IP Adresse
|
||||||
|
thing-type.config.revogi.smartstrip.ipAddress.description = IP Adresse des Smart Strips
|
||||||
|
|
||||||
|
|
||||||
|
thing-type.revogi.smartstrip.group.plug1.label = Steckdose 1
|
||||||
|
thing-type.revogi.smartstrip.group.plug2.label = Steckdose 2
|
||||||
|
thing-type.revogi.smartstrip.group.plug3.label = Steckdose 3
|
||||||
|
thing-type.revogi.smartstrip.group.plug4.label = Steckdose 4
|
||||||
|
thing-type.revogi.smartstrip.group.plug5.label = Steckdose 5
|
||||||
|
thing-type.revogi.smartstrip.group.plug6.label = Steckdose 6
|
||||||
|
thing-type.revogi.smartstrip.group.overallPlug.label = Alle Steckdosen
|
||||||
|
|
||||||
|
# channel types
|
||||||
|
channel-type.revogi.single-plug.label = Schalter
|
||||||
|
channel-type.revogi.single-plug.description = Eine einzelne Steckdose schalten
|
||||||
|
channel-type.revogi.watts.label = Watt
|
||||||
|
channel-type.revogi.watts.description = Enthält die aktuelle genutzte Leistung
|
||||||
|
channel-type.revogi.amps.label = Ampere
|
||||||
|
channel-type.revogi.amps.description = Enthält die aktuelle Stromstärke
|
||||||
|
|
||||||
|
channel-group-type.revogi.plugActor.label = Einzelne Steckdose
|
||||||
|
channel-group-type.revogi.plugActor.description = Schaltet eine einzelne Steckdose und empfängt statistische Daten
|
||||||
|
channel-group-type.revogi.overallPlugActor.label = Alle Steckdosen
|
||||||
|
channel-group-type.revogi.overallPlugActor.description = Schaltet alle Steckdosen
|
|
@ -0,0 +1,91 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<thing:thing-descriptions bindingId="revogi"
|
||||||
|
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">
|
||||||
|
|
||||||
|
<!-- SmartStrip Thing Type -->
|
||||||
|
<thing-type id="smartstrip">
|
||||||
|
<label>SmartStrip</label>
|
||||||
|
<description>A Thing to control Revogi SmartStrip</description>
|
||||||
|
<category>PowerOutlet</category>
|
||||||
|
|
||||||
|
<channel-groups>
|
||||||
|
<channel-group id="overallPlug" typeId="overallPlugActuator"/>
|
||||||
|
<channel-group id="plug1" typeId="plugActuator">
|
||||||
|
<label>Plug 1</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="plug2" typeId="plugActuator">
|
||||||
|
<label>Plug 2</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="plug3" typeId="plugActuator">
|
||||||
|
<label>Plug 3</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="plug4" typeId="plugActuator">
|
||||||
|
<label>Plug 4</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="plug5" typeId="plugActuator">
|
||||||
|
<label>Plug 5</label>
|
||||||
|
</channel-group>
|
||||||
|
<channel-group id="plug6" typeId="plugActuator">
|
||||||
|
<label>Plug 6</label>
|
||||||
|
</channel-group>
|
||||||
|
</channel-groups>
|
||||||
|
|
||||||
|
<representation-property>serialNumber</representation-property>
|
||||||
|
<config-description>
|
||||||
|
<parameter name="serialNumber" type="text" required="true">
|
||||||
|
<label>Serial Number</label>
|
||||||
|
<description>Serial number of your smart strip.</description>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="pollInterval" type="integer" min="10" unit="s">
|
||||||
|
<label>Poll Interval</label>
|
||||||
|
<default>60</default>
|
||||||
|
<description>How often (seconds) should the smart strip status be polled?</description>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="ipAddress" type="text">
|
||||||
|
<label>IP Address</label>
|
||||||
|
<description>IP Address of your smart strip</description>
|
||||||
|
<context>network-address</context>
|
||||||
|
</parameter>
|
||||||
|
</config-description>
|
||||||
|
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
<channel-group-type id="plugActuator">
|
||||||
|
<label>Single Plug Actuator</label>
|
||||||
|
<description>Switches a single plug and retrieve stats for it</description>
|
||||||
|
<channels>
|
||||||
|
<channel id="switch" typeId="single-plug"/>
|
||||||
|
<channel id="watt" typeId="watts"/>
|
||||||
|
<channel id="amp" typeId="amps"/>
|
||||||
|
</channels>
|
||||||
|
</channel-group-type>
|
||||||
|
|
||||||
|
<channel-group-type id="overallPlugActuator">
|
||||||
|
<label>Overall Plug Actuator</label>
|
||||||
|
<description>Switches all plugs</description>
|
||||||
|
<channels>
|
||||||
|
<channel id="switch" typeId="single-plug"/>
|
||||||
|
</channels>
|
||||||
|
</channel-group-type>
|
||||||
|
|
||||||
|
<channel-type id="single-plug">
|
||||||
|
<item-type>Switch</item-type>
|
||||||
|
<label>Switch</label>
|
||||||
|
<description>Switch a single plug</description>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="watts" advanced="true">
|
||||||
|
<item-type>Number:Power</item-type>
|
||||||
|
<label>Power</label>
|
||||||
|
<description>Contains the current watt value for the given plug</description>
|
||||||
|
<state readOnly="true" pattern="%.1f W"/>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="amps" advanced="true">
|
||||||
|
<item-type>Number:ElectricCurrent</item-type>
|
||||||
|
<label>Current</label>
|
||||||
|
<description>Contains the current Amp value for the given plug</description>
|
||||||
|
<state readOnly="true" pattern="%.1f A"/>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
|
</thing:thing-descriptions>
|
|
@ -0,0 +1,76 @@
|
||||||
|
/**
|
||||||
|
* 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.revogi.internal.api;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.openhab.binding.revogi.internal.udp.UdpResponseDTO;
|
||||||
|
import org.openhab.binding.revogi.internal.udp.UdpSenderService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Andi Bräu - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class RevogiDiscoveryServiceTest {
|
||||||
|
|
||||||
|
private final UdpSenderService udpSenderService = mock(UdpSenderService.class);
|
||||||
|
private final RevogiDiscoveryService revogiDiscoveryService = new RevogiDiscoveryService(udpSenderService);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void discoverSmartStripSuccesfully() {
|
||||||
|
// given
|
||||||
|
DiscoveryResponseDTO discoveryResponse = new DiscoveryResponseDTO("1234", "reg", "sak", "Strip", "mac", "5.11");
|
||||||
|
List<UdpResponseDTO> discoveryString = Collections.singletonList(new UdpResponseDTO(
|
||||||
|
"{\"response\":0,\"data\":{\"sn\":\"1234\",\"regid\":\"reg\",\"sak\":\"sak\",\"name\":\"Strip\",\"mac\":\"mac\",\"ver\":\"5.11\"}}",
|
||||||
|
"127.0.0.1"));
|
||||||
|
when(udpSenderService.broadcastUdpDatagram("00sw=all,,,;"))
|
||||||
|
.thenReturn(CompletableFuture.completedFuture(discoveryString));
|
||||||
|
|
||||||
|
// when
|
||||||
|
CompletableFuture<List<DiscoveryRawResponseDTO>> discoverSmartStripsFutures = revogiDiscoveryService
|
||||||
|
.discoverSmartStrips();
|
||||||
|
|
||||||
|
// then
|
||||||
|
List<DiscoveryRawResponseDTO> discoverSmartStrips = discoverSmartStripsFutures.getNow(Collections.emptyList());
|
||||||
|
assertThat(discoverSmartStrips.size(), equalTo(1));
|
||||||
|
assertThat(discoverSmartStrips.get(0).getData(), equalTo(discoveryResponse));
|
||||||
|
assertThat(discoverSmartStrips.get(0).getIpAddress(), equalTo("127.0.0.1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void invalidUdpResponse() throws ExecutionException, InterruptedException {
|
||||||
|
// given
|
||||||
|
List<UdpResponseDTO> discoveryString = Collections
|
||||||
|
.singletonList(new UdpResponseDTO("something invalid", "12345"));
|
||||||
|
when(udpSenderService.broadcastUdpDatagram("00sw=all,,,;"))
|
||||||
|
.thenReturn(CompletableFuture.completedFuture(discoveryString));
|
||||||
|
|
||||||
|
// when
|
||||||
|
CompletableFuture<List<DiscoveryRawResponseDTO>> futureList = revogiDiscoveryService.discoverSmartStrips();
|
||||||
|
|
||||||
|
// then
|
||||||
|
List<DiscoveryRawResponseDTO> discoverSmartStrips = futureList.get();
|
||||||
|
assertThat(discoverSmartStrips.isEmpty(), is(true));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
/**
|
||||||
|
* 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.revogi.internal.api;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.openhab.binding.revogi.internal.udp.UdpResponseDTO;
|
||||||
|
import org.openhab.binding.revogi.internal.udp.UdpSenderService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Andi Bräu - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class StatusServiceTest {
|
||||||
|
|
||||||
|
private final UdpSenderService udpSenderService = mock(UdpSenderService.class);
|
||||||
|
private final StatusService statusService = new StatusService(udpSenderService);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getStatusSuccessfully() {
|
||||||
|
// given
|
||||||
|
StatusDTO status = new StatusDTO(true, 200, Arrays.asList(0, 0, 0, 0, 0, 0), Arrays.asList(0, 0, 0, 0, 0, 0),
|
||||||
|
Arrays.asList(0, 0, 0, 0, 0, 0));
|
||||||
|
List<UdpResponseDTO> statusString = Collections.singletonList(new UdpResponseDTO(
|
||||||
|
"V3{\"response\":90,\"code\":200,\"data\":{\"switch\":[0,0,0,0,0,0],\"watt\":[0,0,0,0,0,0],\"amp\":[0,0,0,0,0,0]}}",
|
||||||
|
"127.0.0.1"));
|
||||||
|
when(udpSenderService.sendMessage("V3{\"sn\":\"serial\", \"cmd\": 90}", "127.0.0.1"))
|
||||||
|
.thenReturn(CompletableFuture.completedFuture(statusString));
|
||||||
|
|
||||||
|
// when
|
||||||
|
CompletableFuture<StatusDTO> statusResponse = statusService.queryStatus("serial", "127.0.0.1");
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(status, statusResponse.getNow(new StatusDTO()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void invalidUdpResponse() {
|
||||||
|
// given
|
||||||
|
List<UdpResponseDTO> statusString = Collections.singletonList(new UdpResponseDTO("something invalid", "12345"));
|
||||||
|
when(udpSenderService.sendMessage("V3{\"sn\":\"serial\", \"cmd\": 90}", "127.0.0.1"))
|
||||||
|
.thenReturn(CompletableFuture.completedFuture(statusString));
|
||||||
|
|
||||||
|
// when
|
||||||
|
CompletableFuture<StatusDTO> futureStatus = statusService.queryStatus("serial", "127.0.0.1");
|
||||||
|
|
||||||
|
// then
|
||||||
|
StatusDTO status = futureStatus.getNow(new StatusDTO());
|
||||||
|
assertEquals(503, status.getResponseCode());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.revogi.internal.api;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.openhab.binding.revogi.internal.udp.UdpResponseDTO;
|
||||||
|
import org.openhab.binding.revogi.internal.udp.UdpSenderService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Andi Bräu - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class SwitchServiceTest {
|
||||||
|
|
||||||
|
private UdpSenderService udpSenderService = mock(UdpSenderService.class);
|
||||||
|
private SwitchService switchService = new SwitchService(udpSenderService);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getStatusSuccesfully() {
|
||||||
|
// given
|
||||||
|
List<UdpResponseDTO> response = Collections
|
||||||
|
.singletonList(new UdpResponseDTO("V3{\"response\":20,\"code\":200}", "127.0.0.1"));
|
||||||
|
when(udpSenderService.sendMessage("V3{\"sn\":\"serial\", \"cmd\": 20, \"port\": 1, \"state\": 1}", "127.0.0.1"))
|
||||||
|
.thenReturn(CompletableFuture.completedFuture(response));
|
||||||
|
|
||||||
|
// when
|
||||||
|
CompletableFuture<SwitchResponseDTO> switchResponse = switchService.switchPort("serial", "127.0.0.1", 1, 1);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(switchResponse.getNow(new SwitchResponseDTO(0, 0)), equalTo(new SwitchResponseDTO(20, 200)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getStatusSuccesfullyWithBroadcast() {
|
||||||
|
// given
|
||||||
|
List<UdpResponseDTO> response = Collections
|
||||||
|
.singletonList(new UdpResponseDTO("V3{\"response\":20,\"code\":200}", "127.0.0.1"));
|
||||||
|
when(udpSenderService.broadcastUdpDatagram("V3{\"sn\":\"serial\", \"cmd\": 20, \"port\": 1, \"state\": 1}"))
|
||||||
|
.thenReturn(CompletableFuture.completedFuture(response));
|
||||||
|
|
||||||
|
// when
|
||||||
|
CompletableFuture<SwitchResponseDTO> switchResponse = switchService.switchPort("serial", "", 1, 1);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(switchResponse.getNow(new SwitchResponseDTO(0, 0)), equalTo(new SwitchResponseDTO(20, 200)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void invalidUdpResponse() {
|
||||||
|
// given
|
||||||
|
List<UdpResponseDTO> response = Collections.singletonList(new UdpResponseDTO("something invalid", "12345"));
|
||||||
|
when(udpSenderService.sendMessage("V3{\"sn\":\"serial\", \"cmd\": 20, \"port\": 1, \"state\": 1}", "127.0.0.1"))
|
||||||
|
.thenReturn(CompletableFuture.completedFuture(response));
|
||||||
|
|
||||||
|
// when
|
||||||
|
CompletableFuture<SwitchResponseDTO> switchResponse = switchService.switchPort("serial", "127.0.0.1", 1, 1);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(switchResponse.getNow(new SwitchResponseDTO(0, 0)), equalTo(new SwitchResponseDTO(0, 503)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getExceptionOnWrongState() {
|
||||||
|
Assertions.assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> switchService.switchPort("serial", "127.0.0.1", 1, 12));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getExceptionOnWrongPort() {
|
||||||
|
Assertions.assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> switchService.switchPort("serial", "127.0.0.1", -1, 1));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
/**
|
||||||
|
* 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.revogi.internal.udp;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.DatagramPacket;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.openhab.core.net.NetUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Andi Bräu - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class UdpSenderServiceTest {
|
||||||
|
|
||||||
|
private final DatagramSocketWrapper datagramSocketWrapper = mock(DatagramSocketWrapper.class);
|
||||||
|
|
||||||
|
private final UdpSenderService udpSenderService = new UdpSenderService(datagramSocketWrapper, 1L);
|
||||||
|
|
||||||
|
private final int numberOfInterfaces = NetUtil.getAllBroadcastAddresses().size();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTimeout() throws IOException, ExecutionException, InterruptedException {
|
||||||
|
// given
|
||||||
|
doThrow(new SocketTimeoutException()).when(datagramSocketWrapper).receiveAnswer(any());
|
||||||
|
|
||||||
|
// when
|
||||||
|
CompletableFuture<List<UdpResponseDTO>> list = udpSenderService.broadcastUdpDatagram("send something");
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(list.get(), equalTo(Collections.emptyList()));
|
||||||
|
verify(datagramSocketWrapper, times(numberOfInterfaces * 2)).receiveAnswer(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOneAnswer() throws IOException, ExecutionException, InterruptedException {
|
||||||
|
// given
|
||||||
|
byte[] receivedBuf = "valid answer".getBytes();
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
DatagramPacket argument = invocation.getArgument(0);
|
||||||
|
argument.setData(receivedBuf);
|
||||||
|
argument.setAddress(InetAddress.getLocalHost());
|
||||||
|
return null;
|
||||||
|
}).doThrow(new SocketTimeoutException()).when(datagramSocketWrapper).receiveAnswer(any());
|
||||||
|
|
||||||
|
// when
|
||||||
|
CompletableFuture<List<UdpResponseDTO>> future = udpSenderService.broadcastUdpDatagram("send something");
|
||||||
|
|
||||||
|
// then
|
||||||
|
List<UdpResponseDTO> udpResponses = future.get();
|
||||||
|
assertThat(udpResponses.get(0).getAnswer(), is("valid answer"));
|
||||||
|
verify(datagramSocketWrapper, times(1 + 2 * numberOfInterfaces)).receiveAnswer(any());
|
||||||
|
}
|
||||||
|
}
|
|
@ -232,6 +232,7 @@
|
||||||
<module>org.openhab.binding.pushbullet</module>
|
<module>org.openhab.binding.pushbullet</module>
|
||||||
<module>org.openhab.binding.radiothermostat</module>
|
<module>org.openhab.binding.radiothermostat</module>
|
||||||
<module>org.openhab.binding.regoheatpump</module>
|
<module>org.openhab.binding.regoheatpump</module>
|
||||||
|
<module>org.openhab.binding.revogi</module>
|
||||||
<module>org.openhab.binding.remoteopenhab</module>
|
<module>org.openhab.binding.remoteopenhab</module>
|
||||||
<module>org.openhab.binding.rfxcom</module>
|
<module>org.openhab.binding.rfxcom</module>
|
||||||
<module>org.openhab.binding.rme</module>
|
<module>org.openhab.binding.rme</module>
|
||||||
|
|
Loading…
Reference in New Issue