mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[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.radiothermostat/ @mlobstein
|
||||
/bundles/org.openhab.binding.regoheatpump/ @crnjan
|
||||
/bundles/org.openhab.binding.revogi/ @andibraeu
|
||||
/bundles/org.openhab.binding.remoteopenhab/ @lolodomo
|
||||
/bundles/org.openhab.binding.rfxcom/ @martinvw @paulianttila
|
||||
/bundles/org.openhab.binding.rme/ @kgoderis
|
||||
|
@ -991,6 +991,11 @@
|
||||
<artifactId>org.openhab.binding.regoheatpump</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.revogi</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.remoteopenhab</artifactId>
|
||||
|
13
bundles/org.openhab.binding.revogi/NOTICE
Normal file
13
bundles/org.openhab.binding.revogi/NOTICE
Normal file
@ -0,0 +1,13 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
78
bundles/org.openhab.binding.revogi/README.md
Normal file
78
bundles/org.openhab.binding.revogi/README.md
Normal file
@ -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"}
|
||||
|
||||
...
|
||||
```
|
18
bundles/org.openhab.binding.revogi/pom.xml
Normal file
18
bundles/org.openhab.binding.revogi/pom.xml
Normal file
@ -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.radiothermostat</module>
|
||||
<module>org.openhab.binding.regoheatpump</module>
|
||||
<module>org.openhab.binding.revogi</module>
|
||||
<module>org.openhab.binding.remoteopenhab</module>
|
||||
<module>org.openhab.binding.rfxcom</module>
|
||||
<module>org.openhab.binding.rme</module>
|
||||
|
Loading…
Reference in New Issue
Block a user