diff --git a/CODEOWNERS b/CODEOWNERS index 4b4e4a859..17363df8e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -226,7 +226,6 @@ /bundles/org.openhab.binding.mycroft/ @dalgwen /bundles/org.openhab.binding.mybmw/ @weymann @ntruchsess /bundles/org.openhab.binding.mynice/ @clinique -/bundles/org.openhab.binding.myq/ @digitaldan /bundles/org.openhab.binding.mystrom/ @pail23 /bundles/org.openhab.binding.nanoleaf/ @stefan-hoehn /bundles/org.openhab.binding.neato/ @jjlauterbach diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index bfee3215a..dadea0ba2 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -1116,11 +1116,6 @@ org.openhab.binding.mynice ${project.version} - - org.openhab.addons.bundles - org.openhab.binding.myq - ${project.version} - org.openhab.addons.bundles org.openhab.binding.mystrom diff --git a/bundles/org.openhab.binding.myq/NOTICE b/bundles/org.openhab.binding.myq/NOTICE deleted file mode 100644 index 0ca708bef..000000000 --- a/bundles/org.openhab.binding.myq/NOTICE +++ /dev/null @@ -1,20 +0,0 @@ -This content is produced and maintained by the openHAB project. - -* Project home: https://www.openhab.org - -== Declared Project Licenses - -This program and the accompanying materials are made available under the terms -of the Eclipse Public License 2.0 which is available at -https://www.eclipse.org/legal/epl-2.0/. - -== Source Code - -https://github.com/openhab/openhab-addons - -== Third-party Content - -jsoup -* License: MIT License -* Project: https://jsoup.org/ -* Source: https://github.com/jhy/jsoup diff --git a/bundles/org.openhab.binding.myq/README.md b/bundles/org.openhab.binding.myq/README.md deleted file mode 100644 index 510285cfa..000000000 --- a/bundles/org.openhab.binding.myq/README.md +++ /dev/null @@ -1,72 +0,0 @@ -# MyQ Binding - -This binding integrates with the [The Chamberlain Group MyQ](https://www.myq.com) cloud service. It allows monitoring and control over [MyQ](https://www.myq.com) enabled garage doors manufactured by LiftMaster, Chamberlain and Craftsman. - -## Supported Things - -### Account - -This represents the MyQ cloud account and uses the same credentials needed when using the MyQ mobile application. - -ThingTypeUID: `account` - -### Garage Door - -This represents a garage door associated with an account. Multiple garage doors are supported. - -ThingTypeUID: `garagedoor` - -### Lamp - -This represents a lamp associated with an account. Multiple lamps are supported. - -ThingTypeUID: `lamp` - -## Discovery - -Once an account has been added, garage doors and lamps will automatically be discovered and added to the inbox. - -## Channels - -| Channel | Item Type | Thing Type | States | -|---------------|---------------|------------------|--------------------------------------------------------| -| status | String | garagedoor | opening, closed, closing, stopped, transition, unknown | -| rollershutter | Rollershutter | garagedoor | UP, DOWN, 0%, 100% | -| closeError | Switch | garagedoor | ON (has error), OFF (doesn't have error) | -| openError | Switch | garagedoor | ON (has error), OFF (doesn't have error) | -| switch | Switch | garagedoor, lamp | ON (open), OFF (closed) | - -## Full Example - -### Thing Configuration - -```java -Bridge myq:account:home "MyQ Account" [ username="foo@bar.com", password="secret", refreshInterval=60 ] { - Thing garagedoor abcd12345 "MyQ Garage Door" [ serialNumber="abcd12345" ] - Thing lamp efgh6789 "MyQ Lamp" [ serialNumber="efgh6789" ] -} -``` - -### Items - -```java -String MyQGarageDoor1Status "Door Status [%s]" {channel = "myq:garagedoor:home:abcd12345:status"} -Switch MyQGarageDoor1Switch "Door Switch [%s]" {channel = "myq:garagedoor:home:abcd12345:switch"} -Switch MyQGarageDoor1CloseError "Door Close Error [%s]" {channel = "myq:garagedoor:home:abcd12345:closeError"} -Switch MyQGarageDoor1OpenError "Door OpenError [%s]" {channel = "myq:garagedoor:home:abcd12345:openError"} -Rollershutter MyQGarageDoor1Rollershutter "Door Rollershutter [%s]" {channel = "myq:garagedoor:home:abcd12345:rollershutter"} -Switch MyQGarageDoorLamp "Lamp [%s]" {channel = "myq:lamp:home:efgh6789:switch"} -} -``` - -### Sitemaps - -```perl -sitemap MyQ label="MyQ Demo Sitemap" { - Frame label="Garage Door" { - String item=MyQGarageDoor1Status - Switch item=MyQGarageDoor1Switch - Rollershutter item=MyQGarageDoor1Rollershutter - } -} -``` diff --git a/bundles/org.openhab.binding.myq/pom.xml b/bundles/org.openhab.binding.myq/pom.xml deleted file mode 100644 index 7a776f7f3..000000000 --- a/bundles/org.openhab.binding.myq/pom.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - 4.0.0 - - - org.openhab.addons.bundles - org.openhab.addons.reactor.bundles - 4.1.0-SNAPSHOT - - - org.openhab.binding.myq - - openHAB Add-ons :: Bundles :: MyQ Binding - - - - org.jsoup - jsoup - 1.14.3 - provided - - - diff --git a/bundles/org.openhab.binding.myq/src/main/feature/feature.xml b/bundles/org.openhab.binding.myq/src/main/feature/feature.xml deleted file mode 100644 index 0c938aa7a..000000000 --- a/bundles/org.openhab.binding.myq/src/main/feature/feature.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features - - - openhab-runtime-base - mvn:org.jsoup/jsoup/1.14.3 - mvn:org.openhab.addons.bundles/org.openhab.binding.myq/${project.version} - - diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/MyQBindingConstants.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/MyQBindingConstants.java deleted file mode 100644 index fc31bb39a..000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/MyQBindingConstants.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) 2010-2023 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.myq.internal; - -import java.util.Set; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.thing.ThingTypeUID; - -/** - * The {@link MyQBindingConstants} class defines common constants, which are - * used across the whole binding. - * - * @author Dan Cunningham - Initial contribution - */ -@NonNullByDefault -public class MyQBindingConstants { - - public static final String BINDING_ID = "myq"; - - public static final ThingTypeUID THING_TYPE_ACCOUNT = new ThingTypeUID(BINDING_ID, "account"); - public static final ThingTypeUID THING_TYPE_GARAGEDOOR = new ThingTypeUID(BINDING_ID, "garagedoor"); - public static final ThingTypeUID THING_TYPE_LAMP = new ThingTypeUID(BINDING_ID, "lamp"); - public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ACCOUNT, THING_TYPE_GARAGEDOOR, - THING_TYPE_LAMP); - public static final Set SUPPORTED_DISCOVERY_THING_TYPES_UIDS = Set.of(THING_TYPE_GARAGEDOOR, - THING_TYPE_LAMP); -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/MyQDiscoveryService.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/MyQDiscoveryService.java deleted file mode 100644 index 48863f228..000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/MyQDiscoveryService.java +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Copyright (c) 2010-2023 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.myq.internal; - -import static org.openhab.binding.myq.internal.MyQBindingConstants.BINDING_ID; - -import java.util.List; -import java.util.Set; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.myq.internal.dto.DeviceDTO; -import org.openhab.binding.myq.internal.handler.MyQAccountHandler; -import org.openhab.core.config.discovery.AbstractDiscoveryService; -import org.openhab.core.config.discovery.DiscoveryResult; -import org.openhab.core.config.discovery.DiscoveryResultBuilder; -import org.openhab.core.config.discovery.DiscoveryService; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.ThingUID; -import org.openhab.core.thing.binding.ThingHandler; -import org.openhab.core.thing.binding.ThingHandlerService; - -/** - * The {@link MyQDiscoveryService} is responsible for discovering MyQ things - * - * @author Dan Cunningham - Initial contribution - */ -@NonNullByDefault -public class MyQDiscoveryService extends AbstractDiscoveryService implements DiscoveryService, ThingHandlerService { - - private static final Set SUPPORTED_DISCOVERY_THING_TYPES_UIDS = Set - .of(MyQBindingConstants.THING_TYPE_GARAGEDOOR, MyQBindingConstants.THING_TYPE_LAMP); - private @Nullable MyQAccountHandler accountHandler; - - public MyQDiscoveryService() { - super(SUPPORTED_DISCOVERY_THING_TYPES_UIDS, 1, true); - } - - @Override - public Set getSupportedThingTypes() { - return SUPPORTED_DISCOVERY_THING_TYPES_UIDS; - } - - @Override - public void startScan() { - MyQAccountHandler accountHandler = this.accountHandler; - if (accountHandler != null) { - List devices = accountHandler.devicesCache(); - if (devices != null) { - devices.forEach(device -> { - ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, device.deviceFamily); - if (SUPPORTED_DISCOVERY_THING_TYPES_UIDS.contains(thingTypeUID)) { - ThingUID thingUID = new ThingUID(thingTypeUID, accountHandler.getThing().getUID(), - device.serialNumber.toLowerCase()); - DiscoveryResult result = DiscoveryResultBuilder.create(thingUID).withLabel("MyQ " + device.name) - .withProperty(Thing.PROPERTY_SERIAL_NUMBER, thingUID.getId()) - .withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER) - .withBridge(accountHandler.getThing().getUID()).build(); - thingDiscovered(result); - } - }); - } - } - } - - @Override - public void startBackgroundDiscovery() { - startScan(); - } - - @Override - public void setThingHandler(ThingHandler handler) { - if (handler instanceof MyQAccountHandler myqAccountHandler) { - accountHandler = myqAccountHandler; - } - } - - @Override - public @Nullable ThingHandler getThingHandler() { - return accountHandler; - } - - @Override - public void activate() { - super.activate(null); - } - - @Override - public void deactivate() { - super.deactivate(); - } -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/MyQHandlerFactory.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/MyQHandlerFactory.java deleted file mode 100644 index 475c9fc5f..000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/MyQHandlerFactory.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright (c) 2010-2023 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.myq.internal; - -import static org.openhab.binding.myq.internal.MyQBindingConstants.*; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.client.HttpClient; -import org.openhab.binding.myq.internal.handler.MyQAccountHandler; -import org.openhab.binding.myq.internal.handler.MyQGarageDoorHandler; -import org.openhab.binding.myq.internal.handler.MyQLampHandler; -import org.openhab.core.auth.client.oauth2.OAuthFactory; -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 MyQHandlerFactory} is responsible for creating things and thing - * handlers. - * - * @author Dan Cunningham - Initial contribution - */ -@NonNullByDefault -@Component(configurationPid = "binding.myq", service = ThingHandlerFactory.class) -public class MyQHandlerFactory extends BaseThingHandlerFactory { - private final HttpClient httpClient; - private OAuthFactory oAuthFactory; - - @Activate - public MyQHandlerFactory(final @Reference HttpClientFactory httpClientFactory, - final @Reference OAuthFactory oAuthFactory) { - this.httpClient = httpClientFactory.getCommonHttpClient(); - this.oAuthFactory = oAuthFactory; - } - - @Override - public boolean supportsThingType(ThingTypeUID thingTypeUID) { - return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); - } - - @Override - protected @Nullable ThingHandler createHandler(Thing thing) { - ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - - if (THING_TYPE_ACCOUNT.equals(thingTypeUID)) { - return new MyQAccountHandler((Bridge) thing, httpClient, oAuthFactory); - } - - if (THING_TYPE_GARAGEDOOR.equals(thingTypeUID)) { - return new MyQGarageDoorHandler(thing); - } - - if (THING_TYPE_LAMP.equals(thingTypeUID)) { - return new MyQLampHandler(thing); - } - - return null; - } -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/config/MyQAccountConfiguration.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/config/MyQAccountConfiguration.java deleted file mode 100644 index 245261f6a..000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/config/MyQAccountConfiguration.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2023 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.myq.internal.config; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link MyQAccountConfiguration} class contains fields mapping thing configuration parameters. - * - * @author Dan Cunningham - Initial contribution - */ -@NonNullByDefault -public class MyQAccountConfiguration { - public String username = ""; - public String password = ""; - public Integer refreshInterval = 60; -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/config/MyQDeviceConfiguration.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/config/MyQDeviceConfiguration.java deleted file mode 100644 index 1a5ed8757..000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/config/MyQDeviceConfiguration.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) 2010-2023 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.myq.internal.config; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link MyQDeviceConfiguration} class contains fields mapping thing configuration parameters. - * - * @author Dan Cunningham - Initial contribution - */ -@NonNullByDefault -public class MyQDeviceConfiguration { - public String serialNumber = ""; -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/AccountDTO.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/AccountDTO.java deleted file mode 100644 index 7a74a3968..000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/AccountDTO.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) 2010-2023 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.myq.internal.dto; - -/** - * The {@link AccountDTO} entity from the MyQ API - * - * @author Dan Cunningham - Initial contribution - */ -public class AccountDTO { - public String id; - public String name; - public String createdBy; -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/AccountsDTO.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/AccountsDTO.java deleted file mode 100644 index 6346476bd..000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/AccountsDTO.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) 2010-2023 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.myq.internal.dto; - -import java.util.List; - -/** - * The {@link AccountsDTO} entity from the MyQ API - * - * @author Dan Cunningham - Initial contribution - */ -public class AccountsDTO { - public List accounts; -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/DeviceDTO.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/DeviceDTO.java deleted file mode 100644 index 96b774911..000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/DeviceDTO.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2010-2023 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.myq.internal.dto; - -/** - * The {@link DeviceDTO} entity from the MyQ API - * - * @author Dan Cunningham - Initial contribution - */ -public class DeviceDTO { - public String href; - public String serialNumber; - public String deviceFamily; - public String devicePlatform; - public String deviceType; - public String name; - public String createdDate; - public String accountId; - public DeviceStateDTO state; - public String parentDevice; - public String parentDeviceId; -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/DeviceStateDTO.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/DeviceStateDTO.java deleted file mode 100644 index 02d306e1a..000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/DeviceStateDTO.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) 2010-2023 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.myq.internal.dto; - -/** - * The {@link DeviceStateDTO} entity from the MyQ API - * - * @author Dan Cunningham - Initial contribution - */ -public class DeviceStateDTO { - - public Boolean gdoLockConnected; - public Boolean attachedWorkLightErrorPresent; - public String learnStatus; - public Boolean hasCamera; - public String lampState; - public String batteryBackupState; - public String doorState; - public String lastUpdate; - public Boolean isUnattendedOpenAllowed; - public Boolean isUnattendedCloseAllowed; - public Integer serviceCycleCount; - public Integer absoluteCycleCount; - public Boolean online; - public String lastStatus; -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/DevicesDTO.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/DevicesDTO.java deleted file mode 100644 index 9f1e99789..000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/dto/DevicesDTO.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2010-2023 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.myq.internal.dto; - -import java.util.List; - -/** - * The {@link DevicesDTO} entity from the MyQ API - * - * @author Dan Cunningham - Initial contribution - */ -public class DevicesDTO { - public String href; - public Integer count; - public List items; -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQAccountHandler.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQAccountHandler.java deleted file mode 100644 index 1911c7337..000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQAccountHandler.java +++ /dev/null @@ -1,675 +0,0 @@ -/** - * Copyright (c) 2010-2023 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.myq.internal.handler; - -import static org.openhab.binding.myq.internal.MyQBindingConstants.*; - -import java.io.IOException; -import java.net.CookieStore; -import java.net.HttpCookie; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Base64; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.stream.Collectors; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.HttpContentResponse; -import org.eclipse.jetty.client.api.ContentProvider; -import org.eclipse.jetty.client.api.ContentResponse; -import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.client.api.Response; -import org.eclipse.jetty.client.api.Result; -import org.eclipse.jetty.client.util.BufferingResponseListener; -import org.eclipse.jetty.client.util.FormContentProvider; -import org.eclipse.jetty.http.HttpMethod; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.util.Fields; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.openhab.binding.myq.internal.MyQDiscoveryService; -import org.openhab.binding.myq.internal.config.MyQAccountConfiguration; -import org.openhab.binding.myq.internal.dto.AccountDTO; -import org.openhab.binding.myq.internal.dto.AccountsDTO; -import org.openhab.binding.myq.internal.dto.DeviceDTO; -import org.openhab.binding.myq.internal.dto.DevicesDTO; -import org.openhab.core.auth.client.oauth2.AccessTokenRefreshListener; -import org.openhab.core.auth.client.oauth2.AccessTokenResponse; -import org.openhab.core.auth.client.oauth2.OAuthClientService; -import org.openhab.core.auth.client.oauth2.OAuthException; -import org.openhab.core.auth.client.oauth2.OAuthFactory; -import org.openhab.core.auth.client.oauth2.OAuthResponseException; -import org.openhab.core.thing.Bridge; -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.ThingTypeUID; -import org.openhab.core.thing.binding.BaseBridgeHandler; -import org.openhab.core.thing.binding.ThingHandler; -import org.openhab.core.thing.binding.ThingHandlerService; -import org.openhab.core.types.Command; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.gson.FieldNamingPolicy; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonSyntaxException; - -/** - * The {@link MyQAccountHandler} is responsible for communicating with the MyQ API based on an account. - * - * @author Dan Cunningham - Initial contribution - */ -@NonNullByDefault -public class MyQAccountHandler extends BaseBridgeHandler implements AccessTokenRefreshListener { - private static final int REQUEST_TIMEOUT_MS = 10_000; - - /* - * MyQ oAuth relate fields - */ - private static final String CLIENT_SECRET = "VUQ0RFhuS3lQV3EyNUJTdw=="; - private static final String CLIENT_ID = "ANDROID_CGI_MYQ"; - private static final String REDIRECT_URI = "com.myqops://android"; - private static final String SCOPE = "MyQ_Residential offline_access"; - /* - * MyQ authentication API endpoints - */ - private static final String LOGIN_BASE_URL = "https://partner-identity.myq-cloud.com"; - private static final String LOGIN_AUTHORIZE_URL = LOGIN_BASE_URL + "/connect/authorize"; - private static final String LOGIN_TOKEN_URL = LOGIN_BASE_URL + "/connect/token"; - // this should never happen, but lets be safe and give up after so many redirects - private static final int LOGIN_MAX_REDIRECTS = 30; - /* - * MyQ device and account API endpoints - */ - private static final String ACCOUNTS_URL = "https://accounts.myq-cloud.com/api/v6.0/accounts"; - private static final String DEVICES_URL = "https://devices.myq-cloud.com/api/v5.2/Accounts/%s/Devices"; - private static final String CMD_LAMP_URL = "https://account-devices-lamp.myq-cloud.com/api/v5.2/Accounts/%s/lamps/%s/%s"; - private static final String CMD_DOOR_URL = "https://account-devices-gdo.myq-cloud.com/api/v5.2/Accounts/%s/door_openers/%s/%s"; - - private static final Integer RAPID_REFRESH_SECONDS = 5; - private final Logger logger = LoggerFactory.getLogger(MyQAccountHandler.class); - private final Gson gsonLowerCase = new GsonBuilder() - .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); - private final OAuthFactory oAuthFactory; - private @Nullable Future normalPollFuture; - private @Nullable Future rapidPollFuture; - private @Nullable AccountsDTO accounts; - - private List devicesCache = new ArrayList(); - private @Nullable OAuthClientService oAuthService; - private Integer normalRefreshSeconds = 60; - private HttpClient httpClient; - private String username = ""; - private String password = ""; - private String userAgent = ""; - // force login, even if we have a token - private boolean needsLogin = false; - - public MyQAccountHandler(Bridge bridge, HttpClient httpClient, final OAuthFactory oAuthFactory) { - super(bridge); - this.httpClient = httpClient; - this.oAuthFactory = oAuthFactory; - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - } - - @Override - public void initialize() { - MyQAccountConfiguration config = getConfigAs(MyQAccountConfiguration.class); - normalRefreshSeconds = config.refreshInterval; - username = config.username; - password = config.password; - // MyQ can get picky about blocking user agents apparently - userAgent = ""; // no agent string - needsLogin = true; - updateStatus(ThingStatus.UNKNOWN); - restartPolls(false); - } - - @Override - public void dispose() { - stopPolls(); - OAuthClientService oAuthService = this.oAuthService; - if (oAuthService != null) { - oAuthService.removeAccessTokenRefreshListener(this); - oAuthFactory.ungetOAuthService(getThing().toString()); - this.oAuthService = null; - } - } - - @Override - public void handleRemoval() { - oAuthFactory.deleteServiceAndAccessToken(getThing().toString()); - super.handleRemoval(); - } - - @Override - public Collection> getServices() { - return Set.of(MyQDiscoveryService.class); - } - - @Override - public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) { - List localDeviceCaches = devicesCache; - if (childHandler instanceof MyQDeviceHandler deviceHandler) { - localDeviceCaches.stream().filter(d -> deviceHandler.getSerialNumber().equalsIgnoreCase(d.serialNumber)) - .findFirst().ifPresent(deviceHandler::handleDeviceUpdate); - } - } - - @Override - public void onAccessTokenResponse(AccessTokenResponse tokenResponse) { - logger.debug("Auth Token Refreshed, expires in {}", tokenResponse.getExpiresIn()); - } - - /** - * Sends a door action to the MyQ API - * - * @param device - * @param action - */ - public void sendDoorAction(DeviceDTO device, String action) { - sendAction(device, action, CMD_DOOR_URL); - } - - /** - * Sends a lamp action to the MyQ API - * - * @param device - * @param action - */ - public void sendLampAction(DeviceDTO device, String action) { - sendAction(device, action, CMD_LAMP_URL); - } - - private void sendAction(DeviceDTO device, String action, String urlFormat) { - if (getThing().getStatus() != ThingStatus.ONLINE) { - logger.debug("Account offline, ignoring action {}", action); - return; - } - - try { - ContentResponse response = sendRequest( - String.format(urlFormat, device.accountId, device.serialNumber, action), HttpMethod.PUT, null, - null); - if (HttpStatus.isSuccess(response.getStatus())) { - restartPolls(true); - } else { - logger.debug("Failed to send action {} : {}", action, response.getContentAsString()); - } - } catch (InterruptedException | MyQCommunicationException | MyQAuthenticationException e) { - logger.debug("Could not send action", e); - } - } - - /** - * Last known state of MyQ Devices - * - * @return cached MyQ devices - */ - public @Nullable List devicesCache() { - return devicesCache; - } - - private void stopPolls() { - stopNormalPoll(); - stopRapidPoll(); - } - - private synchronized void stopNormalPoll() { - stopFuture(normalPollFuture); - normalPollFuture = null; - } - - private synchronized void stopRapidPoll() { - stopFuture(rapidPollFuture); - rapidPollFuture = null; - } - - private void stopFuture(@Nullable Future future) { - if (future != null) { - future.cancel(true); - } - } - - private synchronized void restartPolls(boolean rapid) { - stopPolls(); - if (rapid) { - normalPollFuture = scheduler.scheduleWithFixedDelay(this::normalPoll, 35, normalRefreshSeconds, - TimeUnit.SECONDS); - rapidPollFuture = scheduler.scheduleWithFixedDelay(this::rapidPoll, 3, RAPID_REFRESH_SECONDS, - TimeUnit.SECONDS); - } else { - normalPollFuture = scheduler.scheduleWithFixedDelay(this::normalPoll, 0, normalRefreshSeconds, - TimeUnit.SECONDS); - } - } - - private void normalPoll() { - stopRapidPoll(); - fetchData(); - } - - private void rapidPoll() { - fetchData(); - } - - private synchronized void fetchData() { - try { - if (accounts == null) { - getAccounts(); - } - getDevices(); - } catch (MyQCommunicationException e) { - logger.debug("MyQ communication error", e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - } catch (MyQAuthenticationException e) { - logger.debug("MyQ authentication error", e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); - stopPolls(); - } catch (InterruptedException e) { - // we were shut down, ignore - } - } - - /** - * This attempts to navigate the MyQ oAuth login flow in order to obtain a @AccessTokenResponse - * - * @return AccessTokenResponse token - * @throws InterruptedException - * @throws MyQCommunicationException - * @throws MyQAuthenticationException - */ - private AccessTokenResponse login() - throws InterruptedException, MyQCommunicationException, MyQAuthenticationException { - try { - // make sure we have a fresh session - URI authUri = new URI(LOGIN_BASE_URL); - CookieStore store = httpClient.getCookieStore(); - store.get(authUri).forEach(cookie -> { - store.remove(authUri, cookie); - }); - - String codeVerifier = generateCodeVerifier(); - - ContentResponse loginPageResponse = getLoginPage(codeVerifier); - - // load the login page to get cookies and form parameters - Document loginPage = Jsoup.parse(loginPageResponse.getContentAsString()); - Element form = loginPage.select("form").first(); - Element requestToken = loginPage.select("input[name=__RequestVerificationToken]").first(); - Element returnURL = loginPage.select("input[name=ReturnUrl]").first(); - - if (form == null || requestToken == null) { - throw new MyQCommunicationException("Could not load login page"); - } - - // url that the form will submit to - String action = LOGIN_BASE_URL + form.attr("action"); - - // post our user name and password along with elements from the scraped form - String location = postLogin(action, requestToken.attr("value"), returnURL.attr("value")); - if (location == null) { - throw new MyQAuthenticationException("Could not login with credentials"); - } - - // finally complete the oAuth flow and retrieve a JSON oAuth token response - ContentResponse tokenResponse = getLoginToken(location, codeVerifier); - String loginToken = tokenResponse.getContentAsString(); - - try { - AccessTokenResponse accessTokenResponse = gsonLowerCase.fromJson(loginToken, AccessTokenResponse.class); - if (accessTokenResponse == null) { - throw new MyQAuthenticationException("Could not parse token response"); - } - getOAuthService().importAccessTokenResponse(accessTokenResponse); - return accessTokenResponse; - } catch (JsonSyntaxException e) { - throw new MyQCommunicationException("Invalid Token Response " + loginToken); - } - } catch (IOException | ExecutionException | TimeoutException | OAuthException | URISyntaxException e) { - throw new MyQCommunicationException(e.getMessage()); - } - } - - private void getAccounts() throws InterruptedException, MyQCommunicationException, MyQAuthenticationException { - ContentResponse response = sendRequest(ACCOUNTS_URL, HttpMethod.GET, null, null); - accounts = parseResultAndUpdateStatus(response, gsonLowerCase, AccountsDTO.class); - } - - private void getDevices() throws InterruptedException, MyQCommunicationException, MyQAuthenticationException { - AccountsDTO localAccounts = accounts; - if (localAccounts == null) { - return; - } - - List currentDevices = new ArrayList(); - - for (AccountDTO account : localAccounts.accounts) { - ContentResponse response = sendRequest(String.format(DEVICES_URL, account.id), HttpMethod.GET, null, null); - DevicesDTO devices = parseResultAndUpdateStatus(response, gsonLowerCase, DevicesDTO.class); - currentDevices.addAll(devices.items); - devices.items.forEach(device -> { - ThingTypeUID thingTypeUID = new ThingTypeUID(BINDING_ID, device.deviceFamily); - if (SUPPORTED_DISCOVERY_THING_TYPES_UIDS.contains(thingTypeUID)) { - for (Thing thing : getThing().getThings()) { - ThingHandler handler = thing.getHandler(); - if (handler != null && ((MyQDeviceHandler) handler).getSerialNumber() - .equalsIgnoreCase(device.serialNumber)) { - ((MyQDeviceHandler) handler).handleDeviceUpdate(device); - } - } - } - }); - } - devicesCache = currentDevices; - } - - private synchronized ContentResponse sendRequest(String url, HttpMethod method, @Nullable ContentProvider content, - @Nullable String contentType) - throws InterruptedException, MyQCommunicationException, MyQAuthenticationException { - AccessTokenResponse tokenResponse = null; - // if we don't need to force a login, attempt to use the token we have - if (!needsLogin) { - try { - tokenResponse = getOAuthService().getAccessTokenResponse(); - } catch (OAuthException | IOException | OAuthResponseException e) { - // ignore error, will try to login below - logger.debug("Error accessing token, will attempt to login again", e); - } - } - - // if no token, or we need to login, do so now - if (tokenResponse == null) { - tokenResponse = login(); - needsLogin = false; - } - - Request request = httpClient.newRequest(url).method(method).agent(userAgent) - .timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS) - .header("Authorization", authTokenHeader(tokenResponse)); - if (content != null & contentType != null) { - request = request.content(content, contentType); - } - - // use asyc jetty as the API service will response with a 401 error when credentials are wrong, - // but not a WWW-Authenticate header which causes Jetty to throw a generic execution exception which - // prevents us from knowing the response code - logger.trace("Sending {} to {}", request.getMethod(), request.getURI()); - final CompletableFuture futureResult = new CompletableFuture<>(); - request.send(new BufferingResponseListener() { - @NonNullByDefault({}) - @Override - public void onComplete(Result result) { - Response response = result.getResponse(); - futureResult.complete(new HttpContentResponse(response, getContent(), getMediaType(), getEncoding())); - } - }); - - try { - ContentResponse result = futureResult.get(); - logger.trace("Account Response - status: {} content: {}", result.getStatus(), result.getContentAsString()); - return result; - } catch (ExecutionException e) { - throw new MyQCommunicationException(e.getMessage()); - } - } - - private T parseResultAndUpdateStatus(ContentResponse response, Gson parser, Class classOfT) - throws MyQCommunicationException { - if (HttpStatus.isSuccess(response.getStatus())) { - try { - T responseObject = parser.fromJson(response.getContentAsString(), classOfT); - if (responseObject != null) { - if (getThing().getStatus() != ThingStatus.ONLINE) { - updateStatus(ThingStatus.ONLINE); - } - return responseObject; - } else { - throw new MyQCommunicationException("Bad response from server"); - } - } catch (JsonSyntaxException e) { - throw new MyQCommunicationException("Invalid JSON Response " + response.getContentAsString()); - } - } else if (response.getStatus() == HttpStatus.UNAUTHORIZED_401) { - // our tokens no longer work, will need to login again - needsLogin = true; - throw new MyQCommunicationException("Token was rejected for request"); - } else { - throw new MyQCommunicationException( - "Invalid Response Code " + response.getStatus() + " : " + response.getContentAsString()); - } - } - - /** - * Returns the MyQ login page which contains form elements and cookies needed to login - * - * @param codeVerifier - * @return - * @throws InterruptedException - * @throws ExecutionException - * @throws TimeoutException - */ - private ContentResponse getLoginPage(String codeVerifier) - throws InterruptedException, ExecutionException, TimeoutException { - try { - Request request = httpClient.newRequest(LOGIN_AUTHORIZE_URL) // - .timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS) // - .param("client_id", CLIENT_ID) // - .param("code_challenge", generateCodeChallange(codeVerifier)) // - .param("code_challenge_method", "S256") // - .param("redirect_uri", REDIRECT_URI) // - .param("response_type", "code") // - .param("scope", SCOPE) // - .agent(userAgent).followRedirects(true); - request.header("Accept", "\"*/*\""); - request.header("Authorization", - "Basic " + Base64.getEncoder().encodeToString((CLIENT_ID + ":").getBytes())); - logger.debug("Sending {} to {}", request.getMethod(), request.getURI()); - ContentResponse response = request.send(); - logger.debug("Login Code {} Response {}", response.getStatus(), response.getContentAsString()); - return response; - } catch (NoSuchAlgorithmException e) { - throw new ExecutionException(e.getCause()); - } - } - - /** - * Sends configured credentials and elements from the login page in order to obtain a redirect location header value - * - * @param url - * @param requestToken - * @param returnURL - * @return The location header value - * @throws InterruptedException - * @throws ExecutionException - * @throws TimeoutException - */ - @Nullable - private String postLogin(String url, String requestToken, String returnURL) - throws InterruptedException, ExecutionException, TimeoutException { - /* - * on a successful post to this page we will get several redirects, and a final 301 to: - * com.myqops://ios?code=0123456789&scope=MyQ_Residential%20offline_access&iss=https%3A%2F%2Fpartner-identity. - * myq-cloud.com - * - * We can then take the parameters out of this location and continue the process - */ - Fields fields = new Fields(); - fields.add("Email", username); - fields.add("Password", password); - fields.add("__RequestVerificationToken", requestToken); - fields.add("ReturnUrl", returnURL); - - Request request = httpClient.newRequest(url).method(HttpMethod.POST) // - .timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS) // - .content(new FormContentProvider(fields)) // - .agent(userAgent) // - .followRedirects(false); - setCookies(request); - - logger.debug("Posting Login to {}", url); - ContentResponse response = request.send(); - - String location = null; - - // follow redirects until we match our REDIRECT_URI or hit a redirect safety limit - for (int i = 0; i < LOGIN_MAX_REDIRECTS && HttpStatus.isRedirection(response.getStatus()); i++) { - - String loc = response.getHeaders().get("location"); - if (logger.isTraceEnabled()) { - logger.trace("Redirect Login: Code {} Location Header: {} Response {}", response.getStatus(), loc, - response.getContentAsString()); - } - if (loc == null) { - logger.debug("No location value"); - break; - } - if (loc.indexOf(REDIRECT_URI) == 0) { - location = loc; - break; - } - request = httpClient.newRequest(LOGIN_BASE_URL + loc).timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS) - .agent(userAgent).followRedirects(false); - setCookies(request); - response = request.send(); - } - return location; - } - - /** - * Final step of the login process to get an oAuth access response token - * - * @param redirectLocation - * @param codeVerifier - * @return - * @throws InterruptedException - * @throws ExecutionException - * @throws TimeoutException - */ - private ContentResponse getLoginToken(String redirectLocation, String codeVerifier) - throws InterruptedException, ExecutionException, TimeoutException { - try { - Map params = parseLocationQuery(redirectLocation); - - Fields fields = new Fields(); - fields.add("client_id", CLIENT_ID); - fields.add("client_secret", Base64.getEncoder().encodeToString(CLIENT_SECRET.getBytes())); - fields.add("code", params.get("code")); - fields.add("code_verifier", codeVerifier); - fields.add("grant_type", "authorization_code"); - fields.add("redirect_uri", REDIRECT_URI); - fields.add("scope", params.get("scope")); - - Request request = httpClient.newRequest(LOGIN_TOKEN_URL) // - .timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS) // - .content(new FormContentProvider(fields)) // - .method(HttpMethod.POST) // - .agent(userAgent).followRedirects(true); - setCookies(request); - ContentResponse response = request.send(); - if (logger.isTraceEnabled()) { - logger.trace("Login Code {} Response {}", response.getStatus(), response.getContentAsString()); - } - return response; - } catch (URISyntaxException e) { - throw new ExecutionException(e.getCause()); - } - } - - private OAuthClientService getOAuthService() { - OAuthClientService oAuthService = this.oAuthService; - if (oAuthService == null || oAuthService.isClosed()) { - oAuthService = oAuthFactory.createOAuthClientService(getThing().toString(), LOGIN_TOKEN_URL, - LOGIN_AUTHORIZE_URL, CLIENT_ID, CLIENT_SECRET, SCOPE, false); - oAuthService.addAccessTokenRefreshListener(this); - this.oAuthService = oAuthService; - } - return oAuthService; - } - - private String generateCodeVerifier() { - SecureRandom secureRandom = new SecureRandom(); - byte[] codeVerifier = new byte[32]; - secureRandom.nextBytes(codeVerifier); - return Base64.getUrlEncoder().withoutPadding().encodeToString(codeVerifier); - } - - private String generateCodeChallange(String codeVerifier) throws NoSuchAlgorithmException { - byte[] bytes = codeVerifier.getBytes(StandardCharsets.US_ASCII); - MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); - messageDigest.update(bytes, 0, bytes.length); - byte[] digest = messageDigest.digest(); - return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); - } - - private Map parseLocationQuery(String location) throws URISyntaxException { - URI uri = new URI(location); - return Arrays.stream(uri.getQuery().split("&")).map(str -> str.split("=")) - .collect(Collectors.toMap(str -> str[0], str -> str[1])); - } - - private void setCookies(Request request) { - for (HttpCookie c : httpClient.getCookieStore().getCookies()) { - request.cookie(c); - } - } - - private String authTokenHeader(AccessTokenResponse tokenResponse) { - return tokenResponse.getTokenType() + " " + tokenResponse.getAccessToken(); - } - - /** - * Exception for authenticated related errors - */ - class MyQAuthenticationException extends Exception { - private static final long serialVersionUID = 1L; - - public MyQAuthenticationException(String message) { - super(message); - } - } - - /** - * Generic exception for non authentication related errors when communicating with the MyQ service. - */ - class MyQCommunicationException extends IOException { - private static final long serialVersionUID = 1L; - - public MyQCommunicationException(@Nullable String message) { - super(message); - } - } -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQDeviceHandler.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQDeviceHandler.java deleted file mode 100644 index b216daa4c..000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQDeviceHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2010-2023 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.myq.internal.handler; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.myq.internal.dto.DeviceDTO; - -/** - * The {@link MyQDeviceHandler} is responsible for handling device updates - * - * @author Dan Cunningham - Initial contribution - */ -@NonNullByDefault -public interface MyQDeviceHandler { - void handleDeviceUpdate(DeviceDTO device); - - String getSerialNumber(); -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQGarageDoorHandler.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQGarageDoorHandler.java deleted file mode 100644 index 7f7591600..000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQGarageDoorHandler.java +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Copyright (c) 2010-2023 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.myq.internal.handler; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.myq.internal.MyQBindingConstants; -import org.openhab.binding.myq.internal.config.MyQDeviceConfiguration; -import org.openhab.binding.myq.internal.dto.DeviceDTO; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.library.types.PercentType; -import org.openhab.core.library.types.StringType; -import org.openhab.core.library.types.UpDownType; -import org.openhab.core.thing.Bridge; -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.thing.binding.BridgeHandler; -import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; -import org.openhab.core.types.UnDefType; - -/** - * The {@link MyQGarageDoorHandler} is responsible for handling commands for a garage door thing, which are - * sent to one of the channels. - * - * @author Dan Cunningham - Initial contribution - */ -@NonNullByDefault -public class MyQGarageDoorHandler extends BaseThingHandler implements MyQDeviceHandler { - private @Nullable DeviceDTO device; - private String serialNumber; - - public MyQGarageDoorHandler(Thing thing) { - super(thing); - serialNumber = getConfigAs(MyQDeviceConfiguration.class).serialNumber; - } - - @Override - public void initialize() { - updateStatus(ThingStatus.UNKNOWN); - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - if (command instanceof RefreshType) { - updateState(); - return; - } - Bridge bridge = getBridge(); - final DeviceDTO localDevice = device; - if (bridge != null && localDevice != null) { - BridgeHandler handler = bridge.getHandler(); - if (handler != null) { - String cmd = null; - if (command instanceof OnOffType) { - cmd = command == OnOffType.ON ? "open" : "close"; - } - if (command instanceof UpDownType) { - cmd = command == UpDownType.UP ? "open" : "close"; - } - if (command instanceof PercentType percentage) { - cmd = percentage.as(UpDownType.class) == UpDownType.UP ? "open" : "close"; - } - if (command instanceof StringType) { - cmd = command.toString(); - } - if (cmd != null) { - ((MyQAccountHandler) handler).sendDoorAction(localDevice, cmd); - } - } - } - } - - @Override - public String getSerialNumber() { - return serialNumber; - } - - protected void updateState() { - final DeviceDTO localDevice = device; - if (localDevice != null) { - String doorState = localDevice.state.doorState; - updateState("status", new StringType(doorState)); - switch (doorState) { - case "open": - case "opening": - case "closing": - case "stopped": - case "transition": - updateState("switch", OnOffType.ON); - updateState("rollershutter", UpDownType.UP); - break; - case "closed": - updateState("switch", OnOffType.OFF); - updateState("rollershutter", UpDownType.DOWN); - break; - default: - updateState("switch", UnDefType.UNDEF); - updateState("rollershutter", UnDefType.UNDEF); - break; - } - updateState("closeerror", localDevice.state.isUnattendedCloseAllowed ? OnOffType.OFF : OnOffType.ON); - updateState("openerror", localDevice.state.isUnattendedOpenAllowed ? OnOffType.OFF : OnOffType.ON); - } - } - - @Override - public void handleDeviceUpdate(DeviceDTO device) { - if (!MyQBindingConstants.THING_TYPE_GARAGEDOOR.getId().equals(device.deviceFamily)) { - return; - } - this.device = device; - if (device.state.online) { - updateStatus(ThingStatus.ONLINE); - updateState(); - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Device reports as offline"); - } - } -} diff --git a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQLampHandler.java b/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQLampHandler.java deleted file mode 100644 index 04cb77b92..000000000 --- a/bundles/org.openhab.binding.myq/src/main/java/org/openhab/binding/myq/internal/handler/MyQLampHandler.java +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright (c) 2010-2023 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.myq.internal.handler; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.myq.internal.MyQBindingConstants; -import org.openhab.binding.myq.internal.config.MyQDeviceConfiguration; -import org.openhab.binding.myq.internal.dto.DeviceDTO; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.thing.Bridge; -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.thing.binding.BridgeHandler; -import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; - -/** - * The {@link MyQLampHandler} is responsible for handling commands for a lamp thing, which are - * sent to one of the channels. - * - * @author Dan Cunningham - Initial contribution - */ -@NonNullByDefault -public class MyQLampHandler extends BaseThingHandler implements MyQDeviceHandler { - private @Nullable DeviceDTO device; - private String serialNumber; - - public MyQLampHandler(Thing thing) { - super(thing); - serialNumber = getConfigAs(MyQDeviceConfiguration.class).serialNumber; - } - - @Override - public void initialize() { - updateStatus(ThingStatus.UNKNOWN); - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - if (command instanceof RefreshType) { - updateState(); - return; - } - - if (command instanceof OnOffType) { - Bridge bridge = getBridge(); - final DeviceDTO localDevice = device; - if (bridge != null && localDevice != null) { - BridgeHandler handler = bridge.getHandler(); - if (handler != null) { - ((MyQAccountHandler) handler).sendLampAction(localDevice, command == OnOffType.ON ? "on" : "off"); - } - } - } - } - - @Override - public String getSerialNumber() { - return serialNumber; - } - - protected void updateState() { - final DeviceDTO localDevice = device; - if (localDevice != null) { - String lampState = localDevice.state.lampState; - updateState("switch", "on".equals(lampState) ? OnOffType.ON : OnOffType.OFF); - } - } - - @Override - public void handleDeviceUpdate(DeviceDTO device) { - if (!MyQBindingConstants.THING_TYPE_LAMP.getId().equals(device.deviceFamily)) { - return; - } - this.device = device; - if (device.state.online) { - updateStatus(ThingStatus.ONLINE); - updateState(); - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Device reports as offline"); - } - } -} diff --git a/bundles/org.openhab.binding.myq/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.myq/src/main/resources/OH-INF/addon/addon.xml deleted file mode 100644 index a63b7da74..000000000 --- a/bundles/org.openhab.binding.myq/src/main/resources/OH-INF/addon/addon.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - binding - MyQ Binding - The MyQ binding allows monitoring and control of garage doors that are MyQ enabled. - cloud - - diff --git a/bundles/org.openhab.binding.myq/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.myq/src/main/resources/OH-INF/config/config.xml deleted file mode 100644 index beaf772f2..000000000 --- a/bundles/org.openhab.binding.myq/src/main/resources/OH-INF/config/config.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - Account username - - - - Account password - password - - - - Specifies the refresh interval in seconds - 60 - - - - - - - Serial number of the garage door - - - - - - - Serial number of the lamp - - - - diff --git a/bundles/org.openhab.binding.myq/src/main/resources/OH-INF/i18n/myq.properties b/bundles/org.openhab.binding.myq/src/main/resources/OH-INF/i18n/myq.properties deleted file mode 100644 index 7fafc355e..000000000 --- a/bundles/org.openhab.binding.myq/src/main/resources/OH-INF/i18n/myq.properties +++ /dev/null @@ -1,42 +0,0 @@ -# add-on - -addon.myq.name = MyQ Binding -addon.myq.description = The MyQ binding allows monitoring and control of garage doors that are MyQ enabled. - -# thing types - -thing-type.myq.account.label = MyQ Account -thing-type.myq.account.description = MyQ Cloud Account -thing-type.myq.garagedoor.label = MyQ Garage Door -thing-type.myq.garagedoor.description = MyQ Garage Door -thing-type.myq.lamp.label = MyQ Lamp -thing-type.myq.lamp.description = MyQ Lamp - -# thing types config - -thing-type.config.myq.account.password.label = password -thing-type.config.myq.account.password.description = Account password -thing-type.config.myq.account.refreshInterval.label = Refresh Interval -thing-type.config.myq.account.refreshInterval.description = Specifies the refresh interval in seconds -thing-type.config.myq.account.username.label = User Name -thing-type.config.myq.account.username.description = Account username -thing-type.config.myq.garagedoor.serialNumber.label = Serial Number -thing-type.config.myq.garagedoor.serialNumber.description = Serial number of the garage door -thing-type.config.myq.lamp.serialNumber.label = Serial Number -thing-type.config.myq.lamp.serialNumber.description = Serial number of the lamp - -# channel types - -channel-type.myq.doorcloseerror.label = Garage Door Close Error -channel-type.myq.dooropenerror.label = Garage Door Open Error -channel-type.myq.doorrollershutter.label = Garage Door Rollershutter -channel-type.myq.doorstatus.label = Garage Door Status -channel-type.myq.doorstatus.state.option.open = Open -channel-type.myq.doorstatus.state.option.opening = Opening -channel-type.myq.doorstatus.state.option.closed = Closed -channel-type.myq.doorstatus.state.option.closing = Closing -channel-type.myq.doorstatus.state.option.stopped = Stopped -channel-type.myq.doorstatus.state.option.transition = Transitioning -channel-type.myq.doorstatus.state.option.unknown = Unknown -channel-type.myq.doorswitch.label = Garage Door Switch -channel-type.myq.lampswitch.label = Lamp Switch diff --git a/bundles/org.openhab.binding.myq/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.myq/src/main/resources/OH-INF/thing/thing-types.xml deleted file mode 100644 index 56fb3cb42..000000000 --- a/bundles/org.openhab.binding.myq/src/main/resources/OH-INF/thing/thing-types.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - MyQ Cloud Account - - - - - - - - - MyQ Garage Door - - - - - - - - serialNumber - - - - - - - - - MyQ Lamp - - - - serialNumber - - - - - String - - - - - - - - - - - - - - - Switch - - - - Rollershutter - - - - Switch - - - - - Switch - - - - - Switch - - - diff --git a/bundles/pom.xml b/bundles/pom.xml index 24ba021ef..bc0ef8589 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -258,7 +258,6 @@ org.openhab.binding.mybmw org.openhab.binding.mycroft org.openhab.binding.mynice - org.openhab.binding.myq org.openhab.binding.mystrom org.openhab.binding.nanoleaf org.openhab.binding.neato