added migrated 2.x add-ons

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

View File

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

View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.silvercrestwifisocket.internal;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.silvercrestwifisocket.internal.enums.SilvercrestWifiSocketVendor;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link SilvercrestWifiSocketBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Jaime Vaz - Initial contribution
* @author Christian Heimerl - for integration of EasyHome
*/
@NonNullByDefault
public class SilvercrestWifiSocketBindingConstants {
/**
* The binding id.
*/
public static final String BINDING_ID = "silvercrestwifisocket";
/**
* List of all Thing Type UIDs.
*/
public static final ThingTypeUID THING_TYPE_WIFI_SOCKET = new ThingTypeUID(BINDING_ID, "wifiSocket");
/**
* List of all Channel ids
*/
public static final String WIFI_SOCKET_CHANNEL_ID = "switch";
/**
* The supported thing types.
*/
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_WIFI_SOCKET);
// -------------- Configuration arguments ----------------
/**
* Mac address configuration argument key.
*/
public static final String MAC_ADDRESS_ARG = "macAddress";
/**
* Wifi socket update interval configuration argument key.
*/
public static final String UPDATE_INTERVAL_ARG = "updateInterval";
/**
* Host address configuration argument key.
*/
public static final String HOST_ADDRESS_ARG = "hostAddress";
/**
* Host address configuration argument key.
*/
public static final String VENDOR_ARG = "vendor";
// -------------- Default values ----------------
/**
* Default Wifi socket refresh interval.
*/
public static final long DEFAULT_REFRESH_INTERVAL = 60;
/**
* Default Wifi socket vendor.
*/
public static final SilvercrestWifiSocketVendor DEFAULT_VENDOR = SilvercrestWifiSocketVendor.LIDL_SILVERCREST;
/**
* Default Wifi socket default UDP port.
*/
public static final int WIFI_SOCKET_DEFAULT_UDP_PORT = 8530;
/**
* Discovery timeout in seconds.
*/
public static final int DISCOVERY_TIMEOUT_SECONDS = 4;
}

View File

@@ -0,0 +1,105 @@
/**
* 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.silvercrestwifisocket.internal;
import java.util.Collections;
import java.util.Set;
import org.openhab.binding.silvercrestwifisocket.internal.exceptions.MacAddressNotValidException;
import org.openhab.binding.silvercrestwifisocket.internal.handler.SilvercrestWifiSocketHandler;
import org.openhab.binding.silvercrestwifisocket.internal.handler.SilvercrestWifiSocketMediator;
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;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SilvercrestWifiSocketHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Jaime Vaz - Initial contribution
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.silvercrestwifisocket")
public class SilvercrestWifiSocketHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
.singleton(SilvercrestWifiSocketBindingConstants.THING_TYPE_WIFI_SOCKET);
private final Logger logger = LoggerFactory.getLogger(SilvercrestWifiSocketHandlerFactory.class);
private SilvercrestWifiSocketMediator mediator;
/**
* Used by OSGI to inject the mediator in the handler factory.
*
* @param mediator the mediator
*/
@Reference
public void setMediator(final SilvercrestWifiSocketMediator mediator) {
logger.debug("Mediator has been injected on handler factory service.");
this.mediator = mediator;
}
/**
* Used by OSGI to unsets the mediator from the handler factory.
*
* @param mediator the mediator
*/
public void unsetMediator(final SilvercrestWifiSocketMediator mitsubishiMediator) {
logger.debug("Mediator has been unsetted from discovery service.");
this.mediator = null;
}
@Override
public boolean supportsThingType(final ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected ThingHandler createHandler(final Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(SilvercrestWifiSocketBindingConstants.THING_TYPE_WIFI_SOCKET)) {
SilvercrestWifiSocketHandler handler;
logger.debug("Creating a new SilvercrestWifiSocketHandler...");
try {
handler = new SilvercrestWifiSocketHandler(thing);
logger.debug("SilvercrestWifiSocketMediator will register the handler.");
if (this.mediator != null) {
this.mediator.registerThingAndWifiSocketHandler(thing, handler);
} else {
logger.error(
"The mediator is missing on Handler factory. Without one mediator the handler cannot work!");
return null;
}
return handler;
} catch (MacAddressNotValidException e) {
logger.debug("The mac address passed to WifiSocketHandler by configurations is not valid.");
}
}
return null;
}
@Override
public void unregisterHandler(final Thing thing) {
if (this.mediator != null) {
this.mediator.unregisterWifiSocketHandlerByThing(thing);
}
super.unregisterHandler(thing);
}
}

View File

