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