From c34a9fd91147c7912de15f364a076c2f99da3eb7 Mon Sep 17 00:00:00 2001 From: Av3m <7688354+Av3m@users.noreply.github.com> Date: Tue, 4 Jan 2022 12:23:48 +0100 Subject: [PATCH] initial implementation for Elero devices --- .../MediolaAioGatewayBindingConstants.java | 10 +- .../internal/MediolaAioGatewayHandler.java | 100 ------- .../MediolaAioBridgeConfig.java} | 13 +- .../config/MediolaAioEleroDeviceConfig.java | 25 ++ .../MediolaAioDeviceDiscoveryService.java | 124 +++++++++ .../exception/MediolaAioCommandError.java | 27 ++ .../MediolaAioCommunicationError.java | 26 ++ .../handler/MediolaAioEleroDeviceHandler.java | 244 ++++++++++++++++++ .../MediolaAioGatewayBridgeHandler.java | 163 ++++++++++++ .../MediolaAioGatewayHandlerFactory.java | 22 +- .../internal/mediolaapi/AioCommand.java | 27 ++ .../internal/mediolaapi/AioEleroCommand.java | 43 +++ .../internal/mediolaapi/AioType.java | 25 ++ .../main/resources/OH-INF/binding/binding.xml | 2 +- .../main/resources/OH-INF/thing/bridge.xml | 25 ++ .../resources/OH-INF/thing/thing-types.xml | 115 ++++++--- bundles/pom.xml | 1 + 17 files changed, 844 insertions(+), 148 deletions(-) delete mode 100644 bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/MediolaAioGatewayHandler.java rename bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/{MediolaAioGatewayConfiguration.java => config/MediolaAioBridgeConfig.java} (61%) create mode 100644 bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/config/MediolaAioEleroDeviceConfig.java create mode 100644 bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/discovery/MediolaAioDeviceDiscoveryService.java create mode 100644 bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/exception/MediolaAioCommandError.java create mode 100644 bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/exception/MediolaAioCommunicationError.java create mode 100644 bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/handler/MediolaAioEleroDeviceHandler.java create mode 100644 bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/handler/MediolaAioGatewayBridgeHandler.java rename bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/{ => handler}/MediolaAioGatewayHandlerFactory.java (65%) create mode 100644 bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/mediolaapi/AioCommand.java create mode 100644 bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/mediolaapi/AioEleroCommand.java create mode 100644 bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/mediolaapi/AioType.java create mode 100644 bundles/org.openhab.binding.mediolaaiogateway/src/main/resources/OH-INF/thing/bridge.xml diff --git a/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/MediolaAioGatewayBindingConstants.java b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/MediolaAioGatewayBindingConstants.java index ba0e2efbb..6c1e26091 100644 --- a/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/MediolaAioGatewayBindingConstants.java +++ b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/MediolaAioGatewayBindingConstants.java @@ -24,11 +24,15 @@ import org.openhab.core.thing.ThingTypeUID; @NonNullByDefault public class MediolaAioGatewayBindingConstants { - private static final String BINDING_ID = "mediolaaiogateway"; + public static final String BINDING_ID = "mediolaaiogateway"; // List of all Thing Type UIDs - public static final ThingTypeUID THING_TYPE_SAMPLE = new ThingTypeUID(BINDING_ID, "sample"); + public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge"); + public static final ThingTypeUID THING_TYPE_ELERO_DEVICE = new ThingTypeUID(BINDING_ID, "elero-device"); // List of all Channel ids - public static final String CHANNEL_1 = "channel1"; + public static final String CHANNEL_CONTROL = "control"; + public static final String CHANNEL_STATE = "state"; + public static final String CHANNEL_CONTROL_BLIND = "control-blind"; + public static final String CHANNEL_CONTROL_SWITCH = "control-switch"; } diff --git a/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/MediolaAioGatewayHandler.java b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/MediolaAioGatewayHandler.java deleted file mode 100644 index 43acd7b5e..000000000 --- a/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/MediolaAioGatewayHandler.java +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright (c) 2010-2021 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.mediolaaiogateway.internal; - -import static org.openhab.binding.mediolaaiogateway.internal.MediolaAioGatewayBindingConstants.*; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingStatus; -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 MediolaAioGatewayHandler} is responsible for handling commands, which are - * sent to one of the channels. - * - * @author Av3m - Initial contribution - */ -@NonNullByDefault -public class MediolaAioGatewayHandler extends BaseThingHandler { - - private final Logger logger = LoggerFactory.getLogger(MediolaAioGatewayHandler.class); - - private @Nullable MediolaAioGatewayConfiguration config; - - public MediolaAioGatewayHandler(Thing thing) { - super(thing); - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - if (CHANNEL_1.equals(channelUID.getId())) { - if (command instanceof RefreshType) { - // TODO: handle data refresh - } - - // TODO: handle command - - // Note: if communication with thing fails for some reason, - // indicate that by setting the status with detail information: - // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - // "Could not control device at IP address x.x.x.x"); - } - } - - @Override - public void initialize() { - config = getConfigAs(MediolaAioGatewayConfiguration.class); - - // TODO: Initialize the handler. - // The framework requires you to return from this method quickly. Also, before leaving this method a thing - // status from one of ONLINE, OFFLINE or UNKNOWN must be set. This might already be the real thing status in - // case you can decide it directly. - // In case you can not decide the thing status directly (e.g. for long running connection handshake using WAN - // access or similar) you should set status UNKNOWN here and then decide the real status asynchronously in the - // background. - - // set the thing status to UNKNOWN temporarily and let the background task decide for the real status. - // the framework is then able to reuse the resources from the thing handler initialization. - // we set this upfront to reliably check status updates in unit tests. - updateStatus(ThingStatus.UNKNOWN); - - // Example for background initialization: - scheduler.execute(() -> { - boolean thingReachable = true; // - // when done do: - if (thingReachable) { - updateStatus(ThingStatus.ONLINE); - } else { - updateStatus(ThingStatus.OFFLINE); - } - }); - - // These logging types should be primarily used by bindings - // logger.trace("Example trace message"); - // logger.debug("Example debug message"); - // logger.warn("Example warn message"); - - // Note: When initialization can NOT be done set the status with more details for further - // analysis. See also class ThingStatusDetail for all available status details. - // Add a description to give user information to understand why thing does not work as expected. E.g. - // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - // "Can not access device as username and/or password are invalid"); - } -} diff --git a/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/MediolaAioGatewayConfiguration.java b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/config/MediolaAioBridgeConfig.java similarity index 61% rename from bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/MediolaAioGatewayConfiguration.java rename to bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/config/MediolaAioBridgeConfig.java index 7e1a65579..7b3aa9bcb 100644 --- a/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/MediolaAioGatewayConfiguration.java +++ b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/config/MediolaAioBridgeConfig.java @@ -10,19 +10,20 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.mediolaaiogateway.internal; +package org.openhab.binding.mediolaaiogateway.internal.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; /** - * The {@link MediolaAioGatewayConfiguration} class contains fields mapping thing configuration parameters. + * The {@link MediolaAioBridgeConfig} class contains fields mapping thing configuration parameters. * * @author Av3m - Initial contribution */ -public class MediolaAioGatewayConfiguration { +public class MediolaAioBridgeConfig { /** * Sample configuration parameters. Replace with your own. */ - public String hostname; - public String password; - public int refreshInterval; + public String gatewayAddress; + public Integer gatewayPort; } diff --git a/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/config/MediolaAioEleroDeviceConfig.java b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/config/MediolaAioEleroDeviceConfig.java new file mode 100644 index 000000000..821b52379 --- /dev/null +++ b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/config/MediolaAioEleroDeviceConfig.java @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2010-2021 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.mediolaaiogateway.internal.config; + +/** + * The {@link MediolaAioEleroDeviceConfig} class contains fields mapping thing configuration parameters. + * + * @author Av3m - Initial contribution + */ +public class MediolaAioEleroDeviceConfig { + + public Integer deviceAddress; + public Integer deviceSid; + public Integer refreshTime; +} diff --git a/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/discovery/MediolaAioDeviceDiscoveryService.java b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/discovery/MediolaAioDeviceDiscoveryService.java new file mode 100644 index 000000000..a70ca05ba --- /dev/null +++ b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/discovery/MediolaAioDeviceDiscoveryService.java @@ -0,0 +1,124 @@ +/** + * Copyright (c) 2010-2021 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.mediolaaiogateway.internal.discovery; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mediolaaiogateway.internal.exception.MediolaAioCommandError; +import org.openhab.binding.mediolaaiogateway.internal.exception.MediolaAioCommunicationError; +import org.openhab.binding.mediolaaiogateway.internal.handler.MediolaAioGatewayBridgeHandler; +import org.openhab.binding.mediolaaiogateway.internal.mediolaapi.AioCommand; +import org.openhab.binding.mediolaaiogateway.internal.mediolaapi.AioType; +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.thing.ThingUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static org.openhab.binding.mediolaaiogateway.internal.MediolaAioGatewayBindingConstants.THING_TYPE_ELERO_DEVICE; + +/** + * The {@link MediolaAioDeviceDiscoveryService} is used to discover devices that are connected to a Homematic gateway. + * + * @author Gerhard Riegler - Initial contribution + */ + +public class MediolaAioDeviceDiscoveryService extends AbstractDiscoveryService + implements ThingHandlerService { + private final Logger logger = LoggerFactory.getLogger(MediolaAioDeviceDiscoveryService.class); + private static final int DISCOVER_TIMEOUT_SECONDS = 300; + + private @NonNullByDefault({}) + MediolaAioGatewayBridgeHandler bridgeHandler; + + + + public MediolaAioDeviceDiscoveryService() { + super(Set.of(THING_TYPE_ELERO_DEVICE), DISCOVER_TIMEOUT_SECONDS, false); + logger.info("MediolaAioDeviceDiscoveryService constructor"); + } + + @Override + public void setThingHandler(@Nullable ThingHandler handler) { + logger.info("MediolaAioDeviceDiscoveryService set thing handler"); + if (handler instanceof MediolaAioGatewayBridgeHandler) { + this.bridgeHandler = (MediolaAioGatewayBridgeHandler) handler; + } + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return bridgeHandler; + } + + @Override + public void activate() { + super.activate(null); + } + + @Override + public void deactivate() { + super.deactivate(); + } + + @Override + protected void startScan() { + Map params = new HashMap<>(); + params.put("config", "1"); + + try { + JsonElement devs = bridgeHandler.sendCommand(AioCommand.GetStates, params); + devs.getAsJsonArray().forEach((e) -> { + JsonObject o = e.getAsJsonObject(); + if ( o.has("type") && o.get("type").getAsString().equals(AioType.Elero)) { + int address = Integer.parseInt(o.get("adr").getAsString(),16); + int sid = Integer.parseInt(o.get("sid").getAsString(),16); + + String strAddress = String.format("%02X", address); + + ThingUID thingUID = new ThingUID(THING_TYPE_ELERO_DEVICE, bridgeHandler.getThing().getUID(), strAddress); + + DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID) + .withBridge(bridgeHandler.getThing().getUID()) + .withLabel(String.format("Elero Device %s", strAddress)) + .withProperty("deviceAddress", address) + .withProperty("deviceSid", sid) + .build(); + + thingDiscovered(discoveryResult); + } + }); + + stopScan(); + + } catch (MediolaAioCommandError|MediolaAioCommunicationError ignored) { + } + + logger.debug("Starting AIO Gateway discovery scan"); + } + + @Override + public synchronized void stopScan() { + logger.debug("Stopping AIO Gateway discovery scan"); + super.stopScan(); + } +} diff --git a/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/exception/MediolaAioCommandError.java b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/exception/MediolaAioCommandError.java new file mode 100644 index 000000000..024c26655 --- /dev/null +++ b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/exception/MediolaAioCommandError.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2010-2021 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.mediolaaiogateway.internal.exception; + +/** + * + * @author Av3m - Initial contribution + */ +public class MediolaAioCommandError extends Exception { + public MediolaAioCommandError(String message, Throwable err) { + super(message, err); + } + + public MediolaAioCommandError(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/exception/MediolaAioCommunicationError.java b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/exception/MediolaAioCommunicationError.java new file mode 100644 index 000000000..205e73c54 --- /dev/null +++ b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/exception/MediolaAioCommunicationError.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2010-2021 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.mediolaaiogateway.internal.exception; + +/** + * @author Av3m - Initial contribution + */ +public class MediolaAioCommunicationError extends Exception { + public MediolaAioCommunicationError(String message, Throwable err) { + super(message, err); + } + + public MediolaAioCommunicationError(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/handler/MediolaAioEleroDeviceHandler.java b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/handler/MediolaAioEleroDeviceHandler.java new file mode 100644 index 000000000..5547c2552 --- /dev/null +++ b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/handler/MediolaAioEleroDeviceHandler.java @@ -0,0 +1,244 @@ +/** + * Copyright (c) 2010-2021 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.mediolaaiogateway.internal.handler; + +import static org.openhab.binding.mediolaaiogateway.internal.MediolaAioGatewayBindingConstants.*; + +import com.google.gson.JsonElement; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.mediolaaiogateway.internal.config.MediolaAioEleroDeviceConfig; +import org.openhab.binding.mediolaaiogateway.internal.exception.MediolaAioCommandError; +import org.openhab.binding.mediolaaiogateway.internal.exception.MediolaAioCommunicationError; +import org.openhab.binding.mediolaaiogateway.internal.mediolaapi.AioCommand; +import org.openhab.binding.mediolaaiogateway.internal.mediolaapi.AioEleroCommand; +import org.openhab.binding.mediolaaiogateway.internal.mediolaapi.AioType; +import org.openhab.core.library.types.*; +import org.openhab.core.thing.*; +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; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +/** + * The {@link MediolaAioEleroDeviceHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Av3m - Initial contribution + */ +@NonNullByDefault +public class MediolaAioEleroDeviceHandler extends BaseThingHandler { + + + private static final Map _commandMap = new HashMap<>(); + + private final Logger logger = LoggerFactory.getLogger(MediolaAioEleroDeviceHandler.class); + + private @Nullable MediolaAioEleroDeviceConfig config; + private @Nullable ScheduledFuture pollingJob; + private HttpClient _httpClient; + + public MediolaAioEleroDeviceHandler(Thing thing, HttpClient httpClient) { + super(thing); + _httpClient = httpClient; + } + + static { + _commandMap.put("ON", AioEleroCommand.UpOn); + _commandMap.put("OFF", AioEleroCommand.DownOff); + _commandMap.put("UP", AioEleroCommand.UpOn); + _commandMap.put("DOWN", AioEleroCommand.DownOff); + _commandMap.put("STOP", AioEleroCommand.Stop); + _commandMap.put("DOWN_3S", AioEleroCommand.Down3s); + _commandMap.put("UP_3S", AioEleroCommand.Up3s); + _commandMap.put("DOWN_STEP_BIT", AioEleroCommand.DownStepBit); + _commandMap.put("UP_STEP_BIT", AioEleroCommand.UpStepBit); + _commandMap.put("DOUBLE_TAP_DOWN", AioEleroCommand.DoubleTapDown); + _commandMap.put("DOUBLE_TAP_UP", AioEleroCommand.DoubleTapUp); + _commandMap.put("START_LEARNING", AioEleroCommand.StartLearning); + _commandMap.put("ON_PULSE_MOVE", AioEleroCommand.OnPulseMove); + _commandMap.put("OFF_PULSE_MOVE", AioEleroCommand.OffPulseMove); + _commandMap.put("AS_CLOSE", AioEleroCommand.ASClose); + _commandMap.put("AS_MOVE", AioEleroCommand.ASMove); + _commandMap.put("SAVE_INTERMEDIATE", AioEleroCommand.SaveIntermediatePos); + _commandMap.put("SAVE_VENTILATION", AioEleroCommand.SaveVentilationPos); + } + + public void stateUpdate(ChannelUID channelUID) { + @Nullable Bridge bridge = getBridge(); + if ( bridge == null) { + logger.warn("could not get bridge"); + return; + } + @Nullable MediolaAioGatewayBridgeHandler bHandler = (MediolaAioGatewayBridgeHandler) bridge.getHandler(); + + if ( bHandler == null || config == null) { return; } + + Map params = new HashMap<>(); + params.put("adr", String.format("%02x", config.deviceAddress)); + params.put("type", AioType.Elero); + + try { + JsonElement jsonElem = bHandler.sendCommand(AioCommand.RefreshSC, params); + String strState = jsonElem.getAsJsonObject().get("state").getAsString(); + int state = Integer.parseInt(strState, 16) & 0x00FF; + updateState(channelUID, new StringType(Integer.toString(state))); + + } catch (MediolaAioCommandError | MediolaAioCommunicationError err) { + logger.error("{}",err.getMessage()); + } + } + + private void handleCommandString(String cmd) { + @Nullable Bridge bridge = getBridge(); + @Nullable MediolaAioGatewayBridgeHandler bHandler = (MediolaAioGatewayBridgeHandler) bridge.getHandler(); + + if ( bHandler == null || config == null) { return; } + + Integer eleroCmd = _commandMap.get(cmd); + if (eleroCmd != null ) { + try { + bHandler.sendEleroCommand(config.deviceAddress, eleroCmd); + } catch (MediolaAioCommandError mediolaAioCommandError) { + logger.error("Command not successful: {}", mediolaAioCommandError.getMessage()); + + } catch (MediolaAioCommunicationError mediolaAioCommunicationError) { + updateStatus( + ThingStatus.OFFLINE, + ThingStatusDetail.COMMUNICATION_ERROR, + String.format("Could not reach Mediola AIO Gateway")); + } + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + String id = channelUID.getId(); + + if (CHANNEL_CONTROL.equals(id)) { + if (command instanceof StringType) { + String strCommand = ((StringType) command).toString(); + handleCommandString(strCommand); + } + } + + else if (CHANNEL_CONTROL_BLIND.equals(id)) { + if (command instanceof UpDownType) { + handleCommandString(((UpDownType) command).toString()); + } + else if (command instanceof StopMoveType) { + handleCommandString(((StopMoveType) command).toString()); + } + else if (command instanceof PercentType) { + int val = ((PercentType) command).intValue(); + if (val == 0 ) { + //open position + handleCommandString("UP"); + } + if ( 0 < val && val <= 50 ) { + //intermediate position + handleCommandString("DOUBLE_TAP_DOWN"); + } + if ( 50 < val && val <= 100 ) { + //ventilation position + handleCommandString("DOUBLE_TAP_UP"); + } + if ( val == 100 ) { + //close position + handleCommandString("DOWN"); + } + } + } + + else if (CHANNEL_CONTROL_SWITCH.equals(id)) { + if (command instanceof OnOffType) { + handleCommandString(((OnOffType) command).toString()); + } + } + + else if (CHANNEL_STATE.equals(id)) { + if (command instanceof RefreshType) { + stateUpdate(channelUID); + } + } + } + + @Override + public void dispose() { + if (pollingJob != null ) { + pollingJob.cancel(true); + pollingJob = null; + } + + super.dispose(); + } + + @Override + public void initialize() { + config = getConfigAs(MediolaAioEleroDeviceConfig.class); + + if (config == null) { + throw new RuntimeException("config object was expected!"); + } + + // TODO: Initialize the handler. + // The framework requires you to return from this method quickly. Also, before leaving this method a thing + // status from one of ONLINE, OFFLINE or UNKNOWN must be set. This might already be the real thing status in + // case you can decide it directly. + // In case you can not decide the thing status directly (e.g. for long running connection handshake using WAN + // access or similar) you should set status UNKNOWN here and then decide the real status asynchronously in the + // background. + + // set the thing status to UNKNOWN temporarily and let the background task decide for the real status. + // the framework is then able to reuse the resources from the thing handler initialization. + // we set this upfront to reliably check status updates in unit tests. + updateStatus(ThingStatus.UNKNOWN); + + pollingJob = scheduler.scheduleWithFixedDelay(() -> { + @Nullable Channel ch = thing.getChannel(CHANNEL_STATE); + if ( ch == null ) {return; } + + this.stateUpdate(ch.getUID()); + + }, 0, config.refreshTime, TimeUnit.SECONDS); + + // Example for background initialization: + scheduler.execute(() -> { + boolean thingReachable = true; // + // when done do: + if (thingReachable) { + updateStatus(ThingStatus.ONLINE); + } else { + updateStatus(ThingStatus.OFFLINE); + } + }); + + // These logging types should be primarily used by bindings + // logger.trace("Example trace message"); + // logger.debug("Example debug message"); + // logger.warn("Example warn message"); + + // Note: When initialization can NOT be done set the status with more details for further + // analysis. See also class ThingStatusDetail for all available status details. + // Add a description to give user information to understand why thing does not work as expected. E.g. + // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + // "Can not access device as username and/or password are invalid"); + } +} diff --git a/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/handler/MediolaAioGatewayBridgeHandler.java b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/handler/MediolaAioGatewayBridgeHandler.java new file mode 100644 index 000000000..61fe112e2 --- /dev/null +++ b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/handler/MediolaAioGatewayBridgeHandler.java @@ -0,0 +1,163 @@ +/** + * Copyright (c) 2010-2021 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.mediolaaiogateway.internal.handler; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.http.HttpVersion; +import org.openhab.binding.mediolaaiogateway.internal.config.MediolaAioBridgeConfig; +import org.openhab.binding.mediolaaiogateway.internal.discovery.MediolaAioDeviceDiscoveryService; +import org.openhab.binding.mediolaaiogateway.internal.exception.MediolaAioCommandError; +import org.openhab.binding.mediolaaiogateway.internal.exception.MediolaAioCommunicationError; +import org.openhab.binding.mediolaaiogateway.internal.mediolaapi.AioCommand; +import org.openhab.binding.mediolaaiogateway.internal.mediolaapi.AioType; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.binding.BaseBridgeHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.HttpMethod; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * The {@link MediolaAioGatewayBridgeHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Av3m - Initial contribution + */ +@NonNullByDefault +public class MediolaAioGatewayBridgeHandler extends BaseBridgeHandler { + + private final Logger logger = LoggerFactory.getLogger(MediolaAioGatewayBridgeHandler.class); + + private HttpClient _httpClient; + + private @Nullable MediolaAioBridgeConfig config; + + public MediolaAioGatewayBridgeHandler(Bridge thing, HttpClient httpClient) { + super(thing); + _httpClient = httpClient; + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + } + + @Override + public Collection> getServices() { + return Collections.singleton(MediolaAioDeviceDiscoveryService.class); + } + + public JsonElement sendEleroCommand(Integer deviceAddress, Integer cmd) throws MediolaAioCommandError, MediolaAioCommunicationError { + Map params = new HashMap<>(); + params.put("type", AioType.Elero); + params.put("data", String.format("%02x%02x", deviceAddress, cmd)); + return this.sendCommand(AioCommand.SendSC, params); + } + + public JsonElement sendCommand(String command, Map params) throws MediolaAioCommandError, MediolaAioCommunicationError { + + Request request = _httpClient.newRequest(config.gatewayAddress, config.gatewayPort) + .scheme("http") + .agent("OpenHAB Mediola AIO Gateway Binding") + .version(HttpVersion.HTTP_1_1) + .method(HttpMethod.GET) + .path("/cmd") + .param("XC_FNC", command) + .timeout(5, TimeUnit.SECONDS); + + params.forEach(request::param); + + @Nullable ContentResponse response; + byte[] content; + + synchronized (this) { + try { + response = request.send(); + content = response.getContent(); + } catch (InterruptedException | TimeoutException | ExecutionException e) { + throw new MediolaAioCommunicationError("communication error", e); + } + } + + JsonObject convertedObject = new Gson().fromJson(new String(content), JsonObject.class); + if (!convertedObject.has("XC_SUC")) { + if (convertedObject.has("XC_ERR")) { + throw new MediolaAioCommandError(convertedObject.get("XC_ERR").getAsString()); + } else { + throw new MediolaAioCommandError("unknown reason."); + } + + } + + return convertedObject.get("XC_SUC"); + + } + + + @Override + public void initialize() { + config = getConfigAs(MediolaAioBridgeConfig.class); + + // TODO: Initialize the handler. + // The framework requires you to return from this method quickly. Also, before leaving this method a thing + // status from one of ONLINE, OFFLINE or UNKNOWN must be set. This might already be the real thing status in + // case you can decide it directly. + // In case you can not decide the thing status directly (e.g. for long running connection handshake using WAN + // access or similar) you should set status UNKNOWN here and then decide the real status asynchronously in the + // background. + + // set the thing status to UNKNOWN temporarily and let the background task decide for the real status. + // the framework is then able to reuse the resources from the thing handler initialization. + // we set this upfront to reliably check status updates in unit tests. + updateStatus(ThingStatus.UNKNOWN); + + // Example for background initialization: + scheduler.execute(() -> { + boolean thingReachable = true; // + // when done do: + if (thingReachable) { + updateStatus(ThingStatus.ONLINE); + } else { + updateStatus(ThingStatus.OFFLINE); + } + }); + + // These logging types should be primarily used by bindings + // logger.trace("Example trace message"); + // logger.debug("Example debug message"); + // logger.warn("Example warn message"); + + // Note: When initialization can NOT be done set the status with more details for further + // analysis. See also class ThingStatusDetail for all available status details. + // Add a description to give user information to understand why thing does not work as expected. E.g. + // updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + // "Can not access device as username and/or password are invalid"); + } +} diff --git a/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/MediolaAioGatewayHandlerFactory.java b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/handler/MediolaAioGatewayHandlerFactory.java similarity index 65% rename from bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/MediolaAioGatewayHandlerFactory.java rename to bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/handler/MediolaAioGatewayHandlerFactory.java index 4110fdc90..c58285d9a 100644 --- a/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/MediolaAioGatewayHandlerFactory.java +++ b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/handler/MediolaAioGatewayHandlerFactory.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.mediolaaiogateway.internal; +package org.openhab.binding.mediolaaiogateway.internal.handler; import static org.openhab.binding.mediolaaiogateway.internal.MediolaAioGatewayBindingConstants.*; @@ -18,12 +18,17 @@ import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.openhab.core.io.net.http.HttpClientFactory; +import org.openhab.core.thing.Bridge; 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.Activate; import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; /** * The {@link MediolaAioGatewayHandlerFactory} is responsible for creating things and thing @@ -35,7 +40,14 @@ import org.osgi.service.component.annotations.Component; @Component(configurationPid = "binding.mediolaaiogateway", service = ThingHandlerFactory.class) public class MediolaAioGatewayHandlerFactory extends BaseThingHandlerFactory { - private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_SAMPLE); + private final HttpClient httpClient; + + @Activate + public MediolaAioGatewayHandlerFactory(@Reference HttpClientFactory httpClientFactory) { + httpClient = httpClientFactory.getCommonHttpClient(); + } + + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_BRIDGE, THING_TYPE_ELERO_DEVICE); @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { @@ -46,8 +58,10 @@ public class MediolaAioGatewayHandlerFactory extends BaseThingHandlerFactory { protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - if (THING_TYPE_SAMPLE.equals(thingTypeUID)) { - return new MediolaAioGatewayHandler(thing); + if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { + return new MediolaAioGatewayBridgeHandler((Bridge) thing, httpClient); + } else if (THING_TYPE_ELERO_DEVICE.equals(thingTypeUID)) { + return new MediolaAioEleroDeviceHandler(thing, httpClient); } return null; diff --git a/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/mediolaapi/AioCommand.java b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/mediolaapi/AioCommand.java new file mode 100644 index 000000000..cc22d933f --- /dev/null +++ b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/mediolaapi/AioCommand.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2010-2021 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.mediolaaiogateway.internal.mediolaapi; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * + * + * @author Av3m - Initial contribution + */ +@NonNullByDefault +public class AioCommand { + public static final String RefreshSC = "RefreshSC"; + public static final String SendSC = "SendSC"; + public static final String GetStates = "GetStates"; +} diff --git a/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/mediolaapi/AioEleroCommand.java b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/mediolaapi/AioEleroCommand.java new file mode 100644 index 000000000..165730dfd --- /dev/null +++ b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/mediolaapi/AioEleroCommand.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2021 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.mediolaaiogateway.internal.mediolaapi; + + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * @author Av3m - Initial contribution + */ +@NonNullByDefault +public class AioEleroCommand { + public static final Integer DownOff = 0x00; + public static final Integer UpOn = 0x01; + public static final Integer Stop = 0x02; + public static final Integer UpStepBit = 0x03; + public static final Integer DownStepBit = 0x04; + public static final Integer ManuMode = 0x05; + public static final Integer AutoMode = 0x06; + public static final Integer ToggleMode = 0x07; + public static final Integer Up3s = 0x08; + public static final Integer Down3s = 0x09; + public static final Integer DoubleTapUp = 0x0A; + public static final Integer DoubleTapDown = 0x0B; + public static final Integer StartLearning = 0x0C; + public static final Integer OnPulseMove = 0x0D; + public static final Integer OffPulseMove = 0x0E; + public static final Integer ASClose = 0x0F; + public static final Integer ASMove = 0x10; + public static final Integer SaveVentilationPos = 0x14; + public static final Integer SaveIntermediatePos = 0x12; + +} diff --git a/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/mediolaapi/AioType.java b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/mediolaapi/AioType.java new file mode 100644 index 000000000..33c9fc63e --- /dev/null +++ b/bundles/org.openhab.binding.mediolaaiogateway/src/main/java/org/openhab/binding/mediolaaiogateway/internal/mediolaapi/AioType.java @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2010-2021 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.mediolaaiogateway.internal.mediolaapi; + + + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * @author Av3m - Initial contribution + */ +@NonNullByDefault +public class AioType { + public static final String Elero = "ER"; +} diff --git a/bundles/org.openhab.binding.mediolaaiogateway/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.mediolaaiogateway/src/main/resources/OH-INF/binding/binding.xml index 71c1c8e6b..6c3ec1ee6 100644 --- a/bundles/org.openhab.binding.mediolaaiogateway/src/main/resources/OH-INF/binding/binding.xml +++ b/bundles/org.openhab.binding.mediolaaiogateway/src/main/resources/OH-INF/binding/binding.xml @@ -4,6 +4,6 @@ xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd"> MediolaAioGateway Binding - This is the binding for MediolaAioGateway. + This is an inofficial and work-in-progress binding for Mediola AIO Gateway. diff --git a/bundles/org.openhab.binding.mediolaaiogateway/src/main/resources/OH-INF/thing/bridge.xml b/bundles/org.openhab.binding.mediolaaiogateway/src/main/resources/OH-INF/thing/bridge.xml new file mode 100644 index 000000000..a8e448c55 --- /dev/null +++ b/bundles/org.openhab.binding.mediolaaiogateway/src/main/resources/OH-INF/thing/bridge.xml @@ -0,0 +1,25 @@ + + + + + + + The Mediola AIO Gateway bridge represents a Mediola AIO gateway + + + + network-address + + Network address of the Homematic gateway + + + + + + 80 + + + + diff --git a/bundles/org.openhab.binding.mediolaaiogateway/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.mediolaaiogateway/src/main/resources/OH-INF/thing/thing-types.xml index 0afcb0a2d..e33ef918d 100644 --- a/bundles/org.openhab.binding.mediolaaiogateway/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.mediolaaiogateway/src/main/resources/OH-INF/thing/thing-types.xml @@ -1,46 +1,93 @@ - - - - - - - - Sample thing for MediolaAioGateway Binding + + + + + + + Elero Device (via AIO Gateway) - + + + + - - - network-address - - Hostname or IP address of the device + + + - - password - - Passwort to access the device + + + - - - Interval the device is polled in sec. + + + cycle time for status updates + 10 - - - - Number:Temperature - - Sample channel for MediolaAioGateway Binding + + + String + + + + + + + + + + + + + + + + + + + + + + Rollershutter + + + + Switch + + + + String + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/pom.xml b/bundles/pom.xml index 2acf71abe..5371316ba 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -229,6 +229,7 @@ org.openhab.binding.mcp23017 org.openhab.binding.meater org.openhab.binding.mecmeter + org.openhab.binding.mediolaaiogateway org.openhab.binding.melcloud org.openhab.binding.mercedesme org.openhab.binding.meteoalerte