[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:
Andi Bräu 2020-11-03 18:13:08 +01:00 committed by GitHub
parent c49eeb2528
commit 1c93eb9737
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1806 additions and 0 deletions

View File

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

View File

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

View File

@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@ -0,0 +1,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"}
...
```

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

View File

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

View File

@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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";
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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;
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,67 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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");
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,94 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.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));
}
}

View File

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

View File

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