@@ -0,0 +1,116 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.silvercrestwifisocket.internal.discovery;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.openhab.binding.silvercrestwifisocket.internal.SilvercrestWifiSocketBindingConstants;
import org.openhab.binding.silvercrestwifisocket.internal.handler.SilvercrestWifiSocketMediator;
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.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This is the {@link DiscoveryService} for the Silvercrest Items.
*
* @author Jaime Vaz - Initial contribution
*
*/
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.silvercrestwifisocket")
public class SilvercrestWifiSocketDiscoveryService extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(SilvercrestWifiSocketDiscoveryService.class);
private SilvercrestWifiSocketMediator mediator;
/**
* Used by OSGI to inject the mediator in the discovery service.
*
* @param mediator the mediator
*/
@Reference
public void setMediator(final SilvercrestWifiSocketMediator mediator) {
logger.debug("Mediator has been injected on discovery service.");
this.mediator = mediator;
mediator.setDiscoveryService(this);
}
/**
* Used by OSGI to unset the mediator in the discovery service.
*
* @param mediator the mediator
*/
public void unsetMediator(final SilvercrestWifiSocketMediator mitsubishiMediator) {
logger.debug("Mediator has been unsetted from discovery service.");
this.mediator.setDiscoveryService(null);
this.mediator = null;
}
/**
* Constructor of the discovery service.
*
* @throws IllegalArgumentException if the timeout < 0
*/
public SilvercrestWifiSocketDiscoveryService() throws IllegalArgumentException {
super(SilvercrestWifiSocketBindingConstants.SUPPORTED_THING_TYPES_UIDS,
SilvercrestWifiSocketBindingConstants.DISCOVERY_TIMEOUT_SECONDS);
}
@Override
public Set<ThingTypeUID> getSupportedThingTypes() {
return SilvercrestWifiSocketBindingConstants.SUPPORTED_THING_TYPES_UIDS;
}
@Override
protected void startScan() {
logger.debug("Don't need to start new scan... background scanning in progress by mediator.");
}
/**
* Method called by mediator, when receive one packet from one unknown Wifi Socket.
*
* @param macAddress the mack address from the device.
* @param hostAddress the host address from the device.
*/
public void discoveredWifiSocket(final String macAddress, final String hostAddress) {
Map<String, Object> properties = new HashMap<>(2);
properties.put(SilvercrestWifiSocketBindingConstants.MAC_ADDRESS_ARG, macAddress);
properties.put(SilvercrestWifiSocketBindingConstants.HOST_ADDRESS_ARG, hostAddress);
ThingUID newThingId = new ThingUID(SilvercrestWifiSocketBindingConstants.THING_TYPE_WIFI_SOCKET, macAddress);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(newThingId).withProperties(properties)
.withLabel("Silvercrest Wifi Socket").withRepresentationProperty(macAddress).build();
logger.debug("Discovered new thing with mac address '{}' and host address '{}'", macAddress, hostAddress);
this.thingDiscovered(discoveryResult);
}
// SETTERS AND GETTERS
/**
* Gets the {@link SilvercrestWifiSocketMediator} of this binding.
*
* @return {@link SilvercrestWifiSocketMediator}.
*/
public SilvercrestWifiSocketMediator getMediator() {
return this.mediator;
}
}

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.silvercrestwifisocket.internal.entities;
import org.openhab.binding.silvercrestwifisocket.internal.enums.SilvercrestWifiSocketRequestType;
import org.openhab.binding.silvercrestwifisocket.internal.enums.SilvercrestWifiSocketVendor;
/**
* This POJO represents one Wifi Socket request.
*
* @author Jaime Vaz - Initial contribution
* @author Christian Heimerl - for integration of EasyHome
*
*/
public class SilvercrestWifiSocketRequest {
private String macAddress;
private SilvercrestWifiSocketRequestType type;
private SilvercrestWifiSocketVendor vendor;
/**
* Default constructor.
*
* @param macAddress the mac address
* @param type the {@link SilvercrestWifiSocketRequestType}
* @param vendor the {@link SilvercrestWifiSocketVendor}
*/
public SilvercrestWifiSocketRequest(final String macAddress, final SilvercrestWifiSocketRequestType type,
final SilvercrestWifiSocketVendor vendor) {
this.macAddress = macAddress;
this.type = type;
this.vendor = vendor;
}
public String getMacAddress() {
return this.macAddress;
}
public void setMacAddress(final String macAddress) {
this.macAddress = macAddress;
}
public SilvercrestWifiSocketRequestType getType() {
return this.type;
}
public void setType(final SilvercrestWifiSocketRequestType type) {
this.type = type;
}
public SilvercrestWifiSocketVendor getVendor() {
return vendor;
}
public void setVendor(SilvercrestWifiSocketVendor vendor) {
this.vendor = vendor;
}
}

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.silvercrestwifisocket.internal.entities;
import org.openhab.binding.silvercrestwifisocket.internal.enums.SilvercrestWifiSocketResponseType;
import org.openhab.binding.silvercrestwifisocket.internal.enums.SilvercrestWifiSocketVendor;
/**
* This POJO represents one Wifi Socket Response.
*
* @author Jaime Vaz - Initial contribution
* @author Christian Heimerl - for integration of EasyHome
*
*/
public class SilvercrestWifiSocketResponse {
private String macAddress;
private String hostAddress;
private SilvercrestWifiSocketResponseType type;
private SilvercrestWifiSocketVendor vendor;
/**
* Default constructor.
*
* @param macAddress the mac address
* @param hostAddress the host address
* @param type the {@link SilvercrestWifiSocketResponseType}
* @param vendor the vendor of the socket
*/
public SilvercrestWifiSocketResponse(final String macAddress, final String hostAddress,
final SilvercrestWifiSocketResponseType type, final SilvercrestWifiSocketVendor vendor) {
super();
this.macAddress = macAddress;
this.hostAddress = hostAddress;
this.type = type;
this.vendor = vendor;
}
/**
* Constructor.
*
* @param macAddress the mac address
* @param type the {@link SilvercrestWifiSocketResponseType}
* @param vendor the vendor of the socket
*/
public SilvercrestWifiSocketResponse(final String macAddress, final SilvercrestWifiSocketResponseType type,
final SilvercrestWifiSocketVendor vendor) {
this(macAddress, null, type, vendor);
}
public String getMacAddress() {
return this.macAddress;
}
public void setMacAddress(final String macAddress) {
this.macAddress = macAddress;
}
public SilvercrestWifiSocketResponseType getType() {
return this.type;
}
public void setType(final SilvercrestWifiSocketResponseType type) {
this.type = type;
}
public String getHostAddress() {
return this.hostAddress;
}
public void setHostAddress(final String hostAddress) {
this.hostAddress = hostAddress;
}
public SilvercrestWifiSocketVendor getVendor() {
return vendor;
}
public void setVendor(SilvercrestWifiSocketVendor vendor) {
this.vendor = vendor;
}
}

View File

@@ -0,0 +1,45 @@
/**
* 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.silvercrestwifisocket.internal.enums;
/**
* This enum represents the available Wifi Socket request types.
*
* @author Jaime Vaz - Initial contribution
*
*/
public enum SilvercrestWifiSocketRequestType {
/** Request ON. */
ON("010000FFFF04040404"),
/** Request OFF. */
OFF("01000000FF04040404"),
/** Request Status. */
GPIO_STATUS("020000000004040404"),
/** Discover socket. The command has one placeholder for the mac address. */
DISCOVERY("23%s0202");
private String command;
private SilvercrestWifiSocketRequestType(final String command) {
this.command = command;
}
/**
* Gets the hexadecimal command/format for include in request messages.
*
* @return the hexadecimal command/format
*/
public String getCommand() {
return this.command;
}
}

View File

@@ -0,0 +1,30 @@
/**
* 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.silvercrestwifisocket.internal.enums;
/**
* This enum represents the available Wifi Socket response types.
*
* @author Jaime Vaz - Initial contribution
*
*/
public enum SilvercrestWifiSocketResponseType {
/** Status changed to ON. */
ON,
/** Status changed to OFF. */
OFF,
/** ACKnowledgement. */
ACK,
/** Discovery request. */
DISCOVERY
}

View File

@@ -0,0 +1,65 @@
/**
* 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.silvercrestwifisocket.internal.enums;
/**
* This enum represents the available Wifi Socket vendors.
*
* @author Christian Heimerl - Initial contribution
*
*/
public enum SilvercrestWifiSocketVendor {
LIDL_SILVERCREST("C1", "7150"),
ALDI_EASYHOME("C2", "92DD");
private final String companyCode;
private String authenticationCode;
SilvercrestWifiSocketVendor(String companyCode, String authenticationCode) {
this.companyCode = companyCode;
this.authenticationCode = authenticationCode;
}
/**
* Gets the hexadecimal company code included in a request message.
*
* @return the hexadecimal company code
*/
public String getCompanyCode() {
return this.companyCode;
}
/**
* Gets the hexadecimal authentication code included in a request message
*
* @return the hexadecimal authentication code
*/
public String getAuthenticationCode() {
return this.authenticationCode;
}
/**
* Returns the SilvercrestWifiSocketVendor matching the given two digit company code.
*
* @param companyCode The two digit company code that should be searched for, e.g. C1
* @return A SilvercrestWifiSocketVendor or null if no vendor matches the company code.
*/
public static SilvercrestWifiSocketVendor fromCode(String companyCode) {
for (SilvercrestWifiSocketVendor v : SilvercrestWifiSocketVendor.values()) {
if (v.getCompanyCode().equals(companyCode)) {
return v;
}
}
return null;
}
}

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.silvercrestwifisocket.internal.exceptions;
/**
* Exception throwed when one Mac address is not valid.
*
* @author Jaime Vaz - Initial contribution
*
*/
public class MacAddressNotValidException extends Exception {
private static final long serialVersionUID = 6131138252323778017L;
private final String macAddress;
/**
* Default constructor.
*
* @param message the error message
* @param macAddress the wrong mac address.
*/
public MacAddressNotValidException(final String message, final String macAddress) {
super(message);
this.macAddress = macAddress;
}
// SETTERS AND GETTERS
public String getMacAddress() {
return this.macAddress;
}
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.silvercrestwifisocket.internal.exceptions;
/**
* Exception throwed when some packet is not one response packet.
*
* @author Jaime Vaz - Initial contribution
*
*/
public class NotOneResponsePacketException extends Exception {
private static final long serialVersionUID = -8531181654734497851L;
/**
* Default constructor.
*
* @param message the error message
*/
public NotOneResponsePacketException(final String message) {
super(message);
}
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.silvercrestwifisocket.internal.exceptions;
/**
* Exception throwed when some packet has one integrity error.
*
* @author Jaime Vaz - Initial contribution
*
*/
public class PacketIntegrityErrorException extends Exception {
private static final long serialVersionUID = -8531181654734497851L;
/**
* Default constructor.
*
* @param message the error message
*/
public PacketIntegrityErrorException(final String message) {
super(message);
}
}

View File

@@ -0,0 +1,384 @@
/**
* 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.silvercrestwifisocket.internal.handler;
import static org.openhab.binding.silvercrestwifisocket.internal.SilvercrestWifiSocketBindingConstants.WIFI_SOCKET_CHANNEL_ID;
import java.math.BigDecimal;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.openhab.binding.silvercrestwifisocket.internal.SilvercrestWifiSocketBindingConstants;
import org.openhab.binding.silvercrestwifisocket.internal.entities.SilvercrestWifiSocketRequest;
import org.openhab.binding.silvercrestwifisocket.internal.entities.SilvercrestWifiSocketResponse;
import org.openhab.binding.silvercrestwifisocket.internal.enums.SilvercrestWifiSocketRequestType;
import org.openhab.binding.silvercrestwifisocket.internal.enums.SilvercrestWifiSocketVendor;
import org.openhab.binding.silvercrestwifisocket.internal.exceptions.MacAddressNotValidException;
import org.openhab.binding.silvercrestwifisocket.internal.utils.NetworkUtils;
import org.openhab.binding.silvercrestwifisocket.internal.utils.ValidationUtils;
import org.openhab.binding.silvercrestwifisocket.internal.utils.WifiSocketPacketConverter;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SilvercrestWifiSocketHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Jaime Vaz - Initial contribution
* @author Christian Heimerl - for integration of EasyHome
*/
public class SilvercrestWifiSocketHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(SilvercrestWifiSocketHandler.class);
private String hostAddress;
private String macAddress;
private SilvercrestWifiSocketVendor vendor = SilvercrestWifiSocketBindingConstants.DEFAULT_VENDOR;
private Long updateInterval = SilvercrestWifiSocketBindingConstants.DEFAULT_REFRESH_INTERVAL;
private final WifiSocketPacketConverter converter = new WifiSocketPacketConverter();
private ScheduledFuture<?> keepAliveJob;
private long latestUpdate = -1;
/**
* Default constructor.
*
* @param thing the thing of the handler.
* @throws MacAddressNotValidException if the mac address isn't valid.
*/
public SilvercrestWifiSocketHandler(final Thing thing) throws MacAddressNotValidException {
super(thing);
this.saveMacAddressFromConfiguration(this.getConfig());
this.saveHostAddressFromConfiguration(this.getConfig());
this.saveUpdateIntervalFromConfiguration(this.getConfig());
this.saveVendorFromConfiguration(this.getConfig());
}
@Override
public void handleCommand(final ChannelUID channelUID, final Command command) {
if (channelUID.getId().equals(WIFI_SOCKET_CHANNEL_ID)) {
logger.debug("Silvercrest socket command received: {}", command);
if (command == OnOffType.ON) {
this.sendCommand(SilvercrestWifiSocketRequestType.ON);
} else if (command == OnOffType.OFF) {
this.sendCommand(SilvercrestWifiSocketRequestType.OFF);
} else if (command == RefreshType.REFRESH) {
this.sendCommand(SilvercrestWifiSocketRequestType.GPIO_STATUS);
}
}
}
@Override
public void handleRemoval() {
// stop update thread
this.keepAliveJob.cancel(true);
super.handleRemoval();
}
/**
* Starts one thread that querys the state of the socket, after the defined refresh interval.
*/
private void initGetStatusAndKeepAliveThread() {
if (this.keepAliveJob != null) {
this.keepAliveJob.cancel(true);
}
// try with handler port if is null
Runnable runnable = () -> {
logger.debug(
"Begin of Socket keep alive thread routine. Current configuration update interval: {} seconds.",
SilvercrestWifiSocketHandler.this.updateInterval);
long now = System.currentTimeMillis();
long timePassedFromLastUpdateInSeconds = (now - SilvercrestWifiSocketHandler.this.latestUpdate) / 1000;
logger.trace("Latest Update: {} Now: {} Delta: {} seconds", SilvercrestWifiSocketHandler.this.latestUpdate,
now, timePassedFromLastUpdateInSeconds);
logger.debug("It has been passed {} seconds since the last update on socket with mac address {}.",
timePassedFromLastUpdateInSeconds, SilvercrestWifiSocketHandler.this.macAddress);
boolean mustUpdateHostAddress = timePassedFromLastUpdateInSeconds > (SilvercrestWifiSocketHandler.this.updateInterval
* 2);
if (mustUpdateHostAddress) {
logger.debug("No updates have been received for a long time, search the mac address {} in network...",
SilvercrestWifiSocketHandler.this.getMacAddress());
SilvercrestWifiSocketHandler.this.lookupForSocketHostAddress();
}
boolean considerThingOffline = (SilvercrestWifiSocketHandler.this.latestUpdate < 0)
|| (timePassedFromLastUpdateInSeconds > (SilvercrestWifiSocketHandler.this.updateInterval * 4));
if (considerThingOffline) {
logger.debug(
"No updates have been received for a long long time will put the thing with mac address {} OFFLINE.",
SilvercrestWifiSocketHandler.this.getMacAddress());
SilvercrestWifiSocketHandler.this.updateStatus(ThingStatus.OFFLINE);
}
// request gpio status
SilvercrestWifiSocketHandler.this.sendCommand(SilvercrestWifiSocketRequestType.GPIO_STATUS);
};
this.keepAliveJob = this.scheduler.scheduleWithFixedDelay(runnable, 1,
SilvercrestWifiSocketHandler.this.updateInterval, TimeUnit.SECONDS);
}
@Override
public void initialize() {
this.initGetStatusAndKeepAliveThread();
updateStatus(ThingStatus.ONLINE);
this.saveConfigurationsUsingCurrentStates();
}
/**
* Lookup for socket host address, by sending one broadcast discovery message. Eventually the socket will respond to
* the message. When the mediator receives the message, it will set the host address in this handler, for future
* communications.
*/
private void lookupForSocketHostAddress() {
SilvercrestWifiSocketRequest requestPacket = new SilvercrestWifiSocketRequest(this.macAddress,
SilvercrestWifiSocketRequestType.DISCOVERY, this.vendor);
for (InetAddress broadcastAddressFound : NetworkUtils.getAllBroadcastAddresses()) {
logger.debug("Will query for device with mac address {} in network with broadcast address {}",
this.macAddress, broadcastAddressFound);
this.sendRequestPacket(requestPacket, broadcastAddressFound);
}
}
/**
* Method called by {@link SilvercrestWifiSocketMediator} when one new message has been received for this handler.
*
* @param receivedMessage the received {@link SilvercrestWifiSocketResponse}.
*/
public void newReceivedResponseMessage(final SilvercrestWifiSocketResponse receivedMessage) {
// if the host of the packet is different from the host address set in handler, update the host
// address.
if (!receivedMessage.getHostAddress().equals(this.hostAddress)) {
logger.debug(
"The host of the packet is different from the host address set in handler. "
+ "Will update the host address. handler of mac: {}. "
+ "Old host address: '{}' -> new host address: '{}'",
this.macAddress, this.hostAddress, receivedMessage.getHostAddress());
this.hostAddress = receivedMessage.getHostAddress();
this.saveConfigurationsUsingCurrentStates();
}
switch (receivedMessage.getType()) {
case ACK:
break;
case DISCOVERY:
break;
case OFF:
this.updateState(SilvercrestWifiSocketBindingConstants.WIFI_SOCKET_CHANNEL_ID, OnOffType.OFF);
break;
case ON:
this.updateState(SilvercrestWifiSocketBindingConstants.WIFI_SOCKET_CHANNEL_ID, OnOffType.ON);
break;
default:
logger.debug("Command not found!");
break;
}
this.updateStatus(ThingStatus.ONLINE);
this.latestUpdate = System.currentTimeMillis();
}
/**
* Saves the host address from configuration in field.
*
* @param configuration The {@link Configuration}
*/
private void saveHostAddressFromConfiguration(final Configuration configuration) {
if ((configuration != null)
&& (configuration.get(SilvercrestWifiSocketBindingConstants.HOST_ADDRESS_ARG) != null)) {
this.hostAddress = String
.valueOf(configuration.get(SilvercrestWifiSocketBindingConstants.HOST_ADDRESS_ARG));
}
}
/**
* Saves the host address from configuration in field.
*
* @param configuration The {@link Configuration}
*/
private void saveUpdateIntervalFromConfiguration(final Configuration configuration) {
this.updateInterval = SilvercrestWifiSocketBindingConstants.DEFAULT_REFRESH_INTERVAL;
if ((configuration != null)
&& (configuration.get(SilvercrestWifiSocketBindingConstants.UPDATE_INTERVAL_ARG) instanceof BigDecimal)
&& (((BigDecimal) configuration.get(SilvercrestWifiSocketBindingConstants.UPDATE_INTERVAL_ARG))
.longValue() > 0)) {
this.updateInterval = ((BigDecimal) configuration
.get(SilvercrestWifiSocketBindingConstants.UPDATE_INTERVAL_ARG)).longValue();
}
}
/**
* Saves the mac address from configuration in field.
*
* @param configuration The {@link Configuration}
*/
private void saveMacAddressFromConfiguration(final Configuration configuration) throws MacAddressNotValidException {
if ((configuration != null)
&& (configuration.get(SilvercrestWifiSocketBindingConstants.MAC_ADDRESS_ARG) != null)) {
String macAddress = String
.valueOf(configuration.get(SilvercrestWifiSocketBindingConstants.MAC_ADDRESS_ARG));
if (ValidationUtils.isMacNotValid(macAddress)) {
throw new MacAddressNotValidException("Mac address is not valid", macAddress);
}
this.macAddress = macAddress.replaceAll(":", "").toUpperCase();
}
if (this.macAddress == null) {
throw new MacAddressNotValidException("Mac address is not valid", this.macAddress);
}
}
/**
* Saves the vendor from configuration in field.
*
* @param configuration The {@link Configuration}
*/
private void saveVendorFromConfiguration(final Configuration configuration) {
if ((configuration != null) && (configuration.get(SilvercrestWifiSocketBindingConstants.VENDOR_ARG) != null)) {
this.vendor = SilvercrestWifiSocketVendor
.valueOf(String.valueOf(configuration.get(SilvercrestWifiSocketBindingConstants.VENDOR_ARG)));
}
}
/**
* Sends one command to the Wifi Socket. If the host address is not set, it will trigger the lookup of the
* host address and discard the command queried.
*
* @param type the {@link SilvercrestWifiSocketRequestType} of the command.
*/
private void sendCommand(final SilvercrestWifiSocketRequestType type) {
logger.debug("Send command for mac addr: {} with type: {} with hostaddress: {}", this.getMacAddress(),
type.name(), this.hostAddress);
if (this.hostAddress == null) {
logger.debug(
"Send command cannot proceed until one Host Address is set for mac address: {} Will invoke one mac address lookup!",
this.macAddress);
this.lookupForSocketHostAddress();
} else {
InetAddress address;
try {
address = InetAddress.getByName(this.hostAddress);
this.sendRequestPacket(new SilvercrestWifiSocketRequest(this.macAddress, type, this.vendor), address);
} catch (UnknownHostException e) {
logger.debug("Host Address not found: {}. Will lookup Mac address.", this.hostAddress);
this.hostAddress = null;
this.lookupForSocketHostAddress();
}
}
}
/**
* Sends {@link SilvercrestWifiSocketRequest} to the passed {@link InetAddress}.
*
* @param requestPacket the {@link SilvercrestWifiSocketRequest}.
* @param address the {@link InetAddress}.
*/
private void sendRequestPacket(final SilvercrestWifiSocketRequest requestPacket, final InetAddress address) {
DatagramSocket dsocket = null;
try {
if (address != null) {
byte[] message = this.converter.transformToByteMessage(requestPacket);
logger.trace("Preparing packet to send...");
// Initialize a datagram packet with data and address
DatagramPacket packet = new DatagramPacket(message, message.length, address,
SilvercrestWifiSocketBindingConstants.WIFI_SOCKET_DEFAULT_UDP_PORT);
// Create a datagram socket, send the packet through it, close it.
dsocket = new DatagramSocket();
dsocket.send(packet);
logger.debug("Sent packet to address: {} and port {}", address,
SilvercrestWifiSocketBindingConstants.WIFI_SOCKET_DEFAULT_UDP_PORT);
}
} catch (Exception exception) {
logger.debug("Something wrong happen sending the packet to address: {} and port {}... msg: {}", address,
SilvercrestWifiSocketBindingConstants.WIFI_SOCKET_DEFAULT_UDP_PORT, exception.getMessage());
} finally {
if (dsocket != null) {
dsocket.close();
}
}
}
@Override
protected void updateConfiguration(final Configuration configuration) {
try {
this.latestUpdate = -1;
this.saveMacAddressFromConfiguration(configuration);
this.hostAddress = null;
this.saveHostAddressFromConfiguration(configuration);
if (this.hostAddress == null) {
this.lookupForSocketHostAddress();
}
this.saveUpdateIntervalFromConfiguration(configuration);
this.saveVendorFromConfiguration(configuration);
this.initGetStatusAndKeepAliveThread();
this.saveConfigurationsUsingCurrentStates();
} catch (MacAddressNotValidException e) {
logger.error("The Mac address passed is not valid! {}", e.getMacAddress());
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
}
}
/**
* Save the current runtime configuration of the handler in configuration mechanism.
*/
private void saveConfigurationsUsingCurrentStates() {
Map<String, Object> map = new HashMap<>();
map.put(SilvercrestWifiSocketBindingConstants.MAC_ADDRESS_ARG, this.macAddress);
map.put(SilvercrestWifiSocketBindingConstants.HOST_ADDRESS_ARG, this.hostAddress);
map.put(SilvercrestWifiSocketBindingConstants.VENDOR_ARG, this.vendor.toString());
map.put(SilvercrestWifiSocketBindingConstants.UPDATE_INTERVAL_ARG, this.updateInterval);
Configuration newConfiguration = new Configuration(map);
super.updateConfiguration(newConfiguration);
}
// SETTERS AND GETTERS
public String getHostAddress() {
return this.hostAddress;
}
public String getMacAddress() {
return this.macAddress;
}
public SilvercrestWifiSocketVendor getVendor() {
return this.vendor;
}
}

View File

@@ -0,0 +1,66 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.silvercrestwifisocket.internal.handler;
import java.util.Set;
import org.openhab.binding.silvercrestwifisocket.internal.discovery.SilvercrestWifiSocketDiscoveryService;
import org.openhab.binding.silvercrestwifisocket.internal.entities.SilvercrestWifiSocketResponse;
import org.openhab.binding.silvercrestwifisocket.internal.runnable.SilvercrestWifiSocketUpdateReceiverRunnable;
import org.openhab.core.thing.Thing;
/**
* The {@link SilvercrestWifiSocketMediator} is responsible for receiving all the UDP packets and route correctly to
* each handler.
*
* @author Jaime Vaz - Initial contribution
*/
public interface SilvercrestWifiSocketMediator {
/**
* This method is called by the {@link SilvercrestWifiSocketUpdateReceiverRunnable}, when one new message has been
* received.
*
* @param receivedMessage the {@link SilvercrestWifiSocketResponse} message.
*/
void processReceivedPacket(final SilvercrestWifiSocketResponse receivedMessage);
/**
* Registers a new {@link Thing} and the corresponding {@link SilvercrestWifiSocketHandler}.
*
* @param thing the {@link Thing}.
* @param handler the {@link SilvercrestWifiSocketHandler}.
*/
void registerThingAndWifiSocketHandler(final Thing thing, final SilvercrestWifiSocketHandler handler);
/**
* Unregisters a {@link SilvercrestWifiSocketHandler} by the corresponding {@link Thing}.
*
* @param thing the {@link Thing}.
*/
void unregisterWifiSocketHandlerByThing(final Thing thing);
/**
* Returns all the {@link Thing} registered.
*
* @returns all the {@link Thing}.
*/
Set<Thing> getAllThingsRegistred();
/**
* Sets the discovery service to inform the when one new thing has been found.
*
* @param discoveryService the discovery service.
*/
void setDiscoveryService(SilvercrestWifiSocketDiscoveryService discoveryService);
}

View File

@@ -0,0 +1,171 @@
/**
* 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.silvercrestwifisocket.internal.handler;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.openhab.binding.silvercrestwifisocket.internal.SilvercrestWifiSocketBindingConstants;
import org.openhab.binding.silvercrestwifisocket.internal.discovery.SilvercrestWifiSocketDiscoveryService;
import org.openhab.binding.silvercrestwifisocket.internal.entities.SilvercrestWifiSocketResponse;
import org.openhab.binding.silvercrestwifisocket.internal.runnable.SilvercrestWifiSocketUpdateReceiverRunnable;
import org.openhab.core.thing.Thing;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SilvercrestWifiSocketMediatorImpl} is responsible for receiving all the UDP packets and route correctly to
* each handler.
*
* @author Jaime Vaz - Initial contribution
*/
@Component(service = SilvercrestWifiSocketMediator.class, immediate = true)
public class SilvercrestWifiSocketMediatorImpl implements SilvercrestWifiSocketMediator {
private final Logger logger = LoggerFactory.getLogger(SilvercrestWifiSocketMediatorImpl.class);
private final Map<Thing, SilvercrestWifiSocketHandler> handlersRegistredByThing = new HashMap<>();
private SilvercrestWifiSocketUpdateReceiverRunnable receiver;
private Thread receiverThread;
private SilvercrestWifiSocketDiscoveryService silvercrestDiscoveryService;
/**
* Called at the service activation.
*
* @param componentContext the componentContext
*/
protected void activate(final ComponentContext componentContext) {
logger.debug("Mediator has been activated by OSGI.");
this.initMediatorWifiSocketUpdateReceiverRunnable();
}
/**
* Called at the service deactivation.
*
* @param componentContext the componentContext
*/
protected void deactivate(final ComponentContext componentContext) {
if (this.receiver != null) {
this.receiver.shutdown();
}
}
/**
* This method is called by the {@link SilvercrestWifiSocketUpdateReceiverRunnable}, when one new message has been
* received.
*
* @param receivedMessage the {@link SilvercrestWifiSocketResponse} message.
*/
@Override
public void processReceivedPacket(final SilvercrestWifiSocketResponse receivedMessage) {
logger.debug("Received packet from: {} with content: [{}]", receivedMessage.getHostAddress(),
receivedMessage.getType());
SilvercrestWifiSocketHandler handler = this.getHandlerRegistredByMac(receivedMessage.getMacAddress());
if (handler != null) {
// deliver message to handler.
handler.newReceivedResponseMessage(receivedMessage);
logger.debug("Received message delivered with success to handler of mac {}",
receivedMessage.getMacAddress());
} else {
logger.debug("There is no handler registered for mac address:{}", receivedMessage.getMacAddress());
// notify discovery service of thing found!
this.silvercrestDiscoveryService.discoveredWifiSocket(receivedMessage.getMacAddress(),
receivedMessage.getHostAddress());
}
}
/**
* Regists one new {@link Thing} and the corresponding {@link SilvercrestWifiSocketHandler}.
*
* @param thing the {@link Thing}.
* @param handler the {@link SilvercrestWifiSocketHandler}.
*/
@Override
public void registerThingAndWifiSocketHandler(final Thing thing, final SilvercrestWifiSocketHandler handler) {
this.handlersRegistredByThing.put(thing, handler);
}
/**
* Unregists one {@link SilvercrestWifiSocketHandler} by the corresponding {@link Thing}.
*
* @param thing the {@link Thing}.
*/
@Override
public void unregisterWifiSocketHandlerByThing(final Thing thing) {
SilvercrestWifiSocketHandler handler = this.handlersRegistredByThing.get(thing);
if (handler != null) {
this.handlersRegistredByThing.remove(thing);
}
}
/**
* Utilitary method to get the registered thing handler in mediator by the mac address.
*
* @param macAddress the mac address of the thing of the handler.
* @return {@link SilvercrestWifiSocketHandler} if found.
*/
private SilvercrestWifiSocketHandler getHandlerRegistredByMac(final String macAddress) {
SilvercrestWifiSocketHandler searchedHandler = null;
for (SilvercrestWifiSocketHandler handler : this.handlersRegistredByThing.values()) {
if (macAddress.equals(handler.getMacAddress())) {
searchedHandler = handler;
// don't spend more computation. Found the handler.
break;
}
}
return searchedHandler;
}
/**
* Inits the mediator WifiSocketUpdateReceiverRunnable thread. This thread is responsible to receive all
* packets from Wifi Socket devices, and redirect the messages to mediator.
*/
private void initMediatorWifiSocketUpdateReceiverRunnable() {
// try with handler port if is null
if ((this.receiver == null) || ((this.receiverThread != null)
&& (this.receiverThread.isInterrupted() || !this.receiverThread.isAlive()))) {
try {
this.receiver = new SilvercrestWifiSocketUpdateReceiverRunnable(this,
SilvercrestWifiSocketBindingConstants.WIFI_SOCKET_DEFAULT_UDP_PORT);
this.receiverThread = new Thread(this.receiver);
this.receiverThread.start();
logger.debug("Invoked the start of receiver thread.");
} catch (SocketException e) {
logger.debug("Cannot start the socket with default port...");
}
}
}
/**
* Returns all the {@link Thing} registered.
*
* @returns all the {@link Thing}.
*/
@Override
public Set<Thing> getAllThingsRegistred() {
return this.handlersRegistredByThing.keySet();
}
@Override
public void setDiscoveryService(final SilvercrestWifiSocketDiscoveryService discoveryService) {
this.silvercrestDiscoveryService = discoveryService;
}
}

View File

@@ -0,0 +1,133 @@
/**
* 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.silvercrestwifisocket.internal.runnable;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import org.openhab.binding.silvercrestwifisocket.internal.exceptions.NotOneResponsePacketException;
import org.openhab.binding.silvercrestwifisocket.internal.exceptions.PacketIntegrityErrorException;
import org.openhab.binding.silvercrestwifisocket.internal.handler.SilvercrestWifiSocketMediator;
import org.openhab.binding.silvercrestwifisocket.internal.utils.WifiSocketPacketConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This Thread is responsible to receive all Wifi Socket messages and redirect them to
* {@link SilvercrestWifiSocketMediator}.
*
* @author Jaime Vaz - Initial contribution
*
*/
public class SilvercrestWifiSocketUpdateReceiverRunnable implements Runnable {
private static final int TIMEOUT_TO_DATAGRAM_RECEPTION = 10000;
private final Logger logger = LoggerFactory.getLogger(SilvercrestWifiSocketUpdateReceiverRunnable.class);
private DatagramSocket datagramSocket;
private final SilvercrestWifiSocketMediator mediator;
private final WifiSocketPacketConverter packetConverter = new WifiSocketPacketConverter();
private boolean shutdown;
private int listeningPort;
/**
* Constructor of the receiver runnable thread.
*
* @param mediator the {@link SilvercrestWifiSocketMediator}
* @param listeningPort the listening UDP port
* @throws SocketException is some problem occurs opening the socket.
*/
public SilvercrestWifiSocketUpdateReceiverRunnable(final SilvercrestWifiSocketMediator mediator,
final int listeningPort) throws SocketException {
logger.debug("Starting Update Receiver Runnable...");
// Create a socket to listen on the port.
this.listeningPort = listeningPort;
this.mediator = mediator;
logger.debug("Opening socket and start listening UDP port: {}", listeningPort);
this.datagramSocket = new DatagramSocket(listeningPort);
this.datagramSocket.setSoTimeout(TIMEOUT_TO_DATAGRAM_RECEPTION);
logger.debug("Update Receiver Runnable and socket started with success...");
this.shutdown = false;
}
@Override
public void run() {
// Now loop forever, waiting to receive packets and redirect them to mediator.
while (!this.shutdown) {
datagramSocketHealthRoutine();
// Create a buffer to read datagrams into. If a
// packet is larger than this buffer, the
// excess will simply be discarded!
byte[] buffer = new byte[2048];
// Create a packet to receive data into the buffer
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
// Wait to receive a datagram
try {
this.datagramSocket.receive(packet);
logger.debug("Received packet from: {}. Will process the packet...",
packet.getAddress().getHostAddress());
// Do mediator something with it
this.mediator.processReceivedPacket(this.packetConverter.decryptResponsePacket(packet));
logger.debug("Message delivered with success to mediator.");
} catch (SocketTimeoutException e) {
logger.trace("Socket Timeout receiving packet.");
} catch (IOException e) {
logger.debug("One exception has occurred: {} ", e.getMessage());
} catch (PacketIntegrityErrorException e) {
logger.debug("Packet has one integrity error: {}", e.getMessage());
} catch (NotOneResponsePacketException e) {
logger.debug(
"The message received is not one response. Probably the message received is one broadcast message looking for the socket.");
}
}
// close the socket
if (datagramSocket != null) {
datagramSocket.close();
}
}
private void datagramSocketHealthRoutine() {
if (datagramSocket == null || datagramSocket.isClosed()) {
logger.debug("Datagram Socket has been closed, will reconnect again...");
DatagramSocket newDatagramSocket = null;
try {
newDatagramSocket = new DatagramSocket(listeningPort);
newDatagramSocket.setSoTimeout(TIMEOUT_TO_DATAGRAM_RECEPTION);
datagramSocket = newDatagramSocket;
logger.debug("Datagram Socket reconnected.");
} catch (SocketException exception) {
logger.error("Problem creating one new socket on port {}. Error: {}", listeningPort,
exception.getLocalizedMessage());
}
}
}
/**
* Gracefully shutdown thread. Worst case takes TIMEOUT_TO_DATAGRAM_RECEPTION to shutdown.
*/
public void shutdown() {
this.shutdown = true;
}
}

View File

@@ -0,0 +1,71 @@
/**
* 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.silvercrestwifisocket.internal.utils;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
/**
* Utilitary static class to perform some network routines.
*
* @author Jaime Vaz - Initial contribution
*
*/
public final class NetworkUtils {
private NetworkUtils() {
// Avoid instantiation.
}
/**
* Gets all the broadcast address's from the machine.
*
* @return list with all the broadcast address's
*/
public static List<InetAddress> getAllBroadcastAddresses() {
List<InetAddress> listOfBroadcasts = new ArrayList<>();
Enumeration<NetworkInterface> list;
try {
list = NetworkInterface.getNetworkInterfaces();
while (list.hasMoreElements()) {
NetworkInterface iface = list.nextElement();
if (iface == null) {
continue;
}
if (!iface.isLoopback() && iface.isUp()) {
Iterator<InterfaceAddress> it = iface.getInterfaceAddresses().iterator();
while (it.hasNext()) {
InterfaceAddress address = it.next();
if (address == null) {
continue;
}
InetAddress broadcast = address.getBroadcast();
if (broadcast != null) {
listOfBroadcasts.add(broadcast);
}
}
}
}
} catch (SocketException ex) {
return new ArrayList<>();
}
return listOfBroadcasts;
}
}

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.silvercrestwifisocket.internal.utils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Utilitary static class to perform some validations.
*
* @author Jaime Vaz - Initial contribution
*
*/
public final class ValidationUtils {
private ValidationUtils() {
// avoid instantiation.
}
public static final String MAC_PATTERN = "^([0-9A-Fa-f]{2}[:-]*){5}([0-9A-Fa-f]{2})$";
/**
* Validates if one Mac address is valid.
*
* @param mac the mac, with or without :
* @return true if is valid.
*/
public static boolean isMacValid(final String mac) {
Pattern pattern = Pattern.compile(ValidationUtils.MAC_PATTERN);
Matcher matcher = pattern.matcher(mac);
return matcher.matches();
}
/**
* Validates if one Mac address is not valid.
*
* @param mac the mac, with or without :
* @return true if is not valid.
*/
public static boolean isMacNotValid(final String macAddress) {
return !isMacValid(macAddress);
}
}

View File

@@ -0,0 +1,292 @@
/**
* 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.silvercrestwifisocket.internal.utils;
import java.net.DatagramPacket;
import java.nio.charset.StandardCharsets;
import java.util.regex.Pattern;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.openhab.binding.silvercrestwifisocket.internal.entities.SilvercrestWifiSocketRequest;
import org.openhab.binding.silvercrestwifisocket.internal.entities.SilvercrestWifiSocketResponse;
import org.openhab.binding.silvercrestwifisocket.internal.enums.SilvercrestWifiSocketResponseType;
import org.openhab.binding.silvercrestwifisocket.internal.enums.SilvercrestWifiSocketVendor;
import org.openhab.binding.silvercrestwifisocket.internal.exceptions.NotOneResponsePacketException;
import org.openhab.binding.silvercrestwifisocket.internal.exceptions.PacketIntegrityErrorException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Transforms the the received datagram packet to one
*
* @author Jaime Vaz - Initial contribution
* @author Christian Heimerl - for integration of EasyHome
*
*/
public class WifiSocketPacketConverter {
private final Logger logger = LoggerFactory.getLogger(WifiSocketPacketConverter.class);
private static final String REQUEST_PREFIX = "01";
private static final String RESPONSE_PREFIX = "0142";
private static final String LOCK_STATUS = "40";
/* encryptDataLength */
private static final String ENCRYPT_PREFIX = "00";
private static final String PACKET_NUMBER = "FFFF";
private static final String DEVICE_TYPE = "11";
private static final String ENCRIPTION_KEY = "0123456789abcdef";
private Cipher silvercrestEncryptCipher;
private Cipher silvercrestDecryptCipher;
/**
* START_OF_RECEIVED_PACKET.
* STX - pkt nbr - CompanyCode - device - authCode
*
* 00 -- 0029 -- C1 -- 11 -- 7150 (SilverCrest)
* 00 -- 0029 -- C2 -- 11 -- 92DD (EasyHome)
*/
private static final String REGEX_START_OF_RECEIVED_PACKET = "00([A-F0-9]{4})(?:C21192DD|C1117150)";
private static final String REGEX_HEXADECIMAL_PAIRS = "([A-F0-9]{2})*";
private static final String REGEX_START_OF_RECEIVED_PACKET_SEARCH_MAC_ADDRESS = REGEX_START_OF_RECEIVED_PACKET
+ "23" + REGEX_HEXADECIMAL_PAIRS;
private static final String REGEX_START_OF_RECEIVED_PACKET_HEART_BEAT = REGEX_START_OF_RECEIVED_PACKET + "61"
+ REGEX_HEXADECIMAL_PAIRS;
private static final String REGEX_START_OF_RECEIVED_PACKET_CMD_GPIO_EVENT = REGEX_START_OF_RECEIVED_PACKET + "06"
+ REGEX_HEXADECIMAL_PAIRS;
private static final String REGEX_START_OF_RECEIVED_PACKET_QUERY_STATUS = REGEX_START_OF_RECEIVED_PACKET + "02"
+ REGEX_HEXADECIMAL_PAIRS;
private static final String REGEX_START_OF_RECEIVED_PACKET_RESPONSE_GPIO_CHANGE_REQUEST = REGEX_START_OF_RECEIVED_PACKET
+ "01" + REGEX_HEXADECIMAL_PAIRS;
/**
* Default constructor of the packet converter.
*/
public WifiSocketPacketConverter() {
// init cipher
byte[] encriptionKeyBytes;
try {
encriptionKeyBytes = ENCRIPTION_KEY.getBytes(StandardCharsets.UTF_8);
SecretKeySpec secretKey = new SecretKeySpec(encriptionKeyBytes, "AES");
IvParameterSpec ivKey = new IvParameterSpec(encriptionKeyBytes);
this.silvercrestEncryptCipher = Cipher.getInstance("AES/CBC/NoPadding");
this.silvercrestEncryptCipher.init(Cipher.ENCRYPT_MODE, secretKey, ivKey);
this.silvercrestDecryptCipher = Cipher.getInstance("AES/CBC/NoPadding");
this.silvercrestDecryptCipher.init(Cipher.DECRYPT_MODE, secretKey, ivKey);
} catch (Exception exception) {
logger.debug(
"Failure on WifiSocketPacketConverter creation. There was a problem creating ciphers. Error: {}",
exception.getLocalizedMessage());
}
}
/**
* Method that transforms one {@link SilvercrestWifiSocketRequest} to one byte array to be ready to be transmitted.
*
* @param requestPacket the {@link SilvercrestWifiSocketRequest}.
* @return the byte array with the message.
*/
public byte[] transformToByteMessage(final SilvercrestWifiSocketRequest requestPacket) {
byte[] requestDatagram = null;
String fullCommand = ENCRYPT_PREFIX + PACKET_NUMBER + requestPacket.getVendor().getCompanyCode() + DEVICE_TYPE
+ requestPacket.getVendor().getAuthenticationCode()
+ String.format(requestPacket.getType().getCommand(), requestPacket.getMacAddress());
byte[] inputByte = hexStringToByteArray(fullCommand);
byte[] bEncrypted;
try {
bEncrypted = this.silvercrestEncryptCipher.doFinal(inputByte);
int encryptDataLength = bEncrypted.length;
logger.trace("Encrypted data={{}}", byteArrayToHexString(inputByte));
logger.trace("Decrypted data={{}}", byteArrayToHexString(bEncrypted));
String cryptedCommand = byteArrayToHexString(bEncrypted);
String packetString = REQUEST_PREFIX + LOCK_STATUS + requestPacket.getMacAddress()
+ Integer.toHexString(encryptDataLength) + cryptedCommand;
logger.trace("Request Packet: {}", packetString);
logger.trace("Request packet decrypted data: [{}] with lenght: {}", fullCommand, fullCommand.length());
requestDatagram = hexStringToByteArray(packetString);
} catch (BadPaddingException | IllegalBlockSizeException e) {
logger.debug("Failure processing the build of the request packet for mac '{}' and type '{}'",
requestPacket.getMacAddress(), requestPacket.getType());
}
return requestDatagram;
}
/**
* Decrypts one response {@link DatagramPacket}.
*
* @param packet the {@link DatagramPacket}
* @return the {@link SilvercrestWifiSocketResponse} is successfully decrypted.
* @throws PacketIntegrityErrorException if the message has some integrity error.
* @throws NotOneResponsePacketException if the message received is not one response packet.
*/
public SilvercrestWifiSocketResponse decryptResponsePacket(final DatagramPacket packet)
throws PacketIntegrityErrorException, NotOneResponsePacketException {
SilvercrestWifiSocketResponse responsePacket = this.decryptResponsePacket(
WifiSocketPacketConverter.byteArrayToHexString(packet.getData(), packet.getLength()));
responsePacket.setHostAddress(packet.getAddress().getHostAddress());
return responsePacket;
}
/**
* STX - pkt nbr - CompanyCode - device - authCode
*
* 00 -- 0029 -- C1 -- 11 -- 7150 (Silvercrest)
* 00 -- 0029 -- C2 -- 11 -- 92DD (EasyHome)
*
* @param hexPacket the hex packet to convert
* @return the converted response.
* @throws PacketIntegrityErrorException the packet passed is not recognized.
* @throws NotOneResponsePacketException the packet passed is not one response.
*/
private SilvercrestWifiSocketResponse decryptResponsePacket(final String hexPacket)
throws PacketIntegrityErrorException, NotOneResponsePacketException {
if (!Pattern.matches(RESPONSE_PREFIX + REGEX_HEXADECIMAL_PAIRS, hexPacket)) {
logger.trace("The packet received is not one response! \nPacket:[{}]", hexPacket);
throw new NotOneResponsePacketException("The packet received is not one response.");
}
logger.trace("Response packet: {}", hexPacket);
String macAddress = hexPacket.substring(4, 16);
logger.trace("The mac address of the sender of the packet is: {}", macAddress);
String decryptedData = this.decrypt(hexPacket.substring(18, hexPacket.length()));
logger.trace("Response packet decrypted data: [{}] with lenght: {}", decryptedData, decryptedData.length());
SilvercrestWifiSocketResponseType responseType;
// check packet integrity
if (Pattern.matches(REGEX_START_OF_RECEIVED_PACKET_SEARCH_MAC_ADDRESS, decryptedData)) {
responseType = SilvercrestWifiSocketResponseType.DISCOVERY;
logger.trace("Received answer of mac address search! lenght:{}", decryptedData.length());
} else if (Pattern.matches(REGEX_START_OF_RECEIVED_PACKET_HEART_BEAT, decryptedData)) {
responseType = SilvercrestWifiSocketResponseType.ACK;
logger.trace("Received heart beat!");
} else if (Pattern.matches(REGEX_START_OF_RECEIVED_PACKET_CMD_GPIO_EVENT, decryptedData)) {
logger.trace("Received gpio event!");
String status = decryptedData.substring(20, 22);
responseType = "FF".equalsIgnoreCase(status) ? SilvercrestWifiSocketResponseType.ON
: SilvercrestWifiSocketResponseType.OFF;
logger.trace("Socket status: {}", responseType);
} else if (Pattern.matches(REGEX_START_OF_RECEIVED_PACKET_RESPONSE_GPIO_CHANGE_REQUEST, decryptedData)) {
logger.trace("Received response from a gpio change request!");
String status = decryptedData.substring(20, 22);
responseType = "FF".equalsIgnoreCase(status) ? SilvercrestWifiSocketResponseType.ON
: SilvercrestWifiSocketResponseType.OFF;
logger.trace("Socket status: {}", responseType);
} else if (Pattern.matches(REGEX_START_OF_RECEIVED_PACKET_QUERY_STATUS, decryptedData)) {
logger.trace("Received response from status query!");
String status = decryptedData.substring(20, 22);
responseType = "FF".equalsIgnoreCase(status) ? SilvercrestWifiSocketResponseType.ON
: SilvercrestWifiSocketResponseType.OFF;
logger.trace("Socket status: {}", responseType);
} else {
throw new PacketIntegrityErrorException("The packet decrypted is with wrong format. \nPacket:[" + hexPacket
+ "] \nDecryptedPacket:[" + decryptedData + "]");
}
SilvercrestWifiSocketVendor vendor = SilvercrestWifiSocketVendor.fromCode(decryptedData.substring(6, 8));
if (vendor == null) {
throw new PacketIntegrityErrorException("Could not extract vendor from the decrypted packet. \nPacket:["
+ hexPacket + "] \nDecryptedPacket:[" + decryptedData + "]");
}
logger.trace("Decrypt success. Packet is from socket with mac address [{}] and type is [{}] and vendor is [{}]",
macAddress, responseType, vendor);
return new SilvercrestWifiSocketResponse(macAddress, responseType, vendor);
}
/**
* Decrypts one received message with the correct cypher.
*
* @param inputData the cyphered message
* @return the decrypted message.
*/
private String decrypt(final String inputData) {
byte[] inputByte = hexStringToByteArray(inputData);
byte[] bDecrypted;
try {
bDecrypted = this.silvercrestDecryptCipher.doFinal(inputByte);
logger.trace("Encrypted data={{}}", byteArrayToHexString(inputByte));
logger.trace("Decrypted data={{}}", byteArrayToHexString(bDecrypted));
return byteArrayToHexString(bDecrypted);
} catch (Exception e) {
logger.trace("Problem decrypting the input data. Bad reception?");
}
return null;
}
// String/Array/Hex manipulation
/**
* Converts one hexadecimal string to one byte array.
*
* @param str the string to convert.
* @return the byte array.
*/
private static byte[] hexStringToByteArray(final String str) {
byte[] b = new byte[str.length() / 2];
for (int i = 0; i < b.length; i++) {
int index = i * 2;
int v = Integer.parseInt(str.substring(index, index + 2), 16);
b[i] = (byte) v;
}
return b;
}
/**
* Converts one full byte array to one hexadecimal string.
*
* @param array the byte array to convert.
* @return the hexadecimal string.
*/
private static String byteArrayToHexString(final byte[] array) {
return byteArrayToHexString(array, array.length);
}
/**
* Converts one partial byte array to one hexadecimal string.
*
* @param array the byte array to convert.
* @param length the length to convert.
* @return the hexadecimal string.
*/
private static String byteArrayToHexString(final byte[] array, final int length) {
if ((array == null) || (array.length == 0)) {
return null;
}
StringBuilder builder = new StringBuilder();
String hex = "";
for (int i = 0; i < length; i++) {
hex = Integer.toHexString(0xFF & array[i]).toUpperCase();
if (hex.length() < 2) {
hex = "0" + hex;
}
builder.append(hex);
}
return builder.toString();
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="silvercrestwifisocket" 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>Silvercrest Wifi Socket Binding</name>
<description>This is the binding for Silvercrest Wifi Socket sold by Lidl.</description>
<author>Jaime Vaz</author>
</binding:binding>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="silvercrestwifisocket"
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">
<!-- Socket Thing Type -->
<thing-type id="wifiSocket">
<label>Silvercrest Wifi Socket</label>
<description>Supports Silvercrest Wifi Socket SWS-A1.</description>
<channels>
<channel id="switch" typeId="switch"/>
</channels>
<config-description>
<parameter name="macAddress" type="text" required="true">
<label>MAC Address</label>
<description>The socket MAC address.</description>
</parameter>
<parameter name="hostAddress" type="text">
<label>Host Address</label>
<context>network-address</context>
<description>The socket Host address. The binding is able to discover the host address.</description>
</parameter>
<parameter name="updateInterval" type="integer" min="5">
<label>Update Interval</label>
<description>Update time interval in seconds to request the status of the socket.</description>
</parameter>
<parameter name="vendor" type="text" required="true">
<label>Vendor</label>
<description>The vendor selling the WiFi socket.</description>
<options>
<option value="LIDL_SILVERCREST">Lidl (Silvercrest)</option>
<option value="ALDI_EASYHOME">Aldi (EasyHome)</option>
</options>
</parameter>
</config-description>
</thing-type>
<!-- Socket type Channel Type -->
<channel-type id="switch">
<item-type>Switch</item-type>
<label>Socket Switch</label>
<description>Silvercrest Wifi Socket Switch to turn on or off.</description>
</channel-type>
</thing:thing-descriptions